big first commit

This commit is contained in:
MaxOhn
2021-01-08 09:25:46 +01:00
commit 496216843f
61 changed files with 32981 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
/target
Cargo.lock
+8
View File
@@ -0,0 +1,8 @@
[workspace]
members = [
"fruits",
"mania",
"osu",
"parse",
"taiko",
]
+15
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
/target
Cargo.lock
+10
View File
@@ -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"
+89
View File
@@ -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;
}
}
+149
View File
@@ -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),
}
}
}
+43
View File
@@ -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,
}
}
}
+378
View File
@@ -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
);
}
}
}
+133
View File
@@ -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,
}
}
+148
View File
@@ -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(&current) * 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
+411
View File
@@ -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:
+307
View File
@@ -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
+878
View File
@@ -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:
+2
View File
@@ -0,0 +1,2 @@
/target
Cargo.lock
+10
View File
@@ -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"
+149
View File
@@ -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);
}
}
+133
View File
@@ -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(&current) * 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
+2
View File
@@ -0,0 +1,2 @@
/target
Cargo.lock
+10
View File
@@ -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"
+149
View File
@@ -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),
}
}
}
+70
View File
@@ -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
View File
@@ -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
);
}
}
}
+133
View File
@@ -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,
}
}
+324
View File
@@ -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)
}
}
+99
View File
@@ -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(&current) * 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)
}
}
+101
View File
@@ -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)
}
+2396
View File
File diff suppressed because it is too large Load Diff
+1263
View File
File diff suppressed because it is too large Load Diff
+188
View File
@@ -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:
+391
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
/target
Cargo.lock
+9
View File
@@ -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]
+26
View File
@@ -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)
}
}
+94
View File
@@ -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
}
}
+44
View File
@@ -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)
}
}
+28
View File
@@ -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
}
}
+537
View File
@@ -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,
}
}
}
+111
View File
@@ -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
}
}
+103
View File
@@ -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)
}
}
+118
View File
@@ -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;
}
}
+2
View File
@@ -0,0 +1,2 @@
/target
Cargo.lock
+13
View File
@@ -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"
+34
View File
@@ -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,
}
}
}
+65
View File
@@ -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()
}
+225
View File
@@ -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
);
}
}
}
+83
View File
@@ -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()]
}
}
+19
View File
@@ -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()
}
}
+97
View File
@@ -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(&current, 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)
}
}
+285
View File
@@ -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)
}
+105
View File
@@ -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
+598
View File
@@ -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