big first commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "osu"
|
||||
version = "0.1.0"
|
||||
authors = ["MaxOhn <ohn.m@hotmail.de>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies.parse]
|
||||
path = "../parse"
|
||||
@@ -0,0 +1,149 @@
|
||||
use super::math_util;
|
||||
|
||||
use parse::Pos2;
|
||||
|
||||
const SLIDER_QUALITY: f32 = 50.0;
|
||||
|
||||
pub(crate) enum Points {
|
||||
Single(Pos2),
|
||||
Multi(Vec<Pos2>),
|
||||
}
|
||||
|
||||
pub(crate) enum Curve {
|
||||
Linear {
|
||||
a: Pos2,
|
||||
b: Pos2,
|
||||
},
|
||||
Bezier(Points),
|
||||
Catmull(Points),
|
||||
Perfect {
|
||||
origin: Pos2,
|
||||
cx: f32,
|
||||
cy: f32,
|
||||
radius: f32,
|
||||
},
|
||||
}
|
||||
|
||||
impl Curve {
|
||||
pub(crate) fn linear(a: Pos2, b: Pos2) -> Self {
|
||||
Self::Linear { a, b }
|
||||
}
|
||||
|
||||
pub(crate) fn bezier(points: &[Pos2]) -> Self {
|
||||
if points.len() == 1 {
|
||||
return Self::Bezier(Points::Single(points[0]));
|
||||
}
|
||||
|
||||
let mut start = 0;
|
||||
let mut end = 0;
|
||||
let mut result = Vec::with_capacity(4);
|
||||
|
||||
for i in 0..points.len() - 1 {
|
||||
if end - start > 1 && points[i] == points[end - 1] {
|
||||
Self::_bezier(&mut result, &points[start..end]);
|
||||
start = end;
|
||||
}
|
||||
|
||||
end += 1;
|
||||
}
|
||||
|
||||
Self::_bezier(&mut result, &points[start..end + 1]);
|
||||
|
||||
Self::Bezier(Points::Multi(result))
|
||||
}
|
||||
|
||||
fn _bezier(result: &mut Vec<Pos2>, points: &[Pos2]) {
|
||||
let step = 0.25 / SLIDER_QUALITY / points.len() as f32;
|
||||
let mut i = 0.0;
|
||||
let n = points.len() as i32 - 1;
|
||||
|
||||
while i < 1.0 + step {
|
||||
let point = (0..=n).fold(Pos2 { x: 0.0, y: 0.0 }, |point, p| {
|
||||
let factor = math_util::cpn(p, n) * (1.0 - i).powi(n - p) * i.powi(p);
|
||||
|
||||
point + points[p as usize] * factor
|
||||
});
|
||||
|
||||
result.push(point);
|
||||
i += step;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn catmull(points: &[Pos2]) -> Self {
|
||||
if points.len() == 1 {
|
||||
return Self::Catmull(Points::Single(points[0]));
|
||||
}
|
||||
|
||||
let order = points.len();
|
||||
let step = 2.5 / SLIDER_QUALITY;
|
||||
let target = step + 1.0;
|
||||
|
||||
let mut resulting_points = Vec::with_capacity(4);
|
||||
|
||||
for x in 0..order - 1 {
|
||||
let mut t = 0.0;
|
||||
|
||||
while t < target {
|
||||
let v1 = if x >= 1 { points[x - 1] } else { points[x] };
|
||||
let v2 = points[x];
|
||||
|
||||
let v3 = if x + 1 < order {
|
||||
points[x + 1]
|
||||
} else {
|
||||
v2.add_scaled(v2.add_scaled(v1, -1.0), 1.0)
|
||||
};
|
||||
|
||||
let v4 = if x + 2 < order {
|
||||
points[x + 2]
|
||||
} else {
|
||||
v3.add_scaled(v3.add_scaled(v2, -1.0), 1.0)
|
||||
};
|
||||
|
||||
let point = Self::catmull_point(v1, v2, v3, v4, t);
|
||||
resulting_points.push(point);
|
||||
t += step;
|
||||
}
|
||||
}
|
||||
|
||||
Self::Catmull(Points::Multi(resulting_points))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn catmull_point(p0: Pos2, p1: Pos2, p2: Pos2, p3: Pos2, len: f32) -> Pos2 {
|
||||
Pos2 {
|
||||
x: math_util::catmull(p0.x, p1.x, p2.x, p3.x, len),
|
||||
y: math_util::catmull(p0.y, p1.y, p2.y, p3.y, len),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn perfect(points: &[Pos2]) -> Self {
|
||||
let (cx, cy, mut radius) = math_util::get_circum_circle(&points);
|
||||
radius *= ((!math_util::is_left(&points)) as i8 * 2 - 1) as f32;
|
||||
|
||||
Self::Perfect {
|
||||
origin: points[0],
|
||||
cx,
|
||||
cy,
|
||||
radius,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn point_at_distance(&self, len: f32) -> Pos2 {
|
||||
let points = match self {
|
||||
Self::Bezier(points) => points,
|
||||
Self::Catmull(points) => points,
|
||||
Self::Linear { a, b } => return math_util::point_on_line(*a, *b, len),
|
||||
Self::Perfect {
|
||||
origin,
|
||||
cx,
|
||||
cy,
|
||||
radius,
|
||||
} => return math_util::rotate(*cx, *cy, *origin, len / *radius),
|
||||
};
|
||||
|
||||
match points {
|
||||
Points::Single(point) => *point,
|
||||
Points::Multi(points) => math_util::point_at_distance(points, len),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
use super::OsuObject;
|
||||
|
||||
const NORMALIZED_RADIUS: f32 = 52.0;
|
||||
|
||||
pub(crate) struct DifficultyObject {
|
||||
pub(crate) base: OsuObject,
|
||||
pub(crate) prev: Option<(f32, f32)>, // (jump_dist, strain_time)
|
||||
|
||||
pub(crate) jump_dist: f32,
|
||||
pub(crate) travel_dist: f32,
|
||||
pub(crate) angle: Option<f32>,
|
||||
|
||||
pub(crate) delta: f32,
|
||||
pub(crate) strain_time: f32,
|
||||
}
|
||||
|
||||
impl DifficultyObject {
|
||||
pub(crate) fn new(
|
||||
base: OsuObject,
|
||||
prev: OsuObject,
|
||||
prev_diff: Option<DifficultyObject>,
|
||||
prev_prev: Option<OsuObject>,
|
||||
clock_rate: f32,
|
||||
) -> Self {
|
||||
let delta = (base.time() - prev.time()) / clock_rate;
|
||||
let strain_time = delta.max(50.0);
|
||||
|
||||
let radius = base.radius();
|
||||
let mut scaling_factor = NORMALIZED_RADIUS / radius;
|
||||
|
||||
if radius < 30.0 {
|
||||
let small_circle_bonus = (30.0 - radius).min(5.0) / 50.0;
|
||||
scaling_factor *= 1.0 + small_circle_bonus;
|
||||
}
|
||||
|
||||
let travel_dist = base.travel_dist();
|
||||
let prev_cursor_pos = prev.cursor_end_position();
|
||||
|
||||
let jump_dist = match base {
|
||||
OsuObject::Spinner { .. } => 0.0,
|
||||
_ => (base.stacked_pos() * scaling_factor - prev_cursor_pos * scaling_factor).length(),
|
||||
};
|
||||
|
||||
let angle = prev_prev.map(|prev_prev| {
|
||||
let prev_prev_cursor_pos = prev_prev.cursor_end_position();
|
||||
|
||||
let v1 = prev_prev_cursor_pos - prev.stacked_pos();
|
||||
let v2 = base.stacked_pos() - prev_cursor_pos;
|
||||
|
||||
let dot = v1.dot(v2);
|
||||
let det = v1.x * v2.y - v1.y * v2.x;
|
||||
|
||||
det.atan2(dot).abs()
|
||||
});
|
||||
|
||||
let prev = prev_diff.map(|o| (o.jump_dist, o.strain_time));
|
||||
|
||||
Self {
|
||||
base,
|
||||
prev,
|
||||
|
||||
jump_dist,
|
||||
travel_dist,
|
||||
angle,
|
||||
|
||||
delta,
|
||||
strain_time,
|
||||
}
|
||||
}
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
mod curve;
|
||||
mod difficulty_object;
|
||||
mod math_util;
|
||||
mod osu_object;
|
||||
mod skill;
|
||||
mod skill_kind;
|
||||
|
||||
use difficulty_object::DifficultyObject;
|
||||
use osu_object::OsuObject;
|
||||
use skill::Skill;
|
||||
use skill_kind::SkillKind;
|
||||
|
||||
use parse::{Beatmap, Mods};
|
||||
|
||||
const SECTION_LEN: f32 = 400.0;
|
||||
const DIFFICULTY_MULTIPLIER: f32 = 0.0675;
|
||||
|
||||
/// Star calculation for osu!standard maps
|
||||
pub fn stars(map: &Beatmap, mods: impl Mods) -> f32 {
|
||||
if map.hit_objects.len() < 2 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let attributes = map.attributes().mods(mods);
|
||||
let section_len = SECTION_LEN * attributes.clock_rate;
|
||||
|
||||
let mut hit_objects = map
|
||||
.hit_objects
|
||||
.iter()
|
||||
.map(|h| OsuObject::new(h, map, &attributes));
|
||||
|
||||
let mut skills = vec![Skill::new(SkillKind::Aim), Skill::new(SkillKind::Speed)];
|
||||
|
||||
let mut current_section_end =
|
||||
(map.hit_objects[0].start_time / section_len).ceil() * section_len;
|
||||
|
||||
let mut prev_prev = None;
|
||||
let mut prev = hit_objects.next().unwrap();
|
||||
let mut prev_diff = None;
|
||||
|
||||
for curr in hit_objects {
|
||||
let h = DifficultyObject::new(
|
||||
curr.clone(),
|
||||
prev.clone(),
|
||||
prev_diff,
|
||||
prev_prev,
|
||||
attributes.clock_rate,
|
||||
);
|
||||
|
||||
// println!(
|
||||
// "strain_time={} | travel_dist={} | jump_dist={} | angle={:?}",
|
||||
// h.strain_time, h.travel_dist, h.jump_dist, h.angle
|
||||
// );
|
||||
|
||||
while h.base.time() > current_section_end {
|
||||
for skill in skills.iter_mut() {
|
||||
skill.save_current_peak();
|
||||
skill.start_new_section_from(current_section_end);
|
||||
}
|
||||
|
||||
current_section_end += section_len;
|
||||
}
|
||||
|
||||
for skill in skills.iter_mut() {
|
||||
skill.process(&h);
|
||||
}
|
||||
|
||||
prev_prev = Some(prev);
|
||||
prev = curr;
|
||||
prev_diff = Some(h);
|
||||
}
|
||||
|
||||
for skill in skills.iter_mut() {
|
||||
skill.save_current_peak();
|
||||
}
|
||||
|
||||
let aim_rating = skills[0].difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
||||
let speed_rating = skills[1].difficulty_value().sqrt() * DIFFICULTY_MULTIPLIER;
|
||||
|
||||
aim_rating + speed_rating + (aim_rating - speed_rating).abs() / 2.0
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::File;
|
||||
|
||||
#[test]
|
||||
fn test_single() {
|
||||
// let file = match File::open("E:/Games/osu!/beatmaps/1851299.osu") {
|
||||
// Ok(file) => file,
|
||||
// Err(why) => panic!("Could not open file: {}", why),
|
||||
// };
|
||||
let file = match File::open("C:/Users/Max/Desktop/1851299.osu") {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file: {}", why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map: {}", why),
|
||||
};
|
||||
|
||||
let stars = stars(&map, 0);
|
||||
|
||||
println!("Stars: {}", stars);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_osu() {
|
||||
let margin = 0.005;
|
||||
|
||||
#[rustfmt::skip]
|
||||
let data = vec![
|
||||
(1851299, 1 << 8, 4.23514130038547), // HT
|
||||
(1851299, 0, 5.356786475158158), // NM
|
||||
(1851299, 1 << 6, 7.450616908751305), // DT
|
||||
(1851299, 1 << 4, 5.6834681957637665),// HR
|
||||
(1851299, 1 << 1, 4.937817303399699), // EZ
|
||||
|
||||
(70090, 1 << 8, 2.2929922580201803), // HT
|
||||
(70090, 0, 2.8322940761833983), // NM
|
||||
(70090, 1 << 6, 3.8338563325375485), // DT
|
||||
(70090, 1 << 4, 3.0617492228478174), // HR
|
||||
(70090, 1 << 1, 2.698823231324141), // EZ
|
||||
|
||||
(1241370, 1 << 8, 5.662809600985943), // HT
|
||||
(1241370, 0, 7.0367002127481975), // NM
|
||||
(1241370, 1 << 6, 11.144720506574934),// DT
|
||||
(1241370, 1 << 4, 7.641688110458715), // HR
|
||||
(1241370, 1 << 1, 6.316288616688052), // EZ
|
||||
|
||||
// Slider fiesta
|
||||
// (1657535, 1 << 8, 4.1727975286379895),// HT
|
||||
// (1657535, 0, 5.16048239944917), // NM
|
||||
// (1657535, 1 << 6, 7.125936779100417), // DT
|
||||
// (1657535, 1 << 4, 5.545877027713307), // HR
|
||||
// (1657535, 1 << 1, 4.66015083361088), // EZ
|
||||
];
|
||||
|
||||
for (map_id, mods, expected_stars) in data {
|
||||
let file = match File::open(format!("./test/{}.osu", map_id)) {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
||||
};
|
||||
|
||||
let stars = stars(&map, mods);
|
||||
|
||||
assert!(
|
||||
(stars - expected_stars).abs() < margin,
|
||||
"Stars: {} | Expected: {} => {} margin [map {} | mods {}]",
|
||||
stars,
|
||||
expected_stars,
|
||||
(stars - expected_stars).abs(),
|
||||
map_id,
|
||||
mods
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
use parse::Pos2;
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn cpn(mut p: i32, n: i32) -> f32 {
|
||||
if p < 0 || p > n {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
p = p.min(n - p);
|
||||
let mut out = 1.0;
|
||||
|
||||
for i in 1..=p {
|
||||
out *= (n - p + i) as f32 / i as f32;
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn catmull(p0: f32, p1: f32, p2: f32, p3: f32, t: f32) -> f32 {
|
||||
0.5 * ((2.0 * p1)
|
||||
+ (-p0 + p2) * t
|
||||
+ (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t.powi(2)
|
||||
+ (-p0 + 3.0 * p1 - 3.0 * p2 + p3 * t.powi(3)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn point_on_line(p1: Pos2, p2: Pos2, len: f32) -> Pos2 {
|
||||
let mut full_len = ((p1.x - p2.x).powi(2) + (p1.y - p2.y).powi(2)).sqrt();
|
||||
let n = full_len - len;
|
||||
|
||||
if full_len.abs() < f32::EPSILON {
|
||||
full_len = 1.0;
|
||||
}
|
||||
|
||||
(p1 * n + p2 * len) / full_len
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn angle_from_points(p0: Pos2, p1: Pos2) -> f32 {
|
||||
(p1.y - p0.y).atan2(p1.x - p0.x)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn distance_from_points(arr: &[Pos2]) -> f32 {
|
||||
arr.iter()
|
||||
.skip(1)
|
||||
.zip(arr.iter())
|
||||
.map(|(curr, prev)| curr.distance(prev))
|
||||
.sum()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn cart_from_pol(r: f32, t: f32) -> Pos2 {
|
||||
Pos2 {
|
||||
x: r * t.cos(),
|
||||
y: r * t.sin(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn point_at_distance(array: &[Pos2], distance: f32) -> Pos2 {
|
||||
if array.len() < 2 {
|
||||
return Pos2 { x: 0.0, y: 0.0 };
|
||||
} else if distance.abs() < f32::EPSILON {
|
||||
return array[0];
|
||||
} else if distance_from_points(array) <= distance {
|
||||
return array[array.len() - 1];
|
||||
}
|
||||
|
||||
let mut i = 0;
|
||||
let mut current_distance = 0.0;
|
||||
let mut new_distance = 0.0;
|
||||
|
||||
while i < array.len() - 2 {
|
||||
new_distance = (array[i] - array[i + 1]).length();
|
||||
current_distance += new_distance;
|
||||
|
||||
if distance <= current_distance {
|
||||
break;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
current_distance -= new_distance;
|
||||
|
||||
if (distance - current_distance).abs() <= f32::EPSILON {
|
||||
array[i]
|
||||
} else {
|
||||
let angle = angle_from_points(array[i], array[i + 1]);
|
||||
let cart = cart_from_pol(distance - current_distance, angle);
|
||||
|
||||
if array[i].x > array[i + 1].x {
|
||||
array[i] - cart
|
||||
} else {
|
||||
array[i] + cart
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_circum_circle(p: &[Pos2]) -> (f32, f32, f32) {
|
||||
let d = 2.0
|
||||
* (p[0].x * (p[1].y - p[2].y) + p[1].x * (p[2].y - p[0].y) + p[2].x * (p[0].y - p[1].y));
|
||||
|
||||
let p0 = p[0].x * p[0].x + p[0].y * p[0].y;
|
||||
let p1 = p[1].x * p[1].x + p[1].y * p[1].y;
|
||||
let p2 = p[2].x * p[2].x + p[2].y * p[2].y;
|
||||
|
||||
let ux = (p0 * (p[1].y - p[2].y) + p1 * (p[2].y - p[0].y) + p2 * (p[0].y - p[1].y)) / d;
|
||||
let uy = (p0 * (p[2].x - p[1].x) + p1 * (p[0].x - p[2].x) + p2 * (p[1].x - p[0].x)) / d;
|
||||
|
||||
let px = ux - p[0].x;
|
||||
let py = uy - p[0].y;
|
||||
let r = (px * px + py * py).sqrt();
|
||||
|
||||
(ux, uy, r)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn is_left(p: &[Pos2]) -> bool {
|
||||
((p[1].x - p[0].x) * (p[2].y - p[0].y) - (p[1].y - p[0].y) * (p[2].x - p[0].x)) < 0.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn rotate(cx: f32, cy: f32, p: Pos2, radians: f32) -> Pos2 {
|
||||
let cos = radians.cos();
|
||||
let sin = radians.sin();
|
||||
|
||||
Pos2 {
|
||||
x: (cos * (p.x - cx)) - (sin * (p.y - cy)) + cx,
|
||||
y: (sin * (p.x - cx)) + (cos * (p.y - cy)) + cy,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
use super::curve::Curve;
|
||||
|
||||
use parse::{Beatmap, BeatmapAttributes, HitObject, HitObjectKind, PathType, Pos2};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
macro_rules! binary_search {
|
||||
($slice:expr, $target:expr) => {
|
||||
$slice.binary_search_by(|p| p.time.partial_cmp(&$target).unwrap_or(Ordering::Equal))
|
||||
};
|
||||
}
|
||||
|
||||
const OBJECT_RADIUS: f32 = 64.0;
|
||||
const STACK_DIST: f32 = 3.0;
|
||||
const LEGACY_LAST_TICK_OFFSET: f32 = 36.0;
|
||||
|
||||
#[derive(Clone)] // TODO: Remove clone
|
||||
pub(crate) enum OsuObject {
|
||||
Circle {
|
||||
pos: Pos2,
|
||||
time: f32,
|
||||
|
||||
scale: f32,
|
||||
stack_height: f32,
|
||||
},
|
||||
Slider {
|
||||
pixel_len: f32, // TODO: Redundant?
|
||||
repeats: usize, // TODO: Redundant?
|
||||
|
||||
objects: Vec<SliderTick>,
|
||||
|
||||
scale: f32,
|
||||
stack_height: f32,
|
||||
|
||||
cursor_end_pos: Pos2,
|
||||
cursor_travel_dist: f32,
|
||||
},
|
||||
Spinner {
|
||||
pos: Pos2,
|
||||
time: f32,
|
||||
},
|
||||
}
|
||||
|
||||
impl OsuObject {
|
||||
pub(crate) fn new(h: &HitObject, map: &Beatmap, attributes: &BeatmapAttributes) -> Self {
|
||||
let pos = h.pos;
|
||||
let time = h.start_time;
|
||||
|
||||
let scale = (1.0 - 0.7 * (attributes.cs - 5.0) / 5.0) / 2.0;
|
||||
let mut stack_height = 0.0;
|
||||
|
||||
match &h.kind {
|
||||
HitObjectKind::Circle => Self::Circle {
|
||||
pos,
|
||||
time,
|
||||
scale,
|
||||
stack_height,
|
||||
},
|
||||
HitObjectKind::Slider {
|
||||
pixel_len,
|
||||
repeats,
|
||||
curve_points,
|
||||
path_type,
|
||||
} => {
|
||||
let (beat_len, timing_time) = {
|
||||
match binary_search!(map.timing_points, time) {
|
||||
Ok(idx) => {
|
||||
let point = &map.timing_points[idx];
|
||||
(point.beat_len, point.time)
|
||||
}
|
||||
Err(0) => (1000.0, 0.0),
|
||||
Err(idx) => {
|
||||
let point = &map.timing_points[idx - 1];
|
||||
(point.beat_len, point.time)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let (speed_multiplier, diff_time) = {
|
||||
match binary_search!(map.difficulty_points, time) {
|
||||
Ok(idx) => {
|
||||
let point = &map.difficulty_points[idx];
|
||||
(point.speed_multiplier, point.time)
|
||||
}
|
||||
Err(0) => (1.0, 0.0),
|
||||
Err(idx) => {
|
||||
let point = &map.difficulty_points[idx - 1];
|
||||
(point.speed_multiplier, point.time)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut tick_distance = 100.0 * map.sv / map.tick_rate;
|
||||
|
||||
if map.version >= 8 {
|
||||
tick_distance /= (100.0 / speed_multiplier).max(10.0).min(1000.0) / 100.0;
|
||||
}
|
||||
|
||||
let spm = if timing_time > diff_time {
|
||||
1.0
|
||||
} else {
|
||||
speed_multiplier
|
||||
};
|
||||
|
||||
let duration = *repeats as f32 * beat_len * pixel_len / (map.sv * spm) / 100.0;
|
||||
|
||||
// let velocity = *pixel_len as f32 / duration;
|
||||
|
||||
// println!("duration={}", duration);
|
||||
// println!("velocity={}", velocity);
|
||||
|
||||
let path_type = if *path_type == PathType::PerfectCurve && curve_points.len() > 3 {
|
||||
PathType::Bezier
|
||||
} else if curve_points.len() == 2 {
|
||||
PathType::Linear
|
||||
} else {
|
||||
*path_type
|
||||
};
|
||||
|
||||
let curve = match path_type {
|
||||
PathType::Linear => Curve::linear(curve_points[0], curve_points[1]),
|
||||
PathType::Bezier => Curve::bezier(&curve_points),
|
||||
PathType::Catmull => Curve::catmull(&curve_points),
|
||||
PathType::PerfectCurve => Curve::perfect(&curve_points),
|
||||
};
|
||||
|
||||
let mut current_distance = tick_distance;
|
||||
let time_add = duration * (tick_distance / (pixel_len * *repeats as f32));
|
||||
|
||||
let target = pixel_len - tick_distance / 8.0;
|
||||
let mut ticks = Vec::with_capacity((target / tick_distance) as usize);
|
||||
|
||||
while current_distance < target {
|
||||
let pos = curve.point_at_distance(current_distance);
|
||||
let time = h.start_time + time_add * (ticks.len() + 1) as f32;
|
||||
ticks.push(SliderTick::new(pos, time));
|
||||
current_distance += tick_distance;
|
||||
}
|
||||
|
||||
let mut slider_objects = Vec::with_capacity(repeats * (ticks.len() + 1));
|
||||
slider_objects.push(SliderTick::new(h.pos, h.start_time));
|
||||
|
||||
if *repeats <= 1 {
|
||||
slider_objects.append(&mut ticks);
|
||||
} else {
|
||||
slider_objects.append(&mut ticks.clone());
|
||||
|
||||
for repeat_id in 1..repeats - 1 {
|
||||
let dist = (repeat_id % 2) as f32 * pixel_len;
|
||||
let time_offset = (duration / *repeats as f32) * repeat_id as f32;
|
||||
let pos = curve.point_at_distance(dist);
|
||||
|
||||
// Reverse tick / last legacy tick
|
||||
slider_objects.push(SliderTick::new(pos, h.start_time + time_offset));
|
||||
|
||||
ticks.reverse();
|
||||
slider_objects.extend_from_slice(&ticks); // tick time doesn't need to be adjusted for some reason
|
||||
}
|
||||
|
||||
// Handling last span separatly so that `ticks` vector isn't cloned again
|
||||
let dist = ((repeats - 1) % 2) as f32 * pixel_len;
|
||||
let time_offset = (duration / *repeats as f32) * (repeats - 1) as f32;
|
||||
let pos = curve.point_at_distance(dist);
|
||||
|
||||
slider_objects.push(SliderTick::new(pos, h.start_time + time_offset));
|
||||
|
||||
ticks.reverse();
|
||||
slider_objects.append(&mut ticks);
|
||||
}
|
||||
|
||||
// Slider tail
|
||||
let dist_end = (repeats % 2) as f32 * pixel_len;
|
||||
// let dist_end = velocity * (duration - LEGACY_LAST_TICK_OFFSET);
|
||||
let pos = curve.point_at_distance(dist_end);
|
||||
slider_objects.push(SliderTick::new(pos, h.start_time + duration));
|
||||
|
||||
println!("> Slider: {:?}", slider_objects);
|
||||
|
||||
let radius = OBJECT_RADIUS * scale;
|
||||
|
||||
let stack_offset = {
|
||||
let c = stack_height * scale * -6.4;
|
||||
|
||||
Pos2 { x: c, y: c }
|
||||
};
|
||||
|
||||
// println!("radius={} | stack_offset={:?}", radius, stack_offset);
|
||||
|
||||
let stacked_pos = pos + stack_offset;
|
||||
|
||||
let mut cursor_end_pos = stacked_pos;
|
||||
let mut cursor_travel_dist = 0.0;
|
||||
let approx_follow_circle_radius = radius * 3.0;
|
||||
|
||||
for (i, tick) in slider_objects.iter().skip(1).enumerate() {
|
||||
let diff = stacked_pos + tick.pos - cursor_end_pos;
|
||||
let mut dist = diff.length();
|
||||
|
||||
println!("[{}] diff={:?} | dist={}", i, diff, dist);
|
||||
|
||||
if dist > approx_follow_circle_radius {
|
||||
let normalized = diff.normalize();
|
||||
dist -= approx_follow_circle_radius;
|
||||
cursor_end_pos += normalized * dist;
|
||||
cursor_travel_dist += dist;
|
||||
}
|
||||
}
|
||||
|
||||
println!("---");
|
||||
|
||||
Self::Slider {
|
||||
pixel_len: *pixel_len,
|
||||
repeats: *repeats,
|
||||
|
||||
objects: slider_objects,
|
||||
|
||||
scale,
|
||||
stack_height,
|
||||
|
||||
cursor_end_pos,
|
||||
cursor_travel_dist,
|
||||
}
|
||||
}
|
||||
HitObjectKind::Spinner { .. } => Self::Spinner { pos, time },
|
||||
HitObjectKind::Hold { .. } => panic!("found Hold object in osu!standard file"),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn time(&self) -> f32 {
|
||||
match self {
|
||||
Self::Circle { time, .. } => *time,
|
||||
Self::Slider { objects, .. } => objects[0].time,
|
||||
Self::Spinner { time, .. } => *time,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn radius(&self) -> f32 {
|
||||
OBJECT_RADIUS * self.scale()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn travel_dist(&self) -> f32 {
|
||||
match self {
|
||||
Self::Slider {
|
||||
cursor_travel_dist, ..
|
||||
} => *cursor_travel_dist,
|
||||
_ => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn stacked_pos(&self) -> Pos2 {
|
||||
self.pos() + self.stack_offset()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn cursor_end_position(&self) -> Pos2 {
|
||||
match self {
|
||||
Self::Circle { pos, .. } => *pos,
|
||||
Self::Slider { cursor_end_pos, .. } => *cursor_end_pos,
|
||||
Self::Spinner { pos, .. } => *pos,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn is_spinner(&self) -> bool {
|
||||
matches!(self, Self::Spinner { .. })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn scale(&self) -> f32 {
|
||||
match self {
|
||||
Self::Circle { scale, .. } => *scale,
|
||||
Self::Slider { scale, .. } => *scale,
|
||||
Self::Spinner { .. } => 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn stack_height(&self) -> f32 {
|
||||
match self {
|
||||
Self::Circle { stack_height, .. } => *stack_height,
|
||||
Self::Slider { stack_height, .. } => *stack_height,
|
||||
Self::Spinner { .. } => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pos(&self) -> Pos2 {
|
||||
match self {
|
||||
Self::Circle { pos, .. } => *pos,
|
||||
Self::Slider { objects, .. } => objects[0].pos,
|
||||
Self::Spinner { .. } => Pos2::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn stack_offset(&self) -> Pos2 {
|
||||
let c = self.stack_height() * self.scale() * -6.4;
|
||||
|
||||
Pos2 { x: c, y: c }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) struct SliderTick {
|
||||
pos: Pos2,
|
||||
time: f32,
|
||||
}
|
||||
|
||||
impl SliderTick {
|
||||
fn new(pos: Pos2, time: f32) -> Self {
|
||||
Self { pos, time }
|
||||
}
|
||||
}
|
||||
|
||||
use std::fmt;
|
||||
|
||||
impl fmt::Debug for SliderTick {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{{pos={:?} | time={}}}", self.pos, self.time)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
use super::{DifficultyObject, SkillKind};
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
const SPEED_SKILL_MULTIPLIER: f32 = 1400.0;
|
||||
const SPEED_STRAIN_DECAY_BASE: f32 = 0.3;
|
||||
|
||||
const AIM_SKILL_MULTIPLIER: f32 = 26.25;
|
||||
const AIM_STRAIN_DECAY_BASE: f32 = 0.15;
|
||||
|
||||
const DECAY_WEIGHT: f32 = 0.9;
|
||||
|
||||
pub(crate) struct Skill {
|
||||
pub current_strain: f32,
|
||||
current_section_peak: f32,
|
||||
|
||||
kind: SkillKind,
|
||||
strain_peaks: Vec<f32>,
|
||||
|
||||
prev_time: Option<f32>,
|
||||
}
|
||||
|
||||
impl Skill {
|
||||
#[inline]
|
||||
pub(crate) fn new(kind: SkillKind) -> Self {
|
||||
Self {
|
||||
current_strain: 1.0,
|
||||
current_section_peak: 1.0,
|
||||
|
||||
kind,
|
||||
strain_peaks: Vec::with_capacity(128),
|
||||
|
||||
prev_time: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn save_current_peak(&mut self) {
|
||||
if self.prev_time.is_some() {
|
||||
self.strain_peaks.push(self.current_section_peak);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn start_new_section_from(&mut self, time: f32) {
|
||||
if let Some(prev) = self.prev_time {
|
||||
self.current_section_peak = self.peak_strain(time - prev);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn process(&mut self, current: &DifficultyObject) {
|
||||
self.current_strain *= self.strain_decay(current.delta);
|
||||
self.current_strain += self.kind.strain_value_of(¤t) * self.skill_multiplier();
|
||||
self.current_section_peak = self.current_section_peak.max(self.current_strain);
|
||||
self.prev_time.replace(current.base.time());
|
||||
}
|
||||
|
||||
pub(crate) fn difficulty_value(&mut self) -> f32 {
|
||||
let mut difficulty = 0.0;
|
||||
let mut weight = 1.0;
|
||||
|
||||
self.strain_peaks
|
||||
.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
||||
|
||||
for &strain in self.strain_peaks.iter() {
|
||||
difficulty += strain * weight;
|
||||
weight *= DECAY_WEIGHT;
|
||||
}
|
||||
|
||||
difficulty
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn skill_multiplier(&self) -> f32 {
|
||||
match self.kind {
|
||||
SkillKind::Aim => AIM_SKILL_MULTIPLIER,
|
||||
SkillKind::Speed => SPEED_SKILL_MULTIPLIER,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_decay_base(&self) -> f32 {
|
||||
match self.kind {
|
||||
SkillKind::Aim => AIM_STRAIN_DECAY_BASE,
|
||||
SkillKind::Speed => SPEED_STRAIN_DECAY_BASE,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn peak_strain(&self, delta_time: f32) -> f32 {
|
||||
self.current_strain * self.strain_decay(delta_time)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_decay(&self, ms: f32) -> f32 {
|
||||
self.strain_decay_base().powf(ms / 1000.0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
use super::DifficultyObject;
|
||||
|
||||
const SINGLE_SPACING_TRESHOLD: f32 = 125.0;
|
||||
const SPEED_ANGLE_BONUS_BEGIN: f32 = std::f32::consts::FRAC_PI_6;
|
||||
const PI_OVER_4: f32 = std::f32::consts::FRAC_PI_4;
|
||||
const PI_OVER_2: f32 = std::f32::consts::FRAC_PI_2;
|
||||
|
||||
const MIN_SPEED_BONUS: f32 = 75.0;
|
||||
const MAX_SPEED_BONUS: f32 = 45.0;
|
||||
const SPEED_BALANCING_FACTOR: f32 = 40.0;
|
||||
|
||||
const AIM_ANGLE_BONUS_BEGIN: f32 = std::f32::consts::FRAC_PI_3;
|
||||
const TIMING_THRESHOLD: f32 = 107.0;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum SkillKind {
|
||||
Aim,
|
||||
Speed,
|
||||
}
|
||||
|
||||
impl SkillKind {
|
||||
pub(crate) fn strain_value_of(self, current: &DifficultyObject) -> f32 {
|
||||
match self {
|
||||
Self::Aim => {
|
||||
if current.base.is_spinner() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let mut result = 0.0;
|
||||
|
||||
if let Some((prev_jump_dist, prev_strain_time)) = current.prev {
|
||||
if let Some(angle) = current.angle.filter(|a| *a > AIM_ANGLE_BONUS_BEGIN) {
|
||||
let scale = 90.0;
|
||||
|
||||
let angle_bonus = (((angle - AIM_ANGLE_BONUS_BEGIN).sin()).powi(2)
|
||||
* (prev_jump_dist - scale).max(0.0)
|
||||
* (current.jump_dist - scale).max(0.0))
|
||||
.sqrt();
|
||||
|
||||
result = 1.5 * apply_diminishing_exp(angle_bonus.max(0.0))
|
||||
/ (TIMING_THRESHOLD).max(prev_strain_time)
|
||||
}
|
||||
}
|
||||
|
||||
let jump_dist_exp = apply_diminishing_exp(current.jump_dist);
|
||||
let travel_dist_exp = apply_diminishing_exp(current.travel_dist);
|
||||
|
||||
let dist_exp =
|
||||
jump_dist_exp + travel_dist_exp + (travel_dist_exp * jump_dist_exp).sqrt();
|
||||
|
||||
(result + dist_exp / (current.strain_time).max(TIMING_THRESHOLD))
|
||||
.max(dist_exp / current.strain_time)
|
||||
}
|
||||
Self::Speed => {
|
||||
if current.base.is_spinner() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let dist = SINGLE_SPACING_TRESHOLD.min(current.travel_dist + current.jump_dist);
|
||||
let delta_time = MAX_SPEED_BONUS.max(current.delta);
|
||||
|
||||
let mut speed_bonus = 1.0;
|
||||
|
||||
if delta_time < MIN_SPEED_BONUS {
|
||||
let exp_base = (MIN_SPEED_BONUS - delta_time) / SPEED_BALANCING_FACTOR;
|
||||
speed_bonus = 1.0 + exp_base * exp_base;
|
||||
}
|
||||
|
||||
let mut angle_bonus = 1.0;
|
||||
|
||||
if let Some(angle) = current.angle.filter(|a| *a < SPEED_ANGLE_BONUS_BEGIN) {
|
||||
let exp_base = (1.5 * (SPEED_ANGLE_BONUS_BEGIN - angle)).sin();
|
||||
angle_bonus = 1.0 + exp_base * exp_base / 3.57;
|
||||
|
||||
if angle < PI_OVER_2 {
|
||||
angle_bonus = 1.28;
|
||||
|
||||
// TODO: Improve ifs
|
||||
if dist < 90.0 && angle < PI_OVER_4 {
|
||||
angle_bonus += (1.0 - angle_bonus) * ((90.0 - dist) / 10.0).min(1.0);
|
||||
} else if dist < 90.0 {
|
||||
angle_bonus += (1.0 - angle_bonus)
|
||||
* ((90.0 - dist) / 10.0).min(1.0)
|
||||
* ((PI_OVER_2 - angle) / PI_OVER_4).sin();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(1.0 + (speed_bonus - 1.0) * 0.75)
|
||||
* angle_bonus
|
||||
* (0.95 + speed_bonus * (dist / SINGLE_SPACING_TRESHOLD).powf(3.5))
|
||||
/ current.strain_time
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn apply_diminishing_exp(val: f32) -> f32 {
|
||||
val.powf(0.99)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,188 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
AudioFilename: audio.mp3
|
||||
AudioLeadIn: 0
|
||||
PreviewTime: 2154
|
||||
Countdown: 0
|
||||
SampleSet: Soft
|
||||
StackLeniency: 0.7
|
||||
Mode: 0
|
||||
LetterboxInBreaks: 0
|
||||
WidescreenStoryboard: 1
|
||||
|
||||
[Editor]
|
||||
Bookmarks: 14272,22742,24684,25566,28390,34037
|
||||
DistanceSpacing: 0.8
|
||||
BeatDivisor: 3
|
||||
GridSize: 4
|
||||
TimelineZoom: 1.3
|
||||
|
||||
[Metadata]
|
||||
Title:Harumachi Clover (Swing Arrangement)
|
||||
TitleUnicode:春待ちクローバー (Swing Arrangement)
|
||||
Artist:Will Stetson
|
||||
ArtistUnicode:Will Stetson
|
||||
Creator:Will Stetson
|
||||
Version:Woey's Extra
|
||||
Source:OneRoom
|
||||
Tags:花坂結衣 CV:M・A・O Hanasaka Yui M.A.O kantoku カントク one room ワンルーム mix remix yahuri mohab500 FrenZ396 Chanci -Laura- Corinn Akitoshi Denil25 Dafiely A_r_m_i_n Armin wiz khalifa Jenny Woey Mr Moseby FoxyGrandpa Foxy Grandpa fieryrage
|
||||
BeatmapID:1851299
|
||||
BeatmapSetID:859783
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:6
|
||||
CircleSize:3.8
|
||||
OverallDifficulty:9.1
|
||||
ApproachRate:9.2
|
||||
SliderMultiplier:1.80000000596047
|
||||
SliderTickRate:1
|
||||
|
||||
[Events]
|
||||
//Background and Video events
|
||||
0,0,"exbg.jpg",0,0
|
||||
//Break Periods
|
||||
//Storyboard Layer 0 (Background)
|
||||
//Storyboard Layer 1 (Fail)
|
||||
//Storyboard Layer 2 (Pass)
|
||||
//Storyboard Layer 3 (Foreground)
|
||||
//Storyboard Sound Samples
|
||||
|
||||
[TimingPoints]
|
||||
154,352.941176470588,4,2,22,47,1,0
|
||||
1919,-125,4,2,22,47,0,0
|
||||
2154,-125,4,2,22,47,0,0
|
||||
13919,-125,4,1,47,75,0,0
|
||||
14272,-90.9090909090909,4,2,47,75,0,0
|
||||
24242,-90.9090909090909,4,2,47,75,0,0
|
||||
24331,-90.9090909090909,4,2,47,75,0,0
|
||||
29625,-90.9090909090909,4,2,47,75,0,0
|
||||
29801,-90.9090909090909,4,2,47,75,0,0
|
||||
30154,-90.9090909090909,4,2,47,75,0,0
|
||||
30507,-90.9090909090909,4,2,47,75,0,0
|
||||
30860,-90.9090909090909,4,2,47,75,0,0
|
||||
31213,-90.9090909090909,4,2,47,75,0,0
|
||||
32624,-90.9090909090909,4,2,47,15,0,0
|
||||
|
||||
|
||||
[Colours]
|
||||
Combo1 : 255,43,48
|
||||
Combo2 : 239,186,239
|
||||
Combo3 : 205,218,165
|
||||
Combo4 : 152,211,231
|
||||
Combo5 : 239,226,186
|
||||
|
||||
[HitObjects]
|
||||
256,192,2154,5,2,0:0:0:0:
|
||||
312,180,2272,2,0,P|344:130|334:66,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
210,125,2625,2,0,P|178:175|188:239,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
299,303,2978,6,0,L|414:291,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
465,200,3331,2,0,L|369:209,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
323,384,3684,2,0,L|438:372,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
276,219,4037,2,0,L|180:228,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
111,293,4390,6,0,P|178:323|222:314,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
249,294,4742,1,2,0:0:0:0:
|
||||
203,109,4978,2,0,P|82:154|74:216,1,192.000000635783,2|2,0:0|0:0,0:0:0:0:
|
||||
329,209,5684,5,2,0:0:0:0:
|
||||
315,153,5801,2,0,L|290:47,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
423,181,6154,2,0,L|400:274,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
207,234,6507,2,0,L|229:140,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
378,27,6860,2,0,P|426:69|429:118,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
400,274,7213,6,0,P|352:271|314:244,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
300,188,7566,1,2,0:0:0:0:
|
||||
495,319,7801,2,0,P|414:362|291:339,1,192.000000635783,2|2,0:0|0:0,0:0:0:0:
|
||||
169,265,8507,5,2,0:0:0:0:
|
||||
218,235,8625,2,0,P|269:225|318:259,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
206,335,8978,2,0,P|155:345|106:311,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
87,276,9331,1,2,0:0:0:0:
|
||||
289,325,9566,1,2,0:0:0:0:
|
||||
289,325,9684,1,2,0:0:0:0:
|
||||
175,164,9919,6,0,P|299:144|347:167,1,144.000000476838,2|2,0:0|0:0,0:0:0:0:
|
||||
370,266,10390,2,0,P|315:224|306:172,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
312,148,10742,1,2,0:0:0:0:
|
||||
475,241,10978,1,2,0:0:0:0:
|
||||
475,241,11095,1,2,0:0:0:0:
|
||||
403,57,11331,5,2,0:0:0:0:
|
||||
403,57,11448,2,0,P|441:49|481:60,1,48.0000001589459,2|0,0:0|0:0,0:0:0:0:
|
||||
512,165,11684,1,2,0:0:0:0:
|
||||
290,249,12037,1,2,0:0:0:0:
|
||||
318,12,12389,1,2,0:0:0:0:
|
||||
58,105,12742,6,0,P|94:183|146:139,1,144.000000476838,2|2,0:0|0:0,0:0:0:0:
|
||||
206,88,13213,2,0,P|265:77|305:98,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
334,221,13565,2,0,P|294:175|293:130,1,96.0000003178917,2|2,0:0|0:0,0:0:0:0:
|
||||
398,78,13919,5,0,1:0:0:0:
|
||||
398,78,14007,1,0,1:0:0:0:
|
||||
398,78,14095,1,0,1:0:0:0:
|
||||
203,217,14272,5,2,1:0:0:0:
|
||||
449,215,14448,1,8,0:0:0:0:
|
||||
274,168,14625,5,2,1:0:0:0:
|
||||
489,292,14801,1,8,0:0:0:0:
|
||||
234,329,14978,5,2,1:0:0:0:
|
||||
449,215,15154,1,10,0:0:0:0:
|
||||
203,217,15331,5,2,1:0:0:0:
|
||||
481,373,15507,1,10,0:0:0:0:
|
||||
136,273,15684,6,0,P|117:213|143:155,1,99.0000033490662,2|10,1:0|0:0,0:0:0:0:
|
||||
280,55,16037,1,2,1:0:0:0:
|
||||
360,266,16213,2,0,P|300:285|242:259,1,99.0000033490662,10|0,0:0|1:0,0:0:0:0:
|
||||
178,85,16566,1,8,0:0:0:0:
|
||||
353,131,16742,1,0,1:0:0:0:
|
||||
233,207,16919,1,10,0:0:0:0:
|
||||
280,55,17095,5,2,1:0:0:0:
|
||||
311,199,17272,1,10,0:0:0:0:
|
||||
366,38,17448,5,2,1:0:0:0:
|
||||
381,245,17625,1,10,0:0:0:0:
|
||||
264,21,17801,5,2,1:0:0:0:
|
||||
244,266,17978,1,10,0:0:0:0:
|
||||
153,6,18154,5,2,1:0:0:0:
|
||||
96,249,18331,1,10,0:0:0:0:
|
||||
331,101,18507,5,2,1:0:0:0:
|
||||
88,78,18684,1,8,0:0:0:0:
|
||||
375,19,18860,1,2,1:0:0:0:
|
||||
132,160,19037,2,0,P|191:205|250:197,1,99.0000033490662,10|0,0:0|1:0,0:0:0:0:
|
||||
420,170,19390,1,8,0:0:0:0:
|
||||
202,291,19566,1,0,1:0:0:0:
|
||||
331,101,19742,1,10,0:0:0:0:
|
||||
291,360,19919,5,2,1:0:0:0:
|
||||
318,189,20095,1,10,0:0:0:0:
|
||||
175,357,20272,5,2,1:0:0:0:
|
||||
148,186,20448,1,10,0:0:0:0:
|
||||
353,282,20625,5,2,1:0:0:0:
|
||||
315,39,20801,1,10,0:0:0:0:
|
||||
223,282,20978,5,2,1:0:0:0:
|
||||
185,39,21154,1,10,0:0:0:0:
|
||||
401,202,21331,5,0,1:0:0:0:
|
||||
256,172,21507,1,10,0:0:0:0:
|
||||
471,141,21684,5,2,1:0:0:0:
|
||||
441,286,21860,1,10,0:0:0:0:
|
||||
326,111,22037,5,2,1:0:0:0:
|
||||
402,381,22213,1,10,0:0:0:0:
|
||||
402,54,22390,5,2,1:0:0:0:
|
||||
326,324,22566,1,10,0:0:0:0:
|
||||
268,31,22742,6,0,P|167:29|111:114,1,198.000006698132,6|0,1:2|1:0,0:0:0:0:
|
||||
213,314,23272,2,0,B|232:268|220:220|216:220|212:220|192:180|214:120,1,198.000006698132,12|8,0:0|0:0,0:0:0:0:
|
||||
321,26,23801,1,0,1:0:0:0:
|
||||
481,201,23978,2,0,P|431:253|333:171,1,198.000006698132,14|2,0:0|1:0,0:0:0:0:
|
||||
456,41,24507,1,10,0:0:0:0:
|
||||
498,300,24684,2,0,P|414:331|303:289,1,198.000006698132,14|2,0:0|1:0,0:0:0:0:
|
||||
220,82,25213,1,10,0:0:0:0:
|
||||
383,148,25390,1,10,0:0:0:0:
|
||||
251,245,25566,5,6,1:2:0:0:
|
||||
276,13,25742,1,8,0:0:0:0:
|
||||
454,192,25919,5,4,1:2:0:0:
|
||||
188,162,26095,1,8,0:0:0:0:
|
||||
395,3,26272,5,4,1:2:0:0:
|
||||
364,283,26448,1,10,0:0:0:0:
|
||||
169,75,26625,5,4,1:2:0:0:
|
||||
487,111,26801,1,10,0:0:0:0:
|
||||
277,326,26978,5,4,1:2:0:0:
|
||||
259,42,27154,1,8,0:0:0:0:
|
||||
371,361,27331,5,6,1:2:0:0:
|
||||
364,60,27507,1,10,0:0:0:0:
|
||||
254,344,27684,5,4,1:2:0:0:
|
||||
284,26,27860,1,10,0:0:0:0:
|
||||
126,363,28037,5,4,1:2:0:0:
|
||||
195,22,28213,1,10,0:0:0:0:
|
||||
179,289,28390,6,0,P|125:257|122:133,1,198.000006698132,6|0,1:2|0:0,0:0:0:0:
|
||||
398,195,28919,2,0,B|356:180|304:188|304:192|304:196|260:216|204:193,1,198.000006698132,6|0,1:2|0:0,0:0:0:0:
|
||||
47,39,29448,5,6,1:2:0:0:
|
||||
256,192,29625,12,0,32977,0:0:0:0:
|
||||
@@ -0,0 +1,391 @@
|
||||
osu file format v8
|
||||
|
||||
[General]
|
||||
AudioFilename: tutorial2.ogg
|
||||
AudioLeadIn: 0
|
||||
PreviewTime: 52620
|
||||
Countdown: 0
|
||||
SampleSet: Normal
|
||||
StackLeniency: 0.7
|
||||
Mode: 0
|
||||
LetterboxInBreaks: 0
|
||||
UseSkinSprites: 1
|
||||
|
||||
[Editor]
|
||||
Bookmarks: 42132
|
||||
DistanceSpacing: 1.39999997615814
|
||||
BeatDivisor: 3
|
||||
GridSize: 8
|
||||
|
||||
[Metadata]
|
||||
Title:osu! tutorial
|
||||
Artist:Peter Lambert
|
||||
Creator:Sushi
|
||||
Version:Advanced Gameplay
|
||||
Source:
|
||||
Tags:Agnes
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:4
|
||||
CircleSize:4
|
||||
OverallDifficulty:6
|
||||
ApproachRate:7
|
||||
SliderMultiplier:1.5
|
||||
SliderTickRate:1
|
||||
|
||||
[Events]
|
||||
//Background and Video events
|
||||
0,0,"bg.jpg"
|
||||
//Break Periods
|
||||
//Storyboard Layer 0 (Background)
|
||||
Sprite,Background,Centre,"black.png",320,240
|
||||
M,0,-2119,-124,320,240
|
||||
M,0,-124,6235,320,240
|
||||
F,0,5861,6235,1,0
|
||||
Sprite,Background,Centre,"osulogo.png",320,250
|
||||
L,1746,11
|
||||
S,1,0,374,1.1,1
|
||||
F,0,24,607,0,1
|
||||
M,1,24,1746,0,250,320,250
|
||||
R,1,24,1746,-4,0
|
||||
F,1,5861,6235,1,0
|
||||
S,0,5861,6235,1,3
|
||||
Sprite,Background,Centre,"black.png",320,240
|
||||
F,0,122709,122833,0,1
|
||||
F,0,122833,125951,1,0.984
|
||||
//Storyboard Layer 1 (Fail)
|
||||
//Storyboard Layer 2 (Pass)
|
||||
Sprite,Pass,Centre,"endpass.png",320,240
|
||||
M,0,119965,,316,155
|
||||
F,0,119965,120090,0,1
|
||||
S,1,119965,120090,0.1,1
|
||||
F,0,120090,,1
|
||||
F,0,120090,122958,1
|
||||
F,0,122958,123083,1,0
|
||||
F,0,123083,,0
|
||||
Sprite,Pass,Centre,"inorite.png",320,240
|
||||
M,0,121462,,315,356
|
||||
S,0,121462,,0.4
|
||||
F,0,121462,121836,0,1
|
||||
F,0,121836,122958,1
|
||||
F,0,122958,123332,1,0
|
||||
//Storyboard Layer 3 (Foreground)
|
||||
Sprite,Foreground,Centre,"howto3.png",320,180
|
||||
L,1746,2
|
||||
S,1,0,374,0.8,0.7
|
||||
S,1,374,747,0.8,0.7
|
||||
R,0,746,1120,-0.1,-6.38
|
||||
S,1,748,1120,0.8,0.7
|
||||
S,1,1122,1495,0.8,0.7
|
||||
L,4739,2
|
||||
S,1,0,374,0.8,0.7
|
||||
M,0,1024,1624,310,515,314,319
|
||||
F,0,1024,1624,0,1
|
||||
S,0,1024,1624,0.7
|
||||
R,0,1024,1624,0.5,-0.1
|
||||
R,0,2494,2868,-0.1,-6.38
|
||||
F,0,5487,5861,1,0
|
||||
S,0,5487,5861,0.7,3
|
||||
Sprite,Foreground,Centre,"text1.png",320,240
|
||||
M,1,6235,6609,156,73,239,73
|
||||
F,2,6235,6609,0,1
|
||||
M,0,6609,,239,73
|
||||
F,0,6609,22696,1
|
||||
F,0,22696,22821,1,0
|
||||
Sprite,Foreground,Centre,"text2.png",320,240
|
||||
M,1,8480,8854,513,115,322,115
|
||||
F,2,8480,8854,0,1
|
||||
F,0,8854,22696,1,0.9973262
|
||||
F,0,22696,22946,0.9973262,0
|
||||
Sprite,Foreground,Centre,"text3.png",320,240
|
||||
M,1,10725,11099,76,144,212,140
|
||||
F,2,10725,11099,0,1
|
||||
F,0,11099,22696,1,0.9946524
|
||||
F,0,22696,23070,0.9946524,0
|
||||
Sprite,Foreground,Centre,"text4.png",320,240
|
||||
M,1,12969,13343,537,179,374,179
|
||||
F,2,12969,13343,0,1
|
||||
F,0,13343,22696,1,0.9946524
|
||||
F,0,22696,23195,0.9946524,0
|
||||
Sprite,Foreground,Centre,"text5.png",320,240
|
||||
M,1,15214,15588,79,217,282,211
|
||||
F,2,15214,15588,0,1
|
||||
F,0,15588,22696,1
|
||||
F,0,22696,23320,1,0
|
||||
Sprite,Foreground,Centre,"text6.png",320,240
|
||||
M,1,17459,17833,545,249,417,249
|
||||
F,2,17459,17833,0,1
|
||||
F,0,17833,22696,1
|
||||
F,0,22696,23444,1,0
|
||||
Sprite,Foreground,Centre,"text7.png",320,240
|
||||
M,1,18955,19329,-20,286,62,286
|
||||
F,2,18955,19329,0,1
|
||||
M,0,19329,,62,286
|
||||
F,0,19329,22696,1,0.9629171
|
||||
F,0,22696,23569,0.9629171,0
|
||||
Sprite,Foreground,Centre,"text8.png",320,240
|
||||
M,1,20452,20826,560,358,321,358
|
||||
F,2,20452,20826,0,1
|
||||
F,0,20826,22696,1
|
||||
F,0,22696,23694,1,0
|
||||
Sprite,Foreground,Centre,"osulogo.png",320,240
|
||||
S,0,122335,122709,3.032
|
||||
F,0,122335,122833,0,1
|
||||
S,0,122709,123582,3.032,0.968
|
||||
F,0,122833,,1
|
||||
F,0,122833,124455,1
|
||||
F,0,124455,124829,1,0
|
||||
//Storyboard Sound Samples
|
||||
//Background Colour Transformations
|
||||
3,100,128,128,255
|
||||
|
||||
[TimingPoints]
|
||||
250,374.111485222596,4,1,0,58,1,0
|
||||
29377,-100,4,2,0,69,0,0
|
||||
29939,-100,4,1,0,58,0,0
|
||||
35363,-100,4,2,0,69,0,0
|
||||
35971,-100,4,2,0,100,0,0
|
||||
40461,-100,4,1,0,58,0,0
|
||||
41349,-100,4,2,0,69,0,0
|
||||
41910,-100,4,2,0,100,0,0
|
||||
46518,-100,4,1,0,58,0,0
|
||||
47335,-100,4,2,0,69,0,0
|
||||
47943,-100,4,2,0,100,0,0
|
||||
52451,-100,4,1,0,58,0,0
|
||||
53321,-100,4,2,0,69,0,0
|
||||
53929,-100,4,2,0,100,0,0
|
||||
58980,-100,4,1,0,74,0,0
|
||||
96018,-100,4,1,0,74,0,1
|
||||
119588,-100,4,1,0,74,0,0
|
||||
|
||||
[Colours]
|
||||
Combo1 : 0,255,0
|
||||
Combo2 : 255,0,0
|
||||
Combo3 : 0,0,255
|
||||
Combo4 : 255,255,0
|
||||
|
||||
[HitObjects]
|
||||
256,328,24193,2,0,C|424:328,2,150
|
||||
256,192,25315,1,8
|
||||
256,56,25689,6,0,C|96:56,2,150
|
||||
256,192,26811,1,8
|
||||
136,128,27186,5,0
|
||||
16,192,27560,1,0
|
||||
136,256,27934,1,0
|
||||
256,192,28308,1,8
|
||||
256,328,28682,6,0,C|304:328,3,50
|
||||
440,328,29430,1,4
|
||||
440,328,29804,1,4
|
||||
440,192,30178,6,0,C|480:152,3,50
|
||||
376,64,30927,1,0
|
||||
376,64,31301,1,8
|
||||
312,192,31675,6,0,C|264:144,3,50
|
||||
376,64,32423,1,0
|
||||
376,64,32797,1,8
|
||||
376,200,33171,5,0
|
||||
376,336,33545,1,0
|
||||
376,200,33920,1,0
|
||||
376,336,34294,1,8
|
||||
240,336,34668,6,0,C|176:336,3,50
|
||||
256,192,35416,5,4
|
||||
256,192,35790,1,4
|
||||
144,112,36164,6,0,C|72:144|96:240,2,150,0|2|0
|
||||
256,192,37287,1,2
|
||||
368,272,37661,6,0,C|440:240|416:144,2,150,0|2|0
|
||||
256,192,38783,1,2
|
||||
192,72,39157,6,0,C|128:232,1,150,0|2
|
||||
320,72,39905,2,0,C|384:232,1,150,0|2
|
||||
256,280,40654,6,0,C|256:352,3,50
|
||||
256,192,41402,1,0
|
||||
256,192,41776,1,4
|
||||
392,192,42150,5,0
|
||||
392,192,42275,1,0
|
||||
392,192,42399,1,0
|
||||
392,192,42524,2,0,B|392:288|312:304,2,150,2|0|2
|
||||
392,56,43646,5,0
|
||||
392,56,43771,1,0
|
||||
392,56,43896,1,0
|
||||
392,56,44021,2,0,B|296:56|280:136,2,150,2|0|2
|
||||
344,184,45143,5,0
|
||||
344,184,45268,1,0
|
||||
344,184,45392,1,0
|
||||
344,184,45517,2,0,B|280:256|216:184,2,150,2|0|2
|
||||
344,320,46639,6,0,B|192:320,1,150
|
||||
194,320,47138,1,0
|
||||
194,320,47263,1,0
|
||||
194,320,47388,1,0
|
||||
56,320,47762,1,4
|
||||
56,184,48136,6,0,B|56:96|152:96,1,150,0|2
|
||||
256,192,48884,1,2
|
||||
256,192,49009,1,0
|
||||
256,192,49133,1,0
|
||||
256,192,49258,1,2
|
||||
456,200,49632,6,0,B|456:288|360:288,1,150,0|2
|
||||
256,192,50380,1,2
|
||||
256,192,50505,1,0
|
||||
256,192,50630,1,0
|
||||
256,192,50755,1,2
|
||||
184,72,51129,5,0
|
||||
328,72,51503,1,2
|
||||
184,72,51877,1,0
|
||||
328,72,52251,1,2
|
||||
256,192,52625,6,0,B|256:352,1,150
|
||||
256,342,53124,1,0
|
||||
256,342,53249,1,0
|
||||
256,342,53373,1,0
|
||||
392,344,53747,1,4
|
||||
392,208,54122,6,0,B|456:208,3,50,0|0|0|2
|
||||
392,80,54870,1,2
|
||||
392,80,54994,1,0
|
||||
392,80,55119,1,0
|
||||
392,80,55244,1,2
|
||||
256,80,55618,6,0,B|256:176|152:176,1,150,0|2
|
||||
64,80,56366,1,2
|
||||
64,128,56491,1,0
|
||||
64,176,56616,1,0
|
||||
64,224,56740,1,2
|
||||
184,280,57114,6,0,B|184:344,3,50,0|0|0|2
|
||||
328,280,57863,2,0,B|328:344,3,50,2|0|0|2
|
||||
256,192,58611,12,4,59733
|
||||
256,48,60107,6,0,B|352:48|352:136,2,150,4|8|0
|
||||
200,80,61043,1,8
|
||||
160,136,61230,1,2
|
||||
144,200,61417,1,10
|
||||
144,264,61604,6,0,B|152:360|264:320,2,150,0|8|0
|
||||
144,200,62539,1,0
|
||||
208,176,62726,1,10
|
||||
272,192,62913,1,0
|
||||
328,224,63100,6,0,B|432:256|416:152,2,150,4|8|0
|
||||
272,192,64036,1,8
|
||||
256,128,64223,1,2
|
||||
296,72,64410,1,10
|
||||
256,192,64597,12,8,65719
|
||||
256,72,66093,5,4
|
||||
192,72,66280,1,0
|
||||
128,72,66467,2,0,B|56:104|72:208,2,150,8|0|10
|
||||
192,72,67403,1,2
|
||||
240,120,67590,5,0
|
||||
192,168,67777,2,0,B|160:248,2,75,8|0|0
|
||||
248,200,68338,2,0,B|264:280,3,75,0|0|10|0
|
||||
320,240,69086,6,0,B|472:240,1,150,4|8
|
||||
470,173,69647,1,0
|
||||
470,105,69834,2,8,B|320:105,1,150,0|10
|
||||
256,192,70582,12,8,71705
|
||||
256,64,72079,6,0,B|416:64,1,150,4|8
|
||||
288,128,72827,1,0
|
||||
352,128,73014,1,0
|
||||
416,128,73201,1,10
|
||||
320,224,73575,6,0,B|152:224,1,150,0|8
|
||||
288,288,74324,1,0
|
||||
224,288,74511,1,0
|
||||
160,288,74698,1,10
|
||||
64,192,75072,6,0,B|64:88|152:88,1,150,0|8
|
||||
216,88,75633,1,0
|
||||
280,88,75820,1,0
|
||||
280,152,76007,1,0
|
||||
216,152,76194,1,10
|
||||
256,192,76568,12,8,77691
|
||||
256,48,78065,6,0,B|344:72,1,75
|
||||
400,32,78439,2,0,B|424:120,1,75,8|0
|
||||
464,168,78813,2,0,B|408:256,1,75
|
||||
352,224,79187,1,10
|
||||
256,48,79561,6,0,B|168:72,1,75
|
||||
112,32,79935,2,0,B|88:120,1,75,8|0
|
||||
48,168,80309,2,0,B|104:256,1,75
|
||||
160,224,80683,1,10
|
||||
208,368,81058,6,0,B|176:280,1,75
|
||||
256,197,81432,2,0,B|256:272,1,75,8|0
|
||||
304,368,81806,2,0,B|336:280,1,75,0|0
|
||||
352,224,82180,1,10
|
||||
256,192,82554,12,4,83676
|
||||
256,48,84050,5,8
|
||||
256,48,84175,1,0
|
||||
256,48,84300,1,0
|
||||
256,48,84425,2,2,B|160:48|160:136,1,150,2|8
|
||||
40,48,85173,1,2
|
||||
32,200,85547,5,8
|
||||
32,200,85672,1,0
|
||||
32,200,85796,1,0
|
||||
32,200,85921,2,2,B|32:296|120:296,2,150,2|8|8
|
||||
256,336,87043,5,8
|
||||
256,336,87168,1,0
|
||||
256,336,87293,1,0
|
||||
256,336,87417,2,2,B|352:336|352:248,1,150,2|8
|
||||
472,336,88166,1,2
|
||||
480,184,88540,5,8
|
||||
480,184,88665,1,0
|
||||
480,184,88789,1,0
|
||||
480,184,88914,2,2,B|480:88|392:88,2,150,2|8|2
|
||||
256,64,90036,6,0,B|256:184,1,100,8|0
|
||||
256,216,90410,1,2
|
||||
184,352,90784,1,8
|
||||
232,352,90909,1,0
|
||||
280,352,91034,1,0
|
||||
328,352,91159,1,2
|
||||
320,200,91533,6,0,B|384:200|384:136,1,100,8|0
|
||||
384,88,91907,1,2
|
||||
472,208,92281,1,8
|
||||
448,256,92406,1,0
|
||||
424,304,92530,1,0
|
||||
400,352,92655,2,2,B|296:352,1,100,2|2
|
||||
256,192,93029,12,0,95648
|
||||
376,96,96022,6,0,B|472:96|472:192,2,150,8|8|0
|
||||
328,96,96895,1,0
|
||||
280,96,97020,1,0
|
||||
232,96,97144,1,8
|
||||
168,232,97518,6,0,B|72:232|72:136,2,150,8|8|0
|
||||
216,232,98391,1,0
|
||||
264,232,98516,1,0
|
||||
312,232,98641,1,8
|
||||
256,96,99015,6,0,B|416:96,2,150,8|8|0
|
||||
256,96,99888,1,0
|
||||
256,96,100013,1,0
|
||||
256,96,100137,1,8
|
||||
256,248,100511,6,0,B|256:304,2,50
|
||||
256,200,100885,2,0,B|256:136,3,50,8|0|0|4
|
||||
106,150,101634,1,4
|
||||
96,288,102008,6,0,B|80:344,2,50,8|0|0
|
||||
104,240,102382,1,2
|
||||
216,320,102756,1,0
|
||||
216,184,103130,1,10
|
||||
336,128,103504,6,0,B|352:72,2,50,8|0|0
|
||||
328,176,103878,1,2
|
||||
216,96,104252,1,0
|
||||
216,232,104627,1,10
|
||||
328,304,105001,5,8
|
||||
328,304,105125,1,0
|
||||
328,304,105250,1,0
|
||||
328,304,105375,2,0,B|416:304|416:208,1,150,2|0
|
||||
416,72,106123,1,10
|
||||
256,192,106497,12,4,107619
|
||||
256,56,107994,6,0,B|144:56,1,100
|
||||
96,56,108368,2,0,B|40:152,1,100,10|2
|
||||
40,200,108742,2,0,B|40:272,3,50,0|0|0|10
|
||||
200,336,109490,6,0,B|296:256,1,100
|
||||
312,320,109864,2,2,B|408:240,1,100,10|2
|
||||
424,304,110238,2,0,B|464:272,3,50,0|0|0|10
|
||||
424,96,110986,6,0,B|376:64|328:96,1,100
|
||||
328,152,111361,2,2,B|280:184|232:152,1,100,10|2
|
||||
224,96,111735,2,0,B|200:80|176:96,3,50,0|0|0|10
|
||||
256,256,112483,5,0
|
||||
256,256,112608,1,0
|
||||
256,256,112732,1,0
|
||||
256,256,112857,1,8
|
||||
256,256,112982,1,0
|
||||
256,256,113106,1,0
|
||||
256,256,113231,2,0,B|208:312|320:360|328:296,1,150,4|4
|
||||
120,208,113979,6,0,B|56:208,3,50,8|0|0|2
|
||||
120,80,114728,1,8
|
||||
120,80,114852,1,0
|
||||
120,80,114977,1,0
|
||||
120,80,115102,1,2
|
||||
256,80,115476,6,0,B|256:176|360:176,1,150,8|2
|
||||
448,80,116224,1,8
|
||||
448,128,116349,1,0
|
||||
448,176,116473,1,0
|
||||
448,224,116598,1,2
|
||||
256,192,116972,12,8,118095
|
||||
184,304,118469,6,0,B|296:304,1,100,0|8
|
||||
320,248,118843,2,0,B|208:248,1,100,0|8
|
||||
184,192,119217,1,4
|
||||
184,192,119591,1,4
|
||||
Reference in New Issue
Block a user