big first commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
@@ -0,0 +1,8 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"fruits",
|
||||
"mania",
|
||||
"osu",
|
||||
"parse",
|
||||
"taiko",
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
# rosu-pp
|
||||
|
||||
A standalone crate to calculate star ratings and performance points for all [osu!](https://osu.ppy.sh/home) gamemodes.
|
||||
|
||||
**Most of it is still a WIP.**
|
||||
|
||||
### Roadmap
|
||||
- [ ] osu sr
|
||||
- [x] taiko sr
|
||||
- [x] ctb sr
|
||||
- [x] mania sr
|
||||
- [ ] osu pp
|
||||
- [ ] taiko pp
|
||||
- [ ] ctb pp
|
||||
- [ ] mania pp
|
||||
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "fruits"
|
||||
version = "0.1.0"
|
||||
authors = ["MaxOhn <ohn.m@hotmail.de>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies.parse]
|
||||
path = "../parse"
|
||||
@@ -0,0 +1,89 @@
|
||||
use parse::Pos2;
|
||||
|
||||
const PLAYFIELD_WIDHT: f32 = 512.0;
|
||||
const BASE_SPEED: f32 = 1.0;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CatchObject {
|
||||
pub(crate) pos: f32,
|
||||
pub(crate) time: f32,
|
||||
|
||||
pub(crate) hyper_dash: bool,
|
||||
pub(crate) hyper_dist: f32,
|
||||
}
|
||||
|
||||
impl CatchObject {
|
||||
#[inline]
|
||||
pub(crate) fn new((pos, time): (Pos2, f32)) -> Self {
|
||||
Self {
|
||||
pos: pos.x,
|
||||
time,
|
||||
hyper_dash: false,
|
||||
hyper_dist: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn with_hr(mut self, last_pos: &mut Option<f32>, last_time: &mut f32) -> Self {
|
||||
let mut offset_pos = self.pos;
|
||||
let time_diff = self.time - *last_time;
|
||||
|
||||
if let Some(last_pos_ref) = last_pos.filter(|_| time_diff <= 1000.0) {
|
||||
let pos_diff = offset_pos - last_pos_ref;
|
||||
|
||||
if pos_diff.abs() > f32::EPSILON {
|
||||
if pos_diff.abs() < (time_diff / 3.0).floor() {
|
||||
if pos_diff > 0.0 {
|
||||
if offset_pos + pos_diff < PLAYFIELD_WIDHT {
|
||||
offset_pos += pos_diff;
|
||||
}
|
||||
} else if offset_pos + pos_diff > 0.0 {
|
||||
offset_pos += pos_diff;
|
||||
}
|
||||
}
|
||||
|
||||
last_pos.replace(offset_pos);
|
||||
*last_time = self.time;
|
||||
}
|
||||
|
||||
self.pos = offset_pos;
|
||||
} else {
|
||||
last_pos.replace(offset_pos);
|
||||
*last_time = self.time;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn init_hyper_dash(
|
||||
&mut self,
|
||||
half_catcher_width: f32,
|
||||
next: &CatchObject,
|
||||
last_direction: &mut i8,
|
||||
last_excess: &mut f32,
|
||||
) {
|
||||
let next_x = next.pos;
|
||||
let curr_x = self.pos;
|
||||
|
||||
let this_direction = (next_x > curr_x) as i8 * 2 - 1;
|
||||
let time_to_next = next.time - self.time - 1000.0 / 60.0 / 4.0;
|
||||
|
||||
let sub = if *last_direction == this_direction {
|
||||
*last_excess
|
||||
} else {
|
||||
half_catcher_width
|
||||
};
|
||||
|
||||
let dist_to_next = (next_x - curr_x).abs() - sub;
|
||||
let hyper_dist = time_to_next * BASE_SPEED - dist_to_next;
|
||||
|
||||
if hyper_dist < 0.0 {
|
||||
self.hyper_dash = true;
|
||||
*last_excess = half_catcher_width;
|
||||
} else {
|
||||
self.hyper_dist = hyper_dist;
|
||||
*last_excess = hyper_dist.max(0.0).min(half_catcher_width);
|
||||
}
|
||||
|
||||
*last_direction = this_direction;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
use super::math_util;
|
||||
|
||||
use parse::Pos2;
|
||||
|
||||
const SLIDER_QUALITY: f32 = 50.0;
|
||||
|
||||
pub(crate) enum Points {
|
||||
Single(Pos2),
|
||||
Multi(Vec<Pos2>),
|
||||
}
|
||||
|
||||
pub(crate) enum Curve {
|
||||
Linear {
|
||||
a: Pos2,
|
||||
b: Pos2,
|
||||
},
|
||||
Bezier(Points),
|
||||
Catmull(Points),
|
||||
Perfect {
|
||||
origin: Pos2,
|
||||
cx: f32,
|
||||
cy: f32,
|
||||
radius: f32,
|
||||
},
|
||||
}
|
||||
|
||||
impl Curve {
|
||||
pub(crate) fn linear(a: Pos2, b: Pos2) -> Self {
|
||||
Self::Linear { a, b }
|
||||
}
|
||||
|
||||
pub(crate) fn bezier(points: &[Pos2]) -> Self {
|
||||
if points.len() == 1 {
|
||||
return Self::Bezier(Points::Single(points[0]));
|
||||
}
|
||||
|
||||
let mut start = 0;
|
||||
let mut end = 0;
|
||||
let mut result = Vec::with_capacity(4);
|
||||
|
||||
for i in 0..points.len() - 1 {
|
||||
if end - start > 1 && points[i] == points[end - 1] {
|
||||
Self::_bezier(&mut result, &points[start..end]);
|
||||
start = end;
|
||||
}
|
||||
|
||||
end += 1;
|
||||
}
|
||||
|
||||
Self::_bezier(&mut result, &points[start..end + 1]);
|
||||
|
||||
Self::Bezier(Points::Multi(result))
|
||||
}
|
||||
|
||||
fn _bezier(result: &mut Vec<Pos2>, points: &[Pos2]) {
|
||||
let step = 0.25 / SLIDER_QUALITY / points.len() as f32;
|
||||
let mut i = 0.0;
|
||||
let n = points.len() as i32 - 1;
|
||||
|
||||
while i < 1.0 + step {
|
||||
let point = (0..=n).fold(Pos2 { x: 0.0, y: 0.0 }, |point, p| {
|
||||
let factor = math_util::cpn(p, n) * (1.0 - i).powi(n - p) * i.powi(p);
|
||||
|
||||
point + points[p as usize] * factor
|
||||
});
|
||||
|
||||
result.push(point);
|
||||
i += step;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn catmull(points: &[Pos2]) -> Self {
|
||||
if points.len() == 1 {
|
||||
return Self::Catmull(Points::Single(points[0]));
|
||||
}
|
||||
|
||||
let order = points.len();
|
||||
let step = 2.5 / SLIDER_QUALITY;
|
||||
let target = step + 1.0;
|
||||
|
||||
let mut resulting_points = Vec::with_capacity(4);
|
||||
|
||||
for x in 0..order - 1 {
|
||||
let mut t = 0.0;
|
||||
|
||||
while t < target {
|
||||
let v1 = if x >= 1 { points[x - 1] } else { points[x] };
|
||||
let v2 = points[x];
|
||||
|
||||
let v3 = if x + 1 < order {
|
||||
points[x + 1]
|
||||
} else {
|
||||
v2.add_scaled(v2.add_scaled(v1, -1.0), 1.0)
|
||||
};
|
||||
|
||||
let v4 = if x + 2 < order {
|
||||
points[x + 2]
|
||||
} else {
|
||||
v3.add_scaled(v3.add_scaled(v2, -1.0), 1.0)
|
||||
};
|
||||
|
||||
let point = Self::catmull_point(v1, v2, v3, v4, t);
|
||||
resulting_points.push(point);
|
||||
t += step;
|
||||
}
|
||||
}
|
||||
|
||||
Self::Catmull(Points::Multi(resulting_points))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn catmull_point(p0: Pos2, p1: Pos2, p2: Pos2, p3: Pos2, len: f32) -> Pos2 {
|
||||
Pos2 {
|
||||
x: math_util::catmull(p0.x, p1.x, p2.x, p3.x, len),
|
||||
y: math_util::catmull(p0.y, p1.y, p2.y, p3.y, len),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn perfect(points: &[Pos2]) -> Self {
|
||||
let (cx, cy, mut radius) = math_util::get_circum_circle(&points);
|
||||
radius *= ((!math_util::is_left(&points)) as i8 * 2 - 1) as f32;
|
||||
|
||||
Self::Perfect {
|
||||
origin: points[0],
|
||||
cx,
|
||||
cy,
|
||||
radius,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn point_at_distance(&self, len: f32) -> Pos2 {
|
||||
let points = match self {
|
||||
Self::Bezier(points) => points,
|
||||
Self::Catmull(points) => points,
|
||||
Self::Linear { a, b } => return math_util::point_on_line(*a, *b, len),
|
||||
Self::Perfect {
|
||||
origin,
|
||||
cx,
|
||||
cy,
|
||||
radius,
|
||||
} => return math_util::rotate(*cx, *cy, *origin, len / *radius),
|
||||
};
|
||||
|
||||
match points {
|
||||
Points::Single(point) => *point,
|
||||
Points::Multi(points) => math_util::point_at_distance(points, len),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
use super::CatchObject;
|
||||
|
||||
const NORMALIZED_HITOBJECT_RADIUS: f32 = 41.0;
|
||||
|
||||
pub(crate) struct DifficultyObject<'o> {
|
||||
pub(crate) base: &'o CatchObject,
|
||||
pub(crate) last: &'o CatchObject,
|
||||
|
||||
pub(crate) delta: f32,
|
||||
|
||||
pub(crate) normalized_pos: f32,
|
||||
pub(crate) last_normalized_pos: f32,
|
||||
|
||||
pub(crate) strain_time: f32,
|
||||
pub(crate) clock_rate: f32,
|
||||
}
|
||||
|
||||
impl<'o> DifficultyObject<'o> {
|
||||
#[inline]
|
||||
pub(crate) fn new(
|
||||
base: &'o CatchObject,
|
||||
last: &'o CatchObject,
|
||||
half_catcher_width: f32,
|
||||
clock_rate: f32,
|
||||
) -> Self {
|
||||
let delta = (base.time - last.time) / clock_rate;
|
||||
let strain_time = delta.max(40.0);
|
||||
|
||||
let scaling_factor = NORMALIZED_HITOBJECT_RADIUS / half_catcher_width;
|
||||
let normalized_pos = base.pos * scaling_factor;
|
||||
let last_normalized_pos = last.pos * scaling_factor;
|
||||
|
||||
Self {
|
||||
base,
|
||||
last,
|
||||
delta,
|
||||
normalized_pos,
|
||||
last_normalized_pos,
|
||||
strain_time,
|
||||
clock_rate,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
mod catch_object;
|
||||
mod curve;
|
||||
mod difficulty_object;
|
||||
mod math_util;
|
||||
mod movement;
|
||||
|
||||
use catch_object::CatchObject;
|
||||
use curve::Curve;
|
||||
use difficulty_object::DifficultyObject;
|
||||
use movement::Movement;
|
||||
|
||||
use parse::{Beatmap, HitObjectKind, Mods, PathType};
|
||||
use std::cmp::Ordering;
|
||||
use std::convert::identity;
|
||||
|
||||
const SECTION_LENGTH: f32 = 750.0;
|
||||
const STAR_SCALING_FACTOR: f32 = 0.153;
|
||||
|
||||
const ALLOWED_CATCH_RANGE: f32 = 0.8;
|
||||
const CATCHER_SIZE: f32 = 106.75;
|
||||
|
||||
macro_rules! binary_search {
|
||||
($slice:expr, $target:expr) => {
|
||||
$slice.binary_search_by(|p| p.time.partial_cmp(&$target).unwrap_or(Ordering::Equal))
|
||||
};
|
||||
}
|
||||
|
||||
/// Star calculation for osu!ctb maps
|
||||
// Slider parsing based on https://github.com/osufx/catch-the-pp
|
||||
pub fn stars(map: &Beatmap, mods: impl Mods) -> f32 {
|
||||
if map.hit_objects.len() < 2 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let attributes = map.attributes().mods(mods);
|
||||
let with_hr = mods.hr();
|
||||
let mut ticks = Vec::new(); // using the same buffer for all sliders
|
||||
|
||||
// BUG: Incorrect object order on 2B maps that have fruits within sliders
|
||||
let mut hit_objects = map
|
||||
.hit_objects
|
||||
.iter()
|
||||
.scan((None, 0.0), |(last_pos, last_time), h| match &h.kind {
|
||||
HitObjectKind::Circle => {
|
||||
let mut h = CatchObject::new((h.pos, h.start_time));
|
||||
|
||||
if with_hr {
|
||||
h = h.with_hr(last_pos, last_time);
|
||||
}
|
||||
|
||||
Some(Some(FruitOrJuice::Fruit(Some(h))))
|
||||
}
|
||||
HitObjectKind::Slider {
|
||||
pixel_len,
|
||||
repeats,
|
||||
curve_points,
|
||||
path_type,
|
||||
} => {
|
||||
// HR business
|
||||
last_pos
|
||||
.replace(h.pos.x + curve_points[curve_points.len() - 1].x - curve_points[0].x);
|
||||
*last_time = h.start_time;
|
||||
|
||||
let (beat_len, timing_time) = {
|
||||
match binary_search!(map.timing_points, h.start_time) {
|
||||
Ok(idx) => {
|
||||
let point = &map.timing_points[idx];
|
||||
(point.beat_len, point.time)
|
||||
}
|
||||
Err(0) => (1000.0, 0.0),
|
||||
Err(idx) => {
|
||||
let point = &map.timing_points[idx - 1];
|
||||
(point.beat_len, point.time)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let (speed_multiplier, diff_time) = {
|
||||
match binary_search!(map.difficulty_points, h.start_time) {
|
||||
Ok(idx) => {
|
||||
let point = &map.difficulty_points[idx];
|
||||
(point.speed_multiplier, point.time)
|
||||
}
|
||||
Err(0) => (1.0, 0.0),
|
||||
Err(idx) => {
|
||||
let point = &map.difficulty_points[idx - 1];
|
||||
(point.speed_multiplier, point.time)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut tick_distance = 100.0 * map.sv / map.tick_rate;
|
||||
|
||||
if map.version >= 8 {
|
||||
tick_distance /= (100.0 / speed_multiplier).max(10.0).min(1000.0) / 100.0;
|
||||
}
|
||||
|
||||
let spm = if timing_time > diff_time {
|
||||
1.0
|
||||
} else {
|
||||
speed_multiplier
|
||||
};
|
||||
|
||||
let duration = *repeats as f32 * beat_len * *pixel_len / (map.sv * spm) / 100.0;
|
||||
|
||||
let path_type = if *path_type == PathType::PerfectCurve && curve_points.len() > 3 {
|
||||
PathType::Bezier
|
||||
} else if curve_points.len() == 2 {
|
||||
PathType::Linear
|
||||
} else {
|
||||
*path_type
|
||||
};
|
||||
|
||||
let curve = match path_type {
|
||||
PathType::Linear => Curve::linear(curve_points[0], curve_points[1]),
|
||||
PathType::Bezier => Curve::bezier(curve_points),
|
||||
PathType::Catmull => Curve::catmull(curve_points),
|
||||
PathType::PerfectCurve => Curve::perfect(curve_points),
|
||||
};
|
||||
|
||||
let mut current_distance = tick_distance;
|
||||
let time_add = duration * (tick_distance / (*pixel_len * *repeats as f32));
|
||||
|
||||
let target = *pixel_len - tick_distance / 8.0;
|
||||
ticks.reserve((target / tick_distance) as usize);
|
||||
|
||||
while current_distance < target {
|
||||
let pos = curve.point_at_distance(current_distance);
|
||||
|
||||
ticks.push((pos, h.start_time + time_add * (ticks.len() + 1) as f32));
|
||||
current_distance += tick_distance;
|
||||
}
|
||||
|
||||
let mut slider_objects = Vec::with_capacity(repeats * (ticks.len() + 1));
|
||||
slider_objects.push((h.pos, h.start_time));
|
||||
|
||||
if *repeats <= 1 {
|
||||
slider_objects.append(&mut ticks); // automatically empties buffer for next slider
|
||||
} else {
|
||||
slider_objects.append(&mut ticks.clone());
|
||||
|
||||
for repeat_id in 1..*repeats - 1 {
|
||||
let dist = (repeat_id % 2) as f32 * *pixel_len;
|
||||
let time_offset = (duration / *repeats as f32) * repeat_id as f32;
|
||||
let pos = curve.point_at_distance(dist);
|
||||
|
||||
// Reverse tick / last legacy tick
|
||||
slider_objects.push((pos, h.start_time + time_offset));
|
||||
|
||||
ticks.reverse();
|
||||
slider_objects.extend_from_slice(&ticks); // tick time doesn't need to be adjusted for some reason
|
||||
}
|
||||
|
||||
// Handling last span separatly so that `ticks` vector isn't cloned again
|
||||
let dist = ((*repeats - 1) % 2) as f32 * *pixel_len;
|
||||
let time_offset = (duration / *repeats as f32) * (*repeats - 1) as f32;
|
||||
let pos = curve.point_at_distance(dist);
|
||||
|
||||
slider_objects.push((pos, h.start_time + time_offset));
|
||||
|
||||
ticks.reverse();
|
||||
slider_objects.append(&mut ticks); // automatically empties buffer for next slider
|
||||
}
|
||||
|
||||
// Slider tail
|
||||
let dist_end = (*repeats % 2) as f32 * *pixel_len;
|
||||
let pos = curve.point_at_distance(dist_end);
|
||||
slider_objects.push((pos, h.start_time + duration));
|
||||
|
||||
let iter = slider_objects.into_iter().map(CatchObject::new);
|
||||
|
||||
Some(Some(FruitOrJuice::Juice(iter)))
|
||||
}
|
||||
HitObjectKind::Spinner { .. } | HitObjectKind::Hold { .. } => Some(None),
|
||||
})
|
||||
.filter_map(identity)
|
||||
.flatten();
|
||||
|
||||
// Hyper dash business
|
||||
let half_catcher_width = calculate_catch_width(attributes.cs) / 2.0 / ALLOWED_CATCH_RANGE;
|
||||
let mut last_direction = 0;
|
||||
let mut last_excess = half_catcher_width;
|
||||
|
||||
// Strain business
|
||||
let mut movement = Movement::new(attributes.cs);
|
||||
let section_len = SECTION_LENGTH * attributes.clock_rate;
|
||||
let mut current_section_end =
|
||||
(map.hit_objects[0].start_time / section_len).ceil() * section_len;
|
||||
|
||||
let mut prev = hit_objects.next().unwrap();
|
||||
let mut curr = hit_objects.next().unwrap();
|
||||
|
||||
prev.init_hyper_dash(
|
||||
half_catcher_width,
|
||||
&curr,
|
||||
&mut last_direction,
|
||||
&mut last_excess,
|
||||
);
|
||||
|
||||
for next in hit_objects {
|
||||
curr.init_hyper_dash(
|
||||
half_catcher_width,
|
||||
&next,
|
||||
&mut last_direction,
|
||||
&mut last_excess,
|
||||
);
|
||||
|
||||
let h = DifficultyObject::new(
|
||||
&curr,
|
||||
&prev,
|
||||
movement.half_catcher_width,
|
||||
attributes.clock_rate,
|
||||
);
|
||||
|
||||
while h.base.time > current_section_end {
|
||||
movement.save_current_peak();
|
||||
movement.start_new_section_from(current_section_end);
|
||||
current_section_end += section_len;
|
||||
}
|
||||
|
||||
movement.process(&h);
|
||||
|
||||
prev = curr;
|
||||
curr = next;
|
||||
}
|
||||
|
||||
// Same as in loop but without init_hyper_dash because `curr` is the last element
|
||||
let h = DifficultyObject::new(
|
||||
&curr,
|
||||
&prev,
|
||||
movement.half_catcher_width,
|
||||
attributes.clock_rate,
|
||||
);
|
||||
|
||||
while h.base.time > current_section_end {
|
||||
movement.save_current_peak();
|
||||
movement.start_new_section_from(current_section_end);
|
||||
|
||||
current_section_end += section_len;
|
||||
}
|
||||
|
||||
movement.process(&h);
|
||||
movement.save_current_peak();
|
||||
|
||||
movement.difficulty_value().sqrt() * STAR_SCALING_FACTOR
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn calculate_catch_width(cs: f32) -> f32 {
|
||||
let scale = 1.0 - 0.7 * (cs - 5.0) / 5.0;
|
||||
|
||||
CATCHER_SIZE * scale.abs() * ALLOWED_CATCH_RANGE
|
||||
}
|
||||
|
||||
enum FruitOrJuice<I> {
|
||||
Fruit(Option<CatchObject>),
|
||||
Juice(I),
|
||||
}
|
||||
|
||||
impl<I: Iterator<Item = CatchObject>> Iterator for FruitOrJuice<I> {
|
||||
type Item = CatchObject;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
Self::Fruit(fruit) => fruit.take(),
|
||||
Self::Juice(slider) => slider.next(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
match self {
|
||||
Self::Fruit(Some(_)) => (1, Some(1)),
|
||||
Self::Fruit(None) => (0, Some(0)),
|
||||
Self::Juice(slider) => slider.size_hint(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::File;
|
||||
|
||||
#[test]
|
||||
fn test_single() {
|
||||
let map_id = 1972149;
|
||||
let file = match File::open(format!("E:/Games/osu!/beatmaps/{}.osu", map_id)) {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file: {}", why),
|
||||
};
|
||||
// let file = match File::open(format!("E:/Games/osu!/beatmaps/{}.osu", map_id)) {
|
||||
// Ok(file) => file,
|
||||
// Err(why) => panic!("Could not open file: {}", why),
|
||||
// };
|
||||
|
||||
let map = match Beatmap::parse(file) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map: {}", why),
|
||||
};
|
||||
|
||||
let mods = 0;
|
||||
let stars = stars(&map, mods);
|
||||
|
||||
println!("Stars: {} [map={} | mods={}]", stars, map_id, mods);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fruits() {
|
||||
let margin = 0.005;
|
||||
|
||||
#[rustfmt::skip]
|
||||
let data = vec![
|
||||
(1977380, 1 << 8, 2.0564713386286573),// HT
|
||||
(1977380, 0, 2.5695489769068742), // NM
|
||||
(1977380, 1 << 6, 3.589887228221038), // DT
|
||||
(1977380, 1 << 4, 3.1515873669521928),// HR
|
||||
(1977380, 1 << 1, 3.0035260129778396),// EZ
|
||||
|
||||
(1974968, 1 << 8, 1.9544305373156605),// HT
|
||||
(1974968, 0, 2.521701539665241), // NM
|
||||
(1974968, 1 << 6, 3.650649037957456), // DT
|
||||
(1974968, 1 << 4, 3.566302788963401), // HR
|
||||
(1974968, 1 << 1, 2.2029392066882654),// EZ
|
||||
|
||||
(2420076, 1 << 8, 4.791039358886245), // HT
|
||||
(2420076, 0, 6.223136555625056), // NM
|
||||
(2420076, 1 << 6, 8.908315960310958), // DT
|
||||
(2420076, 1 << 4, 6.54788067620051), // HR
|
||||
(2420076, 1 << 1, 6.067971540209479), // EZ
|
||||
|
||||
(2206596, 1 << 8, 4.767182611189798), // HT
|
||||
(2206596, 0, 6.157660207091584), // NM
|
||||
(2206596, 1 << 6, 8.93391286552717), // DT
|
||||
(2206596, 1 << 4, 6.8639096665110735),// HR
|
||||
(2206596, 1 << 1, 5.60279198088948), // EZ
|
||||
|
||||
// Super long juice stream towards end
|
||||
// (1972149, 1 << 8, 4.671425766413811), // HT
|
||||
// (1972149, 0, 6.043742871084152), // NM
|
||||
// (1972149, 1 << 6, 8.469259368304225), // DT
|
||||
// (1972149, 1 << 4, 6.81222485322862), // HR
|
||||
// (1972149, 1 << 1, 5.289343020686747), // EZ
|
||||
|
||||
// Convert slider fiesta
|
||||
// (1657535, 1 << 8, 3.862453635711741), // HT
|
||||
// (1657535, 0, 4.792543335869686), // NM
|
||||
// (1657535, 1 << 6, 6.655478646330863), // DT
|
||||
// (1657535, 1 << 4, 5.259728567781568), // HR
|
||||
// (1657535, 1 << 1, 4.127535166776765), // EZ
|
||||
];
|
||||
|
||||
for (map_id, mods, expected_stars) in data {
|
||||
let file = match File::open(format!("./test/{}.osu", map_id)) {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
||||
};
|
||||
|
||||
let stars = stars(&map, mods);
|
||||
|
||||
assert!(
|
||||
(stars - expected_stars).abs() < margin,
|
||||
"Stars: {} | Expected: {} => {} margin [map {} | mods {}]",
|
||||
stars,
|
||||
expected_stars,
|
||||
(stars - expected_stars).abs(),
|
||||
map_id,
|
||||
mods
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
use parse::Pos2;
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn cpn(mut p: i32, n: i32) -> f32 {
|
||||
if p < 0 || p > n {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
p = p.min(n - p);
|
||||
let mut out = 1.0;
|
||||
|
||||
for i in 1..=p {
|
||||
out *= (n - p + i) as f32 / i as f32;
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn catmull(p0: f32, p1: f32, p2: f32, p3: f32, t: f32) -> f32 {
|
||||
0.5 * ((2.0 * p1)
|
||||
+ (-p0 + p2) * t
|
||||
+ (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t.powi(2)
|
||||
+ (-p0 + 3.0 * p1 - 3.0 * p2 + p3 * t.powi(3)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn point_on_line(p1: Pos2, p2: Pos2, len: f32) -> Pos2 {
|
||||
let mut full_len = ((p1.x - p2.x).powi(2) + (p1.y - p2.y).powi(2)).sqrt();
|
||||
let n = full_len - len;
|
||||
|
||||
if full_len.abs() < f32::EPSILON {
|
||||
full_len = 1.0;
|
||||
}
|
||||
|
||||
(p1 * n + p2 * len) / full_len
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn angle_from_points(p0: Pos2, p1: Pos2) -> f32 {
|
||||
(p1.y - p0.y).atan2(p1.x - p0.x)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn distance_from_points(arr: &[Pos2]) -> f32 {
|
||||
arr.iter()
|
||||
.skip(1)
|
||||
.zip(arr.iter())
|
||||
.map(|(curr, prev)| curr.distance(prev))
|
||||
.sum()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn cart_from_pol(r: f32, t: f32) -> Pos2 {
|
||||
Pos2 {
|
||||
x: r * t.cos(),
|
||||
y: r * t.sin(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn point_at_distance(array: &[Pos2], distance: f32) -> Pos2 {
|
||||
if array.len() < 2 {
|
||||
return Pos2 { x: 0.0, y: 0.0 };
|
||||
} else if distance.abs() < f32::EPSILON {
|
||||
return array[0];
|
||||
} else if distance_from_points(array) <= distance {
|
||||
return array[array.len() - 1];
|
||||
}
|
||||
|
||||
let mut i = 0;
|
||||
let mut current_distance = 0.0;
|
||||
let mut new_distance = 0.0;
|
||||
|
||||
while i < array.len() - 2 {
|
||||
new_distance = (array[i] - array[i + 1]).length();
|
||||
current_distance += new_distance;
|
||||
|
||||
if distance <= current_distance {
|
||||
break;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
current_distance -= new_distance;
|
||||
|
||||
if (distance - current_distance).abs() <= f32::EPSILON {
|
||||
array[i]
|
||||
} else {
|
||||
let angle = angle_from_points(array[i], array[i + 1]);
|
||||
let cart = cart_from_pol(distance - current_distance, angle);
|
||||
|
||||
if array[i].x > array[i + 1].x {
|
||||
array[i] - cart
|
||||
} else {
|
||||
array[i] + cart
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_circum_circle(p: &[Pos2]) -> (f32, f32, f32) {
|
||||
let d = 2.0
|
||||
* (p[0].x * (p[1].y - p[2].y) + p[1].x * (p[2].y - p[0].y) + p[2].x * (p[0].y - p[1].y));
|
||||
|
||||
let p0 = p[0].x * p[0].x + p[0].y * p[0].y;
|
||||
let p1 = p[1].x * p[1].x + p[1].y * p[1].y;
|
||||
let p2 = p[2].x * p[2].x + p[2].y * p[2].y;
|
||||
|
||||
let ux = (p0 * (p[1].y - p[2].y) + p1 * (p[2].y - p[0].y) + p2 * (p[0].y - p[1].y)) / d;
|
||||
let uy = (p0 * (p[2].x - p[1].x) + p1 * (p[0].x - p[2].x) + p2 * (p[1].x - p[0].x)) / d;
|
||||
|
||||
let px = ux - p[0].x;
|
||||
let py = uy - p[0].y;
|
||||
let r = (px * px + py * py).sqrt();
|
||||
|
||||
(ux, uy, r)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn is_left(p: &[Pos2]) -> bool {
|
||||
((p[1].x - p[0].x) * (p[2].y - p[0].y) - (p[1].y - p[0].y) * (p[2].x - p[0].x)) < 0.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn rotate(cx: f32, cy: f32, p: Pos2, radians: f32) -> Pos2 {
|
||||
let cos = radians.cos();
|
||||
let sin = radians.sin();
|
||||
|
||||
Pos2 {
|
||||
x: (cos * (p.x - cx)) - (sin * (p.y - cy)) + cx,
|
||||
y: (sin * (p.x - cx)) + (cos * (p.y - cy)) + cy,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
use super::DifficultyObject;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
const ABSOLUTE_PLAYER_POSITIONING_ERROR: f32 = 16.0;
|
||||
const NORMALIZED_HITOBJECT_RADIUS: f32 = 41.0;
|
||||
const POSITION_EPSILON: f32 = NORMALIZED_HITOBJECT_RADIUS - ABSOLUTE_PLAYER_POSITIONING_ERROR;
|
||||
const DIRECTION_CHANGE_BONUS: f32 = 21.0;
|
||||
const SKILL_MULTIPLIER: f32 = 900.0;
|
||||
const STRAIN_DECAY_BASE: f32 = 0.2;
|
||||
const DECAY_WEIGHT: f32 = 0.94;
|
||||
|
||||
pub(crate) struct Movement {
|
||||
pub(crate) half_catcher_width: f32,
|
||||
|
||||
last_player_position: Option<f32>,
|
||||
last_distance_moved: f32,
|
||||
last_strain_time: f32,
|
||||
|
||||
current_strain: f32,
|
||||
current_section_peak: f32,
|
||||
|
||||
strain_peaks: Vec<f32>,
|
||||
prev_time: Option<f32>,
|
||||
}
|
||||
|
||||
impl Movement {
|
||||
#[inline]
|
||||
pub(crate) fn new(cs: f32) -> Self {
|
||||
let mut half_catcher_width = super::calculate_catch_width(cs) * 0.5;
|
||||
half_catcher_width *= 1.0 - ((cs - 5.5).max(0.0) * 0.0625);
|
||||
|
||||
Self {
|
||||
half_catcher_width,
|
||||
|
||||
last_player_position: None,
|
||||
last_distance_moved: 0.0,
|
||||
last_strain_time: 0.0,
|
||||
|
||||
current_strain: 1.0,
|
||||
current_section_peak: 1.0,
|
||||
|
||||
strain_peaks: Vec::with_capacity(128),
|
||||
prev_time: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn save_current_peak(&mut self) {
|
||||
// TODO: Remove branching
|
||||
if self.prev_time.is_some() {
|
||||
self.strain_peaks.push(self.current_section_peak);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn start_new_section_from(&mut self, time: f32) {
|
||||
if let Some(prev_time) = self.prev_time {
|
||||
self.current_section_peak = self.peak_strain(time - prev_time);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn process(&mut self, current: &DifficultyObject) {
|
||||
self.current_strain *= strain_decay(current.delta);
|
||||
self.current_strain += self.strain_value_of(¤t) * SKILL_MULTIPLIER;
|
||||
self.current_section_peak = self.current_strain.max(self.current_section_peak);
|
||||
self.prev_time.replace(current.base.time);
|
||||
}
|
||||
|
||||
pub(crate) fn difficulty_value(&mut self) -> f32 {
|
||||
let mut difficulty = 0.0;
|
||||
let mut weight = 1.0;
|
||||
|
||||
self.strain_peaks
|
||||
.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
||||
|
||||
for &strain in self.strain_peaks.iter() {
|
||||
difficulty += strain * weight;
|
||||
weight *= DECAY_WEIGHT;
|
||||
}
|
||||
|
||||
difficulty
|
||||
}
|
||||
|
||||
fn strain_value_of(&mut self, current: &DifficultyObject) -> f32 {
|
||||
let last_player_pos = self
|
||||
.last_player_position
|
||||
.unwrap_or(current.last_normalized_pos);
|
||||
|
||||
let mut pos = last_player_pos
|
||||
.max(current.normalized_pos - POSITION_EPSILON)
|
||||
.min(current.normalized_pos + POSITION_EPSILON);
|
||||
|
||||
let dist_moved = pos - last_player_pos;
|
||||
let weighted_strain_time = current.strain_time + 13.0 + (3.0 / current.clock_rate);
|
||||
|
||||
let mut dist_addition = dist_moved.abs().powf(1.3) / 510.0;
|
||||
|
||||
if dist_moved.abs() > 0.1 {
|
||||
if self.last_distance_moved.abs() > 0.1
|
||||
&& dist_moved.signum() != self.last_distance_moved.signum()
|
||||
{
|
||||
let bonus_factor = dist_moved.abs().min(50.0) / 50.0;
|
||||
let anti_flow_factor = (self.last_distance_moved.abs().min(70.0) / 70.0).max(0.38);
|
||||
|
||||
dist_addition += DIRECTION_CHANGE_BONUS / (self.last_strain_time + 16.0).sqrt()
|
||||
* bonus_factor
|
||||
* anti_flow_factor
|
||||
* (1.0 - (weighted_strain_time / 1000.0).powi(3)).max(0.0);
|
||||
}
|
||||
|
||||
dist_addition += 12.5 * dist_moved.abs().min(NORMALIZED_HITOBJECT_RADIUS * 2.0)
|
||||
/ (NORMALIZED_HITOBJECT_RADIUS * 6.0)
|
||||
/ weighted_strain_time.sqrt();
|
||||
}
|
||||
|
||||
let mut edge_dash_bonus = 0.0;
|
||||
|
||||
if current.last.hyper_dist <= 20.0 {
|
||||
if !current.last.hyper_dash {
|
||||
edge_dash_bonus += 5.7;
|
||||
} else {
|
||||
pos = current.normalized_pos;
|
||||
}
|
||||
|
||||
dist_addition *= 1.0
|
||||
+ edge_dash_bonus
|
||||
* ((20.0 - current.last.hyper_dist) / 20.0)
|
||||
* ((current.strain_time * current.clock_rate).min(265.0) / 265.0).powf(1.5);
|
||||
}
|
||||
|
||||
self.last_player_position.replace(pos);
|
||||
self.last_distance_moved = dist_moved;
|
||||
self.last_strain_time = current.strain_time;
|
||||
|
||||
dist_addition / weighted_strain_time
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn peak_strain(&self, delta_time: f32) -> f32 {
|
||||
self.current_strain * strain_decay(delta_time)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_decay(ms: f32) -> f32 {
|
||||
STRAIN_DECAY_BASE.powf(ms / 1000.0)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,411 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
AudioFilename: audio.mp3
|
||||
AudioLeadIn: 0
|
||||
PreviewTime: 34971
|
||||
Countdown: 0
|
||||
SampleSet: Soft
|
||||
StackLeniency: 0.7
|
||||
Mode: 2
|
||||
LetterboxInBreaks: 0
|
||||
WidescreenStoryboard: 0
|
||||
|
||||
[Editor]
|
||||
DistanceSpacing: 1.4
|
||||
BeatDivisor: 4
|
||||
GridSize: 8
|
||||
TimelineZoom: 2.2
|
||||
|
||||
[Metadata]
|
||||
Title:fffff
|
||||
TitleUnicode:ƒƒƒƒƒ
|
||||
Artist:Five Hammer
|
||||
ArtistUnicode:Five Hammer
|
||||
Creator:Ascendance
|
||||
Version:MBomb's Normal
|
||||
Source:pop'n music 13 カーニバル
|
||||
Tags:Seiya Murai 村井聖夜 beatmania IIDX 15 DJ TROOPERS 19 Lincle HELLO! REFLEC BEAT groovin'!! plus ポップンリズミン rhythmin sanyi sanyi4life mbomb -_magic_bomb_- jbhyperion jbh rocma no492_shaymin _asriel examination yumeno_himiko -plus- chara osu_gangster ster sinnoh
|
||||
BeatmapID:1974968
|
||||
BeatmapSetID:841569
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:4
|
||||
CircleSize:3
|
||||
OverallDifficulty:7
|
||||
ApproachRate:7
|
||||
SliderMultiplier:1.65
|
||||
SliderTickRate:1
|
||||
|
||||
[Events]
|
||||
//Background and Video events
|
||||
0,0,"cropped-1920-1080-415259.jpg",0,0
|
||||
//Break Periods
|
||||
//Storyboard Layer 0 (Background)
|
||||
//Storyboard Layer 1 (Fail)
|
||||
//Storyboard Layer 2 (Pass)
|
||||
//Storyboard Layer 3 (Foreground)
|
||||
//Storyboard Sound Samples
|
||||
|
||||
[TimingPoints]
|
||||
1167,434.782608695652,4,2,1,45,1,0
|
||||
4427,-100,4,2,1,45,0,0
|
||||
4645,-100,4,2,1,55,0,0
|
||||
8123,-100,4,2,1,60,0,0
|
||||
35080,-100,4,2,2,60,0,0
|
||||
35949,-100,4,2,1,65,0,1
|
||||
36166,-100,4,2,1,65,0,1
|
||||
38884,-100,4,2,1,65,0,1
|
||||
39210,-100,4,2,1,65,0,1
|
||||
45840,-100,4,2,1,65,0,1
|
||||
46166,-100,4,2,1,65,0,1
|
||||
49862,-100,4,2,1,60,0,0
|
||||
51058,-100,4,2,1,60,0,0
|
||||
51601,-100,4,2,3,60,0,0
|
||||
54536,-100,4,2,1,60,0,0
|
||||
54862,-100,4,2,1,60,0,0
|
||||
55080,-100,4,2,3,60,0,0
|
||||
60297,-100,4,2,1,60,0,0
|
||||
63340,-100,4,2,1,60,0,0
|
||||
63775,-100,4,2,1,70,0,1
|
||||
75949,-100,4,2,3,70,0,1
|
||||
76384,-100,4,2,3,70,0,1
|
||||
77688,-100,4,2,3,65,0,0
|
||||
77797,-100,4,2,1,65,0,0
|
||||
89862,-100,4,2,3,65,0,0
|
||||
91601,-100,4,2,3,60,0,0
|
||||
91710,-100,4,2,1,60,0,0
|
||||
96819,-100,4,2,1,80,0,1
|
||||
98558,-100,4,2,1,80,0,0
|
||||
99210,-100,4,2,1,80,0,1
|
||||
99862,-100,4,2,1,80,0,0
|
||||
|
||||
|
||||
[Colours]
|
||||
Combo1 : 255,81,255
|
||||
Combo2 : 255,64,64
|
||||
Combo3 : 255,139,83
|
||||
Combo4 : 255,255,100
|
||||
Combo5 : 0,202,0
|
||||
Combo6 : 0,221,221
|
||||
Combo7 : 0,128,255
|
||||
|
||||
[HitObjects]
|
||||
112,192,1167,6,0,L|296:192,1,165,0|0,0:0|0:0,0:0:0:0:
|
||||
128,192,2036,2,0,L|312:192,1,165,0|0,0:0|0:0,0:0:0:0:
|
||||
496,192,2906,6,0,L|304:192,1,165,0|0,0:0|0:0,0:0:0:0:
|
||||
176,192,3775,2,0,L|368:192,1,165,0|0,0:0|0:0,0:0:0:0:
|
||||
248,192,4427,1,8,0:0:0:0:
|
||||
144,192,4645,6,0,L|40:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
176,192,5080,2,0,L|272:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
160,192,5514,2,0,L|256:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
352,192,5949,2,0,L|256:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
144,192,6384,6,0,P|88:176|80:152,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
184,192,6819,2,0,L|88:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
232,192,7253,2,0,L|328:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
184,192,7688,2,0,L|80:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
314,192,8123,6,0,L|418:192,1,82.5,6|8,0:0|0:0,0:0:0:0:
|
||||
280,192,8558,1,2,0:0:0:0:
|
||||
336,192,8666,1,0,0:0:0:0:
|
||||
392,192,8775,1,8,0:0:0:0:
|
||||
280,192,8993,6,0,L|208:192,2,41.25,0|2|8,0:0|0:0|0:0,0:0:0:0:
|
||||
408,192,9427,1,2,0:0:0:0:
|
||||
352,192,9535,1,0,0:0:0:0:
|
||||
296,192,9644,1,8,0:0:0:0:
|
||||
168,192,9862,6,0,L|88:192,2,41.25,2|0|8,0:0|0:0|0:0,0:0:0:0:
|
||||
296,192,10297,1,2,0:0:0:0:
|
||||
352,192,10406,1,0,0:0:0:0:
|
||||
408,192,10514,1,8,0:0:0:0:
|
||||
296,192,10732,6,0,L|200:192,1,82.5,0|8,0:0|0:0,0:0:0:0:
|
||||
312,192,11166,2,0,L|224:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
352,192,11601,6,0,L|416:192,2,41.25,2|0|8,0:0|0:0|0:0,0:0:0:0:
|
||||
240,192,12036,2,0,L|328:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
424,192,12471,6,0,L|328:192,1,82.5,0|8,0:0|0:0,0:0:0:0:
|
||||
248,192,12906,2,0,L|136:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
280,192,13340,5,2,0:0:0:0:
|
||||
224,192,13449,1,0,0:0:0:0:
|
||||
168,192,13558,1,8,0:0:0:0:
|
||||
280,192,13775,1,2,0:0:0:0:
|
||||
336,192,13884,1,0,0:0:0:0:
|
||||
392,192,13993,1,8,0:0:0:0:
|
||||
296,192,14210,6,0,L|192:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
304,192,14645,2,0,L|408:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
168,192,15080,6,0,L|80:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
200,192,15514,2,0,L|256:192,2,41.25,2|0|8,0:0|0:0|0:0,0:0:0:0:
|
||||
85,192,15949,5,0,0:0:0:0:
|
||||
136,192,16058,1,2,0:0:0:0:
|
||||
192,192,16166,1,8,0:0:0:0:
|
||||
320,192,16384,1,2,0:0:0:0:
|
||||
264,192,16493,1,0,0:0:0:0:
|
||||
208,192,16601,1,8,0:0:0:0:
|
||||
336,192,16819,5,2,0:0:0:0:
|
||||
280,192,16928,1,0,0:0:0:0:
|
||||
224,192,17036,1,8,0:0:0:0:
|
||||
352,192,17253,2,0,L|416:192,2,41.25,2|0|8,0:0|0:0|0:0,0:0:0:0:
|
||||
240,192,17688,6,0,L|152:192,1,82.5,0|8,0:0|0:0,0:0:0:0:
|
||||
264,192,18123,2,0,L|360:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
232,192,18558,5,2,0:0:0:0:
|
||||
288,192,18666,1,0,0:0:0:0:
|
||||
344,184,18775,1,8,0:0:0:0:
|
||||
448,192,18993,2,0,L|360:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
264,192,19427,6,0,L|352:192,1,82.5,0|8,0:0|0:0,0:0:0:0:
|
||||
432,192,19862,2,0,L|344:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
240,192,20297,6,0,L|152:192,3,82.5,2|8|2|8,0:0|0:0|0:0|0:0,0:0:0:0:
|
||||
272,192,21166,6,0,L|360:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
248,192,21601,2,0,L|336:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
128,192,22036,6,0,L|24:192,1,82.5,6|8,0:0|0:0,0:0:0:0:
|
||||
168,192,22471,1,6,3:0:0:0:
|
||||
112,192,22580,1,0,0:0:0:0:
|
||||
56,192,22688,1,8,0:0:0:0:
|
||||
168,192,22906,5,0,0:0:0:0:
|
||||
224,192,23014,1,2,0:0:0:0:
|
||||
280,192,23123,1,8,0:0:0:0:
|
||||
152,192,23340,2,0,L|96:192,2,41.25,6|0|8,0:3|0:0|0:0,0:0:0:0:
|
||||
280,192,23775,5,2,0:0:0:0:
|
||||
336,192,23883,1,0,0:0:0:0:
|
||||
392,192,23992,1,8,0:0:0:0:
|
||||
272,192,24210,1,6,3:0:0:0:
|
||||
328,192,24318,1,0,0:0:0:0:
|
||||
384,192,24427,1,8,0:0:0:0:
|
||||
272,192,24645,6,0,L|176:192,1,82.5,0|8,0:0|0:0,0:0:0:0:
|
||||
288,192,25080,2,0,L|200:192,1,82.5,6|8,0:3|0:0,0:0:0:0:
|
||||
88,192,25514,6,0,L|24:192,2,41.25,2|0|8,0:0|0:0|0:0,0:0:0:0:
|
||||
192,192,25949,2,0,L|104:192,1,82.5,6|8,3:0|0:0,0:0:0:0:
|
||||
224,192,26384,6,0,L|320:192,1,82.5,0|8,0:0|0:0,0:0:0:0:
|
||||
208,192,26819,2,0,L|120:192,1,82.5,6|8,0:3|0:0,0:0:0:0:
|
||||
232,192,27253,6,0,L|280:192,2,41.25,2|0|8,0:0|0:0|0:0,0:0:0:0:
|
||||
328,192,27688,2,0,L|384:192,2,41.25,6|0|8,3:0|0:0|0:0,0:0:0:0:
|
||||
224,192,28123,6,0,L|320:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
216,192,28558,2,0,L|120:192,1,82.5,6|8,0:3|0:0,0:0:0:0:
|
||||
360,192,28993,6,0,L|248:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
168,192,29427,1,6,3:0:0:0:
|
||||
224,192,29536,1,0,0:0:0:0:
|
||||
277,192,29645,1,8,0:0:0:0:
|
||||
392,192,29862,6,0,L|448:192,2,41.25,0|2|8,0:0|0:0|0:0,0:0:0:0:
|
||||
264,184,30297,1,6,0:3:0:0:
|
||||
320,192,30406,1,0,0:0:0:0:
|
||||
376,192,30514,1,8,0:0:0:0:
|
||||
256,192,30732,6,0,L|200:192,2,41.25,2|0|8,0:0|0:0|0:0,0:0:0:0:
|
||||
384,192,31166,2,0,L|432:192,2,41.25,6|0|8,3:0|0:0|0:0,0:0:0:0:
|
||||
280,192,31601,6,0,L|376:192,1,82.5,0|8,0:0|0:0,0:0:0:0:
|
||||
456,192,32036,2,0,L|352:192,1,82.5,6|8,0:3|0:0,0:0:0:0:
|
||||
248,192,32471,5,2,0:0:0:0:
|
||||
192,192,32580,1,0,0:0:0:0:
|
||||
136,192,32688,1,8,0:0:0:0:
|
||||
24,192,32906,2,0,L|128:192,1,82.5,6|8,3:0|0:0,0:0:0:0:
|
||||
208,192,33340,6,0,L|328:192,1,82.5,0|8,0:0|0:0,0:0:0:0:
|
||||
192,192,33775,2,0,L|296:192,1,82.5,6|8,0:3|0:0,0:0:0:0:
|
||||
392,192,34210,6,0,L|488:192,3,82.5,2|8|6|8,0:0|0:0|3:0|0:0,0:0:0:0:
|
||||
256,192,35080,1,4,0:0:0:0:
|
||||
424,192,35406,1,4,0:0:0:0:
|
||||
256,192,35732,1,4,0:0:0:0:
|
||||
32,192,35949,5,6,0:0:0:0:
|
||||
48,192,36058,1,0,0:0:0:0:
|
||||
80,192,36166,1,8,0:0:0:0:
|
||||
128,192,36275,1,0,0:0:0:0:
|
||||
192,192,36384,1,2,0:0:0:0:
|
||||
376,192,36819,1,2,0:0:0:0:
|
||||
224,192,37145,2,0,L|360:192,1,123.75,2|8,0:0|0:0,0:0:0:0:
|
||||
120,192,37688,6,0,L|120:96,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
232,192,38123,2,0,L|232:104,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
120,192,38558,2,0,L|56:192,2,41.25,2|0|10,0:0|0:0|0:0,0:0:0:0:
|
||||
192,192,38884,2,0,L|328:192,1,123.75,2|8,0:0|0:0,0:0:0:0:
|
||||
192,192,39427,5,2,0:0:0:0:
|
||||
312,192,39645,2,0,L|408:192,1,82.5,8|2,0:0|0:0,0:0:0:0:
|
||||
280,192,40080,1,8,0:0:0:0:
|
||||
496,192,40297,2,0,L|368:192,2,123.75,2|2|8,0:0|0:0|0:0,0:0:0:0:
|
||||
272,192,41166,6,0,L|152:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
304,192,41601,2,0,L|424:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
272,192,42036,2,0,L|424:192,2,123.75,2|2|8,0:0|0:0|0:0,0:0:0:0:
|
||||
496,192,42906,5,6,0:0:0:0:
|
||||
384,192,43123,2,0,L|288:192,2,82.5,8|2|8,0:0|0:0|0:0,0:0:0:0:
|
||||
272,192,43775,1,2,0:0:0:0:
|
||||
104,192,44101,2,0,L|240:192,1,123.75,2|8,0:0|0:0,0:0:0:0:
|
||||
456,192,44645,6,0,L|456:104,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
336,192,45080,2,0,L|336:104,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
456,192,45514,1,2,0:0:0:0:
|
||||
288,192,45840,2,0,L|144:192,1,123.75,2|8,0:0|0:0,0:0:0:0:
|
||||
288,192,46384,5,2,0:0:0:0:
|
||||
184,192,46601,2,0,L|280:192,1,82.5,8|2,0:0|0:0,0:0:0:0:
|
||||
384,192,47036,1,8,0:0:0:0:
|
||||
160,192,47253,2,0,L|32:192,2,123.75,2|2|8,0:0|0:0|0:0,0:0:0:0:
|
||||
384,192,48123,6,0,L|272:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
424,192,48558,1,2,0:0:0:0:
|
||||
301,192,48775,1,8,0:0:0:0:
|
||||
176,192,48993,2,0,L|128:192,2,41.25,2|0|10,0:0|0:0|0:0,0:0:0:0:
|
||||
336,192,49536,1,2,0:0:0:0:
|
||||
56,192,49862,5,2,0:0:0:0:
|
||||
152,192,50080,1,8,0:0:0:0:
|
||||
248,192,50297,1,2,0:0:0:0:
|
||||
344,192,50514,1,8,0:0:0:0:
|
||||
232,192,50732,6,0,L|184:192,1,41.25,2|0,0:0|0:0,0:0:0:0:
|
||||
304,192,51058,1,2,0:0:0:0:
|
||||
424,192,51275,2,0,L|360:192,1,41.25,2|8,0:0|0:0,0:0:0:0:
|
||||
272,192,51601,5,2,0:0:0:0:
|
||||
384,192,51819,1,12,0:0:0:0:
|
||||
328,192,51927,1,2,0:0:0:0:
|
||||
272,192,52036,1,2,0:0:0:0:
|
||||
160,192,52253,1,12,1:0:0:0:
|
||||
272,192,52471,5,2,0:0:0:0:
|
||||
160,192,52689,1,12,0:0:0:0:
|
||||
216,192,52797,1,2,0:0:0:0:
|
||||
272,192,52906,1,2,0:0:0:0:
|
||||
384,192,53123,1,12,1:0:0:0:
|
||||
160,192,53340,5,2,0:0:0:0:
|
||||
256,192,53558,1,8,0:0:0:0:
|
||||
160,192,53775,1,2,0:0:0:0:
|
||||
256,192,53993,1,8,0:0:0:0:
|
||||
144,192,54210,6,0,L|16:192,2,123.75,2|2|8,0:0|0:0|0:0,0:0:0:0:
|
||||
264,192,55080,5,2,0:0:0:0:
|
||||
144,192,55297,1,12,0:0:0:0:
|
||||
88,192,55406,1,2,0:0:0:0:
|
||||
32,192,55514,1,2,0:0:0:0:
|
||||
152,192,55732,1,12,1:0:0:0:
|
||||
256,192,55949,5,2,0:0:0:0:
|
||||
376,192,56166,1,12,0:0:0:0:
|
||||
432,192,56275,1,2,0:0:0:0:
|
||||
488,192,56383,1,2,0:0:0:0:
|
||||
368,192,56601,1,12,1:0:0:0:
|
||||
144,192,56819,5,2,0:0:0:0:
|
||||
48,192,57036,1,8,0:0:0:0:
|
||||
160,192,57253,1,2,0:0:0:0:
|
||||
48,192,57471,1,8,0:0:0:0:
|
||||
168,192,57688,6,0,L|320:192,2,123.75,2|2|8,0:0|0:0|0:0,0:0:0:0:
|
||||
40,192,58558,5,2,0:0:0:0:
|
||||
168,192,58775,1,12,0:0:0:0:
|
||||
112,192,58884,1,2,0:0:0:0:
|
||||
56,192,58993,1,2,0:0:0:0:
|
||||
184,192,59210,1,12,1:0:0:0:
|
||||
296,192,59427,5,2,0:0:0:0:
|
||||
168,192,59644,1,12,0:0:0:0:
|
||||
224,192,59753,1,2,0:0:0:0:
|
||||
280,192,59862,1,2,0:0:0:0:
|
||||
152,192,60079,1,12,1:0:0:0:
|
||||
376,192,60297,5,2,0:0:0:0:
|
||||
272,192,60514,1,8,0:0:0:0:
|
||||
152,192,60732,1,2,0:0:0:0:
|
||||
272,192,60949,1,8,0:0:0:0:
|
||||
144,192,61166,6,0,L|296:192,2,123.75,2|2|8,0:0|0:0|0:0,0:0:0:0:
|
||||
272,192,62036,5,2,0:0:0:0:
|
||||
144,192,62253,1,8,0:0:0:0:
|
||||
200,192,62362,1,2,0:0:0:0:
|
||||
256,192,62471,1,2,0:0:0:0:
|
||||
128,192,62688,1,8,0:0:0:0:
|
||||
16,192,62906,5,2,0:0:0:0:
|
||||
144,192,63123,1,8,0:0:0:0:
|
||||
200,192,63232,1,2,0:0:0:0:
|
||||
256,192,63341,1,2,0:0:0:0:
|
||||
192,192,63449,1,2,0:0:0:0:
|
||||
128,192,63558,1,8,0:0:0:0:
|
||||
352,192,63775,5,6,0:0:0:0:
|
||||
472,192,63993,2,0,L|376:192,2,82.5,8|2|8,0:0|0:0|0:0,0:0:0:0:
|
||||
360,192,64645,2,0,L|304:192,1,41.25,2|0,0:0|0:0,0:0:0:0:
|
||||
432,192,64971,2,0,L|288:192,1,123.75,2|8,0:0|0:0,0:0:0:0:
|
||||
208,192,65514,5,2,0:0:0:0:
|
||||
320,192,65732,1,8,0:0:0:0:
|
||||
208,192,65949,2,0,L|144:192,1,41.25,2|2,0:0|0:0,0:0:0:0:
|
||||
280,192,66275,1,0,0:0:0:0:
|
||||
400,192,66493,2,0,L|504:192,1,82.5,0|2,0:0|0:0,0:0:0:0:
|
||||
360,192,66982,1,2,0:0:0:0:
|
||||
208,192,67253,5,2,0:0:0:0:
|
||||
96,192,67471,1,8,0:0:0:0:
|
||||
40,192,67580,1,0,0:0:0:0:
|
||||
40,192,67688,1,2,0:0:0:0:
|
||||
96,192,67797,1,2,0:0:0:0:
|
||||
208,192,68014,2,0,L|104:192,2,82.5,0|0|2,0:0|0:0|0:0,0:0:0:0:
|
||||
328,192,68666,2,0,L|384:192,1,41.25,2|8,0:0|0:0,0:0:0:0:
|
||||
240,192,68993,6,0,L|184:192,2,41.25,2|0|8,0:0|0:0|0:0,0:0:0:0:
|
||||
296,192,69319,2,0,L|392:192,1,82.5,0|2,0:0|0:0,0:0:0:0:
|
||||
312,192,69645,1,8,0:0:0:0:
|
||||
264,192,69753,1,0,0:0:0:0:
|
||||
432,192,70080,1,10,0:0:0:0:
|
||||
264,192,70514,1,8,0:0:0:0:
|
||||
50,191,70732,6,0,L|146:191,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
240,192,71166,2,0,L|152:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
272,192,71601,2,0,L|176:192,1,82.5,2|10,0:0|0:0,0:0:0:0:
|
||||
312,192,72036,2,0,L|416:192,1,82.5,0|8,0:0|0:0,0:0:0:0:
|
||||
168,192,72471,6,0,L|80:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
200,192,72906,2,0,L|304:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
176,192,73340,2,0,L|272:192,1,82.5,2|10,0:0|0:0,0:0:0:0:
|
||||
152,192,73775,2,0,L|64:192,1,82.5,0|8,0:0|0:0,0:0:0:0:
|
||||
296,192,74210,6,0,L|392:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
272,192,74645,2,0,L|360:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
461,192,75080,2,0,L|365:192,1,82.5,2|10,0:0|0:0,0:0:0:0:
|
||||
160,192,75514,1,0,0:0:0:0:
|
||||
160,192,75732,1,8,0:0:0:0:
|
||||
384,184,75949,5,6,0:0:0:0:
|
||||
192,192,76384,1,6,1:0:0:0:
|
||||
16,192,76819,2,0,L|160:112,1,165,6|6,0:0|1:0,0:0:0:0:
|
||||
496,192,77688,5,6,0:0:0:0:
|
||||
344,192,78014,1,0,0:0:0:0:
|
||||
496,192,78340,1,8,0:0:0:0:
|
||||
344,192,78666,1,2,0:0:0:0:
|
||||
192,192,78993,2,0,L|312:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
48,192,79427,5,2,0:0:0:0:
|
||||
200,192,79753,1,0,0:0:0:0:
|
||||
48,192,80079,1,8,0:0:0:0:
|
||||
200,192,80405,1,2,0:0:0:0:
|
||||
352,192,80732,2,0,L|232:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
48,192,81166,6,0,L|16:144,2,55,2|0|0,0:0|0:0|0:0,0:0:0:0:
|
||||
128,192,81601,1,2,0:0:0:0:
|
||||
200,192,81746,1,0,0:0:0:0:
|
||||
256,192,81891,1,0,0:0:0:0:
|
||||
192,192,82036,6,0,L|128:192,2,55,2|0|0,0:0|0:0|0:0,0:0:0:0:
|
||||
264,192,82471,1,2,0:0:0:0:
|
||||
344,192,82616,1,0,0:0:0:0:
|
||||
224,192,82906,5,2,0:0:0:0:
|
||||
360,192,83232,1,0,0:0:0:0:
|
||||
232,192,83485,1,0,0:0:0:0:
|
||||
160,192,83630,1,0,0:0:0:0:
|
||||
240,192,83775,6,0,L|304:192,1,55,2|0,0:0|0:0,0:0:0:0:
|
||||
224,192,84065,2,0,P|112:160|96:144,1,137.5,0|8,0:0|0:0,0:0:0:0:
|
||||
224,192,84645,6,0,L|80:192,1,123.75,2|0,0:0|0:0,0:0:0:0:
|
||||
248,192,85297,2,0,L|312:192,1,41.25,8|2,0:0|0:0,0:0:0:0:
|
||||
224,192,85514,1,2,0:0:0:0:
|
||||
112,192,85732,1,8,0:0:0:0:
|
||||
224,192,85949,2,0,L|296:192,2,55,2|0|0,0:0|0:0|0:0,0:0:0:0:
|
||||
144,192,86384,5,2,0:0:0:0:
|
||||
72,192,86529,1,0,0:0:0:0:
|
||||
16,192,86674,1,0,0:0:0:0:
|
||||
88,192,86819,1,2,0:0:0:0:
|
||||
160,192,86964,1,0,0:0:0:0:
|
||||
88,192,87109,1,0,0:0:0:0:
|
||||
16,192,87253,1,2,0:0:0:0:
|
||||
136,192,87471,2,0,L|304:192,1,165,8|8,0:0|0:0,0:0:0:0:
|
||||
192,192,88123,6,0,L|64:192,1,123.75,2|0,0:0|0:0,0:0:0:0:
|
||||
128,192,88558,1,2,0:0:0:0:
|
||||
168,192,88630,1,0,0:0:0:0:
|
||||
168,192,88703,1,0,0:0:0:0:
|
||||
128,192,88775,1,8,0:0:0:0:
|
||||
272,192,89101,1,2,0:0:0:0:
|
||||
112,192,89427,1,2,0:0:0:0:
|
||||
232,192,89645,1,8,0:0:0:0:
|
||||
456,192,89862,6,0,L|456:96,1,82.5,6|8,0:0|0:0,0:0:0:0:
|
||||
328,192,90297,2,0,L|424:192,1,82.5,6|8,1:0|0:0,0:0:0:0:
|
||||
184,192,90732,2,0,L|88:192,1,82.5,6|8,0:0|0:0,0:0:0:0:
|
||||
216,192,91166,2,0,L|312:192,1,82.5,6|8,1:0|0:0,0:0:0:0:
|
||||
72,192,91601,6,0,L|24:120,1,82.5,6|8,0:0|0:0,0:0:0:0:
|
||||
144,192,92036,2,0,L|240:192,1,82.5,6|8,3:0|0:0,0:0:0:0:
|
||||
328,192,92471,2,0,L|432:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
296,192,92906,2,0,L|392:192,1,82.5,6|8,3:0|0:0,0:0:0:0:
|
||||
264,192,93340,6,0,L|176:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
296,192,93775,2,0,L|208:192,1,82.5,6|8,3:0|0:0,0:0:0:0:
|
||||
99,192,94210,2,0,L|187:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
67,192,94645,2,0,L|155:192,1,82.5,6|8,3:0|0:0,0:0:0:0:
|
||||
376,192,95080,6,0,L|424:192,1,41.25,2|2,0:0|0:0,0:0:0:0:
|
||||
312,192,95406,1,0,0:0:0:0:
|
||||
240,192,95514,2,0,L|136:192,1,82.5,6|8,3:0|0:0,0:0:0:0:
|
||||
280,192,95949,6,0,L|392:192,1,82.5,2|8,0:0|0:0,0:0:0:0:
|
||||
488,192,96384,2,0,L|392:192,1,82.5,6|8,3:0|0:0,0:0:0:0:
|
||||
184,192,96819,5,2,0:0:0:0:
|
||||
128,192,96927,1,2,0:0:0:0:
|
||||
72,192,97036,1,8,0:0:0:0:
|
||||
192,192,97253,1,6,3:0:0:0:
|
||||
248,192,97361,1,2,0:0:0:0:
|
||||
304,192,97470,1,8,0:0:0:0:
|
||||
80,192,97688,6,0,L|24:192,2,41.25,2|2|8,0:0|0:0|0:0,0:0:0:0:
|
||||
208,192,98123,1,6,3:0:0:0:
|
||||
152,192,98232,1,2,0:0:0:0:
|
||||
96,192,98340,1,8,0:0:0:0:
|
||||
38,192,98449,1,0,0:0:0:0:
|
||||
496,192,99210,6,0,L|216:192,1,247.5,4|0,0:0|0:0,0:0:0:0:
|
||||
@@ -0,0 +1,307 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
AudioFilename: audio.mp3
|
||||
AudioLeadIn: 0
|
||||
PreviewTime: 65920
|
||||
Countdown: 0
|
||||
SampleSet: Soft
|
||||
StackLeniency: 0.7
|
||||
Mode: 2
|
||||
LetterboxInBreaks: 0
|
||||
WidescreenStoryboard: 0
|
||||
|
||||
[Editor]
|
||||
DistanceSpacing: 1.5
|
||||
BeatDivisor: 4
|
||||
GridSize: 8
|
||||
TimelineZoom: 1.6
|
||||
|
||||
[Metadata]
|
||||
Title:Cold Skin
|
||||
TitleUnicode:Cold Skin
|
||||
Artist:Seven Lions & Echos
|
||||
ArtistUnicode:Seven Lions & Echos
|
||||
Creator:-Ken
|
||||
Version:Platter
|
||||
Source:
|
||||
Tags:mbomb magic bomb monstercat chill electronic edm melodic dubstep Fursum
|
||||
BeatmapID:1977380
|
||||
BeatmapSetID:946446
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:5
|
||||
CircleSize:3.2
|
||||
OverallDifficulty:8
|
||||
ApproachRate:8
|
||||
SliderMultiplier:1.7
|
||||
SliderTickRate:1
|
||||
|
||||
[Events]
|
||||
//Background and Video events
|
||||
0,0,"owo.jpg",0,0
|
||||
//Break Periods
|
||||
//Storyboard Layer 0 (Background)
|
||||
//Storyboard Layer 1 (Fail)
|
||||
//Storyboard Layer 2 (Pass)
|
||||
//Storyboard Layer 3 (Foreground)
|
||||
//Storyboard Layer 4 (Overlay)
|
||||
//Storyboard Sound Samples
|
||||
|
||||
[TimingPoints]
|
||||
87,416.666666666667,4,2,0,40,1,0
|
||||
13003,-100,4,2,0,25,0,0
|
||||
13420,-200,4,2,0,30,0,0
|
||||
19982,-200,4,2,0,5,0,0
|
||||
20087,-200,4,2,0,30,0,0
|
||||
25295,-100,4,2,0,30,0,0
|
||||
26753,-133.333333333333,4,2,2,50,0,0
|
||||
40087,-100,4,2,0,55,0,0
|
||||
51753,-100,4,2,2,55,0,0
|
||||
53420,-100,4,2,0,60,0,0
|
||||
60087,-142.857142857143,4,2,0,40,0,0
|
||||
63420,-142.857142857143,4,2,0,35,0,0
|
||||
63628,-142.857142857143,4,2,0,17,0,0
|
||||
63837,-142.857142857143,4,2,0,25,0,0
|
||||
64045,-142.857142857143,4,2,0,32,0,0
|
||||
64253,-142.857142857143,4,2,0,40,0,0
|
||||
64462,-142.857142857143,4,2,0,50,0,0
|
||||
64670,-142.857142857143,4,2,0,60,0,0
|
||||
64878,-142.857142857143,4,2,0,72,0,0
|
||||
65087,-285.714285714284,4,2,0,40,0,0
|
||||
65816,-285.714285714284,4,2,0,5,0,0
|
||||
65920,-125,4,2,0,40,0,0
|
||||
66753,-62.5,4,2,2,60,0,1
|
||||
78003,-100,4,2,2,60,0,1
|
||||
79253,-66.6666666666667,4,2,2,60,0,1
|
||||
79670,-66.6666666666667,4,2,2,60,0,0
|
||||
80087,-66.6666666666667,4,2,2,70,0,1
|
||||
81337,-83.3333333333333,4,2,2,70,0,1
|
||||
82587,-66.6666666666667,4,2,2,40,0,0
|
||||
83420,-66.6666666666667,4,2,2,70,0,1
|
||||
84253,-83.3333333333333,4,2,2,70,0,1
|
||||
84878,-66.6666666666667,4,2,2,70,0,1
|
||||
85920,-83.3333333333333,4,2,2,70,0,1
|
||||
86545,-66.6666666666667,4,2,2,70,0,1
|
||||
87587,-83.3333333333333,4,2,2,70,0,1
|
||||
88212,-66.6666666666667,4,2,2,70,0,1
|
||||
89253,-133.333333333333,4,2,2,55,0,1
|
||||
91753,-83.3333333333333,4,2,2,55,0,1
|
||||
93420,-83.3333333333333,4,2,2,55,0,0
|
||||
95503,-83.3333333333333,4,2,2,25,0,0
|
||||
|
||||
|
||||
[Colours]
|
||||
Combo1 : 128,128,255
|
||||
Combo2 : 255,194,166
|
||||
Combo3 : 255,128,128
|
||||
|
||||
[HitObjects]
|
||||
256,192,10087,12,0,13003,0:0:0:0:
|
||||
410,158,13420,6,0,L|150:158,1,255,0|0,3:0|0:0,0:0:0:0:
|
||||
453,162,15087,2,0,P|461:218|296:290,1,269.166666666667,0|0,3:0|0:0,0:0:0:0:
|
||||
265,275,16545,2,0,P|171:217|297:144,1,297.5,0|0,0:0|0:0,0:0:0:0:
|
||||
40,272,18420,5,0,3:0:0:0:
|
||||
123,272,18628,1,0,0:0:0:0:
|
||||
184,272,18837,1,0,0:0:0:0:
|
||||
232,272,19045,1,0,0:0:0:0:
|
||||
184,272,19253,1,0,0:0:0:0:
|
||||
123,272,19462,1,0,0:0:0:0:
|
||||
40,272,19670,1,0,0:0:0:0:
|
||||
384,264,20087,6,0,P|456:264|448:120,1,255,0|0,3:0|0:0,0:0:0:0:
|
||||
161,286,21753,2,0,P|91:173|170:144,1,255,0|0,3:0|0:0,0:0:0:0:
|
||||
457,273,23420,2,0,L|457:9,1,255,0|0,3:0|0:0,0:0:0:0:
|
||||
144,235,25087,5,0,3:0:0:0:
|
||||
265,197,25295,2,0,L|84:198,1,170,0|0,0:0|0:0,0:0:0:0:
|
||||
190,157,25920,1,0,0:0:0:0:
|
||||
357,155,26232,1,0,0:0:0:0:
|
||||
190,157,26545,1,0,0:0:0:0:
|
||||
468,120,26753,5,0,1:0:0:0:
|
||||
294,116,27170,1,2,0:3:0:0:
|
||||
152,264,28003,1,2,0:3:0:0:
|
||||
256,264,28212,5,0,0:0:0:0:
|
||||
360,272,28420,2,0,P|392:216|352:144,1,127.500004863739,0|2,1:0|0:3,0:0:0:0:
|
||||
272,144,29045,1,0,0:0:0:0:
|
||||
64,144,29462,1,0,0:0:0:0:
|
||||
176,144,29670,1,2,0:3:0:0:
|
||||
64,144,29878,1,0,0:0:0:0:
|
||||
281,139,30087,6,0,P|321:171|289:243,1,127.500004863739,0|2,1:0|0:3,0:0:0:0:
|
||||
21,297,31337,1,2,0:3:0:0:
|
||||
111,297,31545,1,0,0:0:0:0:
|
||||
301,296,31753,6,0,L|157:296,2,127.500004863739,0|2|0,1:0|0:3|0:0,0:0:0:0:
|
||||
402,286,32795,1,0,0:0:0:0:
|
||||
458,286,33003,1,2,0:3:0:0:
|
||||
402,286,33212,1,0,0:0:0:0:
|
||||
190,201,33420,86,0,P|162:246|179:333,1,127.500004863739,0|2,1:0|0:3,0:0:0:0:
|
||||
371,240,34253,1,0,0:0:0:0:
|
||||
371,240,34462,1,0,0:0:0:0:
|
||||
371,240,34670,1,2,0:3:0:0:
|
||||
371,240,34878,1,0,0:0:0:0:
|
||||
240,224,35087,86,0,P|204:176|249:130,1,127.500004863739,0|2,1:0|0:3,0:0:0:0:
|
||||
440,152,35920,1,0,0:0:0:0:
|
||||
440,152,36128,1,0,0:0:0:0:
|
||||
440,152,36337,1,2,0:3:0:0:
|
||||
440,152,36545,1,0,0:0:0:0:
|
||||
230,265,36753,6,0,L|374:265,1,127.500004863739,0|2,1:0|0:3,0:0:0:0:
|
||||
179,299,37587,1,0,0:0:0:0:
|
||||
251,299,37795,1,0,0:0:0:0:
|
||||
179,299,38003,1,2,0:3:0:0:
|
||||
251,299,38212,1,0,0:0:0:0:
|
||||
456,198,38420,6,0,L|320:198,2,127.500004863739,0|2|0,1:0|0:3|0:0,0:0:0:0:
|
||||
260,206,39462,1,0,1:0:0:0:
|
||||
177,190,39670,1,2,0:3:0:0:
|
||||
158,125,39878,1,0,0:0:0:0:
|
||||
459,200,40087,6,0,L|459:16,1,170,0|8,1:0|0:0,0:0:0:0:
|
||||
357,145,40712,2,0,L|173:145,1,170,0|0,1:0|0:0,0:0:0:0:
|
||||
289,112,41337,1,8,0:0:0:0:
|
||||
24,104,41753,6,0,L|24:304,1,170,0|8,1:0|0:0,0:0:0:0:
|
||||
127,280,42378,1,0,1:0:0:0:
|
||||
230,279,42587,2,0,L|137:126,1,170,0|8,0:0|0:0,0:0:0:0:
|
||||
467,127,43420,86,0,L|467:319,1,170,0|8,1:0|0:0,0:0:0:0:
|
||||
360,286,44045,2,0,L|360:101,1,170,0|0,1:0|0:0,0:0:0:0:
|
||||
474,113,44670,1,8,0:0:0:0:
|
||||
360,116,44878,1,0,0:0:0:0:
|
||||
153,183,45087,6,0,L|334:184,1,170,0|8,1:0|0:0,0:0:0:0:
|
||||
238,181,45712,1,0,1:0:0:0:
|
||||
153,183,45920,2,0,L|358:182,1,170,0|8,0:0|0:0,0:0:0:0:
|
||||
43,78,46753,86,0,L|235:78,1,170,0|8,1:0|0:0,0:0:0:0:
|
||||
315,104,47378,2,0,L|132:106,1,170,0|0,1:0|0:0,0:0:0:0:
|
||||
315,104,48003,1,8,0:0:0:0:
|
||||
485,251,48420,6,0,L|285:250,1,170,0|8,1:0|0:0,0:0:0:0:
|
||||
213,250,49045,1,0,1:0:0:0:
|
||||
402,250,49253,2,0,P|426:196|355:129,1,170,0|8,0:0|0:0,0:0:0:0:
|
||||
249,153,49878,1,0,0:0:0:0:
|
||||
21,163,50087,86,0,L|212:163,1,170,0|8,1:0|0:0,0:0:0:0:
|
||||
293,170,50712,2,0,P|335:188|305:293,1,170,0|0,1:0|0:0,0:0:0:0:
|
||||
212,296,51337,1,8,0:0:0:0:
|
||||
24,299,51753,6,0,L|23:186,1,85,0|0,1:0|0:0,0:0:0:0:
|
||||
92,211,52066,1,2,0:3:0:0:
|
||||
160,209,52170,1,8,0:0:0:0:
|
||||
350,208,52378,2,0,P|370:170|284:110,1,170,0|0,1:0|0:0,0:0:0:0:
|
||||
143,177,53003,2,0,L|35:177,1,85,8|0,0:0|0:0,0:0:0:0:
|
||||
393,336,53420,86,0,L|393:154,1,170,0|8,1:0|0:0,0:0:0:0:
|
||||
201,295,54045,2,0,L|406:295,1,170,0|0,1:0|0:0,0:0:0:0:
|
||||
482,235,54670,2,0,L|383:235,1,85,8|0,0:0|0:0,0:0:0:0:
|
||||
205,232,55087,6,0,L|219:321,1,85,0|0,1:0|0:0,0:0:0:0:
|
||||
342,250,55503,2,0,L|340:339,1,85,8|0,0:0|1:0,0:0:0:0:
|
||||
218,231,55920,2,0,L|414:176,1,170,0|8,0:0|0:0,0:0:0:0:
|
||||
245,135,56545,1,0,0:0:0:0:
|
||||
32,148,56753,86,0,L|32:348,1,170,0|8,1:0|0:0,0:0:0:0:
|
||||
223,180,57378,2,0,L|223:364,1,170,0|0,1:0|0:0,0:0:0:0:
|
||||
104,244,58003,2,0,L|85:346,1,85,8|0,0:0|0:0,0:0:0:0:
|
||||
266,224,58420,6,0,L|207:121,1,85,0|0,1:0|0:0,0:0:0:0:
|
||||
104,139,58837,2,0,L|100:25,1,85,8|0,0:0|1:0,0:0:0:0:
|
||||
306,119,59253,2,0,P|375:124|443:193,1,170,0|8,0:0|0:0,0:0:0:0:
|
||||
358,218,59878,1,0,0:0:0:0:
|
||||
59,249,60087,22,0,L|196:250,2,118.999996368408,0|0|8,1:0|0:0|0:0,0:0:0:0:
|
||||
157,248,61128,1,0,0:0:0:0:
|
||||
59,249,61337,2,0,L|203:236,2,118.999996368408,0|0|0,0:0|0:0|0:0,0:0:0:0:
|
||||
132,249,62378,1,0,1:0:0:0:
|
||||
331,218,62587,2,0,L|468:218,1,118.999996368408,8|0,0:0|0:0,0:0:0:0:
|
||||
159,325,63420,5,0,1:0:0:0:
|
||||
64,270,63628,1,8,0:0:0:0:
|
||||
64,270,63837,1,8,0:0:0:0:
|
||||
159,325,64045,1,8,0:0:0:0:
|
||||
371,327,64253,5,8,0:0:0:0:
|
||||
466,272,64462,1,8,0:0:0:0:
|
||||
496,167,64670,1,8,0:0:0:0:
|
||||
445,73,64878,1,8,0:0:0:0:
|
||||
173,316,65087,6,0,B|64:316,1,104.124996822357
|
||||
34,309,65920,2,0,P|17:258|68:186,1,136,0|0,0:0|1:0,0:0:0:0:
|
||||
168,183,66545,1,8,0:0:0:0:
|
||||
444,174,66753,5,6,1:2:0:0:
|
||||
289,245,67066,2,0,L|299:173,3,68,2|2|2|0,0:3|0:0|0:0|0:0,0:0:0:0:
|
||||
26,169,67587,5,8,0:0:0:0:
|
||||
187,168,67899,1,2,0:3:0:0:
|
||||
121,173,68003,1,2,1:2:0:0:
|
||||
77,125,68107,1,2,0:0:0:0:
|
||||
88,60,68212,1,2,0:0:0:0:
|
||||
278,56,68420,2,0,L|485:56,1,204,2|2,0:0|0:3,0:0:0:0:
|
||||
474,95,68837,2,0,L|490:169,2,68,0|2|2,1:0|0:0|0:0,0:0:0:0:
|
||||
203,126,69253,5,8,0:0:0:0:
|
||||
333,184,69566,1,2,0:3:0:0:
|
||||
399,189,69670,1,2,0:0:0:0:
|
||||
443,141,69774,1,2,0:0:0:0:
|
||||
432,76,69878,1,0,0:0:0:0:
|
||||
242,72,70087,2,0,L|35:72,1,204,2|0,1:2|0:0,0:0:0:0:
|
||||
49,111,70503,2,0,L|37:191,2,68,2|2|0,0:0|0:0|0:0,0:0:0:0:
|
||||
336,143,70920,5,8,0:0:0:0:
|
||||
195,186,71232,1,2,0:3:0:0:
|
||||
181,123,71337,1,2,1:2:0:0:
|
||||
240,83,71441,1,2,0:0:0:0:
|
||||
308,106,71545,1,2,0:0:0:0:
|
||||
496,114,71753,2,0,L|496:288,1,136,2|0,0:0|1:0,0:0:0:0:
|
||||
419,251,72066,1,2,0:3:0:0:
|
||||
342,255,72170,1,0,1:0:0:0:
|
||||
410,181,72378,1,2,0:0:0:0:
|
||||
103,194,72587,5,8,0:0:0:0:
|
||||
37,169,72795,1,0,0:0:0:0:
|
||||
9,106,72899,1,0,0:0:0:0:
|
||||
29,44,73003,1,8,3:0:0:0:
|
||||
86,12,73107,1,8,3:0:0:0:
|
||||
151,26,73211,1,8,0:0:0:0:
|
||||
426,72,73420,6,0,P|350:229|297:235,1,204,4|2,1:0|0:3,0:0:0:0:
|
||||
281,240,73837,1,2,0:0:0:0:
|
||||
222,240,73941,1,2,0:0:0:0:
|
||||
164,240,74045,1,0,0:0:0:0:
|
||||
449,90,74253,5,8,0:0:0:0:
|
||||
449,90,74566,1,2,0:3:0:0:
|
||||
473,151,74670,1,2,1:2:0:0:
|
||||
442,209,74774,1,2,0:0:0:0:
|
||||
378,223,74878,1,2,0:0:0:0:
|
||||
188,226,75087,6,0,P|130:179|240:103,1,204,2|2,0:0|0:3,0:0:0:0:
|
||||
250,90,75503,1,2,1:3:0:0:
|
||||
316,85,75607,1,2,0:3:0:0:
|
||||
337,25,75712,1,2,0:3:0:0:
|
||||
60,91,75920,5,8,0:0:0:0:
|
||||
60,91,76128,1,0,0:0:0:0:
|
||||
44,161,76232,1,2,0:3:0:0:
|
||||
66,230,76336,1,2,0:0:0:0:
|
||||
120,279,76440,1,0,0:0:0:0:
|
||||
192,293,76544,1,0,0:0:0:0:
|
||||
393,259,76753,6,0,P|492:196|413:114,1,204,0|2,1:0|0:3,0:0:0:0:
|
||||
413,87,77170,1,2,0:0:0:0:
|
||||
355,59,77274,1,2,0:3:0:0:
|
||||
295,56,77378,1,2,0:3:0:0:
|
||||
20,173,77587,1,8,0:0:0:0:
|
||||
30,229,77795,6,0,L|92:174,1,68,0|2,0:0|0:3,0:0:0:0:
|
||||
178,162,78003,2,0,L|173:275,1,85,2|2,1:2|0:0,0:0:0:0:
|
||||
379,209,78420,2,0,L|174:208,2,170,2|0|8,0:0|1:0|0:0,0:0:0:0:
|
||||
92,208,79670,1,8,3:0:0:0:
|
||||
112,129,79878,1,8,0:0:0:0:
|
||||
411,300,80087,6,0,P|431:286|321:172,1,255.000009727478,4|2,1:0|0:3,0:0:0:0:
|
||||
228,294,80712,2,0,P|155:267|259:166,1,255.000009727478,0|2,1:0|0:3,0:0:0:0:
|
||||
340,138,81337,2,0,L|106:139,2,203.999993774414,0|0|2,1:0|0:0|0:3,0:0:0:0:
|
||||
137,239,82378,2,0,L|100:174,2,50.9999984436036,0|0|8,1:0|0:0|0:0,0:0:0:0:
|
||||
137,239,83420,5,6,1:2:0:0:
|
||||
197,237,83524,1,2,0:0:0:0:
|
||||
257,240,83628,1,2,0:0:0:0:
|
||||
389,243,83837,2,0,L|414:181,2,63.7500024318696,2|2|2,0:0|0:0|0:0,0:0:0:0:
|
||||
101,237,84253,2,0,L|-13:237,1,101.999996887207,8|0,0:0|0:0,0:0:0:0:
|
||||
110,164,84670,2,0,L|5:88,1,101.999996887207,2|2,1:2|0:0,0:0:0:0:
|
||||
225,256,85087,5,0,0:0:0:0:
|
||||
278,229,85191,1,0,0:0:0:0:
|
||||
225,210,85295,1,0,1:0:0:0:
|
||||
95,217,85503,2,0,L|71:158,2,63.7500024318696,0|2|2,1:0|0:0|0:0,0:0:0:0:
|
||||
399,158,85920,2,0,L|518:158,1,101.999996887207,8|0,0:0|0:0,0:0:0:0:
|
||||
390,95,86337,2,0,L|483:55,1,101.999996887207,2|8,0:0|0:3,0:0:0:0:
|
||||
201,289,86753,6,0,L|201:207,2,63.7500024318696,0|0|0,1:0|0:0|0:0,0:0:0:0:
|
||||
350,228,87170,2,0,L|368:158,2,63.7500024318696,2|2|2,0:3|0:3|0:3,0:0:0:0:
|
||||
65,323,87587,2,0,L|63:198,1,101.999996887207,8|0,0:0|0:0,0:0:0:0:
|
||||
237,214,88003,2,0,L|238:335,1,101.999996887207,0|2,1:0|0:3,0:0:0:0:
|
||||
104,303,88420,5,2,0:3:0:0:
|
||||
173,291,88524,1,8,0:0:0:0:
|
||||
263,228,88732,1,2,0:3:0:0:
|
||||
185,248,88836,1,0,1:0:0:0:
|
||||
114,215,88940,1,2,0:3:0:0:
|
||||
80,145,89044,1,2,0:3:0:0:
|
||||
375,171,89253,2,0,P|445:228|367:274,1,191.250007295609,8|0,0:0|0:0,0:0:0:0:
|
||||
76,318,90087,5,0,1:0:0:0:
|
||||
172,312,90295,2,0,L|172:263,1,31.8750012159348,0|2,0:0|0:0,0:0:0:0:
|
||||
90,196,90607,2,0,L|90:141,1,31.8750012159348,2|0,0:0|1:0,0:0:0:0:
|
||||
386,160,90920,2,0,L|386:264,1,63.7500024318696,8|0,0:0|0:0,0:0:0:0:
|
||||
196,237,91337,6,0,L|196:145,1,63.7500024318696,0|0,1:0|0:0,0:0:0:0:
|
||||
339,108,91753,2,0,L|120:105,2,203.999993774414,0|0|10,0:0|1:0|0:0,0:0:0:0:
|
||||
64,281,93003,2,0,L|23:179,1,101.999996887207,8|8,0:3|0:0,0:0:0:0:
|
||||
302,173,93420,5,0,1:0:0:0:
|
||||
256,192,93628,12,0,95503,0:0:0:0:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,878 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
AudioFilename: audio.mp3
|
||||
AudioLeadIn: 0
|
||||
PreviewTime: 79621
|
||||
Countdown: 0
|
||||
SampleSet: Soft
|
||||
StackLeniency: 0.7
|
||||
Mode: 2
|
||||
LetterboxInBreaks: 0
|
||||
WidescreenStoryboard: 0
|
||||
|
||||
[Editor]
|
||||
DistanceSpacing: 0.9
|
||||
BeatDivisor: 3
|
||||
GridSize: 32
|
||||
TimelineZoom: 3.699999
|
||||
|
||||
[Metadata]
|
||||
Title:DVP
|
||||
TitleUnicode:DVP
|
||||
Artist:PUP
|
||||
ArtistUnicode:PUP
|
||||
Creator:Jemzuu
|
||||
Version:YUH
|
||||
Source:osu!
|
||||
Tags:ajamez jbhyperion jbh hyperion nelly ataraxia featured artist fa the dream is over english indie punk rock
|
||||
BeatmapID:2420076
|
||||
BeatmapSetID:1159828
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:7
|
||||
CircleSize:3
|
||||
OverallDifficulty:9.8
|
||||
ApproachRate:9.8
|
||||
SliderMultiplier:1.6
|
||||
SliderTickRate:1
|
||||
|
||||
[Events]
|
||||
//Background and Video events
|
||||
0,0,"yuh.jpg",0,0
|
||||
//Break Periods
|
||||
//Storyboard Layer 0 (Background)
|
||||
//Storyboard Layer 1 (Fail)
|
||||
//Storyboard Layer 2 (Pass)
|
||||
//Storyboard Layer 3 (Foreground)
|
||||
//Storyboard Layer 4 (Overlay)
|
||||
//Storyboard Sound Samples
|
||||
|
||||
[TimingPoints]
|
||||
62,202.702702702703,4,2,10,80,1,0
|
||||
1039,-100,4,2,10,80,0,0
|
||||
13034,201.342281879195,4,2,10,80,1,0
|
||||
13175,-100,4,2,1,40,0,0
|
||||
13586,-100,4,2,1,50,0,0
|
||||
13787,-100,4,2,10,60,0,0
|
||||
13991,-100,4,2,1,80,0,0
|
||||
14192,-100,4,2,2,70,0,0
|
||||
14595,-100,4,2,10,80,0,0
|
||||
14810,-100,4,2,1,80,0,0
|
||||
15400,-100,4,2,10,80,0,0
|
||||
15450,206.896551724138,4,2,1,80,1,0
|
||||
16277,202.702702702703,4,2,10,80,1,0
|
||||
26006,-83.3333333333333,4,2,10,80,0,0
|
||||
26172,-83.3333333333333,4,2,10,80,0,0
|
||||
26375,-83.3333333333333,4,2,10,80,0,0
|
||||
26578,-83.3333333333333,4,2,10,80,0,0
|
||||
26780,-83.3333333333333,4,2,10,80,0,0
|
||||
26980,-83.3333333333333,4,2,10,80,0,0
|
||||
27180,-83.3333333333333,4,2,10,80,0,0
|
||||
27580,-83.3333333333333,4,2,10,80,0,0
|
||||
27628,-100,4,2,10,80,0,0
|
||||
29202,-100,4,2,11,60,0,0
|
||||
32290,-100,4,2,11,80,0,0
|
||||
37920,-100,4,2,11,60,0,0
|
||||
38126,-100,4,2,11,80,0,0
|
||||
40601,200.66889632107,4,2,10,80,1,0
|
||||
41354,-100,4,2,10,60,0,0
|
||||
41559,-100,4,2,10,80,0,0
|
||||
42206,202.702702702703,4,2,10,80,1,0
|
||||
42206,-83.3333333333333,4,2,10,80,0,0
|
||||
42372,-83.3333333333333,4,2,10,80,0,0
|
||||
42777,-83.3333333333333,4,2,10,80,0,0
|
||||
43182,-83.3333333333333,4,2,10,80,0,0
|
||||
43588,-83.3333333333333,4,2,10,80,0,0
|
||||
43993,-83.3333333333333,4,2,10,80,0,0
|
||||
44638,-100,4,2,10,80,0,0
|
||||
44809,-100,4,2,2,80,0,0
|
||||
45415,-100,4,2,10,80,0,0
|
||||
49690,-100,4,2,1,80,0,0
|
||||
50298,-100,4,2,10,80,0,0
|
||||
51119,-100,4,2,2,80,0,0
|
||||
51124,260.869565217391,4,2,10,80,1,0
|
||||
51325,-100,4,2,10,80,0,0
|
||||
51384,193.548387096774,4,2,10,80,1,0
|
||||
51917,-100,4,2,10,80,0,0
|
||||
51964,202.702702702703,4,2,10,80,1,0
|
||||
56788,-100,4,2,2,80,0,0
|
||||
57599,-100,4,2,1,80,0,0
|
||||
57798,-100,4,2,10,80,0,0
|
||||
58005,-100,4,2,1,80,0,0
|
||||
58203,-100,4,2,10,80,0,0
|
||||
61651,-100,4,1,10,80,0,0
|
||||
61693,-83.3333333333333,4,1,10,80,0,0
|
||||
61853,-83.3333333333333,4,2,10,80,0,0
|
||||
62056,-83.3333333333333,4,2,10,80,0,0
|
||||
62259,-83.3333333333333,4,2,10,80,0,0
|
||||
62461,-83.3333333333333,4,2,10,80,0,0
|
||||
62662,-83.3333333333333,4,2,10,60,0,0
|
||||
62863,-83.3333333333333,4,2,10,80,0,0
|
||||
63065,-83.3333333333333,4,2,10,80,0,0
|
||||
63266,-83.3333333333333,4,2,10,80,0,0
|
||||
63315,-100,4,2,10,80,0,0
|
||||
64126,200,4,2,10,80,1,0
|
||||
64895,-100,4,2,10,60,0,0
|
||||
64926,202.702702702703,4,2,10,80,1,0
|
||||
65088,-100,4,2,12,60,0,0
|
||||
65705,-100,4,2,10,80,0,0
|
||||
73609,-100,4,2,10,60,0,0
|
||||
74216,-100,4,2,10,80,0,0
|
||||
77898,-83.3333333333333,4,2,10,80,0,0
|
||||
78101,-83.3333333333333,4,2,10,80,0,0
|
||||
78507,-83.3333333333333,4,2,10,80,0,0
|
||||
78912,-83.3333333333333,4,2,10,80,0,0
|
||||
79317,-83.3333333333333,4,2,10,80,0,0
|
||||
79723,-83.3333333333333,4,2,10,80,0,0
|
||||
80331,-100,4,2,12,80,0,0
|
||||
80597,-100,4,2,2,80,0,0
|
||||
80806,-100,4,2,10,80,0,0
|
||||
81125,202.702702702703,4,2,10,80,1,1
|
||||
81125,-100,4,2,1,80,0,1
|
||||
81175,-100,4,2,1,80,0,0
|
||||
83557,206.896551724138,4,2,1,80,1,0
|
||||
84384,202.702702702703,4,2,10,80,1,0
|
||||
86005,200.66889632107,4,2,10,80,1,0
|
||||
87610,202.702702702703,4,2,10,80,1,1
|
||||
87612,-100,4,2,1,80,0,1
|
||||
87660,-100,4,2,1,80,0,0
|
||||
93285,-83.3333333333333,4,2,1,80,0,0
|
||||
93691,-83.3333333333333,4,2,2,80,0,0
|
||||
93896,-83.3333333333333,4,2,3,80,0,0
|
||||
94096,-83.3333333333333,4,2,5,80,0,0
|
||||
94299,-33.3333333333333,4,2,5,80,0,1
|
||||
95718,-33.3333333333333,4,2,5,80,0,0
|
||||
95884,-100,4,2,5,90,0,0
|
||||
95895,-100,4,2,5,80,0,0
|
||||
97542,-33.3333333333333,4,2,5,80,0,1
|
||||
98961,-33.3333333333333,4,2,5,80,0,0
|
||||
99128,-100,4,2,5,80,0,0
|
||||
100582,206.896551724138,4,2,5,80,1,0
|
||||
100749,-100,4,2,10,80,0,0
|
||||
100788,-33.3333333333333,4,2,5,80,0,1
|
||||
102237,202.702702702703,4,2,5,80,1,0
|
||||
102842,-100,4,2,10,80,0,0
|
||||
103013,-100,4,2,5,80,0,0
|
||||
103813,-100,4,2,1,80,0,0
|
||||
103908,-100,4,2,5,80,0,0
|
||||
105423,-100,4,2,5,80,0,0
|
||||
105823,-100,4,2,5,80,0,0
|
||||
106235,-100,4,2,10,80,0,0
|
||||
107045,-100,4,2,7,80,0,0
|
||||
107451,-100,4,2,10,80,0,0
|
||||
108419,-83.3333333333333,4,2,10,80,0,0
|
||||
109737,-100,4,2,10,80,0,0
|
||||
112372,213.523131672598,4,2,10,80,1,0
|
||||
112799,202.702702702703,4,2,10,80,1,0
|
||||
113204,192.926045016077,4,2,10,80,1,0
|
||||
113589,202.702702702703,4,2,10,80,1,0
|
||||
115210,-83.3333333333333,4,2,10,80,0,0
|
||||
115580,-100,4,2,10,80,0,0
|
||||
119467,196.078431372549,4,2,10,80,1,0
|
||||
119653,-100,4,2,1,80,0,0
|
||||
120009,-100,4,2,4,90,0,0
|
||||
120055,202.702702702703,4,2,4,90,1,0
|
||||
120830,-100,4,2,4,80,0,0
|
||||
131569,-100,4,2,4,100,0,0
|
||||
132177,-100,4,2,10,80,0,0
|
||||
132993,-83.3333333333333,4,2,10,80,0,0
|
||||
133027,205.831903945111,4,2,10,80,1,0
|
||||
133027,-83.3333333333333,4,2,10,80,0,0
|
||||
133198,-83.3333333333333,4,2,10,100,0,0
|
||||
133609,-83.3333333333333,4,2,10,100,0,0
|
||||
134020,-83.3333333333333,4,2,10,80,0,0
|
||||
134428,-83.3333333333333,4,2,10,80,0,0
|
||||
134467,243.90243902439,4,2,10,80,1,0
|
||||
134705,-100,4,2,10,80,0,0
|
||||
134711,250,4,2,10,80,1,0
|
||||
135461,262.008733624454,4,2,10,80,1,0
|
||||
135985,284.36018957346,4,2,10,80,1,0
|
||||
136553,312.5,4,2,10,80,1,0
|
||||
137178,286.396181384248,4,2,10,80,1,0
|
||||
137750,-100,4,2,10,80,0,1
|
||||
137822,-100,4,2,10,50,0,0
|
||||
138323,-100,4,2,10,45,0,0
|
||||
139469,-100,4,2,10,40,0,0
|
||||
140614,-100,4,2,10,35,0,0
|
||||
141760,-100,4,2,10,30,0,0
|
||||
142905,-100,4,2,10,20,0,0
|
||||
144051,-100,4,2,10,15,0,0
|
||||
145197,-100,4,2,10,10,0,0
|
||||
145626,-100,4,2,10,5,0,0
|
||||
|
||||
|
||||
[Colours]
|
||||
Combo1 : 192,192,192
|
||||
Combo2 : 0,128,128
|
||||
Combo3 : 255,174,255
|
||||
|
||||
[HitObjects]
|
||||
283,311,62,6,0,L|447:293,1,160,12|0,0:1|0:0,0:0:0:0:
|
||||
228,173,366,2,0,L|68:189,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
291,229,670,2,0,P|313:178|297:131,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
110,73,872,2,0,L|193:64,2,80,8|0|2,0:2|0:0|0:0,0:0:0:0:
|
||||
308,41,1176,2,0,L|404:46,2,80,8|2|2,0:0|0:0|0:0,0:0:0:0:
|
||||
123,60,1480,1,8,0:0:0:0:
|
||||
314,58,1582,1,0,0:2:0:0:
|
||||
119,190,1683,6,0,P|55:223|69:309,1,160,12|0,1:2|0:0,0:0:0:0:
|
||||
274,352,1987,2,0,L|449:326,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
242,241,2291,2,0,L|227:154,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
428,93,2494,2,0,L|332:87,2,80,8|0|2,0:0|0:0|0:0,0:0:0:0:
|
||||
252,62,2798,1,8,0:0:0:0:
|
||||
433,189,2899,2,0,L|348:210,1,80,2|2,0:0|0:0,0:0:0:0:
|
||||
180,340,3102,1,8,0:0:0:0:
|
||||
351,232,3203,1,0,0:0:0:0:
|
||||
167,316,3305,6,0,L|13:250,1,160,8|0,1:0|0:0,0:0:0:0:
|
||||
231,67,3609,2,0,L|243:244,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
454,248,3913,2,0,L|465:165,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
245,132,4116,2,0,L|258:33,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
356,36,4318,1,2,0:0:0:0:
|
||||
133,93,4420,2,0,L|147:177,1,80,10|2,0:0|0:0,0:0:0:0:
|
||||
252,156,4622,1,0,0:0:0:0:
|
||||
37,198,4724,1,10,0:0:0:0:
|
||||
221,290,4825,1,2,0:0:0:0:
|
||||
426,321,4926,6,0,L|443:151,1,160,12|0,1:2|0:0,0:0:0:0:
|
||||
244,110,5230,2,0,L|409:82,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
198,18,5534,2,0,L|102:30,1,80,8|2,0:0|0:0,0:0:0:0:
|
||||
327,226,5737,2,0,L|417:231,2,80,8|0|8,0:0|0:0|1:2,0:0:0:0:
|
||||
137,319,6041,1,8,1:2:0:0:
|
||||
318,200,6143,2,0,L|234:193,1,80,8|8,1:2|1:2,0:0:0:0:
|
||||
27,95,6345,2,0,L|140:87,1,80,8|8,1:2|1:2,0:0:0:0:
|
||||
325,338,6548,6,0,L|161:325,1,160,12|0,1:0|0:0,0:0:0:0:
|
||||
398,267,6852,2,0,L|412:76,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
218,38,7156,2,0,L|330:26,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
107,136,7359,2,0,L|17:133,2,80,8|0|2,0:0|0:0|0:0,0:0:0:0:
|
||||
292,236,7663,2,0,L|199:247,2,80,8|2|2,0:0|0:0|0:0,0:0:0:0:
|
||||
480,354,7967,1,8,0:0:0:0:
|
||||
291,378,8068,1,0,0:0:0:0:
|
||||
481,254,8170,6,0,L|451:82,1,160,12|0,1:2|0:0,0:0:0:0:
|
||||
218,38,8474,2,0,L|393:3,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
170,255,8778,2,0,L|183:364,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
414,301,8980,2,0,L|401:211,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
303,195,9183,1,2,0:0:0:0:
|
||||
110,166,9284,2,0,L|125:87,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
226,65,9487,1,0,0:0:0:0:
|
||||
30,42,9589,1,10,0:0:0:0:
|
||||
234,83,9690,1,0,0:0:0:0:
|
||||
32,68,9791,6,0,L|32:238,1,160,8|0,1:1|0:0,0:0:0:0:
|
||||
245,314,10095,2,0,L|55:347,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
298,199,10399,2,0,L|404:205,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
181,108,10602,2,0,L|277:95,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
154,68,10805,1,2,0:0:0:0:
|
||||
340,17,10906,2,0,L|428:24,1,80,10|2,0:0|0:0,0:0:0:0:
|
||||
232,123,11109,1,0,0:0:0:0:
|
||||
426,51,11210,1,10,0:0:0:0:
|
||||
241,231,11312,1,2,0:0:0:0:
|
||||
41,296,11413,6,0,L|26:115,1,160,12|0,2:2|0:0,0:0:0:0:
|
||||
239,97,11717,2,0,L|253:264,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
51,317,12021,2,0,L|144:321,1,80,8|2,0:0|0:0,0:0:0:0:
|
||||
322,176,12224,2,0,P|237:164|151:207,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
405,335,12528,2,0,L|419:162,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
228,69,12832,2,0,L|110:73,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
370,62,13034,6,0,L|423:138,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
319,154,13235,2,0,L|359:242,1,80,0|0,1:0|0:0,0:0:0:0:
|
||||
248,226,13436,2,0,L|266:320,1,80,0|0,1:0|0:0,0:0:0:0:
|
||||
148,297,13638,2,0,L|142:382,1,80,0|0,1:0|0:0,0:0:0:0:
|
||||
384,42,13839,6,0,L|379:140,1,80,8|0,1:2|0:0,0:0:0:0:
|
||||
266,148,14040,2,0,L|243:246,1,80,8|0,1:2|0:0,0:0:0:0:
|
||||
117,252,14242,2,0,L|75:326,1,80,8|0,1:2|0:0,0:0:0:0:
|
||||
293,366,14443,2,0,L|350:278,1,80,8|0,1:2|0:0,0:0:0:0:
|
||||
51,299,14644,6,0,L|18:203,1,80,8|0,1:2|0:0,0:0:0:0:
|
||||
263,215,14846,2,0,L|317:144,1,80,8|0,1:2|0:0,0:0:0:0:
|
||||
75,110,15047,2,0,L|150:62,1,80,8|0,1:2|0:0,0:0:0:0:
|
||||
388,51,15248,2,0,L|496:45,1,80,8|0,1:2|0:0,0:0:0:0:
|
||||
179,147,15450,5,4,1:2:0:0:
|
||||
493,352,15656,2,8,L|393:370,1,80,8|8,1:2|1:2,0:0:0:0:
|
||||
226,296,15863,2,8,L|167:294,2,40,8|0|8,1:2|0:0|1:2,0:0:0:0:
|
||||
466,106,16070,1,8,1:2:0:0:
|
||||
490,136,16121,1,0,0:0:0:0:
|
||||
485,175,16173,1,8,1:2:0:0:
|
||||
453,198,16225,1,0,0:0:0:0:
|
||||
231,193,16277,6,0,L|127:189,1,80,6|0,0:3|0:0,0:0:0:0:
|
||||
351,126,16479,2,0,L|208:53,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
300,42,16783,1,4,0:3:0:0:
|
||||
194,207,16885,2,0,L|88:223,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
294,325,17087,2,0,L|386:313,1,80,4|0,0:3|0:0,0:0:0:0:
|
||||
169,296,17290,2,0,L|319:198,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
213,189,17594,1,4,0:3:0:0:
|
||||
414,106,17695,2,0,L|325:98,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
142,98,17898,5,4,0:3:0:0:
|
||||
323,45,18101,2,0,L|188:148,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
281,146,18405,1,4,0:3:0:0:
|
||||
173,198,18506,2,0,L|280:205,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
450,170,18709,1,4,0:3:0:0:
|
||||
267,375,18912,2,0,L|253:210,1,160,10|4,0:0|0:3,0:0:0:0:
|
||||
161,222,19216,1,4,0:3:0:0:
|
||||
253,215,19317,2,0,L|343:194,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
76,56,19520,5,4,0:3:0:0:
|
||||
413,38,19722,2,0,L|233:66,1,160,10|0,0:0|0:3,0:0:0:0:
|
||||
242,139,20027,1,4,0:3:0:0:
|
||||
332,146,20128,2,0,L|353:231,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
114,149,20331,2,0,P|49:176|26:264,1,160,4|10,0:3|0:0,0:0:0:0:
|
||||
223,334,20635,2,0,L|123:343,2,80,4|0|4,0:3|0:0|0:3,0:0:0:0:
|
||||
415,220,20939,2,0,L|321:211,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
243,244,21141,2,0,L|232:152,1,80,4|0,0:3|0:0,0:0:0:0:
|
||||
461,81,21344,6,0,L|292:66,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
390,36,21648,1,4,0:3:0:0:
|
||||
177,78,21749,2,0,L|88:91,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
330,206,21952,2,0,L|345:318,1,80,12|0,1:1|0:0,0:0:0:0:
|
||||
239,331,22155,1,8,1:2:0:0:
|
||||
30,14,22256,2,0,L|50:113,1,80,4|0,0:3|0:0,0:0:0:0:
|
||||
225,166,22459,1,4,0:3:0:0:
|
||||
30,125,22560,2,0,L|128:111,1,80,8|0,1:2|0:0,0:0:0:0:
|
||||
354,114,22763,5,6,0:3:0:0:
|
||||
47,348,22966,2,0,L|215:361,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
125,326,23270,1,4,0:3:0:0:
|
||||
222,278,23371,2,0,L|305:267,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
109,113,23574,2,0,L|30:126,1,80,4|0,0:3|0:0,0:0:0:0:
|
||||
229,161,23777,2,0,L|394:142,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
307,113,24081,1,4,0:3:0:0:
|
||||
203,44,24182,2,0,L|284:29,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
486,94,24385,5,4,0:3:0:0:
|
||||
305,136,24587,2,0,L|294:298,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
378,313,24891,1,4,0:3:0:0:
|
||||
286,319,24993,1,10,0:0:0:0:
|
||||
373,336,25094,1,0,0:0:0:0:
|
||||
136,358,25195,1,4,0:3:0:0:
|
||||
314,133,25398,2,0,L|165:207,1,160,10|4,0:0|0:3,0:0:0:0:
|
||||
256,127,25702,1,4,0:3:0:0:
|
||||
48,48,25804,2,0,L|32:143,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
284,352,26006,6,0,P|221:314|145:320,1,143.999995605469,6|0,0:3|0:0,0:0:0:0:
|
||||
99,333,26209,2,0,L|90:209,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
294,341,26412,6,0,P|231:303|155:309,1,143.999995605469,4|0,0:3|0:0,0:0:0:0:
|
||||
109,322,26615,2,0,L|100:198,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
303,332,26817,6,0,P|240:294|164:300,1,143.999995605469,4|0,0:3|0:0,0:0:0:0:
|
||||
118,313,27020,2,0,L|109:189,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
313,321,27223,6,0,P|250:283|174:289,1,143.999995605469,4|0,0:3|0:0,0:0:0:0:
|
||||
128,302,27426,2,0,L|119:178,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
343,203,27628,6,0,L|365:107,1,80,4|0,0:3|0:0,0:0:0:0:
|
||||
255,126,27831,2,0,L|232:31,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
457,224,28033,2,0,L|475:317,1,80,4|0,0:3|0:0,0:0:0:0:
|
||||
221,104,28236,2,0,L|192:13,1,80,10|0,0:2|0:0,0:0:0:0:
|
||||
445,40,28439,5,2,0:3:0:0:
|
||||
251,148,28641,2,0,L|155:165,1,80,8|8,1:2|1:2,0:0:0:0:
|
||||
418,232,28844,2,0,L|330:234,1,80,8|8,1:2|1:2,0:0:0:0:
|
||||
130,293,29047,2,0,L|34:305,1,80,8|8,1:2|1:2,0:0:0:0:
|
||||
279,349,29249,5,2,0:0:0:0:
|
||||
100,352,29452,2,0,P|44:342|44:238,1,160,10|0,0:0|0:3,0:0:0:0:
|
||||
117,225,29756,1,4,0:3:0:0:
|
||||
221,211,29858,2,0,L|116:195,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
37,169,30060,1,4,0:3:0:0:
|
||||
317,158,30263,2,0,L|330:72,1,80,10|0,0:0|0:3,0:0:0:0:
|
||||
226,71,30466,2,0,L|306:55,1,80,4|4,0:3|0:3,0:0:0:0:
|
||||
400,55,30668,1,10,0:0:0:0:
|
||||
116,81,30871,5,4,0:3:0:0:
|
||||
307,159,31074,2,0,L|315:324,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
226,329,31378,1,4,0:3:0:0:
|
||||
132,315,31479,2,0,L|291:325,1,160,10|4,0:0|0:3,0:0:0:0:
|
||||
14,120,31885,2,0,L|175:102,1,160,10|4,0:0|0:3,0:0:0:0:
|
||||
88,56,32189,1,4,0:3:0:0:
|
||||
173,102,32290,2,0,L|281:105,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
470,221,32493,5,4,0:3:0:0:
|
||||
298,331,32695,2,0,L|276:243,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
381,225,32898,2,0,L|357:138,1,80,4|4,0:3|0:3,0:0:0:0:
|
||||
151,158,33101,1,10,0:0:0:0:
|
||||
338,44,33304,2,0,L|164:35,1,160,4|10,0:3|0:0,0:0:0:0:
|
||||
261,28,33608,1,0,0:0:0:0:
|
||||
346,48,33709,1,0,0:0:0:0:
|
||||
124,193,33810,2,0,L|29:201,2,80,4|10|4,0:3|0:0|0:3,0:0:0:0:
|
||||
358,351,34114,5,4,0:3:0:0:
|
||||
173,271,34317,2,0,L|342:253,1,160,10|4,0:0|0:3,0:0:0:0:
|
||||
241,222,34621,1,4,0:3:0:0:
|
||||
344,202,34722,2,0,L|356:104,1,80,10|4,0:0|0:3,0:0:0:0:
|
||||
121,80,34925,1,4,0:3:0:0:
|
||||
285,38,35128,2,0,L|185:42,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
405,76,35331,2,0,L|492:86,1,80,2|4,0:0|0:3,0:0:0:0:
|
||||
281,101,35533,2,0,L|197:109,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
424,258,35736,5,4,0:3:0:0:
|
||||
247,340,35939,2,0,P|205:289|265:214,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
334,205,36243,1,4,0:3:0:0:
|
||||
238,175,36344,2,0,L|129:172,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
375,110,36547,1,4,0:3:0:0:
|
||||
190,254,36749,2,0,L|167:79,1,160,10|4,0:0|0:3,0:0:0:0:
|
||||
266,83,37054,1,4,0:3:0:0:
|
||||
149,45,37155,2,0,L|69:39,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
306,20,37358,5,4,0:3:0:0:
|
||||
129,251,37560,2,0,L|304:257,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
195,301,37864,1,4,0:3:0:0:
|
||||
288,256,37966,1,10,0:0:0:0:
|
||||
474,232,38168,1,4,0:3:0:0:
|
||||
177,282,38371,2,0,L|167:186,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
276,150,38574,2,0,L|183:136,1,80,4|4,0:3|0:3,0:0:0:0:
|
||||
91,135,38777,2,0,L|77:39,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
285,48,38979,5,4,0:3:0:0:
|
||||
102,146,39182,2,0,P|39:199|67:293,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
159,315,39486,2,0,L|195:268,2,40,4|0|10,0:3|0:0|0:0,0:0:0:0:
|
||||
44,238,39689,1,0,0:0:0:0:
|
||||
272,173,39790,1,4,0:3:0:0:
|
||||
91,57,39993,2,0,L|179:47,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
256,90,40195,2,0,L|177:103,1,80,0|4,0:3|0:3,0:0:0:0:
|
||||
90,131,40398,2,0,L|88:210,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
322,338,40601,6,0,L|410:323,1,80,4|4,0:3|0:3,0:0:0:0:
|
||||
190,235,40801,1,10,0:0:0:0:
|
||||
374,157,41002,2,0,L|281:141,1,80,4|4,0:3|0:3,0:0:0:0:
|
||||
396,124,41203,2,0,L|492:126,1,80,10|4,0:0|0:3,0:0:0:0:
|
||||
210,54,41403,6,0,L|189:147,1,80,2|0,0:0|0:0,0:0:0:0:
|
||||
292,212,41604,2,0,L|189:222,1,80,8|8,1:2|1:2,0:0:0:0:
|
||||
413,295,41805,2,0,L|529:308,1,80,8|8,1:2|1:2,0:0:0:0:
|
||||
309,372,42005,1,8,1:2:0:0:
|
||||
120,371,42106,1,8,1:2:0:0:
|
||||
348,62,42206,6,0,L|501:42,1,143.999995605469,6|0,0:3|0:0,0:0:0:0:
|
||||
492,21,42408,2,0,L|382:31,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
197,204,42611,6,0,L|350:184,1,143.999995605469,4|0,0:3|0:0,0:0:0:0:
|
||||
341,163,42813,2,0,L|231:173,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
27,98,43016,6,0,L|180:78,1,143.999995605469,4|0,0:3|0:0,0:0:0:0:
|
||||
171,57,43218,2,0,L|61:67,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
296,309,43422,6,0,L|143:289,1,143.999995605469,4|0,0:3|0:0,0:0:0:0:
|
||||
152,268,43624,2,0,L|262:278,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
464,188,43827,6,0,L|311:168,1,143.999995605469,4|0,0:3|0:0,0:0:0:0:
|
||||
320,147,44029,2,0,L|430:157,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
195,40,44233,2,0,L|89:80,1,95.9999970703126,4|0,0:3|0:0,0:0:0:0:
|
||||
339,305,44435,2,0,L|234:272,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
43,306,44638,6,0,L|48:202,1,80,2|0,0:3|0:0,0:0:0:0:
|
||||
151,185,44841,2,0,L|252:174,1,80,8|4,0:3|0:3,0:0:0:0:
|
||||
21,107,45043,2,0,L|123:94,1,80,8|8,1:2|1:2,0:0:0:0:
|
||||
311,42,45246,1,8,1:2:0:0:
|
||||
100,73,45347,1,8,1:2:0:0:
|
||||
320,63,45449,6,0,L|419:64,1,80,6|0,0:3|0:0,0:0:0:0:
|
||||
203,93,45651,2,0,L|188:258,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
278,322,45956,1,4,0:3:0:0:
|
||||
192,308,46057,1,10,0:0:0:0:
|
||||
107,282,46158,1,0,0:0:0:0:
|
||||
344,262,46260,1,4,0:3:0:0:
|
||||
41,363,46462,6,0,L|29:195,1,160,10|4,0:0|0:3,0:0:0:0:
|
||||
146,159,46766,1,4,0:3:0:0:
|
||||
64,117,46868,1,10,0:0:0:0:
|
||||
147,91,46969,1,0,0:0:0:0:
|
||||
370,78,47070,1,4,0:3:0:0:
|
||||
82,28,47273,6,0,L|247:23,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
154,64,47577,1,4,0:3:0:0:
|
||||
360,110,47678,2,0,L|371:204,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
262,201,47881,2,0,L|246:285,1,80,4|0,0:3|0:0,0:0:0:0:
|
||||
455,349,48084,2,0,L|356:360,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
476,318,48287,2,0,L|487:231,1,80,4|4,0:3|0:3,0:0:0:0:
|
||||
278,105,48489,2,0,L|165:109,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
434,51,48692,5,4,0:3:0:0:
|
||||
134,32,48895,2,0,L|128:217,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
215,230,49199,1,4,0:3:0:0:
|
||||
306,248,49300,1,10,0:0:0:0:
|
||||
400,256,49401,1,0,0:0:0:0:
|
||||
134,346,49503,5,10,0:0:0:0:
|
||||
332,145,49706,2,0,L|217:133,1,80,8|8,0:3|0:3,0:0:0:0:
|
||||
139,84,49908,2,0,L|40:97,1,80,4|4,0:3|0:3,0:0:0:0:
|
||||
280,34,50111,2,0,L|359:36,1,80,4|4,0:3|0:3,0:0:0:0:
|
||||
39,184,50314,6,0,B|26:342|26:342|43:151,1,320,6|2,0:3|0:0,0:0:0:0:
|
||||
345,72,51124,1,4,0:3:0:0:
|
||||
393,76,51189,1,4,0:3:0:0:
|
||||
424,114,51254,1,4,0:3:0:0:
|
||||
394,165,51319,1,4,0:3:0:0:
|
||||
341,177,51384,2,0,L|252:182,2,80,4|4|4,1:3|0:3|0:3,0:0:0:0:
|
||||
150,299,51674,2,0,L|62:289,1,80,8|8,1:2|1:2,0:0:0:0:
|
||||
283,314,51867,1,8,1:2:0:0:
|
||||
49,382,51964,6,0,L|32:220,1,160,6|10,0:0|0:0,0:0:0:0:
|
||||
118,194,52268,1,0,0:0:0:0:
|
||||
317,113,52369,2,0,L|306:20,1,80,0|0,0:3|0:0,0:0:0:0:
|
||||
204,93,52572,2,0,L|93:103,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
327,206,52774,6,0,L|338:305,1,80,4|0,0:3|0:0,0:0:0:0:
|
||||
239,316,52977,1,10,0:0:0:0:
|
||||
455,265,53078,2,0,L|466:364,1,80,4|0,0:3|0:0,0:0:0:0:
|
||||
367,375,53281,1,4,0:3:0:0:
|
||||
144,213,53382,2,0,L|236:206,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
449,135,53585,5,4,0:3:0:0:
|
||||
251,129,53788,2,0,L|240:310,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
324,307,54092,1,4,0:3:0:0:
|
||||
222,340,54193,2,0,L|311:358,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
75,262,54396,5,4,0:3:0:0:
|
||||
268,150,54599,2,0,L|95:138,1,160,10|4,0:0|0:3,0:0:0:0:
|
||||
194,100,54903,1,4,0:3:0:0:
|
||||
94,66,55004,2,0,L|10:73,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
288,35,55207,1,4,0:1:0:0:
|
||||
17,277,55409,6,0,L|200:295,1,160,12|4,0:0|0:3,0:0:0:0:
|
||||
92,247,55714,1,4,0:3:0:0:
|
||||
299,208,55815,2,0,L|393:189,1,80,10|0,0:2|0:3,0:0:0:0:
|
||||
162,168,56018,2,0,L|142:75,1,80,4|4,0:3|0:3,0:0:0:0:
|
||||
384,353,56220,2,0,L|399:266,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
168,188,56423,2,0,L|254:171,1,80,4|4,0:3|0:3,0:0:0:0:
|
||||
491,60,56626,2,0,L|393:53,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
174,208,56828,6,0,L|366:157,1,160,12|0,0:0|0:0,0:0:0:0:
|
||||
97,299,57132,2,0,L|283:273,1,160,12|0,0:0|0:0,0:0:0:0:
|
||||
24,371,57436,2,0,L|128:375,1,80,12|0,0:0|0:0,0:0:0:0:
|
||||
490,297,57639,5,12,0:0:0:0:
|
||||
11,149,58045,1,12,0:0:0:0:
|
||||
485,101,58450,5,6,0:3:0:0:
|
||||
278,89,58653,2,0,L|265:255,1,160,12|0,0:0|0:0,0:0:0:0:
|
||||
461,317,58957,2,0,L|359:332,2,80,4|10|0,0:3|0:0|0:0,0:0:0:0:
|
||||
265,354,59261,2,0,L|168:342,1,80,4|4,0:3|0:3,0:0:0:0:
|
||||
399,207,59464,1,10,0:0:0:0:
|
||||
486,183,59565,2,0,L|315:167,1,160,0|4,0:0|0:3,0:0:0:0:
|
||||
101,196,59869,6,0,L|90:117,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
200,86,60072,2,0,L|297:79,1,80,4|0,0:3|0:0,0:0:0:0:
|
||||
50,29,60274,2,0,L|29:192,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
122,223,60578,1,4,0:3:0:0:
|
||||
345,323,60680,2,0,L|255:338,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
365,359,60882,2,0,L|452:374,1,80,4|0,0:3|0:0,0:0:0:0:
|
||||
214,258,61085,2,0,L|390:227,1,160,10|4,0:0|0:3,0:0:0:0:
|
||||
285,205,61389,1,4,0:3:0:0:
|
||||
490,116,61491,2,0,L|372:109,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
169,138,61693,6,0,P|215:106|312:93,1,143.999995605469,4|0,0:1|0:0,0:0:0:0:
|
||||
355,106,61896,2,0,L|363:8,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
150,159,62099,6,0,P|196:127|293:114,1,143.999995605469,4|0,0:3|0:0,0:0:0:0:
|
||||
336,127,62302,2,0,L|344:29,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
138,185,62504,6,0,P|184:153|281:140,1,143.999995605469,4|0,0:3|0:0,0:0:0:0:
|
||||
324,153,62707,2,0,L|332:55,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
125,217,62909,6,0,P|171:185|268:172,1,143.999995605469,4|0,0:3|0:0,0:0:0:0:
|
||||
311,185,63112,2,0,L|319:87,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
91,366,63315,6,0,L|72:285,1,80,4|0,0:3|0:0,0:0:0:0:
|
||||
188,123,63518,2,0,L|203:34,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
421,366,63720,2,0,L|440:285,1,80,4|0,0:3|0:0,0:0:0:0:
|
||||
324,123,63923,2,0,L|309:34,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
88,186,64126,6,0,L|177:206,1,80,6|0,0:3|0:0,0:0:0:0:
|
||||
69,245,64326,2,0,L|52:207,2,40,8|0|8,1:2|0:0|1:2,0:0:0:0:
|
||||
284,307,64526,1,8,1:2:0:0:
|
||||
189,322,64626,2,0,L|161:263,2,40,8|0|8,1:2|0:0|1:2,0:0:0:0:
|
||||
411,353,64826,1,8,1:2:0:0:
|
||||
194,345,64926,5,2,0:2:0:0:
|
||||
34,290,65128,2,0,L|203:234,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
276,197,65432,1,4,0:3:0:0:
|
||||
380,177,65534,1,10,0:0:0:0:
|
||||
188,49,65736,1,4,0:3:0:0:
|
||||
490,358,65939,6,0,P|425:328|317:326,1,160,10|4,0:2|0:3,0:0:0:0:
|
||||
255,318,66243,1,4,0:3:0:0:
|
||||
338,307,66344,1,10,0:0:0:0:
|
||||
427,304,66446,1,0,0:0:0:0:
|
||||
213,218,66547,5,4,0:3:0:0:
|
||||
391,119,66750,2,0,L|232:111,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
317,76,67054,1,4,0:3:0:0:
|
||||
227,47,67155,1,10,0:0:0:0:
|
||||
46,52,67358,1,4,0:3:0:0:
|
||||
341,167,67561,6,0,L|352:333,1,160,10|4,0:0|0:3,0:0:0:0:
|
||||
267,348,67865,1,4,0:3:0:0:
|
||||
169,341,67966,1,10,0:0:0:0:
|
||||
364,141,68169,1,4,0:3:0:0:
|
||||
58,232,68371,6,0,L|17:58,1,160,10|4,0:0|0:3,0:0:0:0:
|
||||
108,56,68676,1,4,0:3:0:0:
|
||||
196,63,68777,1,10,0:0:0:0:
|
||||
103,73,68878,1,4,0:3:0:0:
|
||||
309,166,68980,1,4,0:3:0:0:
|
||||
132,294,69182,2,0,L|40:311,2,80,8|0|4,0:0|0:0|0:3,0:0:0:0:
|
||||
346,348,69486,1,4,0:3:0:0:
|
||||
245,351,69588,2,0,L|228:257,1,80,10|4,0:0|0:3,0:0:0:0:
|
||||
462,217,69790,5,4,0:3:0:0:
|
||||
191,129,69993,2,0,L|358:74,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
251,60,70297,1,4,0:3:0:0:
|
||||
342,78,70398,2,0,L|435:70,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
225,260,70601,2,0,L|115:272,1,80,4|0,0:3|0:0,0:0:0:0:
|
||||
355,358,70804,2,0,L|455:352,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
194,210,71007,2,0,L|294:201,1,80,0|4,0:0|0:3,0:0:0:0:
|
||||
465,180,71209,1,10,0:0:0:0:
|
||||
171,113,71412,6,0,L|156:283,1,160,4|10,0:3|0:0,0:0:0:0:
|
||||
353,84,71817,1,0,0:0:0:0:
|
||||
279,41,71918,1,4,0:3:0:0:
|
||||
196,30,72019,1,10,0:0:0:0:
|
||||
15,120,72223,1,4,0:3:0:0:
|
||||
333,178,72426,6,0,L|349:349,1,160,10|4,0:0|0:3,0:0:0:0:
|
||||
265,355,72730,1,4,0:3:0:0:
|
||||
351,364,72831,1,10,0:0:0:0:
|
||||
438,377,72932,1,0,0:0:0:0:
|
||||
237,248,73034,1,4,0:3:0:0:
|
||||
418,131,73236,2,0,L|259:111,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
344,76,73540,1,4,0:3:0:0:
|
||||
255,56,73642,1,10,0:0:0:0:
|
||||
68,82,73844,1,4,0:3:0:0:
|
||||
418,131,74047,6,0,L|436:303,1,160,10|4,0:0|0:3,0:0:0:0:
|
||||
345,310,74351,1,4,0:3:0:0:
|
||||
437,312,74453,1,10,0:0:0:0:
|
||||
247,357,74655,2,0,L|148:345,1,80,4|0,0:3|0:0,0:0:0:0:
|
||||
375,215,74858,2,0,L|290:208,2,80,10|0|4,0:0|0:0|0:3,0:0:0:0:
|
||||
466,190,75162,2,0,L|484:147,2,40,4|0|10,0:3|0:0|0:0,0:0:0:0:
|
||||
377,201,75365,1,4,0:3:0:0:
|
||||
160,115,75466,6,0,L|14:220,1,160,4|10,0:3|0:0,0:0:0:0:
|
||||
232,278,75770,2,0,L|250:377,1,80,4|0,0:3|0:0,0:0:0:0:
|
||||
28,192,75973,2,0,L|139:184,2,80,4|10|0,0:3|0:0|0:0,0:0:0:0:
|
||||
246,135,76277,1,4,0:3:0:0:
|
||||
66,46,76480,2,0,P|149:13|233:19,1,160,10|4,0:0|0:3,0:0:0:0:
|
||||
438,117,76784,2,0,L|344:102,1,80,4|8,0:3|0:0,0:0:0:0:
|
||||
153,75,76986,1,4,0:3:0:0:
|
||||
371,127,77088,5,6,0:3:0:0:
|
||||
207,190,77290,2,0,L|196:284,1,80,8|8,1:2|1:2,0:0:0:0:
|
||||
392,262,77493,2,0,L|486:270,1,80,8|8,1:2|1:2,0:0:0:0:
|
||||
236,374,77696,2,0,L|324:362,1,80,8|8,1:2|1:2,0:0:0:0:
|
||||
70,318,77898,6,0,P|123:277|211:273,1,143.999995605469,6|0,0:3|0:0,0:0:0:0:
|
||||
144,233,78101,2,0,L|35:183,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
257,170,78304,6,0,P|310:129|398:125,1,143.999995605469,4|0,0:3|0:0,0:0:0:0:
|
||||
331,85,78507,2,0,L|222:35,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
460,184,78709,6,0,P|407:225|319:229,1,143.999995605469,4|0,0:3|0:0,0:0:0:0:
|
||||
386,269,78912,2,0,L|495:319,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
257,170,79115,6,0,P|204:211|116:215,1,143.999995605469,4|0,0:3|0:0,0:0:0:0:
|
||||
183,255,79318,2,0,L|292:305,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
40,35,79520,6,0,P|93:76|181:80,1,143.999995605469,4|0,0:3|0:0,0:0:0:0:
|
||||
114,120,79723,2,0,L|5:170,1,95.9999970703126,10|0,0:2|0:0,0:0:0:0:
|
||||
255,200,79926,2,0,L|271:301,1,95.9999970703126,4|4,0:3|0:3,0:0:0:0:
|
||||
41,348,80128,2,0,L|146:282,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
379,360,80331,6,0,B|484:361|484:361|485:138,1,320,6|0,0:3|2:0,0:0:0:0:
|
||||
259,86,80838,1,0,3:0:0:0:
|
||||
374,67,80939,1,2,0:0:0:0:
|
||||
147,28,81040,1,2,2:0:0:0:
|
||||
399,77,81125,6,0,L|418:259,1,160,12|0,0:1|0:0,0:0:0:0:
|
||||
175,318,81429,2,0,L|340:333,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
107,178,81733,2,0,L|2:190,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
236,154,81935,1,8,0:0:0:0:
|
||||
21,151,82037,2,0,L|117:140,1,80,0|2,0:0|0:0,0:0:0:0:
|
||||
294,66,82239,1,8,0:0:0:0:
|
||||
95,105,82341,1,2,0:0:0:0:
|
||||
305,39,82442,1,2,0:0:0:0:
|
||||
88,64,82543,2,0,L|181:48,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
410,86,82746,6,0,L|428:249,1,160,12|0,0:0|3:3,0:0:0:0:
|
||||
193,225,83050,2,0,L|164:400,1,160,8|0,0:0|3:0,0:0:0:0:
|
||||
381,368,83354,2,0,L|285:358,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
88,151,83557,2,0,L|193:137,1,80,0|0,3:0|3:0,0:0:0:0:
|
||||
362,100,83763,1,10,1:2:0:0:
|
||||
193,58,83867,1,0,1:0:0:0:
|
||||
17,208,83970,1,10,1:2:0:0:
|
||||
192,267,84074,2,0,L|292:279,1,80,0|0,1:0|1:2,0:0:0:0:
|
||||
22,238,84281,1,0,1:0:0:0:
|
||||
299,180,84384,6,0,L|469:174,1,160,12|0,0:1|0:0,0:0:0:0:
|
||||
207,344,84688,2,0,L|374:314,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
104,113,84992,2,0,L|-1:108,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
245,51,85194,2,0,L|254:141,1,80,14|0,0:0|0:0,0:0:0:0:
|
||||
154,191,85397,1,2,0:0:0:0:
|
||||
380,135,85498,2,0,L|353:235,1,80,10|2,0:0|0:0,0:0:0:0:
|
||||
160,220,85701,1,0,0:0:0:0:
|
||||
370,239,85802,2,0,L|483:248,1,80,10|2,0:0|0:0,0:0:0:0:
|
||||
208,327,86005,6,0,L|43:355,1,160,12|0,0:2|0:0,0:0:0:0:
|
||||
262,177,86306,2,0,L|88:170,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
339,53,86607,2,0,L|244:48,1,80,8|2,0:0|0:0,0:0:0:0:
|
||||
39,254,86807,2,0,L|134:265,2,80,8|0|0,0:0|0:0|0:0,0:0:0:0:
|
||||
236,164,87108,2,0,L|329:152,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
214,126,87309,1,0,0:0:0:0:
|
||||
435,318,87409,2,0,L|333:326,1,80,8|2,0:0|0:0,0:0:0:0:
|
||||
110,360,87610,6,0,L|43:194,1,160,12|0,0:1|0:0,0:0:0:0:
|
||||
263,245,87914,2,0,L|269:64,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
495,5,88218,2,0,L|407:-1,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
195,21,88420,2,0,L|204:100,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
410,38,88623,1,2,0:0:0:0:
|
||||
194,140,88724,2,0,L|90:150,1,80,8|2,0:0|0:0,0:0:0:0:
|
||||
308,223,88927,1,2,0:0:0:0:
|
||||
97,188,89028,1,8,0:0:0:0:
|
||||
305,260,89130,1,0,0:0:0:0:
|
||||
82,224,89231,6,0,L|38:377,1,160,12|0,0:0|3:0,0:0:0:0:
|
||||
301,300,89535,2,0,L|107:322,1,160,8|0,0:0|3:0,0:0:0:0:
|
||||
364,384,89839,2,0,L|472:376,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
217,210,90042,2,0,L|322:198,1,80,8|8,3:3|3:3,0:0:0:0:
|
||||
490,135,90245,1,8,1:0:0:0:
|
||||
290,156,90346,2,0,L|196:164,1,80,8|2,1:0|3:3,0:0:0:0:
|
||||
398,38,90549,1,8,1:0:0:0:
|
||||
218,115,90650,1,8,0:0:0:0:
|
||||
27,45,90751,1,0,0:0:0:0:
|
||||
270,295,90853,6,0,L|437:315,1,160,12|0,0:1|0:0,0:0:0:0:
|
||||
193,89,91157,2,0,L|15:69,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
262,215,91461,2,0,L|272:325,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
30,289,91664,2,0,L|41:197,1,80,12|0,0:2|0:0,0:0:0:0:
|
||||
252,323,91866,1,0,0:0:0:0:
|
||||
25,182,91968,2,0,L|136:168,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
295,111,92170,1,0,3:0:0:0:
|
||||
90,142,92272,1,8,0:0:0:0:
|
||||
303,72,92373,1,0,3:0:0:0:
|
||||
69,110,92474,6,0,L|31:283,1,160,12|0,0:1|0:0,0:0:0:0:
|
||||
244,208,92778,2,0,L|260:368,1,160,8|0,0:0|3:0,0:0:0:0:
|
||||
471,367,93082,1,8,0:0:0:0:
|
||||
268,347,93184,1,0,3:0:0:0:
|
||||
72,342,93285,6,0,L|285:283,1,191.999994140625,4|8,0:0|1:2,0:0:0:0:
|
||||
61,310,93589,1,8,1:2:0:0:
|
||||
246,260,93691,1,8,1:2:0:0:
|
||||
76,273,93792,1,8,1:2:0:0:
|
||||
211,232,93893,1,8,1:2:0:0:
|
||||
97,235,93995,1,8,1:2:0:0:
|
||||
185,206,94096,1,0,3:0:0:0:
|
||||
117,200,94197,1,0,3:0:0:0:
|
||||
328,14,94299,6,0,L|10:393,1,480.000018310548,12|0,0:0|3:0,0:0:0:0:
|
||||
103,375,94603,1,0,3:0:0:0:
|
||||
363,8,94704,6,0,L|57:377,1,480.000018310548,12|0,0:0|3:0,0:0:0:0:
|
||||
142,378,95008,1,0,3:0:0:0:
|
||||
416,14,95110,6,0,L|110:383,1,480.000018310548,12|0,0:0|3:0,0:0:0:0:
|
||||
195,384,95414,1,0,3:0:0:0:
|
||||
471,14,95515,6,0,L|165:383,1,480.000018310548,12|4,0:0|3:3,0:0:0:0:
|
||||
250,384,95819,1,0,0:0:0:0:
|
||||
468,363,95920,6,0,L|482:194,1,160,10|0,0:0|0:2,0:0:0:0:
|
||||
383,183,96224,2,0,L|290:173,1,80,2|8,3:2|0:0,0:0:0:0:
|
||||
401,144,96427,1,0,0:0:0:0:
|
||||
170,74,96528,2,0,L|150:189,1,80,2|0,3:2|0:0,0:0:0:0:
|
||||
271,268,96731,2,0,L|287:355,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
65,343,96934,2,0,L|144:322,1,80,2|0,3:2|0:0,0:0:0:0:
|
||||
340,209,97137,2,0,L|223:205,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
344,166,97339,2,0,L|442:166,1,80,0|0,3:0|3:0,0:0:0:0:
|
||||
171,14,97542,6,0,L|489:393,1,480.000018310548,12|0,0:0|3:0,0:0:0:0:
|
||||
396,375,97846,1,0,3:0:0:0:
|
||||
136,8,97947,6,0,L|442:377,1,480.000018310548,12|0,0:0|3:0,0:0:0:0:
|
||||
357,378,98251,1,0,3:0:0:0:
|
||||
83,14,98353,6,0,L|389:383,1,480.000018310548,12|0,0:0|3:0,0:0:0:0:
|
||||
304,384,98657,1,0,3:0:0:0:
|
||||
28,14,98758,6,0,L|334:383,1,480.000018310548,12|4,0:0|3:3,0:0:0:0:
|
||||
249,384,99062,1,0,0:0:0:0:
|
||||
46,364,99164,6,0,L|29:194,1,160,10|2,0:0|3:2,0:0:0:0:
|
||||
114,185,99468,1,0,0:0:0:0:
|
||||
213,128,99569,2,0,L|112:121,1,80,10|0,3:2|0:0,0:0:0:0:
|
||||
341,93,99772,2,0,L|430:77,1,80,2|0,3:2|0:0,0:0:0:0:
|
||||
320,51,99974,2,0,L|207:42,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
452,164,100177,2,0,L|470:266,1,80,2|0,3:2|0:0,0:0:0:0:
|
||||
232,253,100380,2,0,L|344:259,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
214,342,100582,2,0,L|197:237,1,80,0|0,3:0|3:0,0:0:0:0:
|
||||
442,13,100788,6,0,L|136:382,1,480.000018310548,12|0,0:0|3:0,0:0:0:0:
|
||||
221,383,101099,1,0,3:0:0:0:
|
||||
486,14,101202,6,0,L|180:383,1,480.000018310548,12|0,0:0|3:0,0:0:0:0:
|
||||
265,384,101513,1,0,3:0:0:0:
|
||||
26,14,101616,6,0,L|332:383,1,480.000018310548,12|0,0:0|3:0,0:0:0:0:
|
||||
247,384,101927,1,0,3:0:0:0:
|
||||
26,14,102030,6,0,L|332:383,1,480.000018310548,12|4,0:0|0:3,0:0:0:0:
|
||||
134,344,102439,6,0,L|121:161,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
207,149,102743,1,2,3:2:0:0:
|
||||
298,134,102845,1,8,0:0:0:0:
|
||||
106,78,103047,1,0,3:0:0:0:
|
||||
444,49,103250,1,8,0:0:0:0:
|
||||
355,52,103351,2,0,L|342:223,1,160,0|0,3:0|3:0,0:0:0:0:
|
||||
134,344,103655,2,0,L|213:340,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
457,313,103858,5,4,0:0:0:0:
|
||||
268,129,104061,2,0,L|110:165,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
196,211,104365,1,0,3:0:0:0:
|
||||
281,222,104466,1,8,0:0:0:0:
|
||||
101,272,104669,2,0,L|9:286,1,80,0|0,3:0|0:0,0:0:0:0:
|
||||
257,300,104872,6,0,L|268:129,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
180,112,105176,1,0,3:0:0:0:
|
||||
86,102,105277,1,8,0:0:0:0:
|
||||
280,40,105480,2,0,L|375:44,1,80,0|0,3:0|0:0,0:0:0:0:
|
||||
135,238,105682,2,0,L|305:251,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
208,296,105987,1,0,3:0:0:0:
|
||||
308,334,106088,2,0,L|406:338,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
138,367,106291,5,4,0:0:0:0:
|
||||
311,200,106493,2,0,L|212:192,1,80,8|8,1:2|1:2,0:0:0:0:
|
||||
53,121,106696,1,8,1:2:0:0:
|
||||
228,151,106797,1,8,1:2:0:0:
|
||||
416,137,106899,1,8,1:2:0:0:
|
||||
226,117,107000,1,8,1:2:0:0:
|
||||
474,37,107101,6,0,B|477:204|477:204|326:223,1,320,4|10,0:2|0:0,0:0:0:0:
|
||||
68,357,107811,2,0,L|233:291,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
39,173,108115,2,0,L|199:111,1,160,4|10,0:3|0:0,0:0:0:0:
|
||||
379,100,108419,2,0,L|209:212,1,191.999994140625,4|0,0:3|0:0,0:0:0:0:
|
||||
432,333,108723,6,0,B|262:274|262:274|256:68,1,383.99998828125,6|10,0:3|0:0,0:0:0:0:
|
||||
39,266,109432,2,0,L|34:64,1,191.999994140625,8|0,0:0|0:0,0:0:0:0:
|
||||
262,113,109737,2,0,L|119:183,1,160,4|10,0:3|0:0,0:0:0:0:
|
||||
315,301,110041,2,0,L|476:217,1,160,4|0,0:3|0:0,0:0:0:0:
|
||||
267,383,110345,6,0,L|252:213,1,160,6|0,0:3|0:0,0:0:0:0:
|
||||
445,151,110750,2,0,L|289:107,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
80,64,111054,2,0,L|74:149,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
381,285,111358,2,0,L|206:228,1,160,4|10,0:3|0:0,0:0:0:0:
|
||||
28,35,111662,2,0,L|198:93,1,160,4|0,0:3|0:0,0:0:0:0:
|
||||
420,0,111966,6,0,L|428:177,1,160,6|4,0:3|0:3,0:0:0:0:
|
||||
331,206,112270,1,4,0:3:0:0:
|
||||
114,320,112372,1,10,1:2:0:0:
|
||||
301,351,112585,2,0,L|401:348,1,80,0|0,1:2|1:3,0:0:0:0:
|
||||
141,360,112799,2,0,L|117:186,1,160,2|4,1:2|0:3,0:0:0:0:
|
||||
314,185,113103,1,4,0:3:0:0:
|
||||
89,105,113204,2,0,L|255:83,1,160,10|8,0:0|1:2,0:0:0:0:
|
||||
451,65,113493,1,8,1:2:0:0:
|
||||
212,174,113589,6,0,B|66:231|66:231|223:275,1,320,4|10,0:0|0:0,0:0:0:0:
|
||||
435,349,114298,2,0,L|256:346,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
43,328,114602,2,0,L|17:160,1,160,4|10,0:3|0:0,0:0:0:0:
|
||||
243,183,114906,2,0,L|271:-11,1,160,4|0,0:3|0:0,0:0:0:0:
|
||||
54,51,115210,6,0,B|215:100|215:100|223:315,1,383.99998828125,6|10,0:3|0:0,0:0:0:0:
|
||||
464,355,115920,2,0,L|450:158,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
267,153,116224,2,0,L|435:81,1,160,4|10,0:3|0:0,0:0:0:0:
|
||||
230,357,116528,2,0,L|65:300,1,160,4|0,0:3|0:0,0:0:0:0:
|
||||
281,231,116832,6,0,L|292:44,1,160,6|0,0:3|0:0,0:0:0:0:
|
||||
189,65,117136,1,0,0:0:0:0:
|
||||
86,120,117237,2,0,L|246:133,1,160,10|0,0:0|0:0,0:0:0:0:
|
||||
37,252,117541,2,0,L|130:241,2,80,8|0|0,0:0|0:0|0:0,0:0:0:0:
|
||||
249,342,117845,2,0,L|424:316,1,160,4|10,0:3|0:0,0:0:0:0:
|
||||
192,274,118149,2,0,L|171:89,1,160,4|0,0:3|0:0,0:0:0:0:
|
||||
392,72,118453,5,6,0:3:0:0:
|
||||
199,20,118656,2,0,L|291:15,1,80,0|4,0:0|0:3,0:0:0:0:
|
||||
499,235,118859,2,0,L|333:220,1,160,8|0,1:2|1:0,0:0:0:0:
|
||||
141,300,119163,1,0,1:0:0:0:
|
||||
347,334,119264,2,0,L|372:145,1,160,2|8,0:0|1:2,0:0:0:0:
|
||||
279,154,119565,1,8,1:2:0:0:
|
||||
87,137,119663,5,8,1:0:0:0:
|
||||
190,110,119761,1,8,1:0:0:0:
|
||||
15,60,119859,1,8,1:0:0:0:
|
||||
222,22,119957,1,8,1:0:0:0:
|
||||
423,36,120055,6,0,L|428:125,1,80,4|0,0:1|0:0,0:0:0:0:
|
||||
237,334,120257,2,0,L|433:339,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
315,296,120561,1,0,3:0:0:0:
|
||||
110,257,120663,2,0,L|10:239,2,80,8|0|2,0:0|0:0|3:2,0:0:0:0:
|
||||
401,36,121068,2,0,L|206:56,1,160,8|0,0:0|3:0,0:0:0:0:
|
||||
331,88,121372,1,0,3:0:0:0:
|
||||
140,122,121473,2,0,L|24:117,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
245,185,121676,6,0,L|261:280,1,80,0|0,3:0|0:0,0:0:0:0:
|
||||
67,353,121879,2,0,L|252:361,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
140,331,122183,1,0,3:0:0:0:
|
||||
234,300,122284,2,0,L|330:288,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
117,188,122487,2,0,L|8:178,1,80,2|0,3:2|0:0,0:0:0:0:
|
||||
251,209,122690,2,0,L|261:37,1,160,8|0,0:0|3:0,0:0:0:0:
|
||||
56,37,122994,2,0,L|163:22,2,80,0|10|0,3:0|0:0|0:0,0:0:0:0:
|
||||
293,246,123298,5,0,3:0:0:0:
|
||||
21,68,123500,2,0,L|192:77,1,160,10|0,0:0|3:0,0:0:0:0:
|
||||
96,101,123805,1,0,0:0:0:0:
|
||||
298,139,123906,2,0,L|389:143,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
196,277,124109,5,2,3:2:0:0:
|
||||
491,349,124311,2,0,L|314:364,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
410,326,124615,1,2,3:2:0:0:
|
||||
201,289,124717,2,0,L|184:204,1,80,8|8,0:2|0:2,0:0:0:0:
|
||||
292,195,124919,2,0,L|307:109,1,80,0|0,3:0|0:0,0:0:0:0:
|
||||
74,51,125122,6,0,L|171:36,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
55,89,125325,2,0,L|41:181,1,80,0|0,3:0|0:0,0:0:0:0:
|
||||
245,361,125527,2,0,L|346:362,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
98,279,125730,2,0,L|273:255,1,160,4|8,0:1|0:0,0:0:0:0:
|
||||
42,168,126034,2,0,L|142:155,2,80,0|0|0,3:0|0:0|3:0,0:0:0:0:
|
||||
260,73,126338,2,0,L|154:71,1,80,8|8,0:0|0:0,0:0:0:0:
|
||||
425,42,126541,5,4,0:1:0:0:
|
||||
247,119,126744,2,0,L|256:308,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
336,309,127048,1,0,3:0:0:0:
|
||||
120,350,127149,2,0,L|113:252,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
211,188,127352,2,0,L|103:188,1,80,2|0,3:2|0:0,0:0:0:0:
|
||||
346,102,127555,2,0,L|174:88,1,160,8|0,0:0|3:0,0:0:0:0:
|
||||
269,54,127859,1,0,3:0:0:0:
|
||||
64,24,127960,2,0,L|169:7,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
406,32,128163,5,0,3:0:0:0:
|
||||
103,146,128365,2,0,L|72:341,1,160,8|0,0:0|0:0,0:0:0:0:
|
||||
166,334,128669,1,0,3:0:0:0:
|
||||
360,304,128771,2,0,L|443:280,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
331,257,128973,2,0,L|232:245,1,80,2|0,3:2|0:0,0:0:0:0:
|
||||
478,193,129176,2,0,L|495:20,1,160,8|0,0:0|3:0,0:0:0:0:
|
||||
269,60,129480,2,0,L|357:48,2,80,0|10|0,3:0|0:0|0:0,0:0:0:0:
|
||||
37,64,129784,5,0,3:0:0:0:
|
||||
338,160,129987,2,0,L|153:186,1,160,10|2,0:0|3:2,0:0:0:0:
|
||||
101,189,130291,1,0,0:0:0:0:
|
||||
209,238,130392,2,0,L|305:224,1,80,10|2,0:0|3:2,0:0:0:0:
|
||||
60,338,130595,6,0,L|41:252,1,80,2|0,3:2|0:0,0:0:0:0:
|
||||
148,240,130798,2,0,L|162:128,1,80,10|0,0:2|0:0,0:0:0:0:
|
||||
375,95,131000,2,0,L|272:99,1,80,0|0,3:0|0:0,0:0:0:0:
|
||||
79,27,131203,2,0,L|176:17,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
398,188,131406,5,0,3:0:0:0:
|
||||
60,338,131609,2,0,L|243:358,1,160,8|0,0:0|3:0,0:0:0:0:
|
||||
139,302,131913,1,0,0:0:0:0:
|
||||
345,271,132014,2,0,L|236:260,1,80,8|0,0:0|0:0,0:0:0:0:
|
||||
40,182,132217,6,0,L|26:88,1,80,4|0,0:1|0:0,0:0:0:0:
|
||||
126,71,132419,1,8,1:0:0:0:
|
||||
330,109,132521,2,0,L|227:130,1,80,8|0,1:0|1:0,0:0:0:0:
|
||||
350,159,132723,2,0,L|467:163,1,80,0|8,1:0|1:0,0:0:0:0:
|
||||
241,249,132926,1,8,1:0:0:0:
|
||||
463,354,133027,6,0,P|403:318|292:320,1,143.999995605469,6|0,3:0|0:0,0:0:0:0:
|
||||
162,341,133232,2,0,L|150:237,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
416,274,133438,6,0,P|356:238|245:240,1,143.999995605469,4|0,0:3|0:0,0:0:0:0:
|
||||
115,261,133644,2,0,L|103:157,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
376,184,133850,6,0,P|316:148|205:150,1,143.999995605469,4|0,0:3|0:0,0:0:0:0:
|
||||
75,171,134056,2,0,L|63:67,1,95.9999970703126,10|0,0:0|0:0,0:0:0:0:
|
||||
324,109,134261,6,0,P|264:73|153:75,1,143.999995605469,4|0,0:3|0:0,0:0:0:0:
|
||||
23,96,134467,2,0,L|11:-16,1,80,10|0,0:0|0:0,0:0:0:0:
|
||||
308,14,134711,6,0,L|483:53,1,160,6|0,0:3|0:0,0:0:0:0:
|
||||
362,95,135127,1,8,0:0:0:0:
|
||||
199,130,135294,1,8,0:0:0:0:
|
||||
479,226,135461,1,8,0:0:0:0:
|
||||
289,289,135723,1,4,0:3:0:0:
|
||||
478,338,135985,1,4,0:3:0:0:
|
||||
141,224,136174,5,8,0:3:0:0:
|
||||
435,73,136553,1,4,0:3:0:0:
|
||||
199,42,136787,1,8,0:0:0:0:
|
||||
426,62,137178,1,8,0:0:0:0:
|
||||
27,107,137750,5,4,0:0:0:0:
|
||||
256,192,137822,12,0,145626,0:0:0:0:
|
||||
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "mania"
|
||||
version = "0.1.0"
|
||||
authors = ["MaxOhn <ohn.m@hotmail.de>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies.parse]
|
||||
path = "../parse"
|
||||
@@ -0,0 +1,149 @@
|
||||
mod strain;
|
||||
|
||||
use strain::Strain;
|
||||
|
||||
use parse::{Beatmap, HitObject, Mods};
|
||||
|
||||
const SECTION_LEN: f32 = 400.0;
|
||||
const STAR_SCALING_FACTOR: f32 = 0.018;
|
||||
|
||||
/// Star calculation for osu!mania maps
|
||||
pub fn stars(map: &Beatmap, mods: impl Mods) -> f32 {
|
||||
if map.hit_objects.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let clock_rate = mods.speed();
|
||||
let section_len = SECTION_LEN * clock_rate;
|
||||
let mut strain = Strain::new(map.cs as u8);
|
||||
|
||||
let hit_objects = map
|
||||
.hit_objects
|
||||
.iter()
|
||||
.skip(1)
|
||||
.zip(map.hit_objects.iter())
|
||||
.map(|(base, prev)| DifficultyHitObject::new(base, prev, map.cs, clock_rate));
|
||||
|
||||
// No strain for first object
|
||||
let mut current_section_end =
|
||||
(map.hit_objects[0].start_time / section_len).ceil() * section_len;
|
||||
|
||||
for h in hit_objects {
|
||||
while h.base.start_time > current_section_end {
|
||||
strain.save_current_peak();
|
||||
strain.start_new_section_from(current_section_end);
|
||||
|
||||
current_section_end += section_len;
|
||||
}
|
||||
|
||||
strain.process(&h);
|
||||
}
|
||||
|
||||
strain.save_current_peak();
|
||||
|
||||
strain.difficulty_value() * STAR_SCALING_FACTOR
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DifficultyHitObject<'o> {
|
||||
base: &'o HitObject,
|
||||
column: usize,
|
||||
delta: f32,
|
||||
}
|
||||
|
||||
impl<'o> DifficultyHitObject<'o> {
|
||||
fn new(base: &'o HitObject, prev: &'o HitObject, cs: f32, clock_rate: f32) -> Self {
|
||||
let x_divisor = 512.0 / cs;
|
||||
let column = (base.pos.x / x_divisor).floor() as usize;
|
||||
|
||||
Self {
|
||||
base,
|
||||
column,
|
||||
delta: (base.start_time - prev.start_time) / clock_rate,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::File;
|
||||
|
||||
#[test]
|
||||
fn test_single() {
|
||||
let file = match File::open("E:/Games/osu!/beatmaps/1355822.osu") {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file: {}", why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map: {}", why),
|
||||
};
|
||||
|
||||
let stars = stars(&map, 256);
|
||||
|
||||
println!("Stars: {}", stars);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mania() {
|
||||
let margin = 0.005;
|
||||
|
||||
#[rustfmt::skip]
|
||||
let data = vec![
|
||||
(1355822, 1 << 8, 2.2710870990702627), // HT
|
||||
(1355822, 0, 2.7966565927524574), // NM
|
||||
(1355822, 1 << 6, 3.748525363730352), // DT
|
||||
|
||||
(1974394, 1 << 8, 3.8736942117487256), // HT
|
||||
(1974394, 0, 4.801793001581714), // NM
|
||||
(1974394, 1 << 6, 6.517894438878535), // DT
|
||||
|
||||
(992512, 1 << 8, 5.29507262961579), // HT
|
||||
(992512, 0, 6.536292432114728), // NM
|
||||
(992512, 1 << 6, 8.944195050951032), // DT
|
||||
];
|
||||
|
||||
for (map_id, mods, expected_stars) in data {
|
||||
let file = match File::open(format!("./test/{}.osu", map_id)) {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
||||
};
|
||||
|
||||
let stars = stars(&map, mods);
|
||||
|
||||
assert!(
|
||||
(stars - expected_stars).abs() < margin,
|
||||
"Stars: {} | Expected: {} => {} margin [map {} | mods {}]",
|
||||
stars,
|
||||
expected_stars,
|
||||
(stars - expected_stars).abs(),
|
||||
map_id,
|
||||
mods
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wack_map() {
|
||||
let file = match File::open("E:/Games/osu!/beatmaps/1443309.osu") {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file: {}", why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map: {}", why),
|
||||
};
|
||||
|
||||
let stars = stars(&map, 0);
|
||||
|
||||
println!("Stars: {}", stars);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
use super::DifficultyHitObject;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
pub(crate) struct Strain {
|
||||
current_strain: f32,
|
||||
current_section_peak: f32,
|
||||
|
||||
individual_strain: f32,
|
||||
overall_strain: f32,
|
||||
|
||||
hold_end_times: Vec<f32>,
|
||||
individual_strains: Vec<f32>,
|
||||
pub strain_peaks: Vec<f32>,
|
||||
|
||||
prev_time: Option<f32>,
|
||||
}
|
||||
|
||||
const INDIVISUAL_DECAY_BASE: f32 = 0.125;
|
||||
const OVERALL_DECAY_BASE: f32 = 0.3;
|
||||
const STRAIN_DECAY_BASE: f32 = 1.0;
|
||||
|
||||
const SKILL_MULTIPLIER: f32 = 1.0;
|
||||
const DECAY_WEIGHT: f32 = 0.9;
|
||||
|
||||
impl Strain {
|
||||
#[inline]
|
||||
pub(crate) fn new(column_count: u8) -> Self {
|
||||
Self {
|
||||
current_strain: 1.0,
|
||||
current_section_peak: 1.0,
|
||||
|
||||
individual_strain: 0.0,
|
||||
overall_strain: 1.0,
|
||||
|
||||
hold_end_times: vec![0.0; column_count as usize],
|
||||
individual_strains: vec![0.0; column_count as usize],
|
||||
strain_peaks: Vec::with_capacity(128),
|
||||
|
||||
prev_time: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn save_current_peak(&mut self) {
|
||||
if self.prev_time.is_some() {
|
||||
self.strain_peaks.push(self.current_section_peak);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn start_new_section_from(&mut self, time: f32) {
|
||||
if let Some(prev) = self.prev_time {
|
||||
self.current_section_peak = self.peak_strain(time - prev);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn peak_strain(&self, delta_time: f32) -> f32 {
|
||||
apply_decay(self.individual_strain, delta_time, INDIVISUAL_DECAY_BASE)
|
||||
+ apply_decay(self.overall_strain, delta_time, OVERALL_DECAY_BASE)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_decay(&self, ms: f32) -> f32 {
|
||||
STRAIN_DECAY_BASE.powf(ms / 1000.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn process(&mut self, current: &DifficultyHitObject) {
|
||||
self.current_strain *= self.strain_decay(current.delta);
|
||||
self.current_strain += self.strain_value_of(¤t) * SKILL_MULTIPLIER;
|
||||
self.current_section_peak = self.current_strain.max(self.current_section_peak);
|
||||
self.prev_time.replace(current.base.start_time);
|
||||
}
|
||||
|
||||
fn strain_value_of(&mut self, current: &DifficultyHitObject) -> f32 {
|
||||
let end_time = current.base.end_time();
|
||||
|
||||
let mut hold_factor = 1.0;
|
||||
let mut hold_addition = 0.0;
|
||||
|
||||
for col in 0..self.hold_end_times.len() {
|
||||
let hold_end_time = self.hold_end_times[col as usize];
|
||||
|
||||
if end_time > hold_end_time + 1.0 {
|
||||
if hold_end_time > current.base.start_time + 1.0 {
|
||||
hold_addition = 1.0;
|
||||
}
|
||||
} else if (end_time - hold_end_time).abs() < 1.0 {
|
||||
hold_addition = 0.0;
|
||||
} else if end_time < hold_end_time - 1.0 {
|
||||
hold_factor = 1.25;
|
||||
}
|
||||
|
||||
self.individual_strains[col] = apply_decay(
|
||||
self.individual_strains[col],
|
||||
current.delta,
|
||||
INDIVISUAL_DECAY_BASE,
|
||||
);
|
||||
}
|
||||
|
||||
self.hold_end_times[current.column] = end_time;
|
||||
self.individual_strains[current.column] += 2.0 * hold_factor;
|
||||
self.individual_strain = self.individual_strains[current.column];
|
||||
|
||||
self.overall_strain = apply_decay(self.overall_strain, current.delta, OVERALL_DECAY_BASE)
|
||||
+ (1.0 + hold_addition) * hold_factor;
|
||||
|
||||
self.individual_strain + self.overall_strain - self.current_strain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn difficulty_value(&mut self) -> f32 {
|
||||
let mut difficulty = 0.0;
|
||||
let mut weight = 1.0;
|
||||
|
||||
self.strain_peaks
|
||||
.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
||||
|
||||
for &strain in self.strain_peaks.iter() {
|
||||
difficulty += strain * weight;
|
||||
weight *= DECAY_WEIGHT;
|
||||
}
|
||||
|
||||
difficulty
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn apply_decay(value: f32, delta_time: f32, decay_base: f32) -> f32 {
|
||||
value * decay_base.powf(delta_time / 1000.0)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "osu"
|
||||
version = "0.1.0"
|
||||
authors = ["MaxOhn <ohn.m@hotmail.de>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies.parse]
|
||||
path = "../parse"
|
||||
@@ -0,0 +1,149 @@
|
||||
use super::math_util;
|
||||
|
||||
use parse::Pos2;
|
||||
|
||||
const SLIDER_QUALITY: f32 = 50.0;
|
||||
|
||||
pub(crate) enum Points {
|
||||
Single(Pos2),
|
||||
Multi(Vec<Pos2>),
|
||||
}
|
||||
|
||||
pub(crate) enum Curve {
|
||||
Linear {
|
||||
a: Pos2,
|
||||
b: Pos2,
|
||||
},
|
||||
Bezier(Points),
|
||||
Catmull(Points),
|
||||
Perfect {
|
||||
origin: Pos2,
|
||||
cx: f32,
|
||||
cy: f32,
|
||||
radius: f32,
|
||||
},
|
||||
}
|
||||
|
||||
impl Curve {
|
||||
pub(crate) fn linear(a: Pos2, b: Pos2) -> Self {
|
||||
Self::Linear { a, b }
|
||||
}
|
||||
|
||||
pub(crate) fn bezier(points: &[Pos2]) -> Self {
|
||||
if points.len() == 1 {
|
||||
return Self::Bezier(Points::Single(points[0]));
|
||||
}
|
||||
|
||||
let mut start = 0;
|
||||
let mut end = 0;
|
||||
let mut result = Vec::with_capacity(4);
|
||||
|
||||
for i in 0..points.len() - 1 {
|
||||
if end - start > 1 && points[i] == points[end - 1] {
|
||||
Self::_bezier(&mut result, &points[start..end]);
|
||||
start = end;
|
||||
}
|
||||
|
||||
end += 1;
|
||||
}
|
||||
|
||||
Self::_bezier(&mut result, &points[start..end + 1]);
|
||||
|
||||
Self::Bezier(Points::Multi(result))
|
||||
}
|
||||
|
||||
fn _bezier(result: &mut Vec<Pos2>, points: &[Pos2]) {
|
||||
let step = 0.25 / SLIDER_QUALITY / points.len() as f32;
|
||||
let mut i = 0.0;
|
||||
let n = points.len() as i32 - 1;
|
||||
|
||||
while i < 1.0 + step {
|
||||
let point = (0..=n).fold(Pos2 { x: 0.0, y: 0.0 }, |point, p| {
|
||||
let factor = math_util::cpn(p, n) * (1.0 - i).powi(n - p) * i.powi(p);
|
||||
|
||||
point + points[p as usize] * factor
|
||||
});
|
||||
|
||||
result.push(point);
|
||||
i += step;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn catmull(points: &[Pos2]) -> Self {
|
||||
if points.len() == 1 {
|
||||
return Self::Catmull(Points::Single(points[0]));
|
||||
}
|
||||
|
||||
let order = points.len();
|
||||
let step = 2.5 / SLIDER_QUALITY;
|
||||
let target = step + 1.0;
|
||||
|
||||
let mut resulting_points = Vec::with_capacity(4);
|
||||
|
||||
for x in 0..order - 1 {
|
||||
let mut t = 0.0;
|
||||
|
||||
while t < target {
|
||||
let v1 = if x >= 1 { points[x - 1] } else { points[x] };
|
||||
let v2 = points[x];
|
||||
|
||||
let v3 = if x + 1 < order {
|
||||
points[x + 1]
|
||||
} else {
|
||||
v2.add_scaled(v2.add_scaled(v1, -1.0), 1.0)
|
||||
};
|
||||
|
||||
let v4 = if x + 2 < order {
|
||||
points[x + 2]
|
||||
} else {
|
||||
v3.add_scaled(v3.add_scaled(v2, -1.0), 1.0)
|
||||
};
|
||||
|
||||
let point = Self::catmull_point(v1, v2, v3, v4, t);
|
||||
resulting_points.push(point);
|
||||
t += step;
|
||||
}
|
||||
}
|
||||
|
||||
Self::Catmull(Points::Multi(resulting_points))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn catmull_point(p0: Pos2, p1: Pos2, p2: Pos2, p3: Pos2, len: f32) -> Pos2 {
|
||||
Pos2 {
|
||||
x: math_util::catmull(p0.x, p1.x, p2.x, p3.x, len),
|
||||
y: math_util::catmull(p0.y, p1.y, p2.y, p3.y, len),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn perfect(points: &[Pos2]) -> Self {
|
||||
let (cx, cy, mut radius) = math_util::get_circum_circle(&points);
|
||||
radius *= ((!math_util::is_left(&points)) as i8 * 2 - 1) as f32;
|
||||
|
||||
Self::Perfect {
|
||||
origin: points[0],
|
||||
cx,
|
||||
cy,
|
||||
radius,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn point_at_distance(&self, len: f32) -> Pos2 {
|
||||
let points = match self {
|
||||
Self::Bezier(points) => points,
|
||||
Self::Catmull(points) => points,
|
||||
Self::Linear { a, b } => return math_util::point_on_line(*a, *b, len),
|
||||
Self::Perfect {
|
||||
origin,
|
||||
cx,
|
||||
cy,
|
||||
radius,
|
||||
} => return math_util::rotate(*cx, *cy, *origin, len / *radius),
|
||||
};
|
||||
|
||||
match points {
|
||||
Points::Single(point) => *point,
|
||||
Points::Multi(points) => math_util::point_at_distance(points, len),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
use super::OsuObject;
|
||||
|
||||
const NORMALIZED_RADIUS: f32 = 52.0;
|
||||
|
||||
pub(crate) struct DifficultyObject {
|
||||
pub(crate) base: OsuObject,
|
||||
pub(crate) prev: Option<(f32, f32)>, // (jump_dist, strain_time)
|
||||
|
||||
pub(crate) jump_dist: f32,
|
||||
pub(crate) travel_dist: f32,
|
||||
pub(crate) angle: Option<f32>,
|
||||
|
||||
pub(crate) delta: f32,
|
||||
pub(crate) strain_time: f32,
|
||||
}
|
||||
|
||||
impl DifficultyObject {
|
||||
pub(crate) fn new(
|
||||
base: OsuObject,
|
||||
prev: OsuObject,
|
||||
prev_diff: Option<DifficultyObject>,
|
||||
prev_prev: Option<OsuObject>,
|
||||
clock_rate: f32,
|
||||
) -> Self {
|
||||
let delta = (base.time() - prev.time()) / clock_rate;
|
||||
let strain_time = delta.max(50.0);
|
||||
|
||||
let radius = base.radius();
|
||||
let mut scaling_factor = NORMALIZED_RADIUS / radius;
|
||||
|
||||
if radius < 30.0 {
|
||||
let small_circle_bonus = (30.0 - radius).min(5.0) / 50.0;
|
||||
scaling_factor *= 1.0 + small_circle_bonus;
|
||||
}
|
||||
|
||||
let travel_dist = base.travel_dist();
|
||||
let prev_cursor_pos = prev.cursor_end_position();
|
||||
|
||||
let jump_dist = match base {
|
||||
OsuObject::Spinner { .. } => 0.0,
|
||||
_ => (base.stacked_pos() * scaling_factor - prev_cursor_pos * scaling_factor).length(),
|
||||
};
|
||||
|
||||
let angle = prev_prev.map(|prev_prev| {
|
||||
let prev_prev_cursor_pos = prev_prev.cursor_end_position();
|
||||
|
||||
let v1 = prev_prev_cursor_pos - prev.stacked_pos();
|
||||
let v2 = base.stacked_pos() - prev_cursor_pos;
|
||||
|
||||
let dot = v1.dot(v2);
|
||||
let det = v1.x * v2.y - v1.y * v2.x;
|
||||
|
||||
det.atan2(dot).abs()
|
||||
});
|
||||
|
||||
let prev = prev_diff.map(|o| (o.jump_dist, o.strain_time));
|
||||
|
||||
Self {
|
||||
base,
|
||||
prev,
|
||||
|
||||
jump_dist,
|
||||
travel_dist,
|
||||
angle,
|
||||
|
||||
delta,
|
||||
strain_time,
|
||||
}
|
||||
}
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
mod curve;
|
||||
mod difficulty_object;
|
||||
mod math_util;
|
||||
mod osu_object;
|
||||
mod skill;
|
||||
mod skill_kind;
|
||||
|
||||
use difficulty_object::DifficultyObject;
|
||||
use osu_object::OsuObject;
|
||||
use skill::Skill;
|
||||
use skill_kind::SkillKind;
|
||||
|
||||
use parse::{Beatmap, Mods};
|
||||
|
||||
const SECTION_LEN: f32 = 400.0;
|
||||
const DIFFICULTY_MULTIPLIER: f32 = 0.0675;
|
||||
|
||||
/// Star calculation for osu!standard maps
|
||||
pub fn stars(map: &Beatmap, mods: impl Mods) -> f32 {
|
||||
if map.hit_objects.len() < 2 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let attributes = map.attributes().mods(mods);
|
||||
let section_len = SECTION_LEN * attributes.clock_rate;
|
||||
|
||||
let mut hit_objects = map
|
||||
.hit_objects
|
||||
.iter()
|
||||
.map(|h| OsuObject::new(h, map, &attributes));
|
||||
|
||||
let mut skills = vec![Skill::new(SkillKind::Aim), Skill::new(SkillKind::Speed)];
|
||||
|
||||
let mut current_section_end =
|
||||
(map.hit_objects[0].start_time / section_len).ceil() * section_len;
|
||||
|
||||
let mut prev_prev = None;
|
||||
let mut prev = hit_objects.next().unwrap();
|
||||
let mut prev_diff = None;
|
||||
|
||||
for curr in hit_objects {
|
||||
let h = DifficultyObject::new(
|
||||
curr.clone(),
|
||||
prev.clone(),
|
||||
prev_diff,
|
||||
prev_prev,
|
||||
attributes.clock_rate,
|
||||
);
|
||||
|
||||
// println!(
|
||||
// "strain_time={} | travel_dist={} | jump_dist={} | angle={:?}",
|
||||
// h.strain_time, h.travel_dist, h.jump_dist, h.angle
|
||||
// );
|
||||
|
||||
while h.base.time() > current_section_end {
|
||||
for skill in skills.iter_mut() {
|
||||
skill.save_current_peak();
|
||||
skill.start_new_section_from(current_section_end);
|
||||
}
|
||||
|
||||
current_section_end += section_len;
|
||||
}
|
||||
|
||||
for skill in skills.iter_mut() {
|
||||
skill.process(&h);
|
||||
}
|
||||
|
||||
prev_prev = Some(prev);
|
||||
prev = curr;
|
||||
prev_diff = Some(h);
|
||||
}
|
||||
|
||||
for skill in skills.iter_mut() {
|
||||
skill.save_current_peak();
|
||||
}
|
||||
|
||||
let aim_rating = skills[0].difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
||||
let speed_rating = skills[1].difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
||||
|
||||
aim_rating + speed_rating + (aim_rating - speed_rating).abs() / 2.0
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::File;
|
||||
|
||||
#[test]
|
||||
fn test_single() {
|
||||
// let file = match File::open("E:/Games/osu!/beatmaps/1851299.osu") {
|
||||
// Ok(file) => file,
|
||||
// Err(why) => panic!("Could not open file: {}", why),
|
||||
// };
|
||||
let file = match File::open("C:/Users/Max/Desktop/1851299.osu") {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file: {}", why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map: {}", why),
|
||||
};
|
||||
|
||||
let stars = stars(&map, 0);
|
||||
|
||||
println!("Stars: {}", stars);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_osu() {
|
||||
let margin = 0.005;
|
||||
|
||||
#[rustfmt::skip]
|
||||
let data = vec![
|
||||
(1851299, 1 << 8, 4.23514130038547), // HT
|
||||
(1851299, 0, 5.356786475158158), // NM
|
||||
(1851299, 1 << 6, 7.450616908751305), // DT
|
||||
(1851299, 1 << 4, 5.6834681957637665),// HR
|
||||
(1851299, 1 << 1, 4.937817303399699), // EZ
|
||||
|
||||
(70090, 1 << 8, 2.2929922580201803), // HT
|
||||
(70090, 0, 2.8322940761833983), // NM
|
||||
(70090, 1 << 6, 3.8338563325375485), // DT
|
||||
(70090, 1 << 4, 3.0617492228478174), // HR
|
||||
(70090, 1 << 1, 2.698823231324141), // EZ
|
||||
|
||||
(1241370, 1 << 8, 5.662809600985943), // HT
|
||||
(1241370, 0, 7.0367002127481975), // NM
|
||||
(1241370, 1 << 6, 11.144720506574934),// DT
|
||||
(1241370, 1 << 4, 7.641688110458715), // HR
|
||||
(1241370, 1 << 1, 6.316288616688052), // EZ
|
||||
|
||||
// Slider fiesta
|
||||
// (1657535, 1 << 8, 4.1727975286379895),// HT
|
||||
// (1657535, 0, 5.16048239944917), // NM
|
||||
// (1657535, 1 << 6, 7.125936779100417), // DT
|
||||
// (1657535, 1 << 4, 5.545877027713307), // HR
|
||||
// (1657535, 1 << 1, 4.66015083361088), // EZ
|
||||
];
|
||||
|
||||
for (map_id, mods, expected_stars) in data {
|
||||
let file = match File::open(format!("./test/{}.osu", map_id)) {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
||||
};
|
||||
|
||||
let stars = stars(&map, mods);
|
||||
|
||||
assert!(
|
||||
(stars - expected_stars).abs() < margin,
|
||||
"Stars: {} | Expected: {} => {} margin [map {} | mods {}]",
|
||||
stars,
|
||||
expected_stars,
|
||||
(stars - expected_stars).abs(),
|
||||
map_id,
|
||||
mods
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
use parse::Pos2;
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn cpn(mut p: i32, n: i32) -> f32 {
|
||||
if p < 0 || p > n {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
p = p.min(n - p);
|
||||
let mut out = 1.0;
|
||||
|
||||
for i in 1..=p {
|
||||
out *= (n - p + i) as f32 / i as f32;
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn catmull(p0: f32, p1: f32, p2: f32, p3: f32, t: f32) -> f32 {
|
||||
0.5 * ((2.0 * p1)
|
||||
+ (-p0 + p2) * t
|
||||
+ (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t.powi(2)
|
||||
+ (-p0 + 3.0 * p1 - 3.0 * p2 + p3 * t.powi(3)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn point_on_line(p1: Pos2, p2: Pos2, len: f32) -> Pos2 {
|
||||
let mut full_len = ((p1.x - p2.x).powi(2) + (p1.y - p2.y).powi(2)).sqrt();
|
||||
let n = full_len - len;
|
||||
|
||||
if full_len.abs() < f32::EPSILON {
|
||||
full_len = 1.0;
|
||||
}
|
||||
|
||||
(p1 * n + p2 * len) / full_len
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn angle_from_points(p0: Pos2, p1: Pos2) -> f32 {
|
||||
(p1.y - p0.y).atan2(p1.x - p0.x)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn distance_from_points(arr: &[Pos2]) -> f32 {
|
||||
arr.iter()
|
||||
.skip(1)
|
||||
.zip(arr.iter())
|
||||
.map(|(curr, prev)| curr.distance(prev))
|
||||
.sum()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn cart_from_pol(r: f32, t: f32) -> Pos2 {
|
||||
Pos2 {
|
||||
x: r * t.cos(),
|
||||
y: r * t.sin(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn point_at_distance(array: &[Pos2], distance: f32) -> Pos2 {
|
||||
if array.len() < 2 {
|
||||
return Pos2 { x: 0.0, y: 0.0 };
|
||||
} else if distance.abs() < f32::EPSILON {
|
||||
return array[0];
|
||||
} else if distance_from_points(array) <= distance {
|
||||
return array[array.len() - 1];
|
||||
}
|
||||
|
||||
let mut i = 0;
|
||||
let mut current_distance = 0.0;
|
||||
let mut new_distance = 0.0;
|
||||
|
||||
while i < array.len() - 2 {
|
||||
new_distance = (array[i] - array[i + 1]).length();
|
||||
current_distance += new_distance;
|
||||
|
||||
if distance <= current_distance {
|
||||
break;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
current_distance -= new_distance;
|
||||
|
||||
if (distance - current_distance).abs() <= f32::EPSILON {
|
||||
array[i]
|
||||
} else {
|
||||
let angle = angle_from_points(array[i], array[i + 1]);
|
||||
let cart = cart_from_pol(distance - current_distance, angle);
|
||||
|
||||
if array[i].x > array[i + 1].x {
|
||||
array[i] - cart
|
||||
} else {
|
||||
array[i] + cart
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_circum_circle(p: &[Pos2]) -> (f32, f32, f32) {
|
||||
let d = 2.0
|
||||
* (p[0].x * (p[1].y - p[2].y) + p[1].x * (p[2].y - p[0].y) + p[2].x * (p[0].y - p[1].y));
|
||||
|
||||
let p0 = p[0].x * p[0].x + p[0].y * p[0].y;
|
||||
let p1 = p[1].x * p[1].x + p[1].y * p[1].y;
|
||||
let p2 = p[2].x * p[2].x + p[2].y * p[2].y;
|
||||
|
||||
let ux = (p0 * (p[1].y - p[2].y) + p1 * (p[2].y - p[0].y) + p2 * (p[0].y - p[1].y)) / d;
|
||||
let uy = (p0 * (p[2].x - p[1].x) + p1 * (p[0].x - p[2].x) + p2 * (p[1].x - p[0].x)) / d;
|
||||
|
||||
let px = ux - p[0].x;
|
||||
let py = uy - p[0].y;
|
||||
let r = (px * px + py * py).sqrt();
|
||||
|
||||
(ux, uy, r)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn is_left(p: &[Pos2]) -> bool {
|
||||
((p[1].x - p[0].x) * (p[2].y - p[0].y) - (p[1].y - p[0].y) * (p[2].x - p[0].x)) < 0.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn rotate(cx: f32, cy: f32, p: Pos2, radians: f32) -> Pos2 {
|
||||
let cos = radians.cos();
|
||||
let sin = radians.sin();
|
||||
|
||||
Pos2 {
|
||||
x: (cos * (p.x - cx)) - (sin * (p.y - cy)) + cx,
|
||||
y: (sin * (p.x - cx)) + (cos * (p.y - cy)) + cy,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
use super::curve::Curve;
|
||||
|
||||
use parse::{Beatmap, BeatmapAttributes, HitObject, HitObjectKind, PathType, Pos2};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
macro_rules! binary_search {
|
||||
($slice:expr, $target:expr) => {
|
||||
$slice.binary_search_by(|p| p.time.partial_cmp(&$target).unwrap_or(Ordering::Equal))
|
||||
};
|
||||
}
|
||||
|
||||
const OBJECT_RADIUS: f32 = 64.0;
|
||||
const STACK_DIST: f32 = 3.0;
|
||||
const LEGACY_LAST_TICK_OFFSET: f32 = 36.0;
|
||||
|
||||
#[derive(Clone)] // TODO: Remove clone
|
||||
pub(crate) enum OsuObject {
|
||||
Circle {
|
||||
pos: Pos2,
|
||||
time: f32,
|
||||
|
||||
scale: f32,
|
||||
stack_height: f32,
|
||||
},
|
||||
Slider {
|
||||
pixel_len: f32, // TODO: Redundant?
|
||||
repeats: usize, // TODO: Redundant?
|
||||
|
||||
objects: Vec<SliderTick>,
|
||||
|
||||
scale: f32,
|
||||
stack_height: f32,
|
||||
|
||||
cursor_end_pos: Pos2,
|
||||
cursor_travel_dist: f32,
|
||||
},
|
||||
Spinner {
|
||||
pos: Pos2,
|
||||
time: f32,
|
||||
},
|
||||
}
|
||||
|
||||
impl OsuObject {
|
||||
pub(crate) fn new(h: &HitObject, map: &Beatmap, attributes: &BeatmapAttributes) -> Self {
|
||||
let pos = h.pos;
|
||||
let time = h.start_time;
|
||||
|
||||
let scale = (1.0 - 0.7 * (attributes.cs - 5.0) / 5.0) / 2.0;
|
||||
let mut stack_height = 0.0;
|
||||
|
||||
match &h.kind {
|
||||
HitObjectKind::Circle => Self::Circle {
|
||||
pos,
|
||||
time,
|
||||
scale,
|
||||
stack_height,
|
||||
},
|
||||
HitObjectKind::Slider {
|
||||
pixel_len,
|
||||
repeats,
|
||||
curve_points,
|
||||
path_type,
|
||||
} => {
|
||||
let (beat_len, timing_time) = {
|
||||
match binary_search!(map.timing_points, time) {
|
||||
Ok(idx) => {
|
||||
let point = &map.timing_points[idx];
|
||||
(point.beat_len, point.time)
|
||||
}
|
||||
Err(0) => (1000.0, 0.0),
|
||||
Err(idx) => {
|
||||
let point = &map.timing_points[idx - 1];
|
||||
(point.beat_len, point.time)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let (speed_multiplier, diff_time) = {
|
||||
match binary_search!(map.difficulty_points, time) {
|
||||
Ok(idx) => {
|
||||
let point = &map.difficulty_points[idx];
|
||||
(point.speed_multiplier, point.time)
|
||||
}
|
||||
Err(0) => (1.0, 0.0),
|
||||
Err(idx) => {
|
||||
let point = &map.difficulty_points[idx - 1];
|
||||
(point.speed_multiplier, point.time)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut tick_distance = 100.0 * map.sv / map.tick_rate;
|
||||
|
||||
if map.version >= 8 {
|
||||
tick_distance /= (100.0 / speed_multiplier).max(10.0).min(1000.0) / 100.0;
|
||||
}
|
||||
|
||||
let spm = if timing_time > diff_time {
|
||||
1.0
|
||||
} else {
|
||||
speed_multiplier
|
||||
};
|
||||
|
||||
let duration = *repeats as f32 * beat_len * pixel_len / (map.sv * spm) / 100.0;
|
||||
|
||||
// let velocity = *pixel_len as f32 / duration;
|
||||
|
||||
// println!("duration={}", duration);
|
||||
// println!("velocity={}", velocity);
|
||||
|
||||
let path_type = if *path_type == PathType::PerfectCurve && curve_points.len() > 3 {
|
||||
PathType::Bezier
|
||||
} else if curve_points.len() == 2 {
|
||||
PathType::Linear
|
||||
} else {
|
||||
*path_type
|
||||
};
|
||||
|
||||
let curve = match path_type {
|
||||
PathType::Linear => Curve::linear(curve_points[0], curve_points[1]),
|
||||
PathType::Bezier => Curve::bezier(&curve_points),
|
||||
PathType::Catmull => Curve::catmull(&curve_points),
|
||||
PathType::PerfectCurve => Curve::perfect(&curve_points),
|
||||
};
|
||||
|
||||
let mut current_distance = tick_distance;
|
||||
let time_add = duration * (tick_distance / (pixel_len * *repeats as f32));
|
||||
|
||||
let target = pixel_len - tick_distance / 8.0;
|
||||
let mut ticks = Vec::with_capacity((target / tick_distance) as usize);
|
||||
|
||||
while current_distance < target {
|
||||
let pos = curve.point_at_distance(current_distance);
|
||||
let time = h.start_time + time_add * (ticks.len() + 1) as f32;
|
||||
ticks.push(SliderTick::new(pos, time));
|
||||
current_distance += tick_distance;
|
||||
}
|
||||
|
||||
let mut slider_objects = Vec::with_capacity(repeats * (ticks.len() + 1));
|
||||
slider_objects.push(SliderTick::new(h.pos, h.start_time));
|
||||
|
||||
if *repeats <= 1 {
|
||||
slider_objects.append(&mut ticks);
|
||||
} else {
|
||||
slider_objects.append(&mut ticks.clone());
|
||||
|
||||
for repeat_id in 1..repeats - 1 {
|
||||
let dist = (repeat_id % 2) as f32 * pixel_len;
|
||||
let time_offset = (duration / *repeats as f32) * repeat_id as f32;
|
||||
let pos = curve.point_at_distance(dist);
|
||||
|
||||
// Reverse tick / last legacy tick
|
||||
slider_objects.push(SliderTick::new(pos, h.start_time + time_offset));
|
||||
|
||||
ticks.reverse();
|
||||
slider_objects.extend_from_slice(&ticks); // tick time doesn't need to be adjusted for some reason
|
||||
}
|
||||
|
||||
// Handling last span separatly so that `ticks` vector isn't cloned again
|
||||
let dist = ((repeats - 1) % 2) as f32 * pixel_len;
|
||||
let time_offset = (duration / *repeats as f32) * (repeats - 1) as f32;
|
||||
let pos = curve.point_at_distance(dist);
|
||||
|
||||
slider_objects.push(SliderTick::new(pos, h.start_time + time_offset));
|
||||
|
||||
ticks.reverse();
|
||||
slider_objects.append(&mut ticks);
|
||||
}
|
||||
|
||||
// Slider tail
|
||||
let dist_end = (repeats % 2) as f32 * pixel_len;
|
||||
// let dist_end = velocity * (duration - LEGACY_LAST_TICK_OFFSET);
|
||||
let pos = curve.point_at_distance(dist_end);
|
||||
slider_objects.push(SliderTick::new(pos, h.start_time + duration));
|
||||
|
||||
println!("> Slider: {:?}", slider_objects);
|
||||
|
||||
let radius = OBJECT_RADIUS * scale;
|
||||
|
||||
let stack_offset = {
|
||||
let c = stack_height * scale * -6.4;
|
||||
|
||||
Pos2 { x: c, y: c }
|
||||
};
|
||||
|
||||
// println!("radius={} | stack_offset={:?}", radius, stack_offset);
|
||||
|
||||
let stacked_pos = pos + stack_offset;
|
||||
|
||||
let mut cursor_end_pos = stacked_pos;
|
||||
let mut cursor_travel_dist = 0.0;
|
||||
let approx_follow_circle_radius = radius * 3.0;
|
||||
|
||||
for (i, tick) in slider_objects.iter().skip(1).enumerate() {
|
||||
let diff = stacked_pos + tick.pos - cursor_end_pos;
|
||||
let mut dist = diff.length();
|
||||
|
||||
println!("[{}] diff={:?} | dist={}", i, diff, dist);
|
||||
|
||||
if dist > approx_follow_circle_radius {
|
||||
let normalized = diff.normalize();
|
||||
dist -= approx_follow_circle_radius;
|
||||
cursor_end_pos += normalized * dist;
|
||||
cursor_travel_dist += dist;
|
||||
}
|
||||
}
|
||||
|
||||
println!("---");
|
||||
|
||||
Self::Slider {
|
||||
pixel_len: *pixel_len,
|
||||
repeats: *repeats,
|
||||
|
||||
objects: slider_objects,
|
||||
|
||||
scale,
|
||||
stack_height,
|
||||
|
||||
cursor_end_pos,
|
||||
cursor_travel_dist,
|
||||
}
|
||||
}
|
||||
HitObjectKind::Spinner { .. } => Self::Spinner { pos, time },
|
||||
HitObjectKind::Hold { .. } => panic!("found Hold object in osu!standard file"),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn time(&self) -> f32 {
|
||||
match self {
|
||||
Self::Circle { time, .. } => *time,
|
||||
Self::Slider { objects, .. } => objects[0].time,
|
||||
Self::Spinner { time, .. } => *time,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn radius(&self) -> f32 {
|
||||
OBJECT_RADIUS * self.scale()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn travel_dist(&self) -> f32 {
|
||||
match self {
|
||||
Self::Slider {
|
||||
cursor_travel_dist, ..
|
||||
} => *cursor_travel_dist,
|
||||
_ => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn stacked_pos(&self) -> Pos2 {
|
||||
self.pos() + self.stack_offset()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn cursor_end_position(&self) -> Pos2 {
|
||||
match self {
|
||||
Self::Circle { pos, .. } => *pos,
|
||||
Self::Slider { cursor_end_pos, .. } => *cursor_end_pos,
|
||||
Self::Spinner { pos, .. } => *pos,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn is_spinner(&self) -> bool {
|
||||
matches!(self, Self::Spinner { .. })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn scale(&self) -> f32 {
|
||||
match self {
|
||||
Self::Circle { scale, .. } => *scale,
|
||||
Self::Slider { scale, .. } => *scale,
|
||||
Self::Spinner { .. } => 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn stack_height(&self) -> f32 {
|
||||
match self {
|
||||
Self::Circle { stack_height, .. } => *stack_height,
|
||||
Self::Slider { stack_height, .. } => *stack_height,
|
||||
Self::Spinner { .. } => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pos(&self) -> Pos2 {
|
||||
match self {
|
||||
Self::Circle { pos, .. } => *pos,
|
||||
Self::Slider { objects, .. } => objects[0].pos,
|
||||
Self::Spinner { .. } => Pos2::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn stack_offset(&self) -> Pos2 {
|
||||
let c = self.stack_height() * self.scale() * -6.4;
|
||||
|
||||
Pos2 { x: c, y: c }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) struct SliderTick {
|
||||
pos: Pos2,
|
||||
time: f32,
|
||||
}
|
||||
|
||||
impl SliderTick {
|
||||
fn new(pos: Pos2, time: f32) -> Self {
|
||||
Self { pos, time }
|
||||
}
|
||||
}
|
||||
|
||||
use std::fmt;
|
||||
|
||||
impl fmt::Debug for SliderTick {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{{pos={:?} | time={}}}", self.pos, self.time)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
use super::{DifficultyObject, SkillKind};
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
const SPEED_SKILL_MULTIPLIER: f32 = 1400.0;
|
||||
const SPEED_STRAIN_DECAY_BASE: f32 = 0.3;
|
||||
|
||||
const AIM_SKILL_MULTIPLIER: f32 = 26.25;
|
||||
const AIM_STRAIN_DECAY_BASE: f32 = 0.15;
|
||||
|
||||
const DECAY_WEIGHT: f32 = 0.9;
|
||||
|
||||
pub(crate) struct Skill {
|
||||
pub current_strain: f32,
|
||||
current_section_peak: f32,
|
||||
|
||||
kind: SkillKind,
|
||||
strain_peaks: Vec<f32>,
|
||||
|
||||
prev_time: Option<f32>,
|
||||
}
|
||||
|
||||
impl Skill {
|
||||
#[inline]
|
||||
pub(crate) fn new(kind: SkillKind) -> Self {
|
||||
Self {
|
||||
current_strain: 1.0,
|
||||
current_section_peak: 1.0,
|
||||
|
||||
kind,
|
||||
strain_peaks: Vec::with_capacity(128),
|
||||
|
||||
prev_time: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn save_current_peak(&mut self) {
|
||||
if self.prev_time.is_some() {
|
||||
self.strain_peaks.push(self.current_section_peak);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn start_new_section_from(&mut self, time: f32) {
|
||||
if let Some(prev) = self.prev_time {
|
||||
self.current_section_peak = self.peak_strain(time - prev);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn process(&mut self, current: &DifficultyObject) {
|
||||
self.current_strain *= self.strain_decay(current.delta);
|
||||
self.current_strain += self.kind.strain_value_of(¤t) * self.skill_multiplier();
|
||||
self.current_section_peak = self.current_section_peak.max(self.current_strain);
|
||||
self.prev_time.replace(current.base.time());
|
||||
}
|
||||
|
||||
pub(crate) fn difficulty_value(&mut self) -> f32 {
|
||||
let mut difficulty = 0.0;
|
||||
let mut weight = 1.0;
|
||||
|
||||
self.strain_peaks
|
||||
.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
||||
|
||||
for &strain in self.strain_peaks.iter() {
|
||||
difficulty += strain * weight;
|
||||
weight *= DECAY_WEIGHT;
|
||||
}
|
||||
|
||||
difficulty
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn skill_multiplier(&self) -> f32 {
|
||||
match self.kind {
|
||||
SkillKind::Aim => AIM_SKILL_MULTIPLIER,
|
||||
SkillKind::Speed => SPEED_SKILL_MULTIPLIER,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_decay_base(&self) -> f32 {
|
||||
match self.kind {
|
||||
SkillKind::Aim => AIM_STRAIN_DECAY_BASE,
|
||||
SkillKind::Speed => SPEED_STRAIN_DECAY_BASE,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn peak_strain(&self, delta_time: f32) -> f32 {
|
||||
self.current_strain * self.strain_decay(delta_time)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_decay(&self, ms: f32) -> f32 {
|
||||
self.strain_decay_base().powf(ms / 1000.0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
use super::DifficultyObject;
|
||||
|
||||
const SINGLE_SPACING_TRESHOLD: f32 = 125.0;
|
||||
const SPEED_ANGLE_BONUS_BEGIN: f32 = std::f32::consts::FRAC_PI_6;
|
||||
const PI_OVER_4: f32 = std::f32::consts::FRAC_PI_4;
|
||||
const PI_OVER_2: f32 = std::f32::consts::FRAC_PI_2;
|
||||
|
||||
const MIN_SPEED_BONUS: f32 = 75.0;
|
||||
const MAX_SPEED_BONUS: f32 = 45.0;
|
||||
const SPEED_BALANCING_FACTOR: f32 = 40.0;
|
||||
|
||||
const AIM_ANGLE_BONUS_BEGIN: f32 = std::f32::consts::FRAC_PI_3;
|
||||
const TIMING_THRESHOLD: f32 = 107.0;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum SkillKind {
|
||||
Aim,
|
||||
Speed,
|
||||
}
|
||||
|
||||
impl SkillKind {
|
||||
pub(crate) fn strain_value_of(self, current: &DifficultyObject) -> f32 {
|
||||
match self {
|
||||
Self::Aim => {
|
||||
if current.base.is_spinner() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let mut result = 0.0;
|
||||
|
||||
if let Some((prev_jump_dist, prev_strain_time)) = current.prev {
|
||||
if let Some(angle) = current.angle.filter(|a| *a > AIM_ANGLE_BONUS_BEGIN) {
|
||||
let scale = 90.0;
|
||||
|
||||
let angle_bonus = (((angle - AIM_ANGLE_BONUS_BEGIN).sin()).powi(2)
|
||||
* (prev_jump_dist - scale).max(0.0)
|
||||
* (current.jump_dist - scale).max(0.0))
|
||||
.sqrt();
|
||||
|
||||
result = 1.5 * apply_diminishing_exp(angle_bonus.max(0.0))
|
||||
/ (TIMING_THRESHOLD).max(prev_strain_time)
|
||||
}
|
||||
}
|
||||
|
||||
let jump_dist_exp = apply_diminishing_exp(current.jump_dist);
|
||||
let travel_dist_exp = apply_diminishing_exp(current.travel_dist);
|
||||
|
||||
let dist_exp =
|
||||
jump_dist_exp + travel_dist_exp + (travel_dist_exp * jump_dist_exp).sqrt();
|
||||
|
||||
(result + dist_exp / (current.strain_time).max(TIMING_THRESHOLD))
|
||||
.max(dist_exp / current.strain_time)
|
||||
}
|
||||
Self::Speed => {
|
||||
if current.base.is_spinner() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let dist = SINGLE_SPACING_TRESHOLD.min(current.travel_dist + current.jump_dist);
|
||||
let delta_time = MAX_SPEED_BONUS.max(current.delta);
|
||||
|
||||
let mut speed_bonus = 1.0;
|
||||
|
||||
if delta_time < MIN_SPEED_BONUS {
|
||||
let exp_base = (MIN_SPEED_BONUS - delta_time) / SPEED_BALANCING_FACTOR;
|
||||
speed_bonus = 1.0 + exp_base * exp_base;
|
||||
}
|
||||
|
||||
let mut angle_bonus = 1.0;
|
||||
|
||||
if let Some(angle) = current.angle.filter(|a| *a < SPEED_ANGLE_BONUS_BEGIN) {
|
||||
let exp_base = (1.5 * (SPEED_ANGLE_BONUS_BEGIN - angle)).sin();
|
||||
angle_bonus = 1.0 + exp_base * exp_base / 3.57;
|
||||
|
||||
if angle < PI_OVER_2 {
|
||||
angle_bonus = 1.28;
|
||||
|
||||
// TODO: Improve ifs
|
||||
if dist < 90.0 && angle < PI_OVER_4 {
|
||||
angle_bonus += (1.0 - angle_bonus) * ((90.0 - dist) / 10.0).min(1.0);
|
||||
} else if dist < 90.0 {
|
||||
angle_bonus += (1.0 - angle_bonus)
|
||||
* ((90.0 - dist) / 10.0).min(1.0)
|
||||
* ((PI_OVER_2 - angle) / PI_OVER_4).sin();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(1.0 + (speed_bonus - 1.0) * 0.75)
|
||||
* angle_bonus
|
||||
* (0.95 + speed_bonus * (dist / SINGLE_SPACING_TRESHOLD).powf(3.5))
|
||||
/ current.strain_time
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn apply_diminishing_exp(val: f32) -> f32 {
|
||||
val.powf(0.99)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,188 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
AudioFilename: audio.mp3
|
||||
AudioLeadIn: 0
|
||||
PreviewTime: 2154
|
||||
Countdown: 0
|
||||
SampleSet: Soft
|
||||
StackLeniency: 0.7
|
||||
Mode: 0
|
||||
LetterboxInBreaks: 0
|
||||
WidescreenStoryboard: 1
|
||||
|
||||
[Editor]
|
||||
Bookmarks: 14272,22742,24684,25566,28390,34037
|
||||
DistanceSpacing: 0.8
|
||||
BeatDivisor: 3
|
||||
GridSize: 4
|
||||
TimelineZoom: 1.3
|
||||
|
||||
[Metadata]
|
||||
Title:Harumachi Clover (Swing Arrangement)
|
||||
TitleUnicode:春待ちクローバー (Swing Arrangement)
|
||||
Artist:Will Stetson
|
||||
ArtistUnicode:Will Stetson
|
||||
Creator:Will Stetson
|
||||
Version:Woey's Extra
|
||||
Source:OneRoom
|
||||
Tags:花坂結衣 CV:M・A・O Hanasaka Yui M.A.O kantoku カントク one room ワンルーム mix remix yahuri mohab500 FrenZ396 Chanci -Laura- Corinn Akitoshi Denil25 Dafiely A_r_m_i_n Armin wiz khalifa Jenny Woey Mr Moseby FoxyGrandpa Foxy Grandpa fieryrage
|
||||
BeatmapID:1851299
|
||||
BeatmapSetID:859783
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:6
|
||||
CircleSize:3.8
|
||||
OverallDifficulty:9.1
|
||||
ApproachRate:9.2
|
||||
SliderMultiplier:1.80000000596047
|
||||
SliderTickRate:1
|
||||
|
||||
[Events]
|
||||
//Background and Video events
|
||||
0,0,"exbg.jpg",0,0
|
||||
//Break Periods
|
||||
//Storyboard Layer 0 (Background)
|
||||
//Storyboard Layer 1 (Fail)
|
||||
//Storyboard Layer 2 (Pass)
|
||||
//Storyboard Layer 3 (Foreground)
|
||||
//Storyboard Sound Samples
|
||||
|
||||
[TimingPoints]
|
||||
154,352.941176470588,4,2,22,47,1,0
|
||||
1919,-125,4,2,22,47,0,0
|
||||
2154,-125,4,2,22,47,0,0
|
||||
13919,-125,4,1,47,75,0,0
|
||||
14272,-90.9090909090909,4,2,47,75,0,0
|
||||
24242,-90.9090909090909,4,2,47,75,0,0
|
||||
24331,-90.9090909090909,4,2,47,75,0,0
|
||||
29625,-90.9090909090909,4,2,47,75,0,0
|
||||
29801,-90.9090909090909,4,2,47,75,0,0
|
||||
30154,-90.9090909090909,4,2,47,75,0,0
|
||||
30507,-90.9090909090909,4,2,47,75,0,0
|
||||
30860,-90.9090909090909,4,2,47,75,0,0
|
||||
31213,-90.9090909090909,4,2,47,75,0,0
|
||||
32624,-90.9090909090909,4,2,47,15,0,0
|
||||
|
||||
|
||||
[Colours]
|
||||
Combo1 : 255,43,48
|
||||
Combo2 : 239,186,239
|
||||
Combo3 : 205,218,165
|
||||
Combo4 : 152,211,231
|
||||
Combo5 : 239,226,186
|
||||
|
||||
[HitObjects]
|
||||
256,192,2154,5,2,0:0:0:0:
|
||||
312,180,2272,2,0,P|344:130|334:66,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
210,125,2625,2,0,P|178:175|188:239,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
299,303,2978,6,0,L|414:291,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
465,200,3331,2,0,L|369:209,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
323,384,3684,2,0,L|438:372,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
276,219,4037,2,0,L|180:228,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
111,293,4390,6,0,P|178:323|222:314,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
249,294,4742,1,2,0:0:0:0:
|
||||
203,109,4978,2,0,P|82:154|74:216,1,192.000000635783,2|2,0:0|0:0,0:0:0:0:
|
||||
329,209,5684,5,2,0:0:0:0:
|
||||
315,153,5801,2,0,L|290:47,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
423,181,6154,2,0,L|400:274,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
207,234,6507,2,0,L|229:140,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
378,27,6860,2,0,P|426:69|429:118,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
400,274,7213,6,0,P|352:271|314:244,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
300,188,7566,1,2,0:0:0:0:
|
||||
495,319,7801,2,0,P|414:362|291:339,1,192.000000635783,2|2,0:0|0:0,0:0:0:0:
|
||||
169,265,8507,5,2,0:0:0:0:
|
||||
218,235,8625,2,0,P|269:225|318:259,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
206,335,8978,2,0,P|155:345|106:311,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
87,276,9331,1,2,0:0:0:0:
|
||||
289,325,9566,1,2,0:0:0:0:
|
||||
289,325,9684,1,2,0:0:0:0:
|
||||
175,164,9919,6,0,P|299:144|347:167,1,144.000000476838,2|2,0:0|0:0,0:0:0:0:
|
||||
370,266,10390,2,0,P|315:224|306:172,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
312,148,10742,1,2,0:0:0:0:
|
||||
475,241,10978,1,2,0:0:0:0:
|
||||
475,241,11095,1,2,0:0:0:0:
|
||||
403,57,11331,5,2,0:0:0:0:
|
||||
403,57,11448,2,0,P|441:49|481:60,1,48.0000001589459,2|0,0:0|0:0,0:0:0:0:
|
||||
512,165,11684,1,2,0:0:0:0:
|
||||
290,249,12037,1,2,0:0:0:0:
|
||||
318,12,12389,1,2,0:0:0:0:
|
||||
58,105,12742,6,0,P|94:183|146:139,1,144.000000476838,2|2,0:0|0:0,0:0:0:0:
|
||||
206,88,13213,2,0,P|265:77|305:98,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
334,221,13565,2,0,P|294:175|293:130,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
398,78,13919,5,0,1:0:0:0:
|
||||
398,78,14007,1,0,1:0:0:0:
|
||||
398,78,14095,1,0,1:0:0:0:
|
||||
203,217,14272,5,2,1:0:0:0:
|
||||
449,215,14448,1,8,0:0:0:0:
|
||||
274,168,14625,5,2,1:0:0:0:
|
||||
489,292,14801,1,8,0:0:0:0:
|
||||
234,329,14978,5,2,1:0:0:0:
|
||||
449,215,15154,1,10,0:0:0:0:
|
||||
203,217,15331,5,2,1:0:0:0:
|
||||
481,373,15507,1,10,0:0:0:0:
|
||||
136,273,15684,6,0,P|117:213|143:155,1,99.0000033490662,2|10,1:0|0:0,0:0:0:0:
|
||||
280,55,16037,1,2,1:0:0:0:
|
||||
360,266,16213,2,0,P|300:285|242:259,1,99.0000033490662,10|0,0:0|1:0,0:0:0:0:
|
||||
178,85,16566,1,8,0:0:0:0:
|
||||
353,131,16742,1,0,1:0:0:0:
|
||||
233,207,16919,1,10,0:0:0:0:
|
||||
280,55,17095,5,2,1:0:0:0:
|
||||
311,199,17272,1,10,0:0:0:0:
|
||||
366,38,17448,5,2,1:0:0:0:
|
||||
381,245,17625,1,10,0:0:0:0:
|
||||
264,21,17801,5,2,1:0:0:0:
|
||||
244,266,17978,1,10,0:0:0:0:
|
||||
153,6,18154,5,2,1:0:0:0:
|
||||
96,249,18331,1,10,0:0:0:0:
|
||||
331,101,18507,5,2,1:0:0:0:
|
||||
88,78,18684,1,8,0:0:0:0:
|
||||
375,19,18860,1,2,1:0:0:0:
|
||||
132,160,19037,2,0,P|191:205|250:197,1,99.0000033490662,10|0,0:0|1:0,0:0:0:0:
|
||||
420,170,19390,1,8,0:0:0:0:
|
||||
202,291,19566,1,0,1:0:0:0:
|
||||
331,101,19742,1,10,0:0:0:0:
|
||||
291,360,19919,5,2,1:0:0:0:
|
||||
318,189,20095,1,10,0:0:0:0:
|
||||
175,357,20272,5,2,1:0:0:0:
|
||||
148,186,20448,1,10,0:0:0:0:
|
||||
353,282,20625,5,2,1:0:0:0:
|
||||
315,39,20801,1,10,0:0:0:0:
|
||||
223,282,20978,5,2,1:0:0:0:
|
||||
185,39,21154,1,10,0:0:0:0:
|
||||
401,202,21331,5,0,1:0:0:0:
|
||||
256,172,21507,1,10,0:0:0:0:
|
||||
471,141,21684,5,2,1:0:0:0:
|
||||
441,286,21860,1,10,0:0:0:0:
|
||||
326,111,22037,5,2,1:0:0:0:
|
||||
402,381,22213,1,10,0:0:0:0:
|
||||
402,54,22390,5,2,1:0:0:0:
|
||||
326,324,22566,1,10,0:0:0:0:
|
||||
268,31,22742,6,0,P|167:29|111:114,1,198.000006698132,6|0,1:2|1:0,0:0:0:0:
|
||||
213,314,23272,2,0,B|232:268|220:220|216:220|212:220|192:180|214:120,1,198.000006698132,12|8,0:0|0:0,0:0:0:0:
|
||||
321,26,23801,1,0,1:0:0:0:
|
||||
481,201,23978,2,0,P|431:253|333:171,1,198.000006698132,14|2,0:0|1:0,0:0:0:0:
|
||||
456,41,24507,1,10,0:0:0:0:
|
||||
498,300,24684,2,0,P|414:331|303:289,1,198.000006698132,14|2,0:0|1:0,0:0:0:0:
|
||||
220,82,25213,1,10,0:0:0:0:
|
||||
383,148,25390,1,10,0:0:0:0:
|
||||
251,245,25566,5,6,1:2:0:0:
|
||||
276,13,25742,1,8,0:0:0:0:
|
||||
454,192,25919,5,4,1:2:0:0:
|
||||
188,162,26095,1,8,0:0:0:0:
|
||||
395,3,26272,5,4,1:2:0:0:
|
||||
364,283,26448,1,10,0:0:0:0:
|
||||
169,75,26625,5,4,1:2:0:0:
|
||||
487,111,26801,1,10,0:0:0:0:
|
||||
277,326,26978,5,4,1:2:0:0:
|
||||
259,42,27154,1,8,0:0:0:0:
|
||||
371,361,27331,5,6,1:2:0:0:
|
||||
364,60,27507,1,10,0:0:0:0:
|
||||
254,344,27684,5,4,1:2:0:0:
|
||||
284,26,27860,1,10,0:0:0:0:
|
||||
126,363,28037,5,4,1:2:0:0:
|
||||
195,22,28213,1,10,0:0:0:0:
|
||||
179,289,28390,6,0,P|125:257|122:133,1,198.000006698132,6|0,1:2|0:0,0:0:0:0:
|
||||
398,195,28919,2,0,B|356:180|304:188|304:192|304:196|260:216|204:193,1,198.000006698132,6|0,1:2|0:0,0:0:0:0:
|
||||
47,39,29448,5,6,1:2:0:0:
|
||||
256,192,29625,12,0,32977,0:0:0:0:
|
||||
@@ -0,0 +1,391 @@
|
||||
osu file format v8
|
||||
|
||||
[General]
|
||||
AudioFilename: tutorial2.ogg
|
||||
AudioLeadIn: 0
|
||||
PreviewTime: 52620
|
||||
Countdown: 0
|
||||
SampleSet: Normal
|
||||
StackLeniency: 0.7
|
||||
Mode: 0
|
||||
LetterboxInBreaks: 0
|
||||
UseSkinSprites: 1
|
||||
|
||||
[Editor]
|
||||
Bookmarks: 42132
|
||||
DistanceSpacing: 1.39999997615814
|
||||
BeatDivisor: 3
|
||||
GridSize: 8
|
||||
|
||||
[Metadata]
|
||||
Title:osu! tutorial
|
||||
Artist:Peter Lambert
|
||||
Creator:Sushi
|
||||
Version:Advanced Gameplay
|
||||
Source:
|
||||
Tags:Agnes
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:4
|
||||
CircleSize:4
|
||||
OverallDifficulty:6
|
||||
ApproachRate:7
|
||||
SliderMultiplier:1.5
|
||||
SliderTickRate:1
|
||||
|
||||
[Events]
|
||||
//Background and Video events
|
||||
0,0,"bg.jpg"
|
||||
//Break Periods
|
||||
//Storyboard Layer 0 (Background)
|
||||
Sprite,Background,Centre,"black.png",320,240
|
||||
M,0,-2119,-124,320,240
|
||||
M,0,-124,6235,320,240
|
||||
F,0,5861,6235,1,0
|
||||
Sprite,Background,Centre,"osulogo.png",320,250
|
||||
L,1746,11
|
||||
S,1,0,374,1.1,1
|
||||
F,0,24,607,0,1
|
||||
M,1,24,1746,0,250,320,250
|
||||
R,1,24,1746,-4,0
|
||||
F,1,5861,6235,1,0
|
||||
S,0,5861,6235,1,3
|
||||
Sprite,Background,Centre,"black.png",320,240
|
||||
F,0,122709,122833,0,1
|
||||
F,0,122833,125951,1,0.984
|
||||
//Storyboard Layer 1 (Fail)
|
||||
//Storyboard Layer 2 (Pass)
|
||||
Sprite,Pass,Centre,"endpass.png",320,240
|
||||
M,0,119965,,316,155
|
||||
F,0,119965,120090,0,1
|
||||
S,1,119965,120090,0.1,1
|
||||
F,0,120090,,1
|
||||
F,0,120090,122958,1
|
||||
F,0,122958,123083,1,0
|
||||
F,0,123083,,0
|
||||
Sprite,Pass,Centre,"inorite.png",320,240
|
||||
M,0,121462,,315,356
|
||||
S,0,121462,,0.4
|
||||
F,0,121462,121836,0,1
|
||||
F,0,121836,122958,1
|
||||
F,0,122958,123332,1,0
|
||||
//Storyboard Layer 3 (Foreground)
|
||||
Sprite,Foreground,Centre,"howto3.png",320,180
|
||||
L,1746,2
|
||||
S,1,0,374,0.8,0.7
|
||||
S,1,374,747,0.8,0.7
|
||||
R,0,746,1120,-0.1,-6.38
|
||||
S,1,748,1120,0.8,0.7
|
||||
S,1,1122,1495,0.8,0.7
|
||||
L,4739,2
|
||||
S,1,0,374,0.8,0.7
|
||||
M,0,1024,1624,310,515,314,319
|
||||
F,0,1024,1624,0,1
|
||||
S,0,1024,1624,0.7
|
||||
R,0,1024,1624,0.5,-0.1
|
||||
R,0,2494,2868,-0.1,-6.38
|
||||
F,0,5487,5861,1,0
|
||||
S,0,5487,5861,0.7,3
|
||||
Sprite,Foreground,Centre,"text1.png",320,240
|
||||
M,1,6235,6609,156,73,239,73
|
||||
F,2,6235,6609,0,1
|
||||
M,0,6609,,239,73
|
||||
F,0,6609,22696,1
|
||||
F,0,22696,22821,1,0
|
||||
Sprite,Foreground,Centre,"text2.png",320,240
|
||||
M,1,8480,8854,513,115,322,115
|
||||
F,2,8480,8854,0,1
|
||||
F,0,8854,22696,1,0.9973262
|
||||
F,0,22696,22946,0.9973262,0
|
||||
Sprite,Foreground,Centre,"text3.png",320,240
|
||||
M,1,10725,11099,76,144,212,140
|
||||
F,2,10725,11099,0,1
|
||||
F,0,11099,22696,1,0.9946524
|
||||
F,0,22696,23070,0.9946524,0
|
||||
Sprite,Foreground,Centre,"text4.png",320,240
|
||||
M,1,12969,13343,537,179,374,179
|
||||
F,2,12969,13343,0,1
|
||||
F,0,13343,22696,1,0.9946524
|
||||
F,0,22696,23195,0.9946524,0
|
||||
Sprite,Foreground,Centre,"text5.png",320,240
|
||||
M,1,15214,15588,79,217,282,211
|
||||
F,2,15214,15588,0,1
|
||||
F,0,15588,22696,1
|
||||
F,0,22696,23320,1,0
|
||||
Sprite,Foreground,Centre,"text6.png",320,240
|
||||
M,1,17459,17833,545,249,417,249
|
||||
F,2,17459,17833,0,1
|
||||
F,0,17833,22696,1
|
||||
F,0,22696,23444,1,0
|
||||
Sprite,Foreground,Centre,"text7.png",320,240
|
||||
M,1,18955,19329,-20,286,62,286
|
||||
F,2,18955,19329,0,1
|
||||
M,0,19329,,62,286
|
||||
F,0,19329,22696,1,0.9629171
|
||||
F,0,22696,23569,0.9629171,0
|
||||
Sprite,Foreground,Centre,"text8.png",320,240
|
||||
M,1,20452,20826,560,358,321,358
|
||||
F,2,20452,20826,0,1
|
||||
F,0,20826,22696,1
|
||||
F,0,22696,23694,1,0
|
||||
Sprite,Foreground,Centre,"osulogo.png",320,240
|
||||
S,0,122335,122709,3.032
|
||||
F,0,122335,122833,0,1
|
||||
S,0,122709,123582,3.032,0.968
|
||||
F,0,122833,,1
|
||||
F,0,122833,124455,1
|
||||
F,0,124455,124829,1,0
|
||||
//Storyboard Sound Samples
|
||||
//Background Colour Transformations
|
||||
3,100,128,128,255
|
||||
|
||||
[TimingPoints]
|
||||
250,374.111485222596,4,1,0,58,1,0
|
||||
29377,-100,4,2,0,69,0,0
|
||||
29939,-100,4,1,0,58,0,0
|
||||
35363,-100,4,2,0,69,0,0
|
||||
35971,-100,4,2,0,100,0,0
|
||||
40461,-100,4,1,0,58,0,0
|
||||
41349,-100,4,2,0,69,0,0
|
||||
41910,-100,4,2,0,100,0,0
|
||||
46518,-100,4,1,0,58,0,0
|
||||
47335,-100,4,2,0,69,0,0
|
||||
47943,-100,4,2,0,100,0,0
|
||||
52451,-100,4,1,0,58,0,0
|
||||
53321,-100,4,2,0,69,0,0
|
||||
53929,-100,4,2,0,100,0,0
|
||||
58980,-100,4,1,0,74,0,0
|
||||
96018,-100,4,1,0,74,0,1
|
||||
119588,-100,4,1,0,74,0,0
|
||||
|
||||
[Colours]
|
||||
Combo1 : 0,255,0
|
||||
Combo2 : 255,0,0
|
||||
Combo3 : 0,0,255
|
||||
Combo4 : 255,255,0
|
||||
|
||||
[HitObjects]
|
||||
256,328,24193,2,0,C|424:328,2,150
|
||||
256,192,25315,1,8
|
||||
256,56,25689,6,0,C|96:56,2,150
|
||||
256,192,26811,1,8
|
||||
136,128,27186,5,0
|
||||
16,192,27560,1,0
|
||||
136,256,27934,1,0
|
||||
256,192,28308,1,8
|
||||
256,328,28682,6,0,C|304:328,3,50
|
||||
440,328,29430,1,4
|
||||
440,328,29804,1,4
|
||||
440,192,30178,6,0,C|480:152,3,50
|
||||
376,64,30927,1,0
|
||||
376,64,31301,1,8
|
||||
312,192,31675,6,0,C|264:144,3,50
|
||||
376,64,32423,1,0
|
||||
376,64,32797,1,8
|
||||
376,200,33171,5,0
|
||||
376,336,33545,1,0
|
||||
376,200,33920,1,0
|
||||
376,336,34294,1,8
|
||||
240,336,34668,6,0,C|176:336,3,50
|
||||
256,192,35416,5,4
|
||||
256,192,35790,1,4
|
||||
144,112,36164,6,0,C|72:144|96:240,2,150,0|2|0
|
||||
256,192,37287,1,2
|
||||
368,272,37661,6,0,C|440:240|416:144,2,150,0|2|0
|
||||
256,192,38783,1,2
|
||||
192,72,39157,6,0,C|128:232,1,150,0|2
|
||||
320,72,39905,2,0,C|384:232,1,150,0|2
|
||||
256,280,40654,6,0,C|256:352,3,50
|
||||
256,192,41402,1,0
|
||||
256,192,41776,1,4
|
||||
392,192,42150,5,0
|
||||
392,192,42275,1,0
|
||||
392,192,42399,1,0
|
||||
392,192,42524,2,0,B|392:288|312:304,2,150,2|0|2
|
||||
392,56,43646,5,0
|
||||
392,56,43771,1,0
|
||||
392,56,43896,1,0
|
||||
392,56,44021,2,0,B|296:56|280:136,2,150,2|0|2
|
||||
344,184,45143,5,0
|
||||
344,184,45268,1,0
|
||||
344,184,45392,1,0
|
||||
344,184,45517,2,0,B|280:256|216:184,2,150,2|0|2
|
||||
344,320,46639,6,0,B|192:320,1,150
|
||||
194,320,47138,1,0
|
||||
194,320,47263,1,0
|
||||
194,320,47388,1,0
|
||||
56,320,47762,1,4
|
||||
56,184,48136,6,0,B|56:96|152:96,1,150,0|2
|
||||
256,192,48884,1,2
|
||||
256,192,49009,1,0
|
||||
256,192,49133,1,0
|
||||
256,192,49258,1,2
|
||||
456,200,49632,6,0,B|456:288|360:288,1,150,0|2
|
||||
256,192,50380,1,2
|
||||
256,192,50505,1,0
|
||||
256,192,50630,1,0
|
||||
256,192,50755,1,2
|
||||
184,72,51129,5,0
|
||||
328,72,51503,1,2
|
||||
184,72,51877,1,0
|
||||
328,72,52251,1,2
|
||||
256,192,52625,6,0,B|256:352,1,150
|
||||
256,342,53124,1,0
|
||||
256,342,53249,1,0
|
||||
256,342,53373,1,0
|
||||
392,344,53747,1,4
|
||||
392,208,54122,6,0,B|456:208,3,50,0|0|0|2
|
||||
392,80,54870,1,2
|
||||
392,80,54994,1,0
|
||||
392,80,55119,1,0
|
||||
392,80,55244,1,2
|
||||
256,80,55618,6,0,B|256:176|152:176,1,150,0|2
|
||||
64,80,56366,1,2
|
||||
64,128,56491,1,0
|
||||
64,176,56616,1,0
|
||||
64,224,56740,1,2
|
||||
184,280,57114,6,0,B|184:344,3,50,0|0|0|2
|
||||
328,280,57863,2,0,B|328:344,3,50,2|0|0|2
|
||||
256,192,58611,12,4,59733
|
||||
256,48,60107,6,0,B|352:48|352:136,2,150,4|8|0
|
||||
200,80,61043,1,8
|
||||
160,136,61230,1,2
|
||||
144,200,61417,1,10
|
||||
144,264,61604,6,0,B|152:360|264:320,2,150,0|8|0
|
||||
144,200,62539,1,0
|
||||
208,176,62726,1,10
|
||||
272,192,62913,1,0
|
||||
328,224,63100,6,0,B|432:256|416:152,2,150,4|8|0
|
||||
272,192,64036,1,8
|
||||
256,128,64223,1,2
|
||||
296,72,64410,1,10
|
||||
256,192,64597,12,8,65719
|
||||
256,72,66093,5,4
|
||||
192,72,66280,1,0
|
||||
128,72,66467,2,0,B|56:104|72:208,2,150,8|0|10
|
||||
192,72,67403,1,2
|
||||
240,120,67590,5,0
|
||||
192,168,67777,2,0,B|160:248,2,75,8|0|0
|
||||
248,200,68338,2,0,B|264:280,3,75,0|0|10|0
|
||||
320,240,69086,6,0,B|472:240,1,150,4|8
|
||||
470,173,69647,1,0
|
||||
470,105,69834,2,8,B|320:105,1,150,0|10
|
||||
256,192,70582,12,8,71705
|
||||
256,64,72079,6,0,B|416:64,1,150,4|8
|
||||
288,128,72827,1,0
|
||||
352,128,73014,1,0
|
||||
416,128,73201,1,10
|
||||
320,224,73575,6,0,B|152:224,1,150,0|8
|
||||
288,288,74324,1,0
|
||||
224,288,74511,1,0
|
||||
160,288,74698,1,10
|
||||
64,192,75072,6,0,B|64:88|152:88,1,150,0|8
|
||||
216,88,75633,1,0
|
||||
280,88,75820,1,0
|
||||
280,152,76007,1,0
|
||||
216,152,76194,1,10
|
||||
256,192,76568,12,8,77691
|
||||
256,48,78065,6,0,B|344:72,1,75
|
||||
400,32,78439,2,0,B|424:120,1,75,8|0
|
||||
464,168,78813,2,0,B|408:256,1,75
|
||||
352,224,79187,1,10
|
||||
256,48,79561,6,0,B|168:72,1,75
|
||||
112,32,79935,2,0,B|88:120,1,75,8|0
|
||||
48,168,80309,2,0,B|104:256,1,75
|
||||
160,224,80683,1,10
|
||||
208,368,81058,6,0,B|176:280,1,75
|
||||
256,197,81432,2,0,B|256:272,1,75,8|0
|
||||
304,368,81806,2,0,B|336:280,1,75,0|0
|
||||
352,224,82180,1,10
|
||||
256,192,82554,12,4,83676
|
||||
256,48,84050,5,8
|
||||
256,48,84175,1,0
|
||||
256,48,84300,1,0
|
||||
256,48,84425,2,2,B|160:48|160:136,1,150,2|8
|
||||
40,48,85173,1,2
|
||||
32,200,85547,5,8
|
||||
32,200,85672,1,0
|
||||
32,200,85796,1,0
|
||||
32,200,85921,2,2,B|32:296|120:296,2,150,2|8|8
|
||||
256,336,87043,5,8
|
||||
256,336,87168,1,0
|
||||
256,336,87293,1,0
|
||||
256,336,87417,2,2,B|352:336|352:248,1,150,2|8
|
||||
472,336,88166,1,2
|
||||
480,184,88540,5,8
|
||||
480,184,88665,1,0
|
||||
480,184,88789,1,0
|
||||
480,184,88914,2,2,B|480:88|392:88,2,150,2|8|2
|
||||
256,64,90036,6,0,B|256:184,1,100,8|0
|
||||
256,216,90410,1,2
|
||||
184,352,90784,1,8
|
||||
232,352,90909,1,0
|
||||
280,352,91034,1,0
|
||||
328,352,91159,1,2
|
||||
320,200,91533,6,0,B|384:200|384:136,1,100,8|0
|
||||
384,88,91907,1,2
|
||||
472,208,92281,1,8
|
||||
448,256,92406,1,0
|
||||
424,304,92530,1,0
|
||||
400,352,92655,2,2,B|296:352,1,100,2|2
|
||||
256,192,93029,12,0,95648
|
||||
376,96,96022,6,0,B|472:96|472:192,2,150,8|8|0
|
||||
328,96,96895,1,0
|
||||
280,96,97020,1,0
|
||||
232,96,97144,1,8
|
||||
168,232,97518,6,0,B|72:232|72:136,2,150,8|8|0
|
||||
216,232,98391,1,0
|
||||
264,232,98516,1,0
|
||||
312,232,98641,1,8
|
||||
256,96,99015,6,0,B|416:96,2,150,8|8|0
|
||||
256,96,99888,1,0
|
||||
256,96,100013,1,0
|
||||
256,96,100137,1,8
|
||||
256,248,100511,6,0,B|256:304,2,50
|
||||
256,200,100885,2,0,B|256:136,3,50,8|0|0|4
|
||||
106,150,101634,1,4
|
||||
96,288,102008,6,0,B|80:344,2,50,8|0|0
|
||||
104,240,102382,1,2
|
||||
216,320,102756,1,0
|
||||
216,184,103130,1,10
|
||||
336,128,103504,6,0,B|352:72,2,50,8|0|0
|
||||
328,176,103878,1,2
|
||||
216,96,104252,1,0
|
||||
216,232,104627,1,10
|
||||
328,304,105001,5,8
|
||||
328,304,105125,1,0
|
||||
328,304,105250,1,0
|
||||
328,304,105375,2,0,B|416:304|416:208,1,150,2|0
|
||||
416,72,106123,1,10
|
||||
256,192,106497,12,4,107619
|
||||
256,56,107994,6,0,B|144:56,1,100
|
||||
96,56,108368,2,0,B|40:152,1,100,10|2
|
||||
40,200,108742,2,0,B|40:272,3,50,0|0|0|10
|
||||
200,336,109490,6,0,B|296:256,1,100
|
||||
312,320,109864,2,2,B|408:240,1,100,10|2
|
||||
424,304,110238,2,0,B|464:272,3,50,0|0|0|10
|
||||
424,96,110986,6,0,B|376:64|328:96,1,100
|
||||
328,152,111361,2,2,B|280:184|232:152,1,100,10|2
|
||||
224,96,111735,2,0,B|200:80|176:96,3,50,0|0|0|10
|
||||
256,256,112483,5,0
|
||||
256,256,112608,1,0
|
||||
256,256,112732,1,0
|
||||
256,256,112857,1,8
|
||||
256,256,112982,1,0
|
||||
256,256,113106,1,0
|
||||
256,256,113231,2,0,B|208:312|320:360|328:296,1,150,4|4
|
||||
120,208,113979,6,0,B|56:208,3,50,8|0|0|2
|
||||
120,80,114728,1,8
|
||||
120,80,114852,1,0
|
||||
120,80,114977,1,0
|
||||
120,80,115102,1,2
|
||||
256,80,115476,6,0,B|256:176|360:176,1,150,8|2
|
||||
448,80,116224,1,8
|
||||
448,128,116349,1,0
|
||||
448,176,116473,1,0
|
||||
448,224,116598,1,2
|
||||
256,192,116972,12,8,118095
|
||||
184,304,118469,6,0,B|296:304,1,100,0|8
|
||||
320,248,118843,2,0,B|208:248,1,100,0|8
|
||||
184,192,119217,1,4
|
||||
184,192,119591,1,4
|
||||
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "parse"
|
||||
version = "0.1.0"
|
||||
authors = ["MaxOhn <ohn.m@hotmail.de>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,26 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct TimingPoint {
|
||||
pub beat_len: f32,
|
||||
pub bpm: f32,
|
||||
pub time: f32,
|
||||
}
|
||||
|
||||
impl PartialOrd for TimingPoint {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.time.partial_cmp(&other.time)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct DifficultyPoint {
|
||||
pub time: f32,
|
||||
pub speed_multiplier: f32,
|
||||
}
|
||||
|
||||
impl PartialOrd for DifficultyPoint {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.time.partial_cmp(&other.time)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
use super::OSU_FILE_HEADER;
|
||||
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
use std::io::Error as IOError;
|
||||
use std::num::{ParseFloatError, ParseIntError};
|
||||
|
||||
pub type ParseResult<T> = Result<T, ParseError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ParseError {
|
||||
IOError(IOError),
|
||||
IncorrectFileHeader,
|
||||
BadLine,
|
||||
InvalidCurvePoints,
|
||||
InvalidInteger,
|
||||
InvalidEffectFlag,
|
||||
InvalidFloatingPoint,
|
||||
InvalidMode,
|
||||
InvalidPathType,
|
||||
InvalidTimingSignature,
|
||||
MissingField(usize),
|
||||
UnsupportedMode,
|
||||
UnknownHitObjectKind,
|
||||
NoHitobjects,
|
||||
UnsortedHitobjects,
|
||||
UnsortedTimingPoints,
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::IOError(_) => f.write_str("IO error"),
|
||||
Self::IncorrectFileHeader => {
|
||||
write!(f, "expected `{}` at file begin", OSU_FILE_HEADER)
|
||||
}
|
||||
Self::BadLine => f.write_str("line not in `Key:Value` pattern"),
|
||||
Self::InvalidCurvePoints => f.write_str("invalid curve point"),
|
||||
Self::InvalidInteger => f.write_str("invalid integer"),
|
||||
Self::InvalidEffectFlag => f.write_str("invalid effect flag"),
|
||||
Self::InvalidFloatingPoint => f.write_str("invalid floating-point number"),
|
||||
Self::InvalidMode => f.write_str("invalid mode"),
|
||||
Self::InvalidPathType => f.write_str("invalid path type"),
|
||||
Self::InvalidTimingSignature => f.write_str("invalid timing signature"),
|
||||
Self::MissingField(line) => write!(f, "missing field on line {}", line),
|
||||
Self::UnsupportedMode => f.write_str("unsupported osu! mode"),
|
||||
Self::UnknownHitObjectKind => f.write_str("unsupported hitobject kind"),
|
||||
Self::NoHitobjects => f.write_str("beatmap has no hitobjects"),
|
||||
Self::UnsortedHitobjects => f.write_str("hitobjects are not sorted by time"),
|
||||
Self::UnsortedTimingPoints => f.write_str("timing points are not sorted by time"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for ParseError {
|
||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||
match self {
|
||||
Self::IOError(inner) => Some(inner),
|
||||
Self::IncorrectFileHeader => None,
|
||||
Self::BadLine => None,
|
||||
Self::InvalidCurvePoints => None,
|
||||
Self::InvalidInteger => None,
|
||||
Self::InvalidEffectFlag => None,
|
||||
Self::InvalidFloatingPoint => None,
|
||||
Self::InvalidMode => None,
|
||||
Self::InvalidPathType => None,
|
||||
Self::InvalidTimingSignature => None,
|
||||
Self::MissingField(_) => None,
|
||||
Self::UnsupportedMode => None,
|
||||
Self::UnknownHitObjectKind => None,
|
||||
Self::NoHitobjects => None,
|
||||
Self::UnsortedHitobjects => None,
|
||||
Self::UnsortedTimingPoints => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IOError> for ParseError {
|
||||
fn from(other: IOError) -> Self {
|
||||
Self::IOError(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for ParseError {
|
||||
fn from(_: ParseIntError) -> Self {
|
||||
Self::InvalidInteger
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseFloatError> for ParseError {
|
||||
fn from(_: ParseFloatError) -> Self {
|
||||
Self::InvalidFloatingPoint
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
use super::{HitObjectKind, Pos2};
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct HitObject {
|
||||
pub pos: Pos2,
|
||||
pub start_time: f32,
|
||||
pub kind: HitObjectKind,
|
||||
pub sound: u8,
|
||||
}
|
||||
|
||||
impl HitObject {
|
||||
#[inline]
|
||||
pub fn end_time(&self) -> f32 {
|
||||
match &self.kind {
|
||||
HitObjectKind::Circle { .. } => self.start_time,
|
||||
HitObjectKind::Slider { .. } => self.start_time, // wrong but should be unreachable
|
||||
HitObjectKind::Spinner { end_time } => *end_time,
|
||||
HitObjectKind::Hold { end_time, .. } => *end_time,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_circle(&self) -> bool {
|
||||
matches!(self.kind, HitObjectKind::Circle { .. })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_slider(&self) -> bool {
|
||||
matches!(self.kind, HitObjectKind::Slider { .. })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_spinner(&self) -> bool {
|
||||
matches!(self.kind, HitObjectKind::Spinner { .. })
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for HitObject {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.start_time.partial_cmp(&other.start_time)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
const HITSOUND_WHISTLE: u8 = 1 << 1;
|
||||
const HITSOUND_FINISH: u8 = 1 << 2;
|
||||
const HITSOUND_CLAP: u8 = 1 << 3;
|
||||
|
||||
pub trait HitSound {
|
||||
fn normal(self) -> bool;
|
||||
fn whistle(self) -> bool;
|
||||
fn finish(self) -> bool;
|
||||
fn clap(self) -> bool;
|
||||
}
|
||||
|
||||
impl HitSound for u8 {
|
||||
fn normal(self) -> bool {
|
||||
self == 0
|
||||
}
|
||||
|
||||
fn whistle(self) -> bool {
|
||||
self & HITSOUND_WHISTLE > 0
|
||||
}
|
||||
|
||||
fn finish(self) -> bool {
|
||||
self & HITSOUND_FINISH > 0
|
||||
}
|
||||
|
||||
fn clap(self) -> bool {
|
||||
self & HITSOUND_CLAP > 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,537 @@
|
||||
mod control_point;
|
||||
mod error;
|
||||
mod hitobject;
|
||||
mod hitsound;
|
||||
mod mods;
|
||||
mod pos2;
|
||||
mod sort;
|
||||
|
||||
pub use control_point::{DifficultyPoint, TimingPoint};
|
||||
pub use error::{ParseError, ParseResult};
|
||||
pub use hitobject::HitObject;
|
||||
pub use hitsound::HitSound;
|
||||
pub use mods::Mods;
|
||||
pub use pos2::Pos2;
|
||||
use sort::sort;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::io::{BufRead, BufReader, Read};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::File;
|
||||
|
||||
#[test]
|
||||
fn parsing_works() {
|
||||
let file = match File::open("E:/Games/osu!/beatmaps/2223745.osu") {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not read file: {}", why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map: {}", why),
|
||||
};
|
||||
|
||||
println!("Mode: {}", map.mode as u8);
|
||||
println!("n_circles: {}", map.n_circles);
|
||||
println!("n_sliders: {}", map.n_sliders);
|
||||
println!("n_spinners: {}", map.n_spinners);
|
||||
println!("ar: {}", map.ar);
|
||||
println!("od: {}", map.od);
|
||||
println!("cs: {}", map.cs);
|
||||
println!("hp: {}", map.hp);
|
||||
println!("sv: {}", map.sv);
|
||||
println!("tick_rate: {}", map.tick_rate);
|
||||
println!("stack_leniency: {}", map.stack_leniency);
|
||||
println!("hit_objects: {}", map.hit_objects.len());
|
||||
println!("timing_points: {}", map.timing_points.len());
|
||||
println!("difficulty_points: {}", map.difficulty_points.len());
|
||||
|
||||
assert_eq!(2 + 2, 4);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum GameMode {
|
||||
STD = 0,
|
||||
TKO = 1,
|
||||
CTB = 2,
|
||||
MNA = 3,
|
||||
}
|
||||
|
||||
impl Default for GameMode {
|
||||
fn default() -> Self {
|
||||
Self::STD
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! sort {
|
||||
($slice:expr) => {
|
||||
$slice.sort_unstable_by(|p1, p2| p1.partial_cmp(&p2).unwrap_or(Ordering::Equal))
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! next_field {
|
||||
($opt:expr, $nmbr:ident) => {
|
||||
$opt.ok_or(ParseError::MissingField($nmbr))?
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! validate_float {
|
||||
($x:expr) => {{
|
||||
if $x.is_finite() {
|
||||
$x
|
||||
} else {
|
||||
return Err(ParseError::InvalidFloatingPoint);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Beatmap {
|
||||
pub mode: GameMode,
|
||||
pub version: u8,
|
||||
|
||||
pub n_circles: u32,
|
||||
pub n_sliders: u32,
|
||||
pub n_spinners: u32,
|
||||
|
||||
pub ar: f32,
|
||||
pub od: f32,
|
||||
pub cs: f32,
|
||||
pub hp: f32,
|
||||
pub sv: f32,
|
||||
pub tick_rate: f32,
|
||||
pub stack_leniency: f32,
|
||||
|
||||
pub hit_objects: Vec<HitObject>,
|
||||
pub timing_points: Vec<TimingPoint>,
|
||||
pub difficulty_points: Vec<DifficultyPoint>,
|
||||
}
|
||||
|
||||
pub(crate) const OSU_FILE_HEADER: &str = "osu file format v";
|
||||
|
||||
impl Beatmap {
|
||||
const CIRCLE_FLAG: u8 = 1 << 0;
|
||||
const SLIDER_FLAG: u8 = 1 << 1;
|
||||
#[allow(unused)]
|
||||
const NEW_COMBO_FLAG: u8 = 1 << 2;
|
||||
const SPINNER_FLAG: u8 = 1 << 3;
|
||||
#[allow(unused)]
|
||||
const COMBO_OFFSET_FLAG: u8 = (1 << 4) | (1 << 5) | (1 << 6);
|
||||
const HOLD_FLAG: u8 = 1 << 7;
|
||||
|
||||
pub fn parse<R: Read>(input: R) -> ParseResult<Self> {
|
||||
let mut reader = BufReader::new(input);
|
||||
let mut buf = String::new();
|
||||
|
||||
reader.read_line(&mut buf)?;
|
||||
|
||||
let mut map = Self::default();
|
||||
|
||||
map.version = match buf.find(OSU_FILE_HEADER) {
|
||||
Some(idx) => buf[idx + OSU_FILE_HEADER.len()..].trim_end().parse()?,
|
||||
None => return Err(ParseError::IncorrectFileHeader),
|
||||
};
|
||||
|
||||
// version 4 and lower had an incorrect offset (stable has this set as 24ms off)
|
||||
let offset = if map.version < 5 { 24.0 } else { 0.0 };
|
||||
|
||||
buf.clear();
|
||||
map.hit_objects.reserve(256);
|
||||
|
||||
let mut mode = None;
|
||||
let mut ar = None;
|
||||
let mut od = None;
|
||||
let mut cs = None;
|
||||
let mut hp = None;
|
||||
let mut sv = None;
|
||||
let mut tick_rate = None;
|
||||
let mut stack_leniency = None;
|
||||
|
||||
let mut section = Section::None;
|
||||
let mut prev_time = 0.0;
|
||||
let mut prev_diff = 0.0;
|
||||
let mut unsorted_timings = false;
|
||||
let mut unsorted_difficulties = false;
|
||||
let mut unsorted_hits = false;
|
||||
|
||||
let mut nmbr = 1;
|
||||
|
||||
while reader.read_line(&mut buf)? != 0 {
|
||||
let mut line = buf.trim_end();
|
||||
nmbr += 1;
|
||||
|
||||
if line.is_empty()
|
||||
|| line.starts_with("//")
|
||||
|| line.starts_with(' ')
|
||||
|| line.starts_with('_')
|
||||
{
|
||||
buf.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
if line.starts_with('[') && line.ends_with(']') {
|
||||
section = Section::from_str(&line[1..line.len() - 1]);
|
||||
buf.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(idx) = line.find("//") {
|
||||
line = &line[..idx];
|
||||
}
|
||||
|
||||
match section {
|
||||
Section::General => {
|
||||
let (key, value) = split_colon(&line).ok_or(ParseError::BadLine)?;
|
||||
|
||||
if key == "Mode" {
|
||||
mode = match value {
|
||||
"0" => Some(GameMode::STD),
|
||||
"1" => Some(GameMode::TKO),
|
||||
"2" => Some(GameMode::CTB),
|
||||
"3" => Some(GameMode::MNA),
|
||||
_ => return Err(ParseError::InvalidMode),
|
||||
};
|
||||
} else if key == "StackLeniency" {
|
||||
stack_leniency = Some(value.parse()?);
|
||||
}
|
||||
}
|
||||
Section::Difficulty => {
|
||||
let (key, value) = split_colon(&line).ok_or(ParseError::BadLine)?;
|
||||
|
||||
match key {
|
||||
"ApproachRate" => ar = Some(value.parse()?),
|
||||
"OverallDifficulty" => od = Some(value.parse()?),
|
||||
"CircleSize" => cs = Some(value.parse()?),
|
||||
"HPDrainRate" => hp = Some(value.parse()?),
|
||||
"SliderTickRate" => tick_rate = Some(value.parse()?),
|
||||
"SliderMultiplier" => sv = Some(value.parse()?),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Section::TimingPoints => {
|
||||
let mut split = line.split(',');
|
||||
|
||||
let time = offset + next_field!(split.next(), nmbr).trim().parse::<f32>()?;
|
||||
validate_float!(time);
|
||||
|
||||
let beat_len = next_field!(split.next(), nmbr).trim().parse::<f32>()?;
|
||||
|
||||
if beat_len.is_sign_negative() {
|
||||
let point = DifficultyPoint {
|
||||
time,
|
||||
speed_multiplier: -100.0 / beat_len,
|
||||
};
|
||||
|
||||
map.difficulty_points.push(point);
|
||||
|
||||
if time < prev_diff {
|
||||
unsorted_difficulties = true;
|
||||
} else {
|
||||
prev_diff = time;
|
||||
}
|
||||
} else {
|
||||
let point = TimingPoint {
|
||||
time,
|
||||
bpm: 60_000.0 / beat_len,
|
||||
beat_len,
|
||||
};
|
||||
|
||||
map.timing_points.push(point);
|
||||
|
||||
if time < prev_time {
|
||||
unsorted_timings = true;
|
||||
} else {
|
||||
prev_time = time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section::HitObjects => {
|
||||
let mut split = line.split(',');
|
||||
|
||||
let pos = Pos2 {
|
||||
x: next_field!(split.next(), nmbr).parse()?,
|
||||
y: next_field!(split.next(), nmbr).parse()?,
|
||||
};
|
||||
|
||||
let time = offset + next_field!(split.next(), nmbr).trim().parse::<f32>()?;
|
||||
validate_float!(time);
|
||||
|
||||
if !map.hit_objects.is_empty() && time < prev_time {
|
||||
unsorted_hits = true;
|
||||
}
|
||||
|
||||
let kind: u8 = next_field!(split.next(), nmbr).parse()?;
|
||||
let sound = split.next().map(str::parse).transpose()?.unwrap_or(0);
|
||||
|
||||
let kind = if kind & Self::CIRCLE_FLAG > 0 {
|
||||
map.n_circles += 1;
|
||||
|
||||
HitObjectKind::Circle
|
||||
} else if kind & Self::SLIDER_FLAG > 0 {
|
||||
map.n_sliders += 1;
|
||||
let mut curve_points = Vec::with_capacity(16);
|
||||
curve_points.push(pos);
|
||||
|
||||
let mut curve_point_iter = next_field!(split.next(), nmbr).split('|');
|
||||
|
||||
let mut path_type: PathType =
|
||||
next_field!(curve_point_iter.next(), nmbr).parse()?;
|
||||
|
||||
for pos in curve_point_iter {
|
||||
let mut v = pos.split(':').map(str::parse);
|
||||
|
||||
match (v.next(), v.next()) {
|
||||
(Some(Ok(x)), Some(Ok(y))) => curve_points.push(Pos2 { x, y }),
|
||||
_ => return Err(ParseError::InvalidCurvePoints),
|
||||
}
|
||||
}
|
||||
|
||||
if map.version <= 6 && curve_points.len() >= 2 {
|
||||
if path_type == PathType::Linear {
|
||||
path_type = PathType::Bezier;
|
||||
}
|
||||
|
||||
if curve_points.len() == 2
|
||||
&& (pos == curve_points[0] || pos == curve_points[1])
|
||||
{
|
||||
path_type = PathType::Linear;
|
||||
}
|
||||
}
|
||||
|
||||
if curve_points.is_empty() {
|
||||
HitObjectKind::Circle
|
||||
} else {
|
||||
let repeats = next_field!(split.next(), nmbr).parse::<usize>()?;
|
||||
let len: f32 = next_field!(split.next(), nmbr).parse()?;
|
||||
|
||||
HitObjectKind::Slider {
|
||||
repeats,
|
||||
pixel_len: len,
|
||||
curve_points,
|
||||
path_type,
|
||||
}
|
||||
}
|
||||
} else if kind & Self::SPINNER_FLAG > 0 {
|
||||
map.n_spinners += 1;
|
||||
let end_time = next_field!(split.next(), nmbr).parse()?;
|
||||
|
||||
HitObjectKind::Spinner { end_time }
|
||||
} else if kind & Self::HOLD_FLAG > 0 {
|
||||
map.n_sliders += 1;
|
||||
let mut end = time;
|
||||
|
||||
if let Some(next) = split.next() {
|
||||
end = end.max(next_field!(next.split(':').next(), nmbr).parse()?);
|
||||
}
|
||||
|
||||
HitObjectKind::Hold { end_time: end }
|
||||
} else {
|
||||
return Err(ParseError::UnknownHitObjectKind);
|
||||
};
|
||||
|
||||
map.hit_objects.push(HitObject {
|
||||
pos,
|
||||
start_time: time,
|
||||
kind,
|
||||
sound,
|
||||
});
|
||||
|
||||
prev_time = time;
|
||||
}
|
||||
|
||||
Section::None => {}
|
||||
}
|
||||
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
map.mode = next_field!(mode, nmbr);
|
||||
map.ar = next_field!(ar, nmbr);
|
||||
map.od = next_field!(od, nmbr);
|
||||
map.cs = next_field!(cs, nmbr);
|
||||
map.hp = next_field!(hp, nmbr);
|
||||
map.sv = next_field!(sv, nmbr);
|
||||
map.tick_rate = next_field!(tick_rate, nmbr);
|
||||
map.stack_leniency = next_field!(stack_leniency, nmbr);
|
||||
|
||||
if unsorted_timings {
|
||||
sort!(map.timing_points);
|
||||
}
|
||||
|
||||
if unsorted_difficulties {
|
||||
sort!(map.difficulty_points);
|
||||
}
|
||||
|
||||
if map.mode == GameMode::MNA {
|
||||
sort(&mut map.hit_objects);
|
||||
} else if unsorted_hits {
|
||||
sort!(map.hit_objects);
|
||||
}
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn attributes(&self) -> BeatmapAttributes {
|
||||
BeatmapAttributes::new(self.ar, self.od, self.cs, self.hp)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn split_colon(line: &str) -> Option<(&str, &str)> {
|
||||
let mut split = line.split(':');
|
||||
|
||||
Some((split.next()?, split.next()?.trim()))
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum PathType {
|
||||
Catmull = 0,
|
||||
Bezier = 1,
|
||||
Linear = 2,
|
||||
PerfectCurve = 3,
|
||||
}
|
||||
|
||||
impl FromStr for PathType {
|
||||
type Err = ParseError;
|
||||
|
||||
#[inline]
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"L" => Ok(Self::Linear),
|
||||
"C" => Ok(Self::Catmull),
|
||||
"B" => Ok(Self::Bezier),
|
||||
"P" => Ok(Self::PerfectCurve),
|
||||
_ => Err(ParseError::InvalidPathType),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum HitObjectKind {
|
||||
Circle,
|
||||
Slider {
|
||||
pixel_len: f32,
|
||||
repeats: usize,
|
||||
curve_points: Vec<Pos2>,
|
||||
path_type: PathType,
|
||||
},
|
||||
Spinner {
|
||||
end_time: f32,
|
||||
},
|
||||
Hold {
|
||||
end_time: f32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum Section {
|
||||
None,
|
||||
General,
|
||||
Difficulty,
|
||||
TimingPoints,
|
||||
HitObjects,
|
||||
}
|
||||
|
||||
impl Section {
|
||||
#[inline]
|
||||
fn from_str(s: &str) -> Self {
|
||||
match s {
|
||||
"General" => Self::General,
|
||||
"Difficulty" => Self::Difficulty,
|
||||
"TimingPoints" => Self::TimingPoints,
|
||||
"HitObjects" => Self::HitObjects,
|
||||
_ => Self::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BeatmapAttributes {
|
||||
pub ar: f32,
|
||||
pub od: f32,
|
||||
pub cs: f32,
|
||||
pub hp: f32,
|
||||
pub clock_rate: f32,
|
||||
}
|
||||
|
||||
impl BeatmapAttributes {
|
||||
const AR0_MS: f32 = 1800.0;
|
||||
const AR5_MS: f32 = 1200.0;
|
||||
const AR10_MS: f32 = 450.0;
|
||||
const AR_MS_STEP_1: f32 = (Self::AR0_MS - Self::AR5_MS) / 5.0;
|
||||
const AR_MS_STEP_2: f32 = (Self::AR5_MS - Self::AR10_MS) / 5.0;
|
||||
|
||||
const OD0_MS: f32 = 80.0;
|
||||
const OD10_MS: f32 = 20.0;
|
||||
const OD_MS_STEP: f32 = (Self::OD0_MS - Self::OD10_MS) / 10.0;
|
||||
|
||||
fn new(ar: f32, od: f32, cs: f32, hp: f32) -> Self {
|
||||
Self {
|
||||
ar,
|
||||
od,
|
||||
cs,
|
||||
hp,
|
||||
clock_rate: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mods(self, mods: impl Mods) -> Self {
|
||||
if !mods.change_map() {
|
||||
return self;
|
||||
}
|
||||
|
||||
let clock_rate = mods.speed();
|
||||
let multiplier = mods.od_ar_hp_multiplier();
|
||||
|
||||
// AR
|
||||
let mut ar = self.ar * multiplier;
|
||||
let mut ar_ms = if ar <= 5.0 {
|
||||
Self::AR0_MS - Self::AR_MS_STEP_1 * ar
|
||||
} else {
|
||||
Self::AR5_MS - Self::AR_MS_STEP_2 * (ar - 5.0)
|
||||
};
|
||||
|
||||
ar_ms = ar_ms.max(Self::AR10_MS).min(Self::AR0_MS);
|
||||
ar_ms /= clock_rate;
|
||||
|
||||
ar = if ar_ms > Self::AR5_MS {
|
||||
(Self::AR0_MS - ar_ms) / Self::AR_MS_STEP_1
|
||||
} else {
|
||||
5.0 + (Self::AR5_MS - ar_ms) / Self::AR_MS_STEP_2
|
||||
};
|
||||
|
||||
// OD
|
||||
let mut od = self.od * multiplier;
|
||||
let mut od_ms = Self::OD0_MS - (Self::OD_MS_STEP * od).ceil();
|
||||
od_ms = od_ms.max(Self::OD10_MS).min(Self::OD0_MS);
|
||||
od_ms /= clock_rate;
|
||||
od = (Self::OD0_MS - od_ms) / Self::OD_MS_STEP;
|
||||
|
||||
// CS
|
||||
let mut cs = self.cs;
|
||||
if mods.hr() {
|
||||
cs *= 1.3;
|
||||
} else if mods.ez() {
|
||||
cs *= 0.5;
|
||||
}
|
||||
cs = cs.min(10.0);
|
||||
|
||||
// HP
|
||||
let hp = (self.hp * multiplier).min(10.0);
|
||||
|
||||
Self {
|
||||
ar,
|
||||
od,
|
||||
cs,
|
||||
hp,
|
||||
clock_rate,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
pub trait Mods: Copy {
|
||||
const NF: u32 = 1 << 0;
|
||||
const EZ: u32 = 1 << 1;
|
||||
const TD: u32 = 1 << 2;
|
||||
const HD: u32 = 1 << 3;
|
||||
const HR: u32 = 1 << 4;
|
||||
const DT: u32 = 1 << 6;
|
||||
const HT: u32 = 1 << 8;
|
||||
const NC: u32 = Self::DT | (1 << 9);
|
||||
const FL: u32 = 1 << 10;
|
||||
const SO: u32 = 1 << 12;
|
||||
|
||||
fn change_speed(self) -> bool;
|
||||
fn change_map(self) -> bool;
|
||||
fn speed(self) -> f32;
|
||||
fn od_ar_hp_multiplier(self) -> f32;
|
||||
fn nf(self) -> bool;
|
||||
fn ez(self) -> bool;
|
||||
fn td(self) -> bool;
|
||||
fn hd(self) -> bool;
|
||||
fn hr(self) -> bool;
|
||||
fn dt(self) -> bool;
|
||||
fn ht(self) -> bool;
|
||||
fn nc(self) -> bool;
|
||||
fn fl(self) -> bool;
|
||||
fn so(self) -> bool;
|
||||
}
|
||||
|
||||
impl Mods for u32 {
|
||||
#[inline]
|
||||
fn change_speed(self) -> bool {
|
||||
self & (Self::HT | Self::DT) > 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn change_map(self) -> bool {
|
||||
self & (Self::HT | Self::DT | Self::HR | Self::EZ) > 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn speed(self) -> f32 {
|
||||
if self & Self::DT > 0 {
|
||||
1.5
|
||||
} else if self & Self::HT > 0 {
|
||||
0.75
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn od_ar_hp_multiplier(self) -> f32 {
|
||||
if self & Self::HR > 0 {
|
||||
1.4
|
||||
} else if self & Self::EZ > 0 {
|
||||
0.5
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn nf(self) -> bool {
|
||||
self & Self::NF > 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ez(self) -> bool {
|
||||
self & Self::EZ > 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn td(self) -> bool {
|
||||
self & Self::TD > 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn hd(self) -> bool {
|
||||
self & Self::HD > 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn hr(self) -> bool {
|
||||
self & Self::HR > 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn dt(self) -> bool {
|
||||
self & Self::DT > 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ht(self) -> bool {
|
||||
self & Self::HT > 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn nc(self) -> bool {
|
||||
self & Self::NC > 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn fl(self) -> bool {
|
||||
self & Self::FL > 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn so(self) -> bool {
|
||||
self & Self::SO > 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
use std::fmt;
|
||||
use std::ops;
|
||||
|
||||
#[derive(Clone, Copy, Default, PartialEq)]
|
||||
pub struct Pos2 {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
}
|
||||
|
||||
impl Pos2 {
|
||||
#[inline]
|
||||
pub fn length_squared(&self) -> f32 {
|
||||
self.dot(*self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn length(&self) -> f32 {
|
||||
self.length_squared().sqrt()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn dot(&self, other: Self) -> f32 {
|
||||
self.x * other.x + self.y * other.y
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn distance(&self, other: &Self) -> f32 {
|
||||
(*self - *other).length()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn add_scaled(self, other: Pos2, factor: f32) -> Pos2 {
|
||||
self + other * factor
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn normalize(self) -> Pos2 {
|
||||
self / self.length()
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Add<Pos2> for Pos2 {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Self {
|
||||
x: self.x + rhs.x,
|
||||
y: self.y + rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Sub<Pos2> for Pos2 {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
Self {
|
||||
x: self.x - rhs.x,
|
||||
y: self.y - rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Mul<f32> for Pos2 {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn mul(self, rhs: f32) -> Self::Output {
|
||||
Self {
|
||||
x: self.x * rhs,
|
||||
y: self.y * rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Div<f32> for Pos2 {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn div(self, rhs: f32) -> Self::Output {
|
||||
Self {
|
||||
x: self.x / rhs,
|
||||
y: self.y / rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::AddAssign for Pos2 {
|
||||
fn add_assign(&mut self, other: Self) {
|
||||
*self = Self {
|
||||
x: self.x + other.x,
|
||||
y: self.y + other.y,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Pos2 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "({},{})", self.x, self.y)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
use super::HitObject;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
const QUICK_SORT_DEPTH_THRESHOLD: usize = 32;
|
||||
|
||||
/// Algorithm from https://github.com/ppy/osu/blob/master/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs#L21
|
||||
pub(crate) fn sort(keys: &mut [HitObject]) {
|
||||
if keys.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
depth_limited_quick_sort(keys, 0, keys.len() - 1, QUICK_SORT_DEPTH_THRESHOLD);
|
||||
}
|
||||
|
||||
fn depth_limited_quick_sort(
|
||||
keys: &mut [HitObject],
|
||||
mut left: usize,
|
||||
mut right: usize,
|
||||
mut depth_limit: usize,
|
||||
) {
|
||||
loop {
|
||||
if depth_limit == 0 {
|
||||
heap_sort(keys, left, right);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut i = left;
|
||||
let mut j = right;
|
||||
|
||||
let mid = i + ((j - i) >> 1);
|
||||
|
||||
if keys[i] > keys[mid] {
|
||||
keys.swap(i, mid);
|
||||
}
|
||||
|
||||
if keys[i] > keys[j] {
|
||||
keys.swap(i, j);
|
||||
}
|
||||
|
||||
if keys[mid] > keys[j] {
|
||||
keys.swap(mid, j);
|
||||
}
|
||||
|
||||
loop {
|
||||
while keys[i] < keys[mid] {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
while keys[mid] < keys[j] {
|
||||
j -= 1;
|
||||
}
|
||||
|
||||
match i.cmp(&j) {
|
||||
Ordering::Less => keys.swap(i, j),
|
||||
Ordering::Equal => {}
|
||||
Ordering::Greater => break,
|
||||
}
|
||||
|
||||
i += 1;
|
||||
j = j.saturating_sub(1);
|
||||
|
||||
if i > j {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
depth_limit -= 1;
|
||||
|
||||
if j.saturating_sub(left) <= right - i {
|
||||
if left < j {
|
||||
depth_limited_quick_sort(keys, left, j, depth_limit);
|
||||
}
|
||||
|
||||
left = i;
|
||||
} else {
|
||||
if i < right {
|
||||
depth_limited_quick_sort(keys, i, right, depth_limit);
|
||||
}
|
||||
|
||||
right = j;
|
||||
}
|
||||
|
||||
if left >= right {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn heap_sort(keys: &mut [HitObject], lo: usize, hi: usize) {
|
||||
let n = hi - lo + 1;
|
||||
|
||||
for i in (1..=n / 2).rev() {
|
||||
down_heap(keys, i, n, lo);
|
||||
}
|
||||
|
||||
for i in (2..=n).rev() {
|
||||
keys.swap(lo, lo + i - 1);
|
||||
down_heap(keys, 1, i - 1, lo);
|
||||
}
|
||||
}
|
||||
|
||||
fn down_heap(keys: &mut [HitObject], mut i: usize, n: usize, lo: usize) {
|
||||
while i <= n / 2 {
|
||||
let mut child = 2 * i;
|
||||
|
||||
if child < n && keys[lo + child - 1] < keys[lo + child] {
|
||||
child += 1;
|
||||
}
|
||||
|
||||
if keys[lo + i - 1] >= keys[lo + child - 1] {
|
||||
break;
|
||||
}
|
||||
|
||||
keys.swap(lo + i - 1, lo + child - 1);
|
||||
i = child;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "taiko"
|
||||
version = "0.1.0"
|
||||
authors = ["MaxOhn <ohn.m@hotmail.de>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies.parse]
|
||||
path = "../parse"
|
||||
|
||||
[dependencies.lazy_static]
|
||||
version = "1.4"
|
||||
@@ -0,0 +1,34 @@
|
||||
use super::{closest_rhythm, HitObjectRhythm};
|
||||
|
||||
use parse::HitObject;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct DifficultyObject<'o> {
|
||||
pub(crate) idx: usize,
|
||||
pub(crate) base: &'o HitObject,
|
||||
pub(crate) prev: &'o HitObject,
|
||||
pub(crate) delta: f32,
|
||||
pub(crate) rhythm: &'static HitObjectRhythm,
|
||||
}
|
||||
|
||||
impl<'o> DifficultyObject<'o> {
|
||||
#[inline]
|
||||
pub(crate) fn new(
|
||||
idx: usize,
|
||||
base: &'o HitObject,
|
||||
prev: &'o HitObject,
|
||||
prev_prev: &HitObject,
|
||||
clock_rate: f32,
|
||||
) -> Self {
|
||||
let delta = (base.start_time - prev.start_time) / clock_rate;
|
||||
let rhythm = closest_rhythm(delta, prev, prev_prev, clock_rate);
|
||||
|
||||
Self {
|
||||
idx,
|
||||
base,
|
||||
prev,
|
||||
delta,
|
||||
rhythm,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
use parse::HitObject;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
/// lazy_static required for `f32` division's
|
||||
/// lack of const-ness as of now.
|
||||
static ref COMMON_RHYTHMS: Vec<HitObjectRhythm> = vec![
|
||||
HitObjectRhythm::new(1.0, 1.0, 0.0),
|
||||
HitObjectRhythm::new(2.0, 1.0, 0.3),
|
||||
HitObjectRhythm::new(1.0, 2.0, 0.5),
|
||||
HitObjectRhythm::new(3.0, 1.0, 0.3),
|
||||
HitObjectRhythm::new(1.0, 3.0, 0.35),
|
||||
HitObjectRhythm::new(3.0, 2.0, 0.6),
|
||||
HitObjectRhythm::new(2.0, 3.0, 0.4),
|
||||
HitObjectRhythm::new(5.0, 4.0, 0.5),
|
||||
HitObjectRhythm::new(4.0, 5.0, 0.7),
|
||||
];
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct HitObjectRhythm {
|
||||
ratio: f32,
|
||||
pub(crate) difficulty: f32,
|
||||
}
|
||||
|
||||
impl PartialEq for HitObjectRhythm {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
(self.ratio - other.ratio).abs() < f32::EPSILON
|
||||
&& (self.difficulty - other.difficulty).abs() < f32::EPSILON
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for HitObjectRhythm {}
|
||||
|
||||
impl HitObjectRhythm {
|
||||
#[inline]
|
||||
fn new(numerator: f32, denominator: f32, difficulty: f32) -> Self {
|
||||
Self {
|
||||
ratio: numerator / denominator,
|
||||
difficulty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn closest_rhythm(
|
||||
delta_time: f32,
|
||||
last: &HitObject,
|
||||
last_last: &HitObject,
|
||||
clock_rate: f32,
|
||||
) -> &'static HitObjectRhythm {
|
||||
let prev_len = (last.start_time - last_last.start_time) / clock_rate;
|
||||
let ratio = delta_time / prev_len;
|
||||
|
||||
COMMON_RHYTHMS
|
||||
.iter()
|
||||
.min_by(|r1, r2| {
|
||||
(r1.ratio - ratio)
|
||||
.abs()
|
||||
.partial_cmp(&(r2.ratio - ratio).abs())
|
||||
.unwrap_or(Ordering::Equal)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
mod difficulty_object;
|
||||
mod hitobject_rhythm;
|
||||
mod limited_queue;
|
||||
mod rim;
|
||||
mod skill;
|
||||
mod skill_kind;
|
||||
mod stamina_cheese;
|
||||
|
||||
use difficulty_object::DifficultyObject;
|
||||
use hitobject_rhythm::{closest_rhythm, HitObjectRhythm};
|
||||
use limited_queue::LimitedQueue;
|
||||
use rim::Rim;
|
||||
use skill::Skill;
|
||||
use skill_kind::SkillKind;
|
||||
use stamina_cheese::StaminaCheeseDetector;
|
||||
|
||||
use parse::{Beatmap, Mods};
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::f32::consts::PI;
|
||||
|
||||
const SECTION_LEN: f32 = 400.0;
|
||||
|
||||
const COLOR_SKILL_MULTIPLIER: f32 = 0.01;
|
||||
const RHYTHM_SKILL_MULTIPLIER: f32 = 0.014;
|
||||
const STAMINA_SKILL_MULTIPLIER: f32 = 0.02;
|
||||
|
||||
/// Star calculation for osu!taiko maps
|
||||
pub fn stars(map: &Beatmap, mods: impl Mods) -> f32 {
|
||||
if map.hit_objects.len() < 2 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// True if the object at that index is stamina cheese
|
||||
let cheese = map.find_cheese();
|
||||
|
||||
let mut skills = vec![
|
||||
Skill::new(SkillKind::color()),
|
||||
Skill::new(SkillKind::rhythm()),
|
||||
Skill::new(SkillKind::stamina(true)),
|
||||
Skill::new(SkillKind::stamina(false)),
|
||||
];
|
||||
|
||||
let clock_rate = mods.speed();
|
||||
let section_len = SECTION_LEN * clock_rate;
|
||||
|
||||
// No strain for first object
|
||||
let mut current_section_end =
|
||||
(map.hit_objects[0].start_time / section_len).ceil() * section_len;
|
||||
|
||||
let hit_objects = map
|
||||
.hit_objects
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(2)
|
||||
.zip(map.hit_objects.iter().skip(1))
|
||||
.zip(map.hit_objects.iter())
|
||||
.map(|(((idx, base), prev), prev_prev)| {
|
||||
DifficultyObject::new(idx, base, prev, prev_prev, clock_rate)
|
||||
});
|
||||
|
||||
for h in hit_objects {
|
||||
while h.base.start_time > current_section_end {
|
||||
for skill in skills.iter_mut() {
|
||||
skill.save_current_peak();
|
||||
skill.start_new_section_from(current_section_end);
|
||||
}
|
||||
|
||||
current_section_end += section_len;
|
||||
}
|
||||
|
||||
for skill in skills.iter_mut().take(3) {
|
||||
skill.process(h.clone(), &cheese);
|
||||
}
|
||||
|
||||
skills[3].process(h, &cheese);
|
||||
}
|
||||
|
||||
for skill in skills.iter_mut() {
|
||||
skill.save_current_peak();
|
||||
}
|
||||
|
||||
let mut buf = vec![0.0; skills[0].strain_peaks.len()];
|
||||
|
||||
let color_rating = skills[0].difficulty_value(&mut buf) * COLOR_SKILL_MULTIPLIER;
|
||||
let rhythm_rating = skills[1].difficulty_value(&mut buf) * RHYTHM_SKILL_MULTIPLIER;
|
||||
|
||||
let mut stamina_rating = (skills[2].difficulty_value(&mut buf)
|
||||
+ skills[3].difficulty_value(&mut buf))
|
||||
* STAMINA_SKILL_MULTIPLIER;
|
||||
|
||||
let stamina_penalty = simple_color_penalty(stamina_rating, color_rating);
|
||||
stamina_rating *= stamina_penalty;
|
||||
|
||||
let combined_rating = locally_combined_difficulty(&skills, stamina_penalty);
|
||||
let separate_rating = norm(1.5, color_rating, rhythm_rating, stamina_rating);
|
||||
|
||||
rescale(1.4 * separate_rating + 0.5 * combined_rating)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn rescale(stars: f32) -> f32 {
|
||||
if stars < 0.0 {
|
||||
stars
|
||||
} else {
|
||||
10.43 * (stars / 8.0 + 1.0).ln()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn simple_color_penalty(stamina: f32, color: f32) -> f32 {
|
||||
if color <= 0.0 {
|
||||
0.79 - 0.25
|
||||
} else {
|
||||
0.79 - (stamina / color - 12.0).atan() / PI / 2.0
|
||||
}
|
||||
}
|
||||
|
||||
fn locally_combined_difficulty(skills: &[Skill], stamina_penalty: f32) -> f32 {
|
||||
let mut peaks = Vec::with_capacity(skills[0].strain_peaks.len());
|
||||
|
||||
let iter = skills[0]
|
||||
.strain_peaks
|
||||
.iter()
|
||||
.zip(skills[1].strain_peaks.iter())
|
||||
.zip(skills[2].strain_peaks.iter())
|
||||
.zip(skills[3].strain_peaks.iter())
|
||||
.map(|(((&color, &rhythm), &stamina_right), &stamina_left)| {
|
||||
norm(
|
||||
2.0,
|
||||
color * COLOR_SKILL_MULTIPLIER,
|
||||
rhythm * RHYTHM_SKILL_MULTIPLIER,
|
||||
(stamina_right + stamina_left) * STAMINA_SKILL_MULTIPLIER * stamina_penalty,
|
||||
)
|
||||
});
|
||||
|
||||
peaks.extend(iter);
|
||||
peaks.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
||||
|
||||
let mut difficulty = 0.0;
|
||||
let mut weight = 1.0;
|
||||
|
||||
for strain in peaks {
|
||||
difficulty += strain * weight;
|
||||
weight *= 0.9;
|
||||
}
|
||||
|
||||
difficulty
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn norm(p: f32, a: f32, b: f32, c: f32) -> f32 {
|
||||
(a.powf(p) + b.powf(p) + c.powf(p)).powf(p.recip())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::File;
|
||||
|
||||
#[test]
|
||||
fn test_single() {
|
||||
let file = match File::open("E:/Games/osu!/beatmaps/1097541.osu") {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file: {}", why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map: {}", why),
|
||||
};
|
||||
|
||||
let stars = stars(&map, 16);
|
||||
|
||||
println!("Stars: {}", stars);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_taiko() {
|
||||
let margin = 0.005;
|
||||
|
||||
#[rustfmt::skip]
|
||||
let data = vec![
|
||||
(110219, 1 << 8, 4.090461690284154), // HT
|
||||
(110219, 0, 5.137432251440863), // NM
|
||||
(110219, 1 << 6, 6.785308286298745), // DT
|
||||
|
||||
(168450, 1 << 8, 3.9102755155437663), // HT
|
||||
(168450, 0, 4.740171803038067), // NM
|
||||
(168450, 1 << 6, 5.894260068145283), // DT
|
||||
|
||||
(1097541, 1 << 8, 4.0027499635116595),// HT
|
||||
(1097541, 0, 4.891409786886079), // NM
|
||||
(1097541, 1 << 6, 6.587467490088248), // DT
|
||||
|
||||
(1432878, 1 << 8, 3.5850143199594258),// HT
|
||||
(1432878, 0, 4.416206873466799), // NM
|
||||
(1432878, 1 << 6, 5.908970879987477), // DT
|
||||
];
|
||||
|
||||
for (map_id, mods, expected_stars) in data {
|
||||
let file = match File::open(format!("./test/{}.osu", map_id)) {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
||||
};
|
||||
|
||||
let stars = stars(&map, mods);
|
||||
|
||||
assert!(
|
||||
(stars - expected_stars).abs() < margin,
|
||||
"Stars: {} | Expected: {} => {} margin [map {} | mods {}]",
|
||||
stars,
|
||||
expected_stars,
|
||||
(stars - expected_stars).abs(),
|
||||
map_id,
|
||||
mods
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::ops::Index;
|
||||
|
||||
pub(crate) struct LimitedQueue<T> {
|
||||
queue: Vec<T>,
|
||||
start: usize,
|
||||
end: usize,
|
||||
}
|
||||
|
||||
impl<T> LimitedQueue<T> {
|
||||
/// Panics if `capacity` is zero.
|
||||
#[inline]
|
||||
pub(crate) fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
end: capacity - 1,
|
||||
start: 0,
|
||||
queue: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn push(&mut self, elem: T) {
|
||||
let capacity = self.queue.capacity();
|
||||
self.end = (self.end + 1) % capacity;
|
||||
|
||||
if self.queue.len() == capacity {
|
||||
self.start = (self.start + 1) % capacity;
|
||||
self.queue[self.end as usize] = elem;
|
||||
} else {
|
||||
self.queue.push(elem);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.queue.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn last(&self) -> Option<&T> {
|
||||
self.queue.get(self.end as usize)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn clear(&mut self) {
|
||||
self.start = 0;
|
||||
self.end = self.queue.capacity() - 1;
|
||||
self.queue.clear();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn full(&self) -> bool {
|
||||
self.queue.len() == self.queue.capacity()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialOrd> LimitedQueue<T> {
|
||||
pub(crate) fn min(&self) -> Option<&T> {
|
||||
let mut iter = self.queue.iter();
|
||||
|
||||
if let Some(first) = iter.next() {
|
||||
let min = iter.fold(first, |min, next| match min.partial_cmp(next) {
|
||||
Some(Ordering::Less) => min,
|
||||
Some(Ordering::Equal) => min,
|
||||
Some(Ordering::Greater) => next,
|
||||
None => min,
|
||||
});
|
||||
|
||||
Some(min)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Index<usize> for LimitedQueue<T> {
|
||||
type Output = T;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, idx: usize) -> &Self::Output {
|
||||
&self.queue[(self.start + idx) % self.queue.capacity()]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use parse::{HitObject, HitSound};
|
||||
|
||||
pub(crate) trait Rim {
|
||||
fn is_rim(&self) -> bool;
|
||||
}
|
||||
|
||||
impl Rim for HitObject {
|
||||
#[inline]
|
||||
fn is_rim(&self) -> bool {
|
||||
self.sound.clap() || self.sound.whistle()
|
||||
}
|
||||
}
|
||||
|
||||
impl Rim for u8 {
|
||||
#[inline]
|
||||
fn is_rim(&self) -> bool {
|
||||
self.clap() || self.whistle()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
use super::{DifficultyObject, SkillKind};
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
const DECAY_WEIGHT: f32 = 0.9;
|
||||
|
||||
pub(crate) struct Skill<'o> {
|
||||
pub current_strain: f32,
|
||||
current_section_peak: f32,
|
||||
|
||||
kind: SkillKind<'o>,
|
||||
pub(crate) strain_peaks: Vec<f32>,
|
||||
|
||||
prev_time: Option<f32>,
|
||||
}
|
||||
|
||||
impl<'o> Skill<'o> {
|
||||
#[inline]
|
||||
pub(crate) fn new(kind: SkillKind<'o>) -> Self {
|
||||
Self {
|
||||
current_strain: 1.0,
|
||||
current_section_peak: 1.0,
|
||||
|
||||
kind,
|
||||
strain_peaks: Vec::with_capacity(128),
|
||||
|
||||
prev_time: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn save_current_peak(&mut self) {
|
||||
if self.prev_time.is_some() {
|
||||
self.strain_peaks.push(self.current_section_peak);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn start_new_section_from(&mut self, time: f32) {
|
||||
if let Some(prev) = self.prev_time {
|
||||
self.current_section_peak = self.peak_strain(time - prev);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn process(&mut self, current: DifficultyObject<'o>, cheese: &[bool]) {
|
||||
self.current_strain *= self.strain_decay(current.delta);
|
||||
self.current_strain +=
|
||||
self.kind.strain_value_of(¤t, cheese) * self.skill_multiplier();
|
||||
self.current_section_peak = self.current_section_peak.max(self.current_strain);
|
||||
self.prev_time.replace(current.base.start_time);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn difficulty_value(&self, buf: &mut [f32]) -> f32 {
|
||||
let mut difficulty = 0.0;
|
||||
let mut weight = 1.0;
|
||||
|
||||
buf.copy_from_slice(&self.strain_peaks);
|
||||
buf.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
||||
|
||||
for &strain in buf.iter() {
|
||||
difficulty += strain * weight;
|
||||
weight *= DECAY_WEIGHT;
|
||||
}
|
||||
|
||||
difficulty
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn skill_multiplier(&self) -> f32 {
|
||||
match self.kind {
|
||||
SkillKind::Color { .. } => 1.0,
|
||||
SkillKind::Rhythm { .. } => 10.0,
|
||||
SkillKind::Stamina { .. } => 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_decay_base(&self) -> f32 {
|
||||
match self.kind {
|
||||
SkillKind::Color { .. } => 0.4,
|
||||
SkillKind::Rhythm { .. } => 0.0,
|
||||
SkillKind::Stamina { .. } => 0.4,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn peak_strain(&self, delta_time: f32) -> f32 {
|
||||
self.current_strain * self.strain_decay(delta_time)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_decay(&self, ms: f32) -> f32 {
|
||||
self.strain_decay_base().powf(ms / 1000.0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
use super::{DifficultyObject, LimitedQueue, Rim};
|
||||
|
||||
const RHYTHM_STRAIN_DECAY: f32 = 0.96;
|
||||
const MOST_RECENT_PATTERNS_TO_COMPARE: usize = 2;
|
||||
|
||||
const MONO_HISTORY_MAX_LEN: usize = 5;
|
||||
const RHYTHM_HISTORY_MAX_LEN: usize = 8;
|
||||
const STAMINA_HISTORY_MAX_LEN: usize = 2;
|
||||
|
||||
pub(crate) enum SkillKind<'o> {
|
||||
Color {
|
||||
mono_history: LimitedQueue<usize>,
|
||||
prev_is_rim: Option<bool>,
|
||||
current_mono_len: usize,
|
||||
},
|
||||
Rhythm {
|
||||
rhythm_history: LimitedQueue<DifficultyObject<'o>>,
|
||||
notes_since_rhythm_change: usize,
|
||||
current_strain: f32,
|
||||
},
|
||||
Stamina {
|
||||
note_pair_duration_history: LimitedQueue<f32>,
|
||||
hand: u8,
|
||||
off_hand_object_duration: f32,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'o> SkillKind<'o> {
|
||||
#[inline]
|
||||
pub(crate) fn color() -> Self {
|
||||
Self::Color {
|
||||
mono_history: LimitedQueue::new(MONO_HISTORY_MAX_LEN),
|
||||
prev_is_rim: None,
|
||||
current_mono_len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn rhythm() -> Self {
|
||||
Self::Rhythm {
|
||||
rhythm_history: LimitedQueue::new(RHYTHM_HISTORY_MAX_LEN),
|
||||
notes_since_rhythm_change: 0,
|
||||
current_strain: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn stamina(right_hand: bool) -> Self {
|
||||
Self::Stamina {
|
||||
note_pair_duration_history: LimitedQueue::new(STAMINA_HISTORY_MAX_LEN),
|
||||
hand: right_hand as u8,
|
||||
off_hand_object_duration: f32::MAX,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn strain_value_of(
|
||||
&mut self,
|
||||
current: &DifficultyObject<'o>,
|
||||
cheese: &[bool],
|
||||
) -> f32 {
|
||||
match self {
|
||||
Self::Color {
|
||||
mono_history,
|
||||
prev_is_rim,
|
||||
current_mono_len,
|
||||
} => {
|
||||
let prev_is_circle = current.prev.is_circle();
|
||||
let base_is_circle = current.base.is_circle();
|
||||
let curr_is_rim = current.base.is_rim();
|
||||
|
||||
if !(current.delta < 1000.0 && prev_is_circle && base_is_circle) {
|
||||
mono_history.clear();
|
||||
*current_mono_len = base_is_circle as usize;
|
||||
|
||||
*prev_is_rim = if base_is_circle {
|
||||
Some(curr_is_rim)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let mut strain = 0.0;
|
||||
|
||||
if prev_is_rim
|
||||
.filter(|&is_rim| is_rim != curr_is_rim)
|
||||
.is_some()
|
||||
{
|
||||
strain = if mono_history.len() < 2
|
||||
|| (*mono_history.last().unwrap() + *current_mono_len) % 2 == 0
|
||||
{
|
||||
0.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
let mut reps_penalty = 1.0;
|
||||
|
||||
mono_history.push(*current_mono_len);
|
||||
|
||||
let iter = (0..mono_history
|
||||
.len()
|
||||
.saturating_sub(MOST_RECENT_PATTERNS_TO_COMPARE))
|
||||
.rev();
|
||||
|
||||
for start in iter {
|
||||
let different_pattern = (0..MOST_RECENT_PATTERNS_TO_COMPARE).any(|i| {
|
||||
mono_history[start + i]
|
||||
!= mono_history
|
||||
[mono_history.len() + i - MOST_RECENT_PATTERNS_TO_COMPARE]
|
||||
});
|
||||
|
||||
if different_pattern {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut notes_since = 0;
|
||||
|
||||
for i in start..mono_history.len() {
|
||||
notes_since += mono_history[i];
|
||||
}
|
||||
|
||||
reps_penalty *= repetition_penalty(notes_since);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
strain *= reps_penalty;
|
||||
|
||||
*current_mono_len = 1;
|
||||
} else {
|
||||
*current_mono_len += 1;
|
||||
}
|
||||
|
||||
*prev_is_rim = Some(curr_is_rim);
|
||||
|
||||
strain
|
||||
}
|
||||
Self::Rhythm {
|
||||
rhythm_history,
|
||||
notes_since_rhythm_change,
|
||||
current_strain,
|
||||
} => {
|
||||
let base_is_circle = current.base.is_circle();
|
||||
|
||||
if !base_is_circle {
|
||||
*current_strain = 0.0;
|
||||
*notes_since_rhythm_change = 0;
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
*current_strain *= RHYTHM_STRAIN_DECAY;
|
||||
*notes_since_rhythm_change += 1;
|
||||
|
||||
if current.rhythm.difficulty.abs() < f32::EPSILON {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let mut strain = current.rhythm.difficulty;
|
||||
|
||||
rhythm_history.push(current.to_owned());
|
||||
|
||||
let mut reps_penalty = 1.0;
|
||||
|
||||
for most_recent_patterns_to_compare in 2..=RHYTHM_HISTORY_MAX_LEN / 2 {
|
||||
let iter = (0..rhythm_history
|
||||
.len()
|
||||
.saturating_sub(most_recent_patterns_to_compare))
|
||||
.rev();
|
||||
|
||||
for start in iter {
|
||||
let different_pattern = (0..most_recent_patterns_to_compare).any(|i| {
|
||||
rhythm_history[start + i].rhythm
|
||||
!= rhythm_history
|
||||
[rhythm_history.len() + i - most_recent_patterns_to_compare]
|
||||
.rhythm
|
||||
});
|
||||
|
||||
if different_pattern {
|
||||
continue;
|
||||
}
|
||||
|
||||
reps_penalty *= repetition_penalty(current.idx - rhythm_history[start].idx);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let speed_penalty = if current.delta < 80.0 {
|
||||
1.0
|
||||
} else if current.delta < 210.0 {
|
||||
(1.4 - 0.005 * current.delta).max(0.0)
|
||||
} else {
|
||||
*current_strain = 0.0;
|
||||
*notes_since_rhythm_change = 0;
|
||||
|
||||
0.0
|
||||
};
|
||||
|
||||
strain *= reps_penalty;
|
||||
strain *= pattern_len_penalty(*notes_since_rhythm_change);
|
||||
strain *= speed_penalty;
|
||||
|
||||
*notes_since_rhythm_change = 0;
|
||||
*current_strain += strain;
|
||||
|
||||
*current_strain
|
||||
}
|
||||
Self::Stamina {
|
||||
hand,
|
||||
note_pair_duration_history,
|
||||
off_hand_object_duration,
|
||||
} => {
|
||||
let base_is_circle = current.base.is_circle();
|
||||
|
||||
if !base_is_circle {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
if current.idx % 2 == *hand as usize {
|
||||
if current.idx == 1 {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
let mut strain = 1.0;
|
||||
|
||||
note_pair_duration_history.push(current.delta + *off_hand_object_duration);
|
||||
|
||||
let shortest_recent_note = *note_pair_duration_history.min().unwrap();
|
||||
|
||||
strain += speed_bonus(shortest_recent_note);
|
||||
|
||||
if cheese[current.idx] {
|
||||
let p = cheese_penalty(current.delta + *off_hand_object_duration);
|
||||
strain *= p;
|
||||
}
|
||||
|
||||
return strain;
|
||||
}
|
||||
|
||||
*off_hand_object_duration = current.delta;
|
||||
|
||||
0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pattern_len_penalty(pattern_len: usize) -> f32 {
|
||||
let short_pattern_penalty = (0.15 * pattern_len as f32).min(1.0);
|
||||
let long_pattern_penalty = (2.5 - 0.15 * pattern_len as f32).max(0.0).min(1.0);
|
||||
|
||||
short_pattern_penalty.min(long_pattern_penalty)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cheese_penalty(note_pair_duration: f32) -> f32 {
|
||||
if note_pair_duration > 125.0 {
|
||||
1.0
|
||||
} else if note_pair_duration < 100.0 {
|
||||
0.6
|
||||
} else {
|
||||
0.6 + (note_pair_duration - 100.0) * 0.016
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn speed_bonus(note_pair_duration: f32) -> f32 {
|
||||
if note_pair_duration > 200.0 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let mut bonus = 200.0 - note_pair_duration;
|
||||
bonus *= bonus;
|
||||
|
||||
bonus / 100_000.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn repetition_penalty(notes_since: usize) -> f32 {
|
||||
(0.032 * notes_since as f32).min(1.0)
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
use super::{LimitedQueue, Rim};
|
||||
|
||||
use parse::{Beatmap, HitObject};
|
||||
|
||||
const ROLL_MIN_REPETITIONS: usize = 12;
|
||||
const TL_MIN_REPETITIONS: isize = 16;
|
||||
|
||||
pub(crate) trait StaminaCheeseDetector {
|
||||
fn find_cheese(&self) -> Vec<bool>;
|
||||
fn find_rolls(&self, pattern_len: usize, cheese: &mut [bool]);
|
||||
fn find_tl_tap(&self, parity: usize, is_rin: bool, cheese: &mut [bool]);
|
||||
}
|
||||
|
||||
impl StaminaCheeseDetector for Beatmap {
|
||||
fn find_cheese(&self) -> Vec<bool> {
|
||||
let mut cheese = vec![false; self.hit_objects.len()];
|
||||
|
||||
self.find_rolls(3, &mut cheese);
|
||||
self.find_rolls(4, &mut cheese);
|
||||
|
||||
self.find_tl_tap(0, true, &mut cheese);
|
||||
self.find_tl_tap(1, true, &mut cheese);
|
||||
self.find_tl_tap(0, false, &mut cheese);
|
||||
self.find_tl_tap(1, false, &mut cheese);
|
||||
|
||||
cheese
|
||||
}
|
||||
|
||||
fn find_rolls(&self, pattern_len: usize, cheese: &mut [bool]) {
|
||||
let mut history = LimitedQueue::new(2 * pattern_len);
|
||||
|
||||
let mut index_before_last_repeat = -1;
|
||||
let mut last_mark_end = 0;
|
||||
|
||||
for i in 0..self.hit_objects.len() {
|
||||
history.push(&self.hit_objects[i]);
|
||||
|
||||
if !history.full() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let contains = contains_pattern_repeat(&history, pattern_len);
|
||||
|
||||
if !contains {
|
||||
index_before_last_repeat = (i + 1 - history.len()) as isize;
|
||||
continue;
|
||||
}
|
||||
|
||||
let repeated_len = (i as isize - index_before_last_repeat) as usize;
|
||||
|
||||
if repeated_len < ROLL_MIN_REPETITIONS {
|
||||
continue;
|
||||
}
|
||||
|
||||
mark_as_cheese(last_mark_end.max(i + 1 - repeated_len), i, cheese);
|
||||
|
||||
last_mark_end = i;
|
||||
}
|
||||
}
|
||||
|
||||
fn find_tl_tap(&self, parity: usize, is_rin: bool, cheese: &mut [bool]) {
|
||||
let mut tl_len = -2;
|
||||
let mut last_mark_end = 0;
|
||||
|
||||
for i in (parity..self.hit_objects.len()).step_by(2) {
|
||||
if self.hit_objects[i].is_rim() == is_rin {
|
||||
tl_len += 2;
|
||||
} else {
|
||||
tl_len = -2;
|
||||
}
|
||||
|
||||
if tl_len < TL_MIN_REPETITIONS {
|
||||
continue;
|
||||
}
|
||||
|
||||
mark_as_cheese(
|
||||
(i as isize + 1 - tl_len).max(last_mark_end as isize) as usize,
|
||||
i,
|
||||
cheese,
|
||||
);
|
||||
|
||||
last_mark_end = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn mark_as_cheese(start: usize, end: usize, cheese: &mut [bool]) {
|
||||
cheese
|
||||
.iter_mut()
|
||||
.take(end + 1)
|
||||
.skip(start)
|
||||
.for_each(|b| *b = true);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn contains_pattern_repeat(history: &LimitedQueue<&HitObject>, pattern_len: usize) -> bool {
|
||||
for j in 0..pattern_len {
|
||||
if history[j].is_rim() != history[j + pattern_len].is_rim() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,598 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
AudioFilename: audio.mp3
|
||||
AudioLeadIn: 0
|
||||
PreviewTime: 47433
|
||||
Countdown: 0
|
||||
SampleSet: Normal
|
||||
StackLeniency: 0.7
|
||||
Mode: 1
|
||||
LetterboxInBreaks: 0
|
||||
WidescreenStoryboard: 0
|
||||
|
||||
[Editor]
|
||||
DistanceSpacing: 0.8
|
||||
BeatDivisor: 4
|
||||
GridSize: 32
|
||||
TimelineZoom: 2.6
|
||||
|
||||
[Metadata]
|
||||
Title:Physicality
|
||||
TitleUnicode:Physicality
|
||||
Artist:Renard + Adraen
|
||||
ArtistUnicode:Renard + Adraen
|
||||
Creator:Surono
|
||||
Version:Oni
|
||||
Source:
|
||||
Tags:lapfox trax vulpvibe records acid intravenous electronic breakcore system of a down
|
||||
BeatmapID:1432878
|
||||
BeatmapSetID:601403
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:5
|
||||
CircleSize:2
|
||||
OverallDifficulty:6
|
||||
ApproachRate:10
|
||||
SliderMultiplier:1.4
|
||||
SliderTickRate:1
|
||||
|
||||
[Events]
|
||||
//Background and Video events
|
||||
0,0,"fox stupud.jpg",0,0
|
||||
//Break Periods
|
||||
//Storyboard Layer 0 (Background)
|
||||
//Storyboard Layer 1 (Fail)
|
||||
//Storyboard Layer 2 (Pass)
|
||||
//Storyboard Layer 3 (Foreground)
|
||||
//Storyboard Sound Samples
|
||||
|
||||
[TimingPoints]
|
||||
115,218.181818181818,4,1,0,45,1,0
|
||||
115,-151.515151515152,4,1,0,45,0,0
|
||||
5339,331.455087835598,4,1,0,90,1,0
|
||||
47765,-100,4,1,0,90,0,1
|
||||
68978,-100,4,1,0,90,0,0
|
||||
90191,218.181818181818,4,1,0,45,1,0
|
||||
90191,-151.515151515152,4,1,0,45,0,0
|
||||
|
||||
|
||||
[HitObjects]
|
||||
256,192,115,12,0,5133,0:0:0:0:
|
||||
256,192,5339,5,0,0:0:0:0:
|
||||
256,192,5421,1,0,0:0:0:0:
|
||||
256,192,5504,1,0,0:0:0:0:
|
||||
256,192,5670,1,8,0:0:0:0:
|
||||
256,192,5836,1,0,0:0:0:0:
|
||||
256,192,5919,1,0,0:0:0:0:
|
||||
256,192,6001,1,0,0:0:0:0:
|
||||
256,192,6250,1,0,0:0:0:0:
|
||||
256,192,6333,1,8,0:0:0:0:
|
||||
256,192,6499,1,0,0:0:0:0:
|
||||
256,192,6664,1,8,0:0:0:0:
|
||||
256,192,6747,1,8,0:0:0:0:
|
||||
256,192,6830,1,8,0:0:0:0:
|
||||
256,192,6996,1,0,0:0:0:0:
|
||||
256,192,7162,1,8,0:0:0:0:
|
||||
256,192,7244,1,8,0:0:0:0:
|
||||
256,192,7327,1,8,0:0:0:0:
|
||||
256,192,7576,1,8,0:0:0:0:
|
||||
256,192,7659,1,0,0:0:0:0:
|
||||
256,192,7824,1,8,0:0:0:0:
|
||||
256,192,7990,5,0,0:0:0:0:
|
||||
256,192,8073,1,0,0:0:0:0:
|
||||
256,192,8156,1,0,0:0:0:0:
|
||||
256,192,8322,1,8,0:0:0:0:
|
||||
256,192,8487,1,0,0:0:0:0:
|
||||
256,192,8570,1,0,0:0:0:0:
|
||||
256,192,8653,1,0,0:0:0:0:
|
||||
256,192,8902,1,0,0:0:0:0:
|
||||
256,192,8985,1,8,0:0:0:0:
|
||||
256,192,9150,1,0,0:0:0:0:
|
||||
256,192,9316,1,8,0:0:0:0:
|
||||
256,192,9399,1,8,0:0:0:0:
|
||||
256,192,9482,1,8,0:0:0:0:
|
||||
256,192,9647,1,0,0:0:0:0:
|
||||
256,192,9813,1,8,0:0:0:0:
|
||||
256,192,9896,1,8,0:0:0:0:
|
||||
256,192,9979,1,8,0:0:0:0:
|
||||
256,192,10227,1,0,0:0:0:0:
|
||||
256,192,10310,1,8,0:0:0:0:
|
||||
256,192,10476,1,8,0:0:0:0:
|
||||
256,192,10642,5,0,0:0:0:0:
|
||||
256,192,10725,1,0,0:0:0:0:
|
||||
256,192,10808,1,0,0:0:0:0:
|
||||
256,192,10973,1,8,0:0:0:0:
|
||||
256,192,11139,1,0,0:0:0:0:
|
||||
256,192,11222,1,0,0:0:0:0:
|
||||
256,192,11305,1,0,0:0:0:0:
|
||||
256,192,11553,1,0,0:0:0:0:
|
||||
256,192,11636,1,8,0:0:0:0:
|
||||
256,192,11802,1,0,0:0:0:0:
|
||||
256,192,11968,1,8,0:0:0:0:
|
||||
256,192,12050,1,8,0:0:0:0:
|
||||
256,192,12133,1,8,0:0:0:0:
|
||||
256,192,12299,1,0,0:0:0:0:
|
||||
256,192,12465,1,8,0:0:0:0:
|
||||
256,192,12548,1,8,0:0:0:0:
|
||||
256,192,12631,1,8,0:0:0:0:
|
||||
256,192,12879,1,8,0:0:0:0:
|
||||
256,192,12962,1,0,0:0:0:0:
|
||||
256,192,13128,1,8,0:0:0:0:
|
||||
256,192,13293,5,0,0:0:0:0:
|
||||
256,192,13376,1,0,0:0:0:0:
|
||||
256,192,13459,1,0,0:0:0:0:
|
||||
256,192,13625,1,8,0:0:0:0:
|
||||
256,192,13791,1,0,0:0:0:0:
|
||||
256,192,13873,1,0,0:0:0:0:
|
||||
256,192,13956,1,0,0:0:0:0:
|
||||
256,192,14205,1,0,0:0:0:0:
|
||||
256,192,14288,1,8,0:0:0:0:
|
||||
256,192,14454,1,0,0:0:0:0:
|
||||
256,192,14619,1,8,0:0:0:0:
|
||||
256,192,14702,1,8,0:0:0:0:
|
||||
256,192,14785,1,8,0:0:0:0:
|
||||
256,192,14951,1,0,0:0:0:0:
|
||||
256,192,15116,1,8,0:0:0:0:
|
||||
256,192,15199,1,8,0:0:0:0:
|
||||
256,192,15282,1,8,0:0:0:0:
|
||||
256,192,15531,1,0,0:0:0:0:
|
||||
256,192,15614,1,8,0:0:0:0:
|
||||
256,192,15779,1,8,0:0:0:0:
|
||||
256,192,15862,1,8,0:0:0:0:
|
||||
256,192,15945,1,4,0:0:0:0:
|
||||
256,192,16277,1,4,0:0:0:0:
|
||||
256,192,16608,1,8,0:0:0:0:
|
||||
256,192,16774,1,0,0:0:0:0:
|
||||
256,192,16939,1,0,0:0:0:0:
|
||||
256,192,17105,1,8,0:0:0:0:
|
||||
256,192,17188,1,8,0:0:0:0:
|
||||
256,192,17271,1,0,0:0:0:0:
|
||||
256,192,17437,1,0,0:0:0:0:
|
||||
256,192,17519,1,0,0:0:0:0:
|
||||
256,192,17602,1,0,0:0:0:0:
|
||||
256,192,17768,1,0,0:0:0:0:
|
||||
256,192,17851,1,0,0:0:0:0:
|
||||
256,192,17934,1,8,0:0:0:0:
|
||||
256,192,18100,1,0,0:0:0:0:
|
||||
256,192,18182,1,0,0:0:0:0:
|
||||
256,192,18265,1,8,0:0:0:0:
|
||||
256,192,18431,1,8,0:0:0:0:
|
||||
256,192,18597,1,4,0:0:0:0:
|
||||
256,192,18928,1,0,0:0:0:0:
|
||||
256,192,19011,1,0,0:0:0:0:
|
||||
256,192,19094,1,0,0:0:0:0:
|
||||
256,192,19260,1,8,0:0:0:0:
|
||||
256,192,19342,1,0,0:0:0:0:
|
||||
256,192,19425,1,0,0:0:0:0:
|
||||
256,192,19591,1,0,0:0:0:0:
|
||||
256,192,19674,1,0,0:0:0:0:
|
||||
256,192,19757,1,8,0:0:0:0:
|
||||
256,192,19923,1,0,0:0:0:0:
|
||||
256,192,20088,1,8,0:0:0:0:
|
||||
256,192,20254,1,0,0:0:0:0:
|
||||
256,192,20420,1,0,0:0:0:0:
|
||||
256,192,20585,1,8,0:0:0:0:
|
||||
256,192,20751,1,0,0:0:0:0:
|
||||
256,192,20834,1,8,0:0:0:0:
|
||||
256,192,20917,1,0,0:0:0:0:
|
||||
256,192,21000,1,0,0:0:0:0:
|
||||
256,192,21083,1,8,0:0:0:0:
|
||||
256,192,21248,1,4,0:0:0:0:
|
||||
256,192,21580,1,4,0:0:0:0:
|
||||
256,192,21911,1,8,0:0:0:0:
|
||||
256,192,22077,1,0,0:0:0:0:
|
||||
256,192,22243,1,0,0:0:0:0:
|
||||
256,192,22408,1,8,0:0:0:0:
|
||||
256,192,22491,1,8,0:0:0:0:
|
||||
256,192,22574,1,0,0:0:0:0:
|
||||
256,192,22740,1,0,0:0:0:0:
|
||||
256,192,22823,1,0,0:0:0:0:
|
||||
256,192,22906,1,0,0:0:0:0:
|
||||
256,192,23071,1,0,0:0:0:0:
|
||||
256,192,23154,1,0,0:0:0:0:
|
||||
256,192,23237,1,8,0:0:0:0:
|
||||
256,192,23403,1,0,0:0:0:0:
|
||||
256,192,23486,1,0,0:0:0:0:
|
||||
256,192,23569,1,8,0:0:0:0:
|
||||
256,192,23734,1,8,0:0:0:0:
|
||||
256,192,23900,1,4,0:0:0:0:
|
||||
256,192,24231,1,0,0:0:0:0:
|
||||
256,192,24314,1,0,0:0:0:0:
|
||||
256,192,24397,1,0,0:0:0:0:
|
||||
256,192,24563,1,8,0:0:0:0:
|
||||
256,192,24646,1,0,0:0:0:0:
|
||||
256,192,24729,1,0,0:0:0:0:
|
||||
256,192,24894,1,0,0:0:0:0:
|
||||
256,192,24977,1,0,0:0:0:0:
|
||||
256,192,25060,1,8,0:0:0:0:
|
||||
256,192,25226,1,0,0:0:0:0:
|
||||
256,192,25392,1,8,0:0:0:0:
|
||||
256,192,25557,1,0,0:0:0:0:
|
||||
256,192,25723,1,0,0:0:0:0:
|
||||
256,192,25889,1,8,0:0:0:0:
|
||||
256,192,26054,1,0,0:0:0:0:
|
||||
256,192,26137,1,8,0:0:0:0:
|
||||
256,192,26220,1,0,0:0:0:0:
|
||||
256,192,26303,1,0,0:0:0:0:
|
||||
256,192,26386,1,8,0:0:0:0:
|
||||
256,192,26552,5,0,0:0:0:0:
|
||||
256,192,26634,1,0,0:0:0:0:
|
||||
256,192,26717,1,0,0:0:0:0:
|
||||
256,192,26883,1,8,0:0:0:0:
|
||||
256,192,27049,1,0,0:0:0:0:
|
||||
256,192,27132,1,8,0:0:0:0:
|
||||
256,192,27215,1,0,0:0:0:0:
|
||||
256,192,27463,1,0,0:0:0:0:
|
||||
256,192,27546,1,8,0:0:0:0:
|
||||
256,192,27712,1,0,0:0:0:0:
|
||||
256,192,27877,1,8,0:0:0:0:
|
||||
256,192,27960,1,8,0:0:0:0:
|
||||
256,192,28043,1,8,0:0:0:0:
|
||||
256,192,28209,1,0,0:0:0:0:
|
||||
256,192,28375,1,8,0:0:0:0:
|
||||
256,192,28457,1,0,0:0:0:0:
|
||||
256,192,28540,1,8,0:0:0:0:
|
||||
256,192,28789,1,8,0:0:0:0:
|
||||
256,192,28872,1,0,0:0:0:0:
|
||||
256,192,29038,1,8,0:0:0:0:
|
||||
256,192,29203,5,0,0:0:0:0:
|
||||
256,192,29286,1,0,0:0:0:0:
|
||||
256,192,29369,1,0,0:0:0:0:
|
||||
256,192,29535,1,8,0:0:0:0:
|
||||
256,192,29700,1,0,0:0:0:0:
|
||||
256,192,29783,1,8,0:0:0:0:
|
||||
256,192,29866,1,0,0:0:0:0:
|
||||
256,192,30115,1,0,0:0:0:0:
|
||||
256,192,30198,1,8,0:0:0:0:
|
||||
256,192,30363,1,0,0:0:0:0:
|
||||
256,192,30529,1,8,0:0:0:0:
|
||||
256,192,30612,1,8,0:0:0:0:
|
||||
256,192,30695,1,8,0:0:0:0:
|
||||
256,192,30861,1,0,0:0:0:0:
|
||||
256,192,31026,1,8,0:0:0:0:
|
||||
256,192,31109,1,0,0:0:0:0:
|
||||
256,192,31192,1,8,0:0:0:0:
|
||||
256,192,31441,1,0,0:0:0:0:
|
||||
256,192,31523,1,8,0:0:0:0:
|
||||
256,192,31689,1,8,0:0:0:0:
|
||||
256,192,31855,5,0,0:0:0:0:
|
||||
256,192,31938,1,0,0:0:0:0:
|
||||
256,192,32021,1,0,0:0:0:0:
|
||||
256,192,32186,1,8,0:0:0:0:
|
||||
256,192,32352,1,0,0:0:0:0:
|
||||
256,192,32435,1,8,0:0:0:0:
|
||||
256,192,32518,1,0,0:0:0:0:
|
||||
256,192,32766,1,0,0:0:0:0:
|
||||
256,192,32849,1,8,0:0:0:0:
|
||||
256,192,33015,1,0,0:0:0:0:
|
||||
256,192,33181,1,8,0:0:0:0:
|
||||
256,192,33264,1,8,0:0:0:0:
|
||||
256,192,33346,1,8,0:0:0:0:
|
||||
256,192,33512,1,0,0:0:0:0:
|
||||
256,192,33678,1,8,0:0:0:0:
|
||||
256,192,33761,1,0,0:0:0:0:
|
||||
256,192,33844,1,8,0:0:0:0:
|
||||
256,192,34092,1,8,0:0:0:0:
|
||||
256,192,34175,1,0,0:0:0:0:
|
||||
256,192,34341,1,8,0:0:0:0:
|
||||
256,192,34507,5,0,0:0:0:0:
|
||||
256,192,34589,1,0,0:0:0:0:
|
||||
256,192,34672,1,0,0:0:0:0:
|
||||
256,192,34838,1,8,0:0:0:0:
|
||||
256,192,35004,1,0,0:0:0:0:
|
||||
256,192,35087,1,8,0:0:0:0:
|
||||
256,192,35169,1,0,0:0:0:0:
|
||||
256,192,35418,1,0,0:0:0:0:
|
||||
256,192,35501,1,8,0:0:0:0:
|
||||
256,192,35667,1,0,0:0:0:0:
|
||||
256,192,35832,1,8,0:0:0:0:
|
||||
256,192,35915,1,8,0:0:0:0:
|
||||
256,192,35998,1,8,0:0:0:0:
|
||||
256,192,36164,1,0,0:0:0:0:
|
||||
256,192,36330,1,8,0:0:0:0:
|
||||
256,192,36412,1,0,0:0:0:0:
|
||||
256,192,36495,1,8,0:0:0:0:
|
||||
256,192,36744,1,0,0:0:0:0:
|
||||
256,192,36827,1,8,0:0:0:0:
|
||||
256,192,37158,1,4,0:0:0:0:
|
||||
256,192,37490,1,8,0:0:0:0:
|
||||
256,192,37738,1,4,0:0:0:0:
|
||||
256,192,37987,1,0,0:0:0:0:
|
||||
256,192,38153,1,8,0:0:0:0:
|
||||
256,192,38484,1,12,0:0:0:0:
|
||||
256,192,38815,1,0,0:0:0:0:
|
||||
256,192,39064,1,12,0:0:0:0:
|
||||
256,192,39313,1,0,0:0:0:0:
|
||||
256,192,39478,1,8,0:0:0:0:
|
||||
256,192,39644,1,0,0:0:0:0:
|
||||
256,192,39810,1,4,0:0:0:0:
|
||||
256,192,40141,1,8,0:0:0:0:
|
||||
256,192,40390,1,4,0:0:0:0:
|
||||
256,192,40638,1,0,0:0:0:0:
|
||||
256,192,40804,1,8,0:0:0:0:
|
||||
256,192,41136,1,12,0:0:0:0:
|
||||
256,192,41467,1,0,0:0:0:0:
|
||||
256,192,41716,1,12,0:0:0:0:
|
||||
256,192,41964,1,0,0:0:0:0:
|
||||
256,192,42130,1,8,0:0:0:0:
|
||||
256,192,42296,1,8,0:0:0:0:
|
||||
256,192,42461,1,4,0:0:0:0:
|
||||
256,192,42793,1,8,0:0:0:0:
|
||||
256,192,43042,1,4,0:0:0:0:
|
||||
256,192,43290,1,0,0:0:0:0:
|
||||
256,192,43456,1,8,0:0:0:0:
|
||||
256,192,43787,1,12,0:0:0:0:
|
||||
256,192,44119,1,0,0:0:0:0:
|
||||
256,192,44367,1,12,0:0:0:0:
|
||||
256,192,44616,1,0,0:0:0:0:
|
||||
256,192,44782,1,8,0:0:0:0:
|
||||
256,192,44947,1,0,0:0:0:0:
|
||||
256,192,45113,1,4,0:0:0:0:
|
||||
256,192,45445,1,8,0:0:0:0:
|
||||
256,192,45693,1,4,0:0:0:0:
|
||||
256,192,45942,1,0,0:0:0:0:
|
||||
256,192,46107,1,8,0:0:0:0:
|
||||
256,192,46439,1,12,0:0:0:0:
|
||||
256,192,46770,1,8,0:0:0:0:
|
||||
256,192,46936,1,0,0:0:0:0:
|
||||
256,192,47019,1,0,0:0:0:0:
|
||||
256,192,47102,1,8,0:0:0:0:
|
||||
256,192,47268,1,0,0:0:0:0:
|
||||
256,192,47350,1,0,0:0:0:0:
|
||||
256,192,47433,1,8,0:0:0:0:
|
||||
256,192,47765,1,4,0:0:0:0:
|
||||
256,192,48096,1,4,0:0:0:0:
|
||||
256,192,48428,1,8,0:0:0:0:
|
||||
256,192,48593,1,0,0:0:0:0:
|
||||
256,192,48759,1,0,0:0:0:0:
|
||||
256,192,48925,1,8,0:0:0:0:
|
||||
256,192,49008,1,8,0:0:0:0:
|
||||
256,192,49091,1,0,0:0:0:0:
|
||||
256,192,49256,1,0,0:0:0:0:
|
||||
256,192,49339,1,0,0:0:0:0:
|
||||
256,192,49422,1,8,0:0:0:0:
|
||||
256,192,49588,1,0,0:0:0:0:
|
||||
256,192,49671,1,0,0:0:0:0:
|
||||
256,192,49753,1,8,0:0:0:0:
|
||||
256,192,49919,1,0,0:0:0:0:
|
||||
256,192,50002,1,0,0:0:0:0:
|
||||
256,192,50085,1,8,0:0:0:0:
|
||||
256,192,50251,1,8,0:0:0:0:
|
||||
256,192,50416,1,4,0:0:0:0:
|
||||
256,192,50748,1,0,0:0:0:0:
|
||||
256,192,50831,1,0,0:0:0:0:
|
||||
256,192,50914,1,0,0:0:0:0:
|
||||
256,192,51079,1,8,0:0:0:0:
|
||||
256,192,51162,1,0,0:0:0:0:
|
||||
256,192,51245,1,0,0:0:0:0:
|
||||
256,192,51411,1,0,0:0:0:0:
|
||||
256,192,51494,1,0,0:0:0:0:
|
||||
256,192,51576,1,8,0:0:0:0:
|
||||
256,192,51742,1,0,0:0:0:0:
|
||||
256,192,51908,1,8,0:0:0:0:
|
||||
256,192,52074,1,8,0:0:0:0:
|
||||
256,192,52239,1,0,0:0:0:0:
|
||||
256,192,52405,1,8,0:0:0:0:
|
||||
256,192,52571,1,8,0:0:0:0:
|
||||
256,192,52654,1,8,0:0:0:0:
|
||||
256,192,52737,1,0,0:0:0:0:
|
||||
256,192,52819,1,0,0:0:0:0:
|
||||
256,192,52902,1,8,0:0:0:0:
|
||||
256,192,53068,1,4,0:0:0:0:
|
||||
256,192,53399,1,4,0:0:0:0:
|
||||
256,192,53731,1,8,0:0:0:0:
|
||||
256,192,53897,1,0,0:0:0:0:
|
||||
256,192,54062,1,0,0:0:0:0:
|
||||
256,192,54228,1,8,0:0:0:0:
|
||||
256,192,54311,1,8,0:0:0:0:
|
||||
256,192,54394,1,0,0:0:0:0:
|
||||
256,192,54560,1,0,0:0:0:0:
|
||||
256,192,54642,1,0,0:0:0:0:
|
||||
256,192,54725,1,8,0:0:0:0:
|
||||
256,192,54891,1,0,0:0:0:0:
|
||||
256,192,54974,1,0,0:0:0:0:
|
||||
256,192,55057,1,8,0:0:0:0:
|
||||
256,192,55222,1,0,0:0:0:0:
|
||||
256,192,55305,1,0,0:0:0:0:
|
||||
256,192,55388,1,8,0:0:0:0:
|
||||
256,192,55554,1,8,0:0:0:0:
|
||||
256,192,55720,1,4,0:0:0:0:
|
||||
256,192,56051,1,0,0:0:0:0:
|
||||
256,192,56134,1,0,0:0:0:0:
|
||||
256,192,56217,1,0,0:0:0:0:
|
||||
256,192,56383,1,8,0:0:0:0:
|
||||
256,192,56465,1,0,0:0:0:0:
|
||||
256,192,56548,1,0,0:0:0:0:
|
||||
256,192,56714,1,0,0:0:0:0:
|
||||
256,192,56797,1,0,0:0:0:0:
|
||||
256,192,56880,1,8,0:0:0:0:
|
||||
256,192,57045,1,0,0:0:0:0:
|
||||
256,192,57211,1,8,0:0:0:0:
|
||||
256,192,57377,1,8,0:0:0:0:
|
||||
256,192,57543,1,0,0:0:0:0:
|
||||
256,192,57708,1,8,0:0:0:0:
|
||||
256,192,57874,1,8,0:0:0:0:
|
||||
256,192,57957,1,8,0:0:0:0:
|
||||
256,192,58040,1,0,0:0:0:0:
|
||||
256,192,58123,1,0,0:0:0:0:
|
||||
256,192,58206,1,8,0:0:0:0:
|
||||
256,192,58371,1,4,0:0:0:0:
|
||||
256,192,58703,1,4,0:0:0:0:
|
||||
256,192,59034,1,8,0:0:0:0:
|
||||
256,192,59200,1,8,0:0:0:0:
|
||||
256,192,59366,1,0,0:0:0:0:
|
||||
256,192,59531,1,8,0:0:0:0:
|
||||
256,192,59614,1,8,0:0:0:0:
|
||||
256,192,59697,1,0,0:0:0:0:
|
||||
256,192,59863,1,0,0:0:0:0:
|
||||
256,192,59946,1,8,0:0:0:0:
|
||||
256,192,60029,1,0,0:0:0:0:
|
||||
256,192,60194,1,0,0:0:0:0:
|
||||
256,192,60277,1,0,0:0:0:0:
|
||||
256,192,60360,1,8,0:0:0:0:
|
||||
256,192,60526,1,0,0:0:0:0:
|
||||
256,192,60609,1,0,0:0:0:0:
|
||||
256,192,60691,1,8,0:0:0:0:
|
||||
256,192,60857,1,8,0:0:0:0:
|
||||
256,192,61023,1,4,0:0:0:0:
|
||||
256,192,61189,1,0,0:0:0:0:
|
||||
256,192,61354,1,8,0:0:0:0:
|
||||
256,192,61437,1,8,0:0:0:0:
|
||||
256,192,61520,1,0,0:0:0:0:
|
||||
256,192,61686,1,8,0:0:0:0:
|
||||
256,192,61769,1,8,0:0:0:0:
|
||||
256,192,61852,1,0,0:0:0:0:
|
||||
256,192,62017,1,0,0:0:0:0:
|
||||
256,192,62100,1,0,0:0:0:0:
|
||||
256,192,62183,1,8,0:0:0:0:
|
||||
256,192,62349,1,0,0:0:0:0:
|
||||
256,192,62515,1,8,0:0:0:0:
|
||||
256,192,62680,1,8,0:0:0:0:
|
||||
256,192,62763,1,0,0:0:0:0:
|
||||
256,192,62846,1,0,0:0:0:0:
|
||||
256,192,63012,1,8,0:0:0:0:
|
||||
256,192,63177,1,8,0:0:0:0:
|
||||
256,192,63260,1,8,0:0:0:0:
|
||||
256,192,63343,1,0,0:0:0:0:
|
||||
256,192,63426,1,0,0:0:0:0:
|
||||
256,192,63509,1,8,0:0:0:0:
|
||||
256,192,63675,1,4,0:0:0:0:
|
||||
256,192,64006,1,4,0:0:0:0:
|
||||
256,192,64338,1,8,0:0:0:0:
|
||||
256,192,64503,1,8,0:0:0:0:
|
||||
256,192,64669,1,0,0:0:0:0:
|
||||
256,192,64835,1,8,0:0:0:0:
|
||||
256,192,64918,1,8,0:0:0:0:
|
||||
256,192,65000,1,0,0:0:0:0:
|
||||
256,192,65166,1,0,0:0:0:0:
|
||||
256,192,65249,1,8,0:0:0:0:
|
||||
256,192,65332,1,0,0:0:0:0:
|
||||
256,192,65498,1,0,0:0:0:0:
|
||||
256,192,65580,1,0,0:0:0:0:
|
||||
256,192,65663,1,8,0:0:0:0:
|
||||
256,192,65829,1,0,0:0:0:0:
|
||||
256,192,65912,1,0,0:0:0:0:
|
||||
256,192,65995,1,8,0:0:0:0:
|
||||
256,192,66161,1,8,0:0:0:0:
|
||||
256,192,66326,1,4,0:0:0:0:
|
||||
256,192,66492,1,0,0:0:0:0:
|
||||
256,192,66658,1,8,0:0:0:0:
|
||||
256,192,66741,1,8,0:0:0:0:
|
||||
256,192,66823,1,0,0:0:0:0:
|
||||
256,192,66989,1,8,0:0:0:0:
|
||||
256,192,67072,1,8,0:0:0:0:
|
||||
256,192,67155,1,0,0:0:0:0:
|
||||
256,192,67321,1,0,0:0:0:0:
|
||||
256,192,67403,1,0,0:0:0:0:
|
||||
256,192,67486,1,8,0:0:0:0:
|
||||
256,192,67652,1,0,0:0:0:0:
|
||||
256,192,67818,1,8,0:0:0:0:
|
||||
256,192,67984,1,8,0:0:0:0:
|
||||
256,192,68066,1,0,0:0:0:0:
|
||||
256,192,68149,1,0,0:0:0:0:
|
||||
256,192,68315,1,8,0:0:0:0:
|
||||
256,192,68481,1,8,0:0:0:0:
|
||||
256,192,68564,1,8,0:0:0:0:
|
||||
256,192,68646,1,0,0:0:0:0:
|
||||
256,192,68729,1,0,0:0:0:0:
|
||||
256,192,68812,1,8,0:0:0:0:
|
||||
256,192,68978,5,4,0:0:0:0:
|
||||
256,192,69475,1,0,0:0:0:0:
|
||||
256,192,70304,1,8,0:0:0:0:
|
||||
256,192,70801,1,8,0:0:0:0:
|
||||
256,192,71630,1,0,0:0:0:0:
|
||||
256,192,72127,1,0,0:0:0:0:
|
||||
256,192,72955,1,8,0:0:0:0:
|
||||
256,192,73453,1,8,0:0:0:0:
|
||||
256,192,73950,1,8,0:0:0:0:
|
||||
256,192,74281,1,4,0:0:0:0:
|
||||
256,192,74778,2,4,L|128:128,1,140
|
||||
256,192,75607,1,8,0:0:0:0:
|
||||
256,192,76104,2,0,L|128:96,1,140
|
||||
256,192,76933,1,0,0:0:0:0:
|
||||
256,192,77430,2,4,L|128:96,1,140
|
||||
256,192,78259,1,8,0:0:0:0:
|
||||
256,192,78756,2,8,L|128:128,1,140
|
||||
256,192,79253,1,8,0:0:0:0:
|
||||
256,192,79584,5,0,0:0:0:0:
|
||||
256,192,79667,1,0,0:0:0:0:
|
||||
256,192,79750,1,0,0:0:0:0:
|
||||
256,192,79916,1,8,0:0:0:0:
|
||||
256,192,80082,1,0,0:0:0:0:
|
||||
256,192,80164,1,8,0:0:0:0:
|
||||
256,192,80247,1,0,0:0:0:0:
|
||||
256,192,80413,1,0,0:0:0:0:
|
||||
256,192,80579,1,8,0:0:0:0:
|
||||
256,192,80745,1,0,0:0:0:0:
|
||||
256,192,80910,1,8,0:0:0:0:
|
||||
256,192,80993,1,8,0:0:0:0:
|
||||
256,192,81076,1,8,0:0:0:0:
|
||||
256,192,81242,1,0,0:0:0:0:
|
||||
256,192,81407,1,8,0:0:0:0:
|
||||
256,192,81490,1,0,0:0:0:0:
|
||||
256,192,81573,1,8,0:0:0:0:
|
||||
256,192,81739,1,8,0:0:0:0:
|
||||
256,192,81905,1,0,0:0:0:0:
|
||||
256,192,82070,1,8,0:0:0:0:
|
||||
256,192,82236,5,0,0:0:0:0:
|
||||
256,192,82319,1,0,0:0:0:0:
|
||||
256,192,82402,1,0,0:0:0:0:
|
||||
256,192,82568,1,8,0:0:0:0:
|
||||
256,192,82733,1,0,0:0:0:0:
|
||||
256,192,82816,1,8,0:0:0:0:
|
||||
256,192,82899,1,0,0:0:0:0:
|
||||
256,192,83065,1,0,0:0:0:0:
|
||||
256,192,83230,1,8,0:0:0:0:
|
||||
256,192,83396,1,0,0:0:0:0:
|
||||
256,192,83562,1,8,0:0:0:0:
|
||||
256,192,83645,1,8,0:0:0:0:
|
||||
256,192,83728,1,8,0:0:0:0:
|
||||
256,192,83893,1,0,0:0:0:0:
|
||||
256,192,84059,1,8,0:0:0:0:
|
||||
256,192,84142,1,0,0:0:0:0:
|
||||
256,192,84225,1,8,0:0:0:0:
|
||||
256,192,84391,1,0,0:0:0:0:
|
||||
256,192,84556,1,8,0:0:0:0:
|
||||
256,192,84722,1,8,0:0:0:0:
|
||||
256,192,84888,5,0,0:0:0:0:
|
||||
256,192,84971,1,0,0:0:0:0:
|
||||
256,192,85053,1,0,0:0:0:0:
|
||||
256,192,85219,1,8,0:0:0:0:
|
||||
256,192,85385,1,0,0:0:0:0:
|
||||
256,192,85468,1,8,0:0:0:0:
|
||||
256,192,85551,1,0,0:0:0:0:
|
||||
256,192,85716,1,0,0:0:0:0:
|
||||
256,192,85882,1,8,0:0:0:0:
|
||||
256,192,86048,1,0,0:0:0:0:
|
||||
256,192,86214,1,8,0:0:0:0:
|
||||
256,192,86296,1,8,0:0:0:0:
|
||||
256,192,86379,1,8,0:0:0:0:
|
||||
256,192,86545,1,0,0:0:0:0:
|
||||
256,192,86711,1,8,0:0:0:0:
|
||||
256,192,86794,1,0,0:0:0:0:
|
||||
256,192,86876,1,8,0:0:0:0:
|
||||
256,192,87042,1,8,0:0:0:0:
|
||||
256,192,87208,1,0,0:0:0:0:
|
||||
256,192,87374,1,8,0:0:0:0:
|
||||
256,192,87539,5,0,0:0:0:0:
|
||||
256,192,87622,1,0,0:0:0:0:
|
||||
256,192,87705,1,0,0:0:0:0:
|
||||
256,192,87871,1,8,0:0:0:0:
|
||||
256,192,88037,1,0,0:0:0:0:
|
||||
256,192,88119,1,8,0:0:0:0:
|
||||
256,192,88202,1,0,0:0:0:0:
|
||||
256,192,88368,1,0,0:0:0:0:
|
||||
256,192,88534,1,8,0:0:0:0:
|
||||
256,192,88699,1,0,0:0:0:0:
|
||||
256,192,88865,1,8,0:0:0:0:
|
||||
256,192,88948,1,8,0:0:0:0:
|
||||
256,192,89031,1,8,0:0:0:0:
|
||||
256,192,89197,1,0,0:0:0:0:
|
||||
256,192,89362,1,8,0:0:0:0:
|
||||
256,192,89445,1,0,0:0:0:0:
|
||||
256,192,89528,1,8,0:0:0:0:
|
||||
256,192,89694,1,0,0:0:0:0:
|
||||
256,192,89860,1,8,0:0:0:0:
|
||||
256,192,90025,1,8,0:0:0:0:
|
||||
256,192,90191,12,0,95427,0:0:0:0:
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user