big first commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "taiko"
|
||||
version = "0.1.0"
|
||||
authors = ["MaxOhn <ohn.m@hotmail.de>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies.parse]
|
||||
path = "../parse"
|
||||
|
||||
[dependencies.lazy_static]
|
||||
version = "1.4"
|
||||
@@ -0,0 +1,34 @@
|
||||
use super::{closest_rhythm, HitObjectRhythm};
|
||||
|
||||
use parse::HitObject;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct DifficultyObject<'o> {
|
||||
pub(crate) idx: usize,
|
||||
pub(crate) base: &'o HitObject,
|
||||
pub(crate) prev: &'o HitObject,
|
||||
pub(crate) delta: f32,
|
||||
pub(crate) rhythm: &'static HitObjectRhythm,
|
||||
}
|
||||
|
||||
impl<'o> DifficultyObject<'o> {
|
||||
#[inline]
|
||||
pub(crate) fn new(
|
||||
idx: usize,
|
||||
base: &'o HitObject,
|
||||
prev: &'o HitObject,
|
||||
prev_prev: &HitObject,
|
||||
clock_rate: f32,
|
||||
) -> Self {
|
||||
let delta = (base.start_time - prev.start_time) / clock_rate;
|
||||
let rhythm = closest_rhythm(delta, prev, prev_prev, clock_rate);
|
||||
|
||||
Self {
|
||||
idx,
|
||||
base,
|
||||
prev,
|
||||
delta,
|
||||
rhythm,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
use parse::HitObject;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
/// lazy_static required for `f32` division's
|
||||
/// lack of const-ness as of now.
|
||||
static ref COMMON_RHYTHMS: Vec<HitObjectRhythm> = vec![
|
||||
HitObjectRhythm::new(1.0, 1.0, 0.0),
|
||||
HitObjectRhythm::new(2.0, 1.0, 0.3),
|
||||
HitObjectRhythm::new(1.0, 2.0, 0.5),
|
||||
HitObjectRhythm::new(3.0, 1.0, 0.3),
|
||||
HitObjectRhythm::new(1.0, 3.0, 0.35),
|
||||
HitObjectRhythm::new(3.0, 2.0, 0.6),
|
||||
HitObjectRhythm::new(2.0, 3.0, 0.4),
|
||||
HitObjectRhythm::new(5.0, 4.0, 0.5),
|
||||
HitObjectRhythm::new(4.0, 5.0, 0.7),
|
||||
];
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct HitObjectRhythm {
|
||||
ratio: f32,
|
||||
pub(crate) difficulty: f32,
|
||||
}
|
||||
|
||||
impl PartialEq for HitObjectRhythm {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
(self.ratio - other.ratio).abs() < f32::EPSILON
|
||||
&& (self.difficulty - other.difficulty).abs() < f32::EPSILON
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for HitObjectRhythm {}
|
||||
|
||||
impl HitObjectRhythm {
|
||||
#[inline]
|
||||
fn new(numerator: f32, denominator: f32, difficulty: f32) -> Self {
|
||||
Self {
|
||||
ratio: numerator / denominator,
|
||||
difficulty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn closest_rhythm(
|
||||
delta_time: f32,
|
||||
last: &HitObject,
|
||||
last_last: &HitObject,
|
||||
clock_rate: f32,
|
||||
) -> &'static HitObjectRhythm {
|
||||
let prev_len = (last.start_time - last_last.start_time) / clock_rate;
|
||||
let ratio = delta_time / prev_len;
|
||||
|
||||
COMMON_RHYTHMS
|
||||
.iter()
|
||||
.min_by(|r1, r2| {
|
||||
(r1.ratio - ratio)
|
||||
.abs()
|
||||
.partial_cmp(&(r2.ratio - ratio).abs())
|
||||
.unwrap_or(Ordering::Equal)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
mod difficulty_object;
|
||||
mod hitobject_rhythm;
|
||||
mod limited_queue;
|
||||
mod rim;
|
||||
mod skill;
|
||||
mod skill_kind;
|
||||
mod stamina_cheese;
|
||||
|
||||
use difficulty_object::DifficultyObject;
|
||||
use hitobject_rhythm::{closest_rhythm, HitObjectRhythm};
|
||||
use limited_queue::LimitedQueue;
|
||||
use rim::Rim;
|
||||
use skill::Skill;
|
||||
use skill_kind::SkillKind;
|
||||
use stamina_cheese::StaminaCheeseDetector;
|
||||
|
||||
use parse::{Beatmap, Mods};
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::f32::consts::PI;
|
||||
|
||||
const SECTION_LEN: f32 = 400.0;
|
||||
|
||||
const COLOR_SKILL_MULTIPLIER: f32 = 0.01;
|
||||
const RHYTHM_SKILL_MULTIPLIER: f32 = 0.014;
|
||||
const STAMINA_SKILL_MULTIPLIER: f32 = 0.02;
|
||||
|
||||
/// Star calculation for osu!taiko maps
|
||||
pub fn stars(map: &Beatmap, mods: impl Mods) -> f32 {
|
||||
if map.hit_objects.len() < 2 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// True if the object at that index is stamina cheese
|
||||
let cheese = map.find_cheese();
|
||||
|
||||
let mut skills = vec![
|
||||
Skill::new(SkillKind::color()),
|
||||
Skill::new(SkillKind::rhythm()),
|
||||
Skill::new(SkillKind::stamina(true)),
|
||||
Skill::new(SkillKind::stamina(false)),
|
||||
];
|
||||
|
||||
let clock_rate = mods.speed();
|
||||
let section_len = SECTION_LEN * clock_rate;
|
||||
|
||||
// No strain for first object
|
||||
let mut current_section_end =
|
||||
(map.hit_objects[0].start_time / section_len).ceil() * section_len;
|
||||
|
||||
let hit_objects = map
|
||||
.hit_objects
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(2)
|
||||
.zip(map.hit_objects.iter().skip(1))
|
||||
.zip(map.hit_objects.iter())
|
||||
.map(|(((idx, base), prev), prev_prev)| {
|
||||
DifficultyObject::new(idx, base, prev, prev_prev, clock_rate)
|
||||
});
|
||||
|
||||
for h in hit_objects {
|
||||
while h.base.start_time > current_section_end {
|
||||
for skill in skills.iter_mut() {
|
||||
skill.save_current_peak();
|
||||
skill.start_new_section_from(current_section_end);
|
||||
}
|
||||
|
||||
current_section_end += section_len;
|
||||
}
|
||||
|
||||
for skill in skills.iter_mut().take(3) {
|
||||
skill.process(h.clone(), &cheese);
|
||||
}
|
||||
|
||||
skills[3].process(h, &cheese);
|
||||
}
|
||||
|
||||
for skill in skills.iter_mut() {
|
||||
skill.save_current_peak();
|
||||
}
|
||||
|
||||
let mut buf = vec![0.0; skills[0].strain_peaks.len()];
|
||||
|
||||
let color_rating = skills[0].difficulty_value(&mut buf) * COLOR_SKILL_MULTIPLIER;
|
||||
let rhythm_rating = skills[1].difficulty_value(&mut buf) * RHYTHM_SKILL_MULTIPLIER;
|
||||
|
||||
let mut stamina_rating = (skills[2].difficulty_value(&mut buf)
|
||||
+ skills[3].difficulty_value(&mut buf))
|
||||
* STAMINA_SKILL_MULTIPLIER;
|
||||
|
||||
let stamina_penalty = simple_color_penalty(stamina_rating, color_rating);
|
||||
stamina_rating *= stamina_penalty;
|
||||
|
||||
let combined_rating = locally_combined_difficulty(&skills, stamina_penalty);
|
||||
let separate_rating = norm(1.5, color_rating, rhythm_rating, stamina_rating);
|
||||
|
||||
rescale(1.4 * separate_rating + 0.5 * combined_rating)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn rescale(stars: f32) -> f32 {
|
||||
if stars < 0.0 {
|
||||
stars
|
||||
} else {
|
||||
10.43 * (stars / 8.0 + 1.0).ln()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn simple_color_penalty(stamina: f32, color: f32) -> f32 {
|
||||
if color <= 0.0 {
|
||||
0.79 - 0.25
|
||||
} else {
|
||||
0.79 - (stamina / color - 12.0).atan() / PI / 2.0
|
||||
}
|
||||
}
|
||||
|
||||
fn locally_combined_difficulty(skills: &[Skill], stamina_penalty: f32) -> f32 {
|
||||
let mut peaks = Vec::with_capacity(skills[0].strain_peaks.len());
|
||||
|
||||
let iter = skills[0]
|
||||
.strain_peaks
|
||||
.iter()
|
||||
.zip(skills[1].strain_peaks.iter())
|
||||
.zip(skills[2].strain_peaks.iter())
|
||||
.zip(skills[3].strain_peaks.iter())
|
||||
.map(|(((&color, &rhythm), &stamina_right), &stamina_left)| {
|
||||
norm(
|
||||
2.0,
|
||||
color * COLOR_SKILL_MULTIPLIER,
|
||||
rhythm * RHYTHM_SKILL_MULTIPLIER,
|
||||
(stamina_right + stamina_left) * STAMINA_SKILL_MULTIPLIER * stamina_penalty,
|
||||
)
|
||||
});
|
||||
|
||||
peaks.extend(iter);
|
||||
peaks.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
||||
|
||||
let mut difficulty = 0.0;
|
||||
let mut weight = 1.0;
|
||||
|
||||
for strain in peaks {
|
||||
difficulty += strain * weight;
|
||||
weight *= 0.9;
|
||||
}
|
||||
|
||||
difficulty
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn norm(p: f32, a: f32, b: f32, c: f32) -> f32 {
|
||||
(a.powf(p) + b.powf(p) + c.powf(p)).powf(p.recip())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::File;
|
||||
|
||||
#[test]
|
||||
fn test_single() {
|
||||
let file = match File::open("E:/Games/osu!/beatmaps/1097541.osu") {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file: {}", why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map: {}", why),
|
||||
};
|
||||
|
||||
let stars = stars(&map, 16);
|
||||
|
||||
println!("Stars: {}", stars);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_taiko() {
|
||||
let margin = 0.005;
|
||||
|
||||
#[rustfmt::skip]
|
||||
let data = vec![
|
||||
(110219, 1 << 8, 4.090461690284154), // HT
|
||||
(110219, 0, 5.137432251440863), // NM
|
||||
(110219, 1 << 6, 6.785308286298745), // DT
|
||||
|
||||
(168450, 1 << 8, 3.9102755155437663), // HT
|
||||
(168450, 0, 4.740171803038067), // NM
|
||||
(168450, 1 << 6, 5.894260068145283), // DT
|
||||
|
||||
(1097541, 1 << 8, 4.0027499635116595),// HT
|
||||
(1097541, 0, 4.891409786886079), // NM
|
||||
(1097541, 1 << 6, 6.587467490088248), // DT
|
||||
|
||||
(1432878, 1 << 8, 3.5850143199594258),// HT
|
||||
(1432878, 0, 4.416206873466799), // NM
|
||||
(1432878, 1 << 6, 5.908970879987477), // DT
|
||||
];
|
||||
|
||||
for (map_id, mods, expected_stars) in data {
|
||||
let file = match File::open(format!("./test/{}.osu", map_id)) {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
||||
};
|
||||
|
||||
let stars = stars(&map, mods);
|
||||
|
||||
assert!(
|
||||
(stars - expected_stars).abs() < margin,
|
||||
"Stars: {} | Expected: {} => {} margin [map {} | mods {}]",
|
||||
stars,
|
||||
expected_stars,
|
||||
(stars - expected_stars).abs(),
|
||||
map_id,
|
||||
mods
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::ops::Index;
|
||||
|
||||
pub(crate) struct LimitedQueue<T> {
|
||||
queue: Vec<T>,
|
||||
start: usize,
|
||||
end: usize,
|
||||
}
|
||||
|
||||
impl<T> LimitedQueue<T> {
|
||||
/// Panics if `capacity` is zero.
|
||||
#[inline]
|
||||
pub(crate) fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
end: capacity - 1,
|
||||
start: 0,
|
||||
queue: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn push(&mut self, elem: T) {
|
||||
let capacity = self.queue.capacity();
|
||||
self.end = (self.end + 1) % capacity;
|
||||
|
||||
if self.queue.len() == capacity {
|
||||
self.start = (self.start + 1) % capacity;
|
||||
self.queue[self.end as usize] = elem;
|
||||
} else {
|
||||
self.queue.push(elem);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.queue.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn last(&self) -> Option<&T> {
|
||||
self.queue.get(self.end as usize)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn clear(&mut self) {
|
||||
self.start = 0;
|
||||
self.end = self.queue.capacity() - 1;
|
||||
self.queue.clear();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn full(&self) -> bool {
|
||||
self.queue.len() == self.queue.capacity()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialOrd> LimitedQueue<T> {
|
||||
pub(crate) fn min(&self) -> Option<&T> {
|
||||
let mut iter = self.queue.iter();
|
||||
|
||||
if let Some(first) = iter.next() {
|
||||
let min = iter.fold(first, |min, next| match min.partial_cmp(next) {
|
||||
Some(Ordering::Less) => min,
|
||||
Some(Ordering::Equal) => min,
|
||||
Some(Ordering::Greater) => next,
|
||||
None => min,
|
||||
});
|
||||
|
||||
Some(min)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Index<usize> for LimitedQueue<T> {
|
||||
type Output = T;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, idx: usize) -> &Self::Output {
|
||||
&self.queue[(self.start + idx) % self.queue.capacity()]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use parse::{HitObject, HitSound};
|
||||
|
||||
pub(crate) trait Rim {
|
||||
fn is_rim(&self) -> bool;
|
||||
}
|
||||
|
||||
impl Rim for HitObject {
|
||||
#[inline]
|
||||
fn is_rim(&self) -> bool {
|
||||
self.sound.clap() || self.sound.whistle()
|
||||
}
|
||||
}
|
||||
|
||||
impl Rim for u8 {
|
||||
#[inline]
|
||||
fn is_rim(&self) -> bool {
|
||||
self.clap() || self.whistle()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
use super::{DifficultyObject, SkillKind};
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
const DECAY_WEIGHT: f32 = 0.9;
|
||||
|
||||
pub(crate) struct Skill<'o> {
|
||||
pub current_strain: f32,
|
||||
current_section_peak: f32,
|
||||
|
||||
kind: SkillKind<'o>,
|
||||
pub(crate) strain_peaks: Vec<f32>,
|
||||
|
||||
prev_time: Option<f32>,
|
||||
}
|
||||
|
||||
impl<'o> Skill<'o> {
|
||||
#[inline]
|
||||
pub(crate) fn new(kind: SkillKind<'o>) -> Self {
|
||||
Self {
|
||||
current_strain: 1.0,
|
||||
current_section_peak: 1.0,
|
||||
|
||||
kind,
|
||||
strain_peaks: Vec::with_capacity(128),
|
||||
|
||||
prev_time: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn save_current_peak(&mut self) {
|
||||
if self.prev_time.is_some() {
|
||||
self.strain_peaks.push(self.current_section_peak);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn start_new_section_from(&mut self, time: f32) {
|
||||
if let Some(prev) = self.prev_time {
|
||||
self.current_section_peak = self.peak_strain(time - prev);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn process(&mut self, current: DifficultyObject<'o>, cheese: &[bool]) {
|
||||
self.current_strain *= self.strain_decay(current.delta);
|
||||
self.current_strain +=
|
||||
self.kind.strain_value_of(¤t, cheese) * self.skill_multiplier();
|
||||
self.current_section_peak = self.current_section_peak.max(self.current_strain);
|
||||
self.prev_time.replace(current.base.start_time);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn difficulty_value(&self, buf: &mut [f32]) -> f32 {
|
||||
let mut difficulty = 0.0;
|
||||
let mut weight = 1.0;
|
||||
|
||||
buf.copy_from_slice(&self.strain_peaks);
|
||||
buf.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
||||
|
||||
for &strain in buf.iter() {
|
||||
difficulty += strain * weight;
|
||||
weight *= DECAY_WEIGHT;
|
||||
}
|
||||
|
||||
difficulty
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn skill_multiplier(&self) -> f32 {
|
||||
match self.kind {
|
||||
SkillKind::Color { .. } => 1.0,
|
||||
SkillKind::Rhythm { .. } => 10.0,
|
||||
SkillKind::Stamina { .. } => 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_decay_base(&self) -> f32 {
|
||||
match self.kind {
|
||||
SkillKind::Color { .. } => 0.4,
|
||||
SkillKind::Rhythm { .. } => 0.0,
|
||||
SkillKind::Stamina { .. } => 0.4,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn peak_strain(&self, delta_time: f32) -> f32 {
|
||||
self.current_strain * self.strain_decay(delta_time)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_decay(&self, ms: f32) -> f32 {
|
||||
self.strain_decay_base().powf(ms / 1000.0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
use super::{DifficultyObject, LimitedQueue, Rim};
|
||||
|
||||
const RHYTHM_STRAIN_DECAY: f32 = 0.96;
|
||||
const MOST_RECENT_PATTERNS_TO_COMPARE: usize = 2;
|
||||
|
||||
const MONO_HISTORY_MAX_LEN: usize = 5;
|
||||
const RHYTHM_HISTORY_MAX_LEN: usize = 8;
|
||||
const STAMINA_HISTORY_MAX_LEN: usize = 2;
|
||||
|
||||
pub(crate) enum SkillKind<'o> {
|
||||
Color {
|
||||
mono_history: LimitedQueue<usize>,
|
||||
prev_is_rim: Option<bool>,
|
||||
current_mono_len: usize,
|
||||
},
|
||||
Rhythm {
|
||||
rhythm_history: LimitedQueue<DifficultyObject<'o>>,
|
||||
notes_since_rhythm_change: usize,
|
||||
current_strain: f32,
|
||||
},
|
||||
Stamina {
|
||||
note_pair_duration_history: LimitedQueue<f32>,
|
||||
hand: u8,
|
||||
off_hand_object_duration: f32,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'o> SkillKind<'o> {
|
||||
#[inline]
|
||||
pub(crate) fn color() -> Self {
|
||||
Self::Color {
|
||||
mono_history: LimitedQueue::new(MONO_HISTORY_MAX_LEN),
|
||||
prev_is_rim: None,
|
||||
current_mono_len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn rhythm() -> Self {
|
||||
Self::Rhythm {
|
||||
rhythm_history: LimitedQueue::new(RHYTHM_HISTORY_MAX_LEN),
|
||||
notes_since_rhythm_change: 0,
|
||||
current_strain: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn stamina(right_hand: bool) -> Self {
|
||||
Self::Stamina {
|
||||
note_pair_duration_history: LimitedQueue::new(STAMINA_HISTORY_MAX_LEN),
|
||||
hand: right_hand as u8,
|
||||
off_hand_object_duration: f32::MAX,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn strain_value_of(
|
||||
&mut self,
|
||||
current: &DifficultyObject<'o>,
|
||||
cheese: &[bool],
|
||||
) -> f32 {
|
||||
match self {
|
||||
Self::Color {
|
||||
mono_history,
|
||||
prev_is_rim,
|
||||
current_mono_len,
|
||||
} => {
|
||||
let prev_is_circle = current.prev.is_circle();
|
||||
let base_is_circle = current.base.is_circle();
|
||||
let curr_is_rim = current.base.is_rim();
|
||||
|
||||
if !(current.delta < 1000.0 && prev_is_circle && base_is_circle) {
|
||||
mono_history.clear();
|
||||
*current_mono_len = base_is_circle as usize;
|
||||
|
||||
*prev_is_rim = if base_is_circle {
|
||||
Some(curr_is_rim)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let mut strain = 0.0;
|
||||
|
||||
if prev_is_rim
|
||||
.filter(|&is_rim| is_rim != curr_is_rim)
|
||||
.is_some()
|
||||
{
|
||||
strain = if mono_history.len() < 2
|
||||
|| (*mono_history.last().unwrap() + *current_mono_len) % 2 == 0
|
||||
{
|
||||
0.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
let mut reps_penalty = 1.0;
|
||||
|
||||
mono_history.push(*current_mono_len);
|
||||
|
||||
let iter = (0..mono_history
|
||||
.len()
|
||||
.saturating_sub(MOST_RECENT_PATTERNS_TO_COMPARE))
|
||||
.rev();
|
||||
|
||||
for start in iter {
|
||||
let different_pattern = (0..MOST_RECENT_PATTERNS_TO_COMPARE).any(|i| {
|
||||
mono_history[start + i]
|
||||
!= mono_history
|
||||
[mono_history.len() + i - MOST_RECENT_PATTERNS_TO_COMPARE]
|
||||
});
|
||||
|
||||
if different_pattern {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut notes_since = 0;
|
||||
|
||||
for i in start..mono_history.len() {
|
||||
notes_since += mono_history[i];
|
||||
}
|
||||
|
||||
reps_penalty *= repetition_penalty(notes_since);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
strain *= reps_penalty;
|
||||
|
||||
*current_mono_len = 1;
|
||||
} else {
|
||||
*current_mono_len += 1;
|
||||
}
|
||||
|
||||
*prev_is_rim = Some(curr_is_rim);
|
||||
|
||||
strain
|
||||
}
|
||||
Self::Rhythm {
|
||||
rhythm_history,
|
||||
notes_since_rhythm_change,
|
||||
current_strain,
|
||||
} => {
|
||||
let base_is_circle = current.base.is_circle();
|
||||
|
||||
if !base_is_circle {
|
||||
*current_strain = 0.0;
|
||||
*notes_since_rhythm_change = 0;
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
*current_strain *= RHYTHM_STRAIN_DECAY;
|
||||
*notes_since_rhythm_change += 1;
|
||||
|
||||
if current.rhythm.difficulty.abs() < f32::EPSILON {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let mut strain = current.rhythm.difficulty;
|
||||
|
||||
rhythm_history.push(current.to_owned());
|
||||
|
||||
let mut reps_penalty = 1.0;
|
||||
|
||||
for most_recent_patterns_to_compare in 2..=RHYTHM_HISTORY_MAX_LEN / 2 {
|
||||
let iter = (0..rhythm_history
|
||||
.len()
|
||||
.saturating_sub(most_recent_patterns_to_compare))
|
||||
.rev();
|
||||
|
||||
for start in iter {
|
||||
let different_pattern = (0..most_recent_patterns_to_compare).any(|i| {
|
||||
rhythm_history[start + i].rhythm
|
||||
!= rhythm_history
|
||||
[rhythm_history.len() + i - most_recent_patterns_to_compare]
|
||||
.rhythm
|
||||
});
|
||||
|
||||
if different_pattern {
|
||||
continue;
|
||||
}
|
||||
|
||||
reps_penalty *= repetition_penalty(current.idx - rhythm_history[start].idx);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let speed_penalty = if current.delta < 80.0 {
|
||||
1.0
|
||||
} else if current.delta < 210.0 {
|
||||
(1.4 - 0.005 * current.delta).max(0.0)
|
||||
} else {
|
||||
*current_strain = 0.0;
|
||||
*notes_since_rhythm_change = 0;
|
||||
|
||||
0.0
|
||||
};
|
||||
|
||||
strain *= reps_penalty;
|
||||
strain *= pattern_len_penalty(*notes_since_rhythm_change);
|
||||
strain *= speed_penalty;
|
||||
|
||||
*notes_since_rhythm_change = 0;
|
||||
*current_strain += strain;
|
||||
|
||||
*current_strain
|
||||
}
|
||||
Self::Stamina {
|
||||
hand,
|
||||
note_pair_duration_history,
|
||||
off_hand_object_duration,
|
||||
} => {
|
||||
let base_is_circle = current.base.is_circle();
|
||||
|
||||
if !base_is_circle {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
if current.idx % 2 == *hand as usize {
|
||||
if current.idx == 1 {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
let mut strain = 1.0;
|
||||
|
||||
note_pair_duration_history.push(current.delta + *off_hand_object_duration);
|
||||
|
||||
let shortest_recent_note = *note_pair_duration_history.min().unwrap();
|
||||
|
||||
strain += speed_bonus(shortest_recent_note);
|
||||
|
||||
if cheese[current.idx] {
|
||||
let p = cheese_penalty(current.delta + *off_hand_object_duration);
|
||||
strain *= p;
|
||||
}
|
||||
|
||||
return strain;
|
||||
}
|
||||
|
||||
*off_hand_object_duration = current.delta;
|
||||
|
||||
0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pattern_len_penalty(pattern_len: usize) -> f32 {
|
||||
let short_pattern_penalty = (0.15 * pattern_len as f32).min(1.0);
|
||||
let long_pattern_penalty = (2.5 - 0.15 * pattern_len as f32).max(0.0).min(1.0);
|
||||
|
||||
short_pattern_penalty.min(long_pattern_penalty)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cheese_penalty(note_pair_duration: f32) -> f32 {
|
||||
if note_pair_duration > 125.0 {
|
||||
1.0
|
||||
} else if note_pair_duration < 100.0 {
|
||||
0.6
|
||||
} else {
|
||||
0.6 + (note_pair_duration - 100.0) * 0.016
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn speed_bonus(note_pair_duration: f32) -> f32 {
|
||||
if note_pair_duration > 200.0 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let mut bonus = 200.0 - note_pair_duration;
|
||||
bonus *= bonus;
|
||||
|
||||
bonus / 100_000.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn repetition_penalty(notes_since: usize) -> f32 {
|
||||
(0.032 * notes_since as f32).min(1.0)
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
use super::{LimitedQueue, Rim};
|
||||
|
||||
use parse::{Beatmap, HitObject};
|
||||
|
||||
const ROLL_MIN_REPETITIONS: usize = 12;
|
||||
const TL_MIN_REPETITIONS: isize = 16;
|
||||
|
||||
pub(crate) trait StaminaCheeseDetector {
|
||||
fn find_cheese(&self) -> Vec<bool>;
|
||||
fn find_rolls(&self, pattern_len: usize, cheese: &mut [bool]);
|
||||
fn find_tl_tap(&self, parity: usize, is_rin: bool, cheese: &mut [bool]);
|
||||
}
|
||||
|
||||
impl StaminaCheeseDetector for Beatmap {
|
||||
fn find_cheese(&self) -> Vec<bool> {
|
||||
let mut cheese = vec![false; self.hit_objects.len()];
|
||||
|
||||
self.find_rolls(3, &mut cheese);
|
||||
self.find_rolls(4, &mut cheese);
|
||||
|
||||
self.find_tl_tap(0, true, &mut cheese);
|
||||
self.find_tl_tap(1, true, &mut cheese);
|
||||
self.find_tl_tap(0, false, &mut cheese);
|
||||
self.find_tl_tap(1, false, &mut cheese);
|
||||
|
||||
cheese
|
||||
}
|
||||
|
||||
fn find_rolls(&self, pattern_len: usize, cheese: &mut [bool]) {
|
||||
let mut history = LimitedQueue::new(2 * pattern_len);
|
||||
|
||||
let mut index_before_last_repeat = -1;
|
||||
let mut last_mark_end = 0;
|
||||
|
||||
for i in 0..self.hit_objects.len() {
|
||||
history.push(&self.hit_objects[i]);
|
||||
|
||||
if !history.full() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let contains = contains_pattern_repeat(&history, pattern_len);
|
||||
|
||||
if !contains {
|
||||
index_before_last_repeat = (i + 1 - history.len()) as isize;
|
||||
continue;
|
||||
}
|
||||
|
||||
let repeated_len = (i as isize - index_before_last_repeat) as usize;
|
||||
|
||||
if repeated_len < ROLL_MIN_REPETITIONS {
|
||||
continue;
|
||||
}
|
||||
|
||||
mark_as_cheese(last_mark_end.max(i + 1 - repeated_len), i, cheese);
|
||||
|
||||
last_mark_end = i;
|
||||
}
|
||||
}
|
||||
|
||||
fn find_tl_tap(&self, parity: usize, is_rin: bool, cheese: &mut [bool]) {
|
||||
let mut tl_len = -2;
|
||||
let mut last_mark_end = 0;
|
||||
|
||||
for i in (parity..self.hit_objects.len()).step_by(2) {
|
||||
if self.hit_objects[i].is_rim() == is_rin {
|
||||
tl_len += 2;
|
||||
} else {
|
||||
tl_len = -2;
|
||||
}
|
||||
|
||||
if tl_len < TL_MIN_REPETITIONS {
|
||||
continue;
|
||||
}
|
||||
|
||||
mark_as_cheese(
|
||||
(i as isize + 1 - tl_len).max(last_mark_end as isize) as usize,
|
||||
i,
|
||||
cheese,
|
||||
);
|
||||
|
||||
last_mark_end = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn mark_as_cheese(start: usize, end: usize, cheese: &mut [bool]) {
|
||||
cheese
|
||||
.iter_mut()
|
||||
.take(end + 1)
|
||||
.skip(start)
|
||||
.for_each(|b| *b = true);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn contains_pattern_repeat(history: &LimitedQueue<&HitObject>, pattern_len: usize) -> bool {
|
||||
for j in 0..pattern_len {
|
||||
if history[j].is_rim() != history[j + pattern_len].is_rim() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,598 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
AudioFilename: audio.mp3
|
||||
AudioLeadIn: 0
|
||||
PreviewTime: 47433
|
||||
Countdown: 0
|
||||
SampleSet: Normal
|
||||
StackLeniency: 0.7
|
||||
Mode: 1
|
||||
LetterboxInBreaks: 0
|
||||
WidescreenStoryboard: 0
|
||||
|
||||
[Editor]
|
||||
DistanceSpacing: 0.8
|
||||
BeatDivisor: 4
|
||||
GridSize: 32
|
||||
TimelineZoom: 2.6
|
||||
|
||||
[Metadata]
|
||||
Title:Physicality
|
||||
TitleUnicode:Physicality
|
||||
Artist:Renard + Adraen
|
||||
ArtistUnicode:Renard + Adraen
|
||||
Creator:Surono
|
||||
Version:Oni
|
||||
Source:
|
||||
Tags:lapfox trax vulpvibe records acid intravenous electronic breakcore system of a down
|
||||
BeatmapID:1432878
|
||||
BeatmapSetID:601403
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:5
|
||||
CircleSize:2
|
||||
OverallDifficulty:6
|
||||
ApproachRate:10
|
||||
SliderMultiplier:1.4
|
||||
SliderTickRate:1
|
||||
|
||||
[Events]
|
||||
//Background and Video events
|
||||
0,0,"fox stupud.jpg",0,0
|
||||
//Break Periods
|
||||
//Storyboard Layer 0 (Background)
|
||||
//Storyboard Layer 1 (Fail)
|
||||
//Storyboard Layer 2 (Pass)
|
||||
//Storyboard Layer 3 (Foreground)
|
||||
//Storyboard Sound Samples
|
||||
|
||||
[TimingPoints]
|
||||
115,218.181818181818,4,1,0,45,1,0
|
||||
115,-151.515151515152,4,1,0,45,0,0
|
||||
5339,331.455087835598,4,1,0,90,1,0
|
||||
47765,-100,4,1,0,90,0,1
|
||||
68978,-100,4,1,0,90,0,0
|
||||
90191,218.181818181818,4,1,0,45,1,0
|
||||
90191,-151.515151515152,4,1,0,45,0,0
|
||||
|
||||
|
||||
[HitObjects]
|
||||
256,192,115,12,0,5133,0:0:0:0:
|
||||
256,192,5339,5,0,0:0:0:0:
|
||||
256,192,5421,1,0,0:0:0:0:
|
||||
256,192,5504,1,0,0:0:0:0:
|
||||
256,192,5670,1,8,0:0:0:0:
|
||||
256,192,5836,1,0,0:0:0:0:
|
||||
256,192,5919,1,0,0:0:0:0:
|
||||
256,192,6001,1,0,0:0:0:0:
|
||||
256,192,6250,1,0,0:0:0:0:
|
||||
256,192,6333,1,8,0:0:0:0:
|
||||
256,192,6499,1,0,0:0:0:0:
|
||||
256,192,6664,1,8,0:0:0:0:
|
||||
256,192,6747,1,8,0:0:0:0:
|
||||
256,192,6830,1,8,0:0:0:0:
|
||||
256,192,6996,1,0,0:0:0:0:
|
||||
256,192,7162,1,8,0:0:0:0:
|
||||
256,192,7244,1,8,0:0:0:0:
|
||||
256,192,7327,1,8,0:0:0:0:
|
||||
256,192,7576,1,8,0:0:0:0:
|
||||
256,192,7659,1,0,0:0:0:0:
|
||||
256,192,7824,1,8,0:0:0:0:
|
||||
256,192,7990,5,0,0:0:0:0:
|
||||
256,192,8073,1,0,0:0:0:0:
|
||||
256,192,8156,1,0,0:0:0:0:
|
||||
256,192,8322,1,8,0:0:0:0:
|
||||
256,192,8487,1,0,0:0:0:0:
|
||||
256,192,8570,1,0,0:0:0:0:
|
||||
256,192,8653,1,0,0:0:0:0:
|
||||
256,192,8902,1,0,0:0:0:0:
|
||||
256,192,8985,1,8,0:0:0:0:
|
||||
256,192,9150,1,0,0:0:0:0:
|
||||
256,192,9316,1,8,0:0:0:0:
|
||||
256,192,9399,1,8,0:0:0:0:
|
||||
256,192,9482,1,8,0:0:0:0:
|
||||
256,192,9647,1,0,0:0:0:0:
|
||||
256,192,9813,1,8,0:0:0:0:
|
||||
256,192,9896,1,8,0:0:0:0:
|
||||
256,192,9979,1,8,0:0:0:0:
|
||||
256,192,10227,1,0,0:0:0:0:
|
||||
256,192,10310,1,8,0:0:0:0:
|
||||
256,192,10476,1,8,0:0:0:0:
|
||||
256,192,10642,5,0,0:0:0:0:
|
||||
256,192,10725,1,0,0:0:0:0:
|
||||
256,192,10808,1,0,0:0:0:0:
|
||||
256,192,10973,1,8,0:0:0:0:
|
||||
256,192,11139,1,0,0:0:0:0:
|
||||
256,192,11222,1,0,0:0:0:0:
|
||||
256,192,11305,1,0,0:0:0:0:
|
||||
256,192,11553,1,0,0:0:0:0:
|
||||
256,192,11636,1,8,0:0:0:0:
|
||||
256,192,11802,1,0,0:0:0:0:
|
||||
256,192,11968,1,8,0:0:0:0:
|
||||
256,192,12050,1,8,0:0:0:0:
|
||||
256,192,12133,1,8,0:0:0:0:
|
||||
256,192,12299,1,0,0:0:0:0:
|
||||
256,192,12465,1,8,0:0:0:0:
|
||||
256,192,12548,1,8,0:0:0:0:
|
||||
256,192,12631,1,8,0:0:0:0:
|
||||
256,192,12879,1,8,0:0:0:0:
|
||||
256,192,12962,1,0,0:0:0:0:
|
||||
256,192,13128,1,8,0:0:0:0:
|
||||
256,192,13293,5,0,0:0:0:0:
|
||||
256,192,13376,1,0,0:0:0:0:
|
||||
256,192,13459,1,0,0:0:0:0:
|
||||
256,192,13625,1,8,0:0:0:0:
|
||||
256,192,13791,1,0,0:0:0:0:
|
||||
256,192,13873,1,0,0:0:0:0:
|
||||
256,192,13956,1,0,0:0:0:0:
|
||||
256,192,14205,1,0,0:0:0:0:
|
||||
256,192,14288,1,8,0:0:0:0:
|
||||
256,192,14454,1,0,0:0:0:0:
|
||||
256,192,14619,1,8,0:0:0:0:
|
||||
256,192,14702,1,8,0:0:0:0:
|
||||
256,192,14785,1,8,0:0:0:0:
|
||||
256,192,14951,1,0,0:0:0:0:
|
||||
256,192,15116,1,8,0:0:0:0:
|
||||
256,192,15199,1,8,0:0:0:0:
|
||||
256,192,15282,1,8,0:0:0:0:
|
||||
256,192,15531,1,0,0:0:0:0:
|
||||
256,192,15614,1,8,0:0:0:0:
|
||||
256,192,15779,1,8,0:0:0:0:
|
||||
256,192,15862,1,8,0:0:0:0:
|
||||
256,192,15945,1,4,0:0:0:0:
|
||||
256,192,16277,1,4,0:0:0:0:
|
||||
256,192,16608,1,8,0:0:0:0:
|
||||
256,192,16774,1,0,0:0:0:0:
|
||||
256,192,16939,1,0,0:0:0:0:
|
||||
256,192,17105,1,8,0:0:0:0:
|
||||
256,192,17188,1,8,0:0:0:0:
|
||||
256,192,17271,1,0,0:0:0:0:
|
||||
256,192,17437,1,0,0:0:0:0:
|
||||
256,192,17519,1,0,0:0:0:0:
|
||||
256,192,17602,1,0,0:0:0:0:
|
||||
256,192,17768,1,0,0:0:0:0:
|
||||
256,192,17851,1,0,0:0:0:0:
|
||||
256,192,17934,1,8,0:0:0:0:
|
||||
256,192,18100,1,0,0:0:0:0:
|
||||
256,192,18182,1,0,0:0:0:0:
|
||||
256,192,18265,1,8,0:0:0:0:
|
||||
256,192,18431,1,8,0:0:0:0:
|
||||
256,192,18597,1,4,0:0:0:0:
|
||||
256,192,18928,1,0,0:0:0:0:
|
||||
256,192,19011,1,0,0:0:0:0:
|
||||
256,192,19094,1,0,0:0:0:0:
|
||||
256,192,19260,1,8,0:0:0:0:
|
||||
256,192,19342,1,0,0:0:0:0:
|
||||
256,192,19425,1,0,0:0:0:0:
|
||||
256,192,19591,1,0,0:0:0:0:
|
||||
256,192,19674,1,0,0:0:0:0:
|
||||
256,192,19757,1,8,0:0:0:0:
|
||||
256,192,19923,1,0,0:0:0:0:
|
||||
256,192,20088,1,8,0:0:0:0:
|
||||
256,192,20254,1,0,0:0:0:0:
|
||||
256,192,20420,1,0,0:0:0:0:
|
||||
256,192,20585,1,8,0:0:0:0:
|
||||
256,192,20751,1,0,0:0:0:0:
|
||||
256,192,20834,1,8,0:0:0:0:
|
||||
256,192,20917,1,0,0:0:0:0:
|
||||
256,192,21000,1,0,0:0:0:0:
|
||||
256,192,21083,1,8,0:0:0:0:
|
||||
256,192,21248,1,4,0:0:0:0:
|
||||
256,192,21580,1,4,0:0:0:0:
|
||||
256,192,21911,1,8,0:0:0:0:
|
||||
256,192,22077,1,0,0:0:0:0:
|
||||
256,192,22243,1,0,0:0:0:0:
|
||||
256,192,22408,1,8,0:0:0:0:
|
||||
256,192,22491,1,8,0:0:0:0:
|
||||
256,192,22574,1,0,0:0:0:0:
|
||||
256,192,22740,1,0,0:0:0:0:
|
||||
256,192,22823,1,0,0:0:0:0:
|
||||
256,192,22906,1,0,0:0:0:0:
|
||||
256,192,23071,1,0,0:0:0:0:
|
||||
256,192,23154,1,0,0:0:0:0:
|
||||
256,192,23237,1,8,0:0:0:0:
|
||||
256,192,23403,1,0,0:0:0:0:
|
||||
256,192,23486,1,0,0:0:0:0:
|
||||
256,192,23569,1,8,0:0:0:0:
|
||||
256,192,23734,1,8,0:0:0:0:
|
||||
256,192,23900,1,4,0:0:0:0:
|
||||
256,192,24231,1,0,0:0:0:0:
|
||||
256,192,24314,1,0,0:0:0:0:
|
||||
256,192,24397,1,0,0:0:0:0:
|
||||
256,192,24563,1,8,0:0:0:0:
|
||||
256,192,24646,1,0,0:0:0:0:
|
||||
256,192,24729,1,0,0:0:0:0:
|
||||
256,192,24894,1,0,0:0:0:0:
|
||||
256,192,24977,1,0,0:0:0:0:
|
||||
256,192,25060,1,8,0:0:0:0:
|
||||
256,192,25226,1,0,0:0:0:0:
|
||||
256,192,25392,1,8,0:0:0:0:
|
||||
256,192,25557,1,0,0:0:0:0:
|
||||
256,192,25723,1,0,0:0:0:0:
|
||||
256,192,25889,1,8,0:0:0:0:
|
||||
256,192,26054,1,0,0:0:0:0:
|
||||
256,192,26137,1,8,0:0:0:0:
|
||||
256,192,26220,1,0,0:0:0:0:
|
||||
256,192,26303,1,0,0:0:0:0:
|
||||
256,192,26386,1,8,0:0:0:0:
|
||||
256,192,26552,5,0,0:0:0:0:
|
||||
256,192,26634,1,0,0:0:0:0:
|
||||
256,192,26717,1,0,0:0:0:0:
|
||||
256,192,26883,1,8,0:0:0:0:
|
||||
256,192,27049,1,0,0:0:0:0:
|
||||
256,192,27132,1,8,0:0:0:0:
|
||||
256,192,27215,1,0,0:0:0:0:
|
||||
256,192,27463,1,0,0:0:0:0:
|
||||
256,192,27546,1,8,0:0:0:0:
|
||||
256,192,27712,1,0,0:0:0:0:
|
||||
256,192,27877,1,8,0:0:0:0:
|
||||
256,192,27960,1,8,0:0:0:0:
|
||||
256,192,28043,1,8,0:0:0:0:
|
||||
256,192,28209,1,0,0:0:0:0:
|
||||
256,192,28375,1,8,0:0:0:0:
|
||||
256,192,28457,1,0,0:0:0:0:
|
||||
256,192,28540,1,8,0:0:0:0:
|
||||
256,192,28789,1,8,0:0:0:0:
|
||||
256,192,28872,1,0,0:0:0:0:
|
||||
256,192,29038,1,8,0:0:0:0:
|
||||
256,192,29203,5,0,0:0:0:0:
|
||||
256,192,29286,1,0,0:0:0:0:
|
||||
256,192,29369,1,0,0:0:0:0:
|
||||
256,192,29535,1,8,0:0:0:0:
|
||||
256,192,29700,1,0,0:0:0:0:
|
||||
256,192,29783,1,8,0:0:0:0:
|
||||
256,192,29866,1,0,0:0:0:0:
|
||||
256,192,30115,1,0,0:0:0:0:
|
||||
256,192,30198,1,8,0:0:0:0:
|
||||
256,192,30363,1,0,0:0:0:0:
|
||||
256,192,30529,1,8,0:0:0:0:
|
||||
256,192,30612,1,8,0:0:0:0:
|
||||
256,192,30695,1,8,0:0:0:0:
|
||||
256,192,30861,1,0,0:0:0:0:
|
||||
256,192,31026,1,8,0:0:0:0:
|
||||
256,192,31109,1,0,0:0:0:0:
|
||||
256,192,31192,1,8,0:0:0:0:
|
||||
256,192,31441,1,0,0:0:0:0:
|
||||
256,192,31523,1,8,0:0:0:0:
|
||||
256,192,31689,1,8,0:0:0:0:
|
||||
256,192,31855,5,0,0:0:0:0:
|
||||
256,192,31938,1,0,0:0:0:0:
|
||||
256,192,32021,1,0,0:0:0:0:
|
||||
256,192,32186,1,8,0:0:0:0:
|
||||
256,192,32352,1,0,0:0:0:0:
|
||||
256,192,32435,1,8,0:0:0:0:
|
||||
256,192,32518,1,0,0:0:0:0:
|
||||
256,192,32766,1,0,0:0:0:0:
|
||||
256,192,32849,1,8,0:0:0:0:
|
||||
256,192,33015,1,0,0:0:0:0:
|
||||
256,192,33181,1,8,0:0:0:0:
|
||||
256,192,33264,1,8,0:0:0:0:
|
||||
256,192,33346,1,8,0:0:0:0:
|
||||
256,192,33512,1,0,0:0:0:0:
|
||||
256,192,33678,1,8,0:0:0:0:
|
||||
256,192,33761,1,0,0:0:0:0:
|
||||
256,192,33844,1,8,0:0:0:0:
|
||||
256,192,34092,1,8,0:0:0:0:
|
||||
256,192,34175,1,0,0:0:0:0:
|
||||
256,192,34341,1,8,0:0:0:0:
|
||||
256,192,34507,5,0,0:0:0:0:
|
||||
256,192,34589,1,0,0:0:0:0:
|
||||
256,192,34672,1,0,0:0:0:0:
|
||||
256,192,34838,1,8,0:0:0:0:
|
||||
256,192,35004,1,0,0:0:0:0:
|
||||
256,192,35087,1,8,0:0:0:0:
|
||||
256,192,35169,1,0,0:0:0:0:
|
||||
256,192,35418,1,0,0:0:0:0:
|
||||
256,192,35501,1,8,0:0:0:0:
|
||||
256,192,35667,1,0,0:0:0:0:
|
||||
256,192,35832,1,8,0:0:0:0:
|
||||
256,192,35915,1,8,0:0:0:0:
|
||||
256,192,35998,1,8,0:0:0:0:
|
||||
256,192,36164,1,0,0:0:0:0:
|
||||
256,192,36330,1,8,0:0:0:0:
|
||||
256,192,36412,1,0,0:0:0:0:
|
||||
256,192,36495,1,8,0:0:0:0:
|
||||
256,192,36744,1,0,0:0:0:0:
|
||||
256,192,36827,1,8,0:0:0:0:
|
||||
256,192,37158,1,4,0:0:0:0:
|
||||
256,192,37490,1,8,0:0:0:0:
|
||||
256,192,37738,1,4,0:0:0:0:
|
||||
256,192,37987,1,0,0:0:0:0:
|
||||
256,192,38153,1,8,0:0:0:0:
|
||||
256,192,38484,1,12,0:0:0:0:
|
||||
256,192,38815,1,0,0:0:0:0:
|
||||
256,192,39064,1,12,0:0:0:0:
|
||||
256,192,39313,1,0,0:0:0:0:
|
||||
256,192,39478,1,8,0:0:0:0:
|
||||
256,192,39644,1,0,0:0:0:0:
|
||||
256,192,39810,1,4,0:0:0:0:
|
||||
256,192,40141,1,8,0:0:0:0:
|
||||
256,192,40390,1,4,0:0:0:0:
|
||||
256,192,40638,1,0,0:0:0:0:
|
||||
256,192,40804,1,8,0:0:0:0:
|
||||
256,192,41136,1,12,0:0:0:0:
|
||||
256,192,41467,1,0,0:0:0:0:
|
||||
256,192,41716,1,12,0:0:0:0:
|
||||
256,192,41964,1,0,0:0:0:0:
|
||||
256,192,42130,1,8,0:0:0:0:
|
||||
256,192,42296,1,8,0:0:0:0:
|
||||
256,192,42461,1,4,0:0:0:0:
|
||||
256,192,42793,1,8,0:0:0:0:
|
||||
256,192,43042,1,4,0:0:0:0:
|
||||
256,192,43290,1,0,0:0:0:0:
|
||||
256,192,43456,1,8,0:0:0:0:
|
||||
256,192,43787,1,12,0:0:0:0:
|
||||
256,192,44119,1,0,0:0:0:0:
|
||||
256,192,44367,1,12,0:0:0:0:
|
||||
256,192,44616,1,0,0:0:0:0:
|
||||
256,192,44782,1,8,0:0:0:0:
|
||||
256,192,44947,1,0,0:0:0:0:
|
||||
256,192,45113,1,4,0:0:0:0:
|
||||
256,192,45445,1,8,0:0:0:0:
|
||||
256,192,45693,1,4,0:0:0:0:
|
||||
256,192,45942,1,0,0:0:0:0:
|
||||
256,192,46107,1,8,0:0:0:0:
|
||||
256,192,46439,1,12,0:0:0:0:
|
||||
256,192,46770,1,8,0:0:0:0:
|
||||
256,192,46936,1,0,0:0:0:0:
|
||||
256,192,47019,1,0,0:0:0:0:
|
||||
256,192,47102,1,8,0:0:0:0:
|
||||
256,192,47268,1,0,0:0:0:0:
|
||||
256,192,47350,1,0,0:0:0:0:
|
||||
256,192,47433,1,8,0:0:0:0:
|
||||
256,192,47765,1,4,0:0:0:0:
|
||||
256,192,48096,1,4,0:0:0:0:
|
||||
256,192,48428,1,8,0:0:0:0:
|
||||
256,192,48593,1,0,0:0:0:0:
|
||||
256,192,48759,1,0,0:0:0:0:
|
||||
256,192,48925,1,8,0:0:0:0:
|
||||
256,192,49008,1,8,0:0:0:0:
|
||||
256,192,49091,1,0,0:0:0:0:
|
||||
256,192,49256,1,0,0:0:0:0:
|
||||
256,192,49339,1,0,0:0:0:0:
|
||||
256,192,49422,1,8,0:0:0:0:
|
||||
256,192,49588,1,0,0:0:0:0:
|
||||
256,192,49671,1,0,0:0:0:0:
|
||||
256,192,49753,1,8,0:0:0:0:
|
||||
256,192,49919,1,0,0:0:0:0:
|
||||
256,192,50002,1,0,0:0:0:0:
|
||||
256,192,50085,1,8,0:0:0:0:
|
||||
256,192,50251,1,8,0:0:0:0:
|
||||
256,192,50416,1,4,0:0:0:0:
|
||||
256,192,50748,1,0,0:0:0:0:
|
||||
256,192,50831,1,0,0:0:0:0:
|
||||
256,192,50914,1,0,0:0:0:0:
|
||||
256,192,51079,1,8,0:0:0:0:
|
||||
256,192,51162,1,0,0:0:0:0:
|
||||
256,192,51245,1,0,0:0:0:0:
|
||||
256,192,51411,1,0,0:0:0:0:
|
||||
256,192,51494,1,0,0:0:0:0:
|
||||
256,192,51576,1,8,0:0:0:0:
|
||||
256,192,51742,1,0,0:0:0:0:
|
||||
256,192,51908,1,8,0:0:0:0:
|
||||
256,192,52074,1,8,0:0:0:0:
|
||||
256,192,52239,1,0,0:0:0:0:
|
||||
256,192,52405,1,8,0:0:0:0:
|
||||
256,192,52571,1,8,0:0:0:0:
|
||||
256,192,52654,1,8,0:0:0:0:
|
||||
256,192,52737,1,0,0:0:0:0:
|
||||
256,192,52819,1,0,0:0:0:0:
|
||||
256,192,52902,1,8,0:0:0:0:
|
||||
256,192,53068,1,4,0:0:0:0:
|
||||
256,192,53399,1,4,0:0:0:0:
|
||||
256,192,53731,1,8,0:0:0:0:
|
||||
256,192,53897,1,0,0:0:0:0:
|
||||
256,192,54062,1,0,0:0:0:0:
|
||||
256,192,54228,1,8,0:0:0:0:
|
||||
256,192,54311,1,8,0:0:0:0:
|
||||
256,192,54394,1,0,0:0:0:0:
|
||||
256,192,54560,1,0,0:0:0:0:
|
||||
256,192,54642,1,0,0:0:0:0:
|
||||
256,192,54725,1,8,0:0:0:0:
|
||||
256,192,54891,1,0,0:0:0:0:
|
||||
256,192,54974,1,0,0:0:0:0:
|
||||
256,192,55057,1,8,0:0:0:0:
|
||||
256,192,55222,1,0,0:0:0:0:
|
||||
256,192,55305,1,0,0:0:0:0:
|
||||
256,192,55388,1,8,0:0:0:0:
|
||||
256,192,55554,1,8,0:0:0:0:
|
||||
256,192,55720,1,4,0:0:0:0:
|
||||
256,192,56051,1,0,0:0:0:0:
|
||||
256,192,56134,1,0,0:0:0:0:
|
||||
256,192,56217,1,0,0:0:0:0:
|
||||
256,192,56383,1,8,0:0:0:0:
|
||||
256,192,56465,1,0,0:0:0:0:
|
||||
256,192,56548,1,0,0:0:0:0:
|
||||
256,192,56714,1,0,0:0:0:0:
|
||||
256,192,56797,1,0,0:0:0:0:
|
||||
256,192,56880,1,8,0:0:0:0:
|
||||
256,192,57045,1,0,0:0:0:0:
|
||||
256,192,57211,1,8,0:0:0:0:
|
||||
256,192,57377,1,8,0:0:0:0:
|
||||
256,192,57543,1,0,0:0:0:0:
|
||||
256,192,57708,1,8,0:0:0:0:
|
||||
256,192,57874,1,8,0:0:0:0:
|
||||
256,192,57957,1,8,0:0:0:0:
|
||||
256,192,58040,1,0,0:0:0:0:
|
||||
256,192,58123,1,0,0:0:0:0:
|
||||
256,192,58206,1,8,0:0:0:0:
|
||||
256,192,58371,1,4,0:0:0:0:
|
||||
256,192,58703,1,4,0:0:0:0:
|
||||
256,192,59034,1,8,0:0:0:0:
|
||||
256,192,59200,1,8,0:0:0:0:
|
||||
256,192,59366,1,0,0:0:0:0:
|
||||
256,192,59531,1,8,0:0:0:0:
|
||||
256,192,59614,1,8,0:0:0:0:
|
||||
256,192,59697,1,0,0:0:0:0:
|
||||
256,192,59863,1,0,0:0:0:0:
|
||||
256,192,59946,1,8,0:0:0:0:
|
||||
256,192,60029,1,0,0:0:0:0:
|
||||
256,192,60194,1,0,0:0:0:0:
|
||||
256,192,60277,1,0,0:0:0:0:
|
||||
256,192,60360,1,8,0:0:0:0:
|
||||
256,192,60526,1,0,0:0:0:0:
|
||||
256,192,60609,1,0,0:0:0:0:
|
||||
256,192,60691,1,8,0:0:0:0:
|
||||
256,192,60857,1,8,0:0:0:0:
|
||||
256,192,61023,1,4,0:0:0:0:
|
||||
256,192,61189,1,0,0:0:0:0:
|
||||
256,192,61354,1,8,0:0:0:0:
|
||||
256,192,61437,1,8,0:0:0:0:
|
||||
256,192,61520,1,0,0:0:0:0:
|
||||
256,192,61686,1,8,0:0:0:0:
|
||||
256,192,61769,1,8,0:0:0:0:
|
||||
256,192,61852,1,0,0:0:0:0:
|
||||
256,192,62017,1,0,0:0:0:0:
|
||||
256,192,62100,1,0,0:0:0:0:
|
||||
256,192,62183,1,8,0:0:0:0:
|
||||
256,192,62349,1,0,0:0:0:0:
|
||||
256,192,62515,1,8,0:0:0:0:
|
||||
256,192,62680,1,8,0:0:0:0:
|
||||
256,192,62763,1,0,0:0:0:0:
|
||||
256,192,62846,1,0,0:0:0:0:
|
||||
256,192,63012,1,8,0:0:0:0:
|
||||
256,192,63177,1,8,0:0:0:0:
|
||||
256,192,63260,1,8,0:0:0:0:
|
||||
256,192,63343,1,0,0:0:0:0:
|
||||
256,192,63426,1,0,0:0:0:0:
|
||||
256,192,63509,1,8,0:0:0:0:
|
||||
256,192,63675,1,4,0:0:0:0:
|
||||
256,192,64006,1,4,0:0:0:0:
|
||||
256,192,64338,1,8,0:0:0:0:
|
||||
256,192,64503,1,8,0:0:0:0:
|
||||
256,192,64669,1,0,0:0:0:0:
|
||||
256,192,64835,1,8,0:0:0:0:
|
||||
256,192,64918,1,8,0:0:0:0:
|
||||
256,192,65000,1,0,0:0:0:0:
|
||||
256,192,65166,1,0,0:0:0:0:
|
||||
256,192,65249,1,8,0:0:0:0:
|
||||
256,192,65332,1,0,0:0:0:0:
|
||||
256,192,65498,1,0,0:0:0:0:
|
||||
256,192,65580,1,0,0:0:0:0:
|
||||
256,192,65663,1,8,0:0:0:0:
|
||||
256,192,65829,1,0,0:0:0:0:
|
||||
256,192,65912,1,0,0:0:0:0:
|
||||
256,192,65995,1,8,0:0:0:0:
|
||||
256,192,66161,1,8,0:0:0:0:
|
||||
256,192,66326,1,4,0:0:0:0:
|
||||
256,192,66492,1,0,0:0:0:0:
|
||||
256,192,66658,1,8,0:0:0:0:
|
||||
256,192,66741,1,8,0:0:0:0:
|
||||
256,192,66823,1,0,0:0:0:0:
|
||||
256,192,66989,1,8,0:0:0:0:
|
||||
256,192,67072,1,8,0:0:0:0:
|
||||
256,192,67155,1,0,0:0:0:0:
|
||||
256,192,67321,1,0,0:0:0:0:
|
||||
256,192,67403,1,0,0:0:0:0:
|
||||
256,192,67486,1,8,0:0:0:0:
|
||||
256,192,67652,1,0,0:0:0:0:
|
||||
256,192,67818,1,8,0:0:0:0:
|
||||
256,192,67984,1,8,0:0:0:0:
|
||||
256,192,68066,1,0,0:0:0:0:
|
||||
256,192,68149,1,0,0:0:0:0:
|
||||
256,192,68315,1,8,0:0:0:0:
|
||||
256,192,68481,1,8,0:0:0:0:
|
||||
256,192,68564,1,8,0:0:0:0:
|
||||
256,192,68646,1,0,0:0:0:0:
|
||||
256,192,68729,1,0,0:0:0:0:
|
||||
256,192,68812,1,8,0:0:0:0:
|
||||
256,192,68978,5,4,0:0:0:0:
|
||||
256,192,69475,1,0,0:0:0:0:
|
||||
256,192,70304,1,8,0:0:0:0:
|
||||
256,192,70801,1,8,0:0:0:0:
|
||||
256,192,71630,1,0,0:0:0:0:
|
||||
256,192,72127,1,0,0:0:0:0:
|
||||
256,192,72955,1,8,0:0:0:0:
|
||||
256,192,73453,1,8,0:0:0:0:
|
||||
256,192,73950,1,8,0:0:0:0:
|
||||
256,192,74281,1,4,0:0:0:0:
|
||||
256,192,74778,2,4,L|128:128,1,140
|
||||
256,192,75607,1,8,0:0:0:0:
|
||||
256,192,76104,2,0,L|128:96,1,140
|
||||
256,192,76933,1,0,0:0:0:0:
|
||||
256,192,77430,2,4,L|128:96,1,140
|
||||
256,192,78259,1,8,0:0:0:0:
|
||||
256,192,78756,2,8,L|128:128,1,140
|
||||
256,192,79253,1,8,0:0:0:0:
|
||||
256,192,79584,5,0,0:0:0:0:
|
||||
256,192,79667,1,0,0:0:0:0:
|
||||
256,192,79750,1,0,0:0:0:0:
|
||||
256,192,79916,1,8,0:0:0:0:
|
||||
256,192,80082,1,0,0:0:0:0:
|
||||
256,192,80164,1,8,0:0:0:0:
|
||||
256,192,80247,1,0,0:0:0:0:
|
||||
256,192,80413,1,0,0:0:0:0:
|
||||
256,192,80579,1,8,0:0:0:0:
|
||||
256,192,80745,1,0,0:0:0:0:
|
||||
256,192,80910,1,8,0:0:0:0:
|
||||
256,192,80993,1,8,0:0:0:0:
|
||||
256,192,81076,1,8,0:0:0:0:
|
||||
256,192,81242,1,0,0:0:0:0:
|
||||
256,192,81407,1,8,0:0:0:0:
|
||||
256,192,81490,1,0,0:0:0:0:
|
||||
256,192,81573,1,8,0:0:0:0:
|
||||
256,192,81739,1,8,0:0:0:0:
|
||||
256,192,81905,1,0,0:0:0:0:
|
||||
256,192,82070,1,8,0:0:0:0:
|
||||
256,192,82236,5,0,0:0:0:0:
|
||||
256,192,82319,1,0,0:0:0:0:
|
||||
256,192,82402,1,0,0:0:0:0:
|
||||
256,192,82568,1,8,0:0:0:0:
|
||||
256,192,82733,1,0,0:0:0:0:
|
||||
256,192,82816,1,8,0:0:0:0:
|
||||
256,192,82899,1,0,0:0:0:0:
|
||||
256,192,83065,1,0,0:0:0:0:
|
||||
256,192,83230,1,8,0:0:0:0:
|
||||
256,192,83396,1,0,0:0:0:0:
|
||||
256,192,83562,1,8,0:0:0:0:
|
||||
256,192,83645,1,8,0:0:0:0:
|
||||
256,192,83728,1,8,0:0:0:0:
|
||||
256,192,83893,1,0,0:0:0:0:
|
||||
256,192,84059,1,8,0:0:0:0:
|
||||
256,192,84142,1,0,0:0:0:0:
|
||||
256,192,84225,1,8,0:0:0:0:
|
||||
256,192,84391,1,0,0:0:0:0:
|
||||
256,192,84556,1,8,0:0:0:0:
|
||||
256,192,84722,1,8,0:0:0:0:
|
||||
256,192,84888,5,0,0:0:0:0:
|
||||
256,192,84971,1,0,0:0:0:0:
|
||||
256,192,85053,1,0,0:0:0:0:
|
||||
256,192,85219,1,8,0:0:0:0:
|
||||
256,192,85385,1,0,0:0:0:0:
|
||||
256,192,85468,1,8,0:0:0:0:
|
||||
256,192,85551,1,0,0:0:0:0:
|
||||
256,192,85716,1,0,0:0:0:0:
|
||||
256,192,85882,1,8,0:0:0:0:
|
||||
256,192,86048,1,0,0:0:0:0:
|
||||
256,192,86214,1,8,0:0:0:0:
|
||||
256,192,86296,1,8,0:0:0:0:
|
||||
256,192,86379,1,8,0:0:0:0:
|
||||
256,192,86545,1,0,0:0:0:0:
|
||||
256,192,86711,1,8,0:0:0:0:
|
||||
256,192,86794,1,0,0:0:0:0:
|
||||
256,192,86876,1,8,0:0:0:0:
|
||||
256,192,87042,1,8,0:0:0:0:
|
||||
256,192,87208,1,0,0:0:0:0:
|
||||
256,192,87374,1,8,0:0:0:0:
|
||||
256,192,87539,5,0,0:0:0:0:
|
||||
256,192,87622,1,0,0:0:0:0:
|
||||
256,192,87705,1,0,0:0:0:0:
|
||||
256,192,87871,1,8,0:0:0:0:
|
||||
256,192,88037,1,0,0:0:0:0:
|
||||
256,192,88119,1,8,0:0:0:0:
|
||||
256,192,88202,1,0,0:0:0:0:
|
||||
256,192,88368,1,0,0:0:0:0:
|
||||
256,192,88534,1,8,0:0:0:0:
|
||||
256,192,88699,1,0,0:0:0:0:
|
||||
256,192,88865,1,8,0:0:0:0:
|
||||
256,192,88948,1,8,0:0:0:0:
|
||||
256,192,89031,1,8,0:0:0:0:
|
||||
256,192,89197,1,0,0:0:0:0:
|
||||
256,192,89362,1,8,0:0:0:0:
|
||||
256,192,89445,1,0,0:0:0:0:
|
||||
256,192,89528,1,8,0:0:0:0:
|
||||
256,192,89694,1,0,0:0:0:0:
|
||||
256,192,89860,1,8,0:0:0:0:
|
||||
256,192,90025,1,8,0:0:0:0:
|
||||
256,192,90191,12,0,95427,0:0:0:0:
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user