Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove mod specific buffs #48

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
* @tsunyoku @cmyui

# Infrastructure
/.github/* @cmyui
/tf/* @cmyui
/chart/* @cmyui
11 changes: 5 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
[package]
name = "rosu-pp"
version = "1.1.0"
name = "akatsuki-pp"
version = "1.1.2"
edition = "2021"
authors = ["MaxOhn <[email protected]>"]
authors = ["MaxOhn <[email protected]>", "tsunyoku <[email protected]>"]
license = "MIT"
readme = "README.md"
repository = "https://github.com/MaxOhn/rosu-pp"
documentation = "https://docs.rs/rosu-pp/"
repository = "https://github.com/osuAkatsuki/akatsuki-pp-rs"
description = "Difficulty and performance calculation for osu!"
keywords = ["osu", "pp", "stars", "performance", "difficulty"]

Expand All @@ -27,4 +26,4 @@ proptest = "1.4.0"
opt-level = 3

[profile.test.package.rand_chacha]
opt-level = 3
opt-level = 3
146 changes: 2 additions & 144 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,145 +1,3 @@
[![crates.io](https://img.shields.io/crates/v/rosu-pp.svg)](https://crates.io/crates/rosu-pp) [![docs](https://docs.rs/rosu-pp/badge.svg)](https://docs.rs/rosu-pp)
# akatsuki-pp-rs

# rosu-pp

<!-- cargo-rdme start -->

Library to calculate difficulty and performance attributes for all [osu!] gamemodes.

A large part of `rosu-pp` is a port of [osu!lazer]'s difficulty and performance calculation
with emphasis on a precise translation to Rust for the most [accurate results](#accuracy)
while also providing a significant [boost in performance](#speed).

Last commits of the ported code:
- [osu!lazer] : `8bd65d9938a10fc42e6409501b0282f0fa4a25ef` (2024-11-08)
- [osu!tools] : `89b8f3b1c2e4e5674004eac4723120e7d3aef997` (2024-11-03)

News posts of the latest updates: <https://osu.ppy.sh/home/news/2024-10-28-performance-points-star-rating-updates>

### Usage

```rust
// Decode the map
let map = rosu_pp::Beatmap::from_path("./resources/2785319.osu").unwrap();

// Calculate difficulty attributes
let diff_attrs = rosu_pp::Difficulty::new()
.mods(8 + 16) // HDHR
.calculate(&map);

let stars = diff_attrs.stars();

// Calculate performance attributes
let perf_attrs = rosu_pp::Performance::new(diff_attrs)
// To speed up the calculation, we used the previous attributes.
// **Note** that this should only be done if the map and all difficulty
// settings stay the same, otherwise the final attributes will be incorrect!
.mods(24) // HDHR, must be the same as before
.combo(789)
.accuracy(99.2)
.misses(2)
.calculate();

let pp = perf_attrs.pp();

// Again, we re-use the previous attributes for maximum efficiency.
let max_pp = perf_attrs.performance()
.mods(24) // Still the same
.calculate()
.pp();

println!("Stars: {stars} | PP: {pp}/{max_pp}");
```

### Gradual calculation

Gradually calculating attributes provides an efficient way to process each hitobject
separately and calculate the attributes only up to that point.

For difficulty attributes, there is `GradualDifficulty` which implements `Iterator`
and for performance attributes there is `GradualPerformance` which requires the current
score state.

```rust
use rosu_pp::{Beatmap, GradualPerformance, Difficulty, any::ScoreState};

let map = Beatmap::from_path("./resources/1028484.osu").unwrap();

let mut gradual = Difficulty::new()
.mods(16 + 64) // HRDT
.clock_rate(1.2)
.gradual_performance(&map);

let mut state = ScoreState::new(); // empty state, everything is on 0.

// The first 10 hitresults are 300s
for _ in 0..10 {
state.n300 += 1;
state.max_combo += 1;
let attrs = gradual.next(state.clone()).unwrap();
println!("PP: {}", attrs.pp());
}

// Fast-forward to the end
state.max_combo = ...
state.n300 = ...
state.n_katu = ...
...
let attrs = gradual.last(state).unwrap();
println!("PP: {}", attrs.pp());
```

### Accuracy

`rosu-pp` was tested against all current beatmaps on multiple mod combinations and delivered
values that matched osu!lazer perfectly down to the last decimal place.

However, there is one small caveat: the values are only this precise on debug mode.
On release mode, Rust's compiler performs optimizations that produce the tiniest discrepancies
due to floating point inaccuracies which can cascade into larger differences in the end.
With this in mind, `rosu-pp` is still as accurate as can be without targeting the
.NET compiler itself. Realistically, the inaccuracies in release mode are negligibly small.

### Speed

An important factor for `rosu-pp` is the calculation speed. Optimizations and an accurate translation
unfortunately don't always go hand-in-hand. Nonetheless, performance improvements are still
snuck in wherever possible, providing a significantly faster runtime than the native C# code.

Results of a rudimentary [benchmark] of osu!lazer and rosu-pp:
```txt
osu!lazer:
Decoding maps: Median: 378.10ms | Mean: 381.47ms
Calculating difficulties: Median: 588.89ms | Mean: 597.11ms
Calculating performances: Median: 315.90µs | Mean: 310.60µs

rosu-pp:
Decoding maps: Median: 46.94ms | Mean: 47.21ms
Calculating difficulties: Median: 72.90ms | Mean: 73.13ms
Calculating performances: Median: 44.13µs | Mean: 45.53µs
```

### Features

| Flag | Description | Dependencies
| ------------- | ------------------- | ------------
| `default` | No features enabled |
| `raw_strains` | With this feature, internal strain values will be stored in a plain `Vec`. This introduces an out-of-memory risk on maliciously long maps (see [/b/3739922](https://osu.ppy.sh/b/3739922)), but comes with a ~5% gain in performance. |
| `sync` | Some gradual calculation types can only be shared across threads if this feature is enabled. This feature adds a small performance penalty. |
| `tracing` | Any error encountered during beatmap decoding will be logged through `tracing::error`. If this feature is **not** enabled, errors will be ignored. | [`tracing`]

### Bindings

Using `rosu-pp` from other languages than Rust:
- JavaScript: [rosu-pp-js]
- Python: [rosu-pp-py]

[osu!]: https://osu.ppy.sh/home
[osu!lazer]: https://github.com/ppy/osu
[osu!tools]: https://github.com/ppy/osu-tools
[`tracing`]: https://docs.rs/tracing
[rosu-pp-js]: https://github.com/MaxOhn/rosu-pp-js
[rosu-pp-py]: https://github.com/MaxOhn/rosu-pp-py
[benchmark]: https://gist.github.com/MaxOhn/625af10011f6d7e13a171b08ccf959ff

<!-- cargo-rdme end -->
`akatsuki-pp-rs` is a fork of [rosu-pp](https://github.com/MaxOhn/rosu-pp) with customized calculations for relax and autopilot.
2 changes: 1 addition & 1 deletion src/any/difficulty/gradual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::{
/// # Example
///
/// ```
/// use rosu_pp::{Beatmap, GradualDifficulty, Difficulty};
/// use akatsuki_pp::{Beatmap, GradualDifficulty, Difficulty};
///
/// let map = Beatmap::from_path("./resources/2785319.osu").unwrap();
/// let difficulty = Difficulty::new().mods(64); // DT
Expand Down
2 changes: 1 addition & 1 deletion src/any/difficulty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use crate::model::mode::IGameMode;
/// # Example
///
/// ```
/// use rosu_pp::{Beatmap, Difficulty, any::DifficultyAttributes};
/// use akatsuki_pp::{Beatmap, Difficulty, any::DifficultyAttributes};
///
/// let map = Beatmap::from_path("./resources/2118524.osu").unwrap();
///
Expand Down
2 changes: 1 addition & 1 deletion src/any/performance/gradual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use crate::{
/// # Example
///
/// ```
/// use rosu_pp::{Beatmap, GradualPerformance, Difficulty, any::ScoreState};
/// use akatsuki_pp::{Beatmap, GradualPerformance, Difficulty, any::ScoreState};
///
/// let map = Beatmap::from_path("./resources/2785319.osu").unwrap();
/// let difficulty = Difficulty::new().mods(64); // DT
Expand Down
4 changes: 2 additions & 2 deletions src/catch/difficulty/gradual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ use super::{
/// # Example
///
/// ```
/// use rosu_pp::{Beatmap, Difficulty};
/// use rosu_pp::catch::{Catch, CatchGradualDifficulty};
/// use akatsuki_pp::{Beatmap, Difficulty};
/// use akatsuki_pp::catch::{Catch, CatchGradualDifficulty};
///
/// let map = Beatmap::from_path("./resources/2118524.osu").unwrap();
///
Expand Down
4 changes: 2 additions & 2 deletions src/catch/performance/gradual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ use crate::{
/// # Example
///
/// ```
/// use rosu_pp::{Beatmap, Difficulty};
/// use rosu_pp::catch::{Catch, CatchGradualPerformance, CatchScoreState};
/// use akatsuki_pp::{Beatmap, Difficulty};
/// use akatsuki_pp::catch::{Catch, CatchGradualPerformance, CatchScoreState};
///
/// let map = Beatmap::from_path("./resources/2118524.osu").unwrap();
///
Expand Down
11 changes: 7 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@
//!
//! ```
//! // Decode the map
//! let map = rosu_pp::Beatmap::from_path("./resources/2785319.osu").unwrap();
//! let map = akatsuki_pp::Beatmap::from_path("./resources/2785319.osu").unwrap();
//!
//! // Calculate difficulty attributes
//! let diff_attrs = rosu_pp::Difficulty::new()
//! let diff_attrs = akatsuki_pp::Difficulty::new()
//! .mods(8 + 16) // HDHR
//! .calculate(&map);
//!
//! let stars = diff_attrs.stars();
//!
//! // Calculate performance attributes
//! let perf_attrs = rosu_pp::Performance::new(diff_attrs)
//! let perf_attrs = akatsuki_pp::Performance::new(diff_attrs)
//! // To speed up the calculation, we used the previous attributes.
//! // **Note** that this should only be done if the map and all difficulty
//! // settings stay the same, otherwise the final attributes will be incorrect!
Expand Down Expand Up @@ -55,7 +55,7 @@
//! score state.
//!
//! ```
//! use rosu_pp::{Beatmap, GradualPerformance, Difficulty, any::ScoreState};
//! use akatsuki_pp::{Beatmap, GradualPerformance, Difficulty, any::ScoreState};
//!
//! let map = Beatmap::from_path("./resources/1028484.osu").unwrap();
//!
Expand Down Expand Up @@ -177,6 +177,9 @@ pub mod catch;
/// Types for osu!mania calculations.
pub mod mania;

/// Types for osu!standard 2019 for relax calculations.
pub mod osu_2019;

/// Types used in and around this crate.
pub mod model;

Expand Down
4 changes: 2 additions & 2 deletions src/mania/difficulty/gradual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ use super::{
/// # Example
///
/// ```
/// use rosu_pp::{Beatmap, Difficulty};
/// use rosu_pp::mania::ManiaGradualDifficulty;
/// use akatsuki_pp::{Beatmap, Difficulty};
/// use akatsuki_pp::mania::ManiaGradualDifficulty;
///
/// let map = Beatmap::from_path("./resources/1638954.osu").unwrap();
///
Expand Down
4 changes: 2 additions & 2 deletions src/mania/performance/gradual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ use super::{ManiaPerformanceAttributes, ManiaScoreState};
/// # Example
///
/// ```
/// use rosu_pp::{Beatmap, Difficulty};
/// use rosu_pp::mania::{Mania, ManiaGradualPerformance, ManiaScoreState};
/// use akatsuki_pp::{Beatmap, Difficulty};
/// use akatsuki_pp::mania::{Mania, ManiaGradualPerformance, ManiaScoreState};
///
/// let map = Beatmap::from_path("./resources/1638954.osu").unwrap();
///
Expand Down
20 changes: 19 additions & 1 deletion src/model/beatmap/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use rosu_map::{
hit_samples::{HitSoundType, ParseHitSoundTypeError},
HitObjectType, ParseHitObjectTypeError, PathControlPoint, PathType,
},
metadata::MetadataKey,
timing_points::{ControlPoint, EffectFlags, ParseEffectFlagsError},
},
util::{KeyValue, ParseNumber, ParseNumberError, Pos, StrExt, MAX_PARSE_VALUE},
Expand Down Expand Up @@ -49,6 +50,9 @@ pub struct BeatmapState {
curve_points: Vec<PathControlPoint>,
vertices: Vec<PathControlPoint>,
point_split: Vec<*const str>,

creator: String,
beatmap_id: i32,
}

impl BeatmapState {
Expand Down Expand Up @@ -270,6 +274,8 @@ impl DecodeState for BeatmapState {
vertices: Vec::with_capacity(8),
// mean=19.97 | median=8
point_split: Vec::with_capacity(8),
creator: String::default(),
beatmap_id: i32::default(),
}
}
}
Expand Down Expand Up @@ -315,6 +321,8 @@ impl From<BeatmapState> for Beatmap {
effect_points: state.effect_points,
hit_objects: state.hit_objects,
hit_sounds: state.hit_sounds,
creator: state.creator,
beatmap_id: state.beatmap_id,
}
}
}
Expand Down Expand Up @@ -447,7 +455,17 @@ impl DecodeBeatmap for Beatmap {
Ok(())
}

fn parse_metadata(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
fn parse_metadata(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
let Ok(KeyValue { key, value }) = KeyValue::parse(line.trim_comment()) else {
return Ok(());
};

match key {
MetadataKey::Creator => state.creator = value.to_string(),
MetadataKey::BeatmapID => state.beatmap_id = value.parse_num()?,
_ => {}
}

Ok(())
}

Expand Down
5 changes: 5 additions & 0 deletions src/model/beatmap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ pub struct Beatmap {
// HitObjects
pub hit_objects: Vec<HitObject>,
pub hit_sounds: Vec<HitSoundType>,

pub creator: String,
pub beatmap_id: i32,
}

impl Beatmap {
Expand Down Expand Up @@ -215,6 +218,8 @@ impl Default for Beatmap {
effect_points: Vec::default(),
hit_objects: Vec::default(),
hit_sounds: Vec::default(),
creator: String::default(),
beatmap_id: i32::default(),
}
}
}
6 changes: 5 additions & 1 deletion src/model/mods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use rosu_mods::{
/// # Example
///
/// ```
/// use rosu_pp::GameMods;
/// use akatsuki_pp::GameMods;
/// use rosu_mods::{GameModsIntermode, GameModsLegacy, GameMods as GameModsLazer};
///
/// let int = GameMods::from(64 + 8);
Expand Down Expand Up @@ -305,6 +305,10 @@ impl_has_mod! {
rx: + Relax ["Relax"],
fl: + Flashlight ["Flashlight"],
so: + SpunOut ["SpunOut"],
dt: + DoubleTime ["DoubleTime"],
nc: + Nightcore ["Nightcore"],
ht: + HalfTime ["HalfTime"],
ap: + Autopilot ["Autopilot"],
bl: - Blinds ["Blinds"],
tc: - Traceable ["Traceable"],
}
Expand Down
4 changes: 2 additions & 2 deletions src/osu/difficulty/gradual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ use super::{
/// # Example
///
/// ```
/// use rosu_pp::{Beatmap, Difficulty};
/// use rosu_pp::osu::{Osu, OsuGradualDifficulty};
/// use akatsuki_pp::{Beatmap, Difficulty};
/// use akatsuki_pp::osu::{Osu, OsuGradualDifficulty};
///
/// let map = Beatmap::from_path("./resources/2785319.osu").unwrap();
///
Expand Down
Loading