updated readme
This commit is contained in:
@@ -4,187 +4,81 @@ Difficulty and performance calculation for all [osu!](https://osu.ppy.sh/) modes
|
||||
|
||||
This is a python binding to the Rust library [rosu-pp](https://github.com/MaxOhn/rosu-pp) which was bootstrapped through [PyO3](https://github.com/PyO3/PyO3).
|
||||
Since all the heavy lifting is done by Rust, rosu-pp-py comes with a very fast performance.
|
||||
Check out rosu-pp's README for more info.
|
||||
Check out rosu-pp's [README](https://github.com/MaxOhn/rosu-pp/blob/main/README.md) for more info.
|
||||
|
||||
## Exposed types
|
||||
|
||||
The library exposes the following classes:
|
||||
|
||||
- `Calculator`: Contains various parameters to calculate strains or map, difficulty, or performance attributes
|
||||
- `Beatmap`: Contains a parsed beatmap; passed to the calculator
|
||||
- `BeatmapAttributes`: Contains various attributes about the map itself
|
||||
- `DifficultyAttributes`: Contains various attributes about the difficulty based on the mode
|
||||
- `PerformanceAttributes`: Contains various attributes about the performance and difficulty based on the mode
|
||||
- `Strains`: Contains strain values for each skill based on the mode
|
||||
|
||||
Additionally, the following error types are exposed:
|
||||
- `ParseError`: Failed to parse a beatmap
|
||||
- `KwargsError`: Invalid kwargs were provided
|
||||
|
||||
## How to use rosu-pp-py
|
||||
|
||||
The library exposes four classes: `Calculator`, `ScoreParams`, `CalculateResult`, and `Strains`.
|
||||
1) The first step is to create a new `Beatmap` instance by providing appropriate kwargs.
|
||||
Either of the kwargs `path`, `content`, or `bytes` **must** be given. The kwargs `ar`, `cs`, `hp`, and `od` are optional.
|
||||
With the setters `set_ar`, `set_cs`, `set_hp`, and `set_od` you can specify custom attributes.
|
||||
```py
|
||||
map = Beatmap(path = "/path/to/file.osu", ar = 9.87)
|
||||
map.set_od(1.23)
|
||||
|
||||
1) The first step is to create a new `Calculator` instance by providing the constructor the path to a `.osu` beatmap file like so
|
||||
with open("/path/to/file.osu", "rb") as file:
|
||||
map = Beatmap(bytes = file.read())
|
||||
|
||||
with open("/path/to/file.osu") as file:
|
||||
map = Beatmap(content = file.read())
|
||||
```
|
||||
|
||||
2) Next, you need to create `Calculator` instance by providing the appropriate kwargs again.
|
||||
Any of the following kwargs are allowed: `mode`, `mods`, `acc`, `n_geki`, `n_katu`, `n300`, `n100`, `n50`, `n_misses`, `combo`, `passed_objects`, `clock_rate`, and `difficulty`.
|
||||
Each of these also have a setter method e.g. `set_n_misses`.
|
||||
```py
|
||||
calculator = Calculator('/path/to/file.osu')
|
||||
calc = Calculator(mode = 2, acc = 98.76)
|
||||
calc.set_mods(8 + 64) # HDDT
|
||||
```
|
||||
Optionally, you can also provide the kwargs `ar`, `cs`, `hp`, or `od` to adjust the map's attributes
|
||||
or alternatively, after creating the calculator, you can call `set_ar(v)`, `set_cs(v)`, `set_hp(v)`, or `set_od(v)`.
|
||||
```py
|
||||
calculator = Calculator('/path/to/file.osu', ar = 10.0)
|
||||
calculator.set_od(9.2)
|
||||
```
|
||||
2) Next, you need to create `ScoreParams`. It has the following fields:
|
||||
```
|
||||
mode: Optional[int],
|
||||
specify for scores on convert maps, default to the map's native mode
|
||||
available values are 0 for standard, 1 for taiko, 2 for catch, and 3 for mania
|
||||
mods: Optional[int],
|
||||
bit value for mods, defaults to 0 (NM) see https://github.com/ppy/osu-api/wiki#mods
|
||||
acc: Optional[float],
|
||||
if neither acc nor hitresults are specified, acc defaults to 100.0
|
||||
n300: Optional[int],
|
||||
defaults to value based on acc
|
||||
n100: Optional[int],
|
||||
defaults to value based on acc
|
||||
n50: Optional[int],
|
||||
defaults to value based on acc
|
||||
nMisses: Optional[int],
|
||||
defaults to 0
|
||||
nKatu: Optional[int],
|
||||
only relevant for osu!ctb
|
||||
combo: Optional[int],
|
||||
defaults to full combo
|
||||
score: Optional[int],
|
||||
only relevant for osu!mania
|
||||
passedObjects: Optional[int],
|
||||
only consider this many hit objects; useful for failed scores; defaults to all objects
|
||||
clockRate: Optional[float]
|
||||
defaults to the mod's clock rate.
|
||||
```
|
||||
Note that all fields are optional. If nothing is specified, the parameters are equivalent to the parameters of the best possible NM score.
|
||||
`ScoreParams` can be created either by calling the constructor without arguments and then set the fields manually like so
|
||||
```py
|
||||
params = ScoreParams()
|
||||
params.acc = 98.76
|
||||
```
|
||||
or they can be created by passing kwargs to the constructor directly like so
|
||||
```py
|
||||
params = ScoreParams(acc = 98.76)
|
||||
```
|
||||
3) The last step is to provide the `ScoreParams` to the `Calculator` through the function `calculate`. This function takes one argument which must be either a single `ScoreParams` or an `Iterable[ScoreParams]`, i.e. anything that python can iterate over like a list, set, ...
|
||||
|
||||
3) The last step is to call any of the methods `map_attributes`, `difficulty`, `performance`, or `strains` on the calculator and provide them a `Beatmap`.
|
||||
|
||||
## Example
|
||||
|
||||
```py
|
||||
from rosu_pp_py import Calculator, ScoreParams
|
||||
from rosu_pp_py import Beatmap, Calculator
|
||||
|
||||
calculator = Calculator('./maps/1980365.osu')
|
||||
map = Beatmap(path = './maps/100.osu')
|
||||
calc = Calculator(mods = 8)
|
||||
|
||||
params1 = ScoreParams(
|
||||
mods = 8 + 16, # HDHR
|
||||
acc = 97.89,
|
||||
nMisses = 13,
|
||||
combo = 1388,
|
||||
)
|
||||
params2 = ScoreParams(mods = 24)
|
||||
# Calculate an SS on HD
|
||||
max_perf = calc.performance(map)
|
||||
|
||||
# provide params for a single score, returns a list with one element
|
||||
[result] = calculator.calculate(params1)
|
||||
# The mods are still set to HD
|
||||
calc.set_acc(99.11)
|
||||
calc.set_n_misses(1)
|
||||
calc.set_combo(200)
|
||||
|
||||
# provide multiple params
|
||||
results = calculator.calculate([params1, params2])
|
||||
# A good way to speed up the calculation is to provide
|
||||
# the difficulty attributes of a previous calculation
|
||||
# so that they don't need to be recalculated.
|
||||
# **Note** that this should only be done if neither
|
||||
# the map, mode, mods, nor passed objects amount changed.
|
||||
calc.set_difficulty(max_perf.difficulty)
|
||||
|
||||
assert result == results[0]
|
||||
curr_perf = calc.performance(map)
|
||||
|
||||
print(f'PP: {results[0].pp}/{results[1].pp} | Stars: {results[1].stars}')
|
||||
```
|
||||
print(f'PP: {curr_perf.pp}/{max_perf.pp} | Stars: {max_perf.difficulty.stars}')
|
||||
|
||||
## Return object structure
|
||||
map_attrs = calc.map_attributes(map)
|
||||
print(f'BPM: {map_attrs.bpm}')
|
||||
|
||||
The `Calculator::calculate` function will provide you a **list of `CalculateResult`**, one for each score you specified parameters for. `CalculateResult` contains the difficulty and performance attributes. Most of its attributes are optional based on the map's mode. In the following, O/T/C/M will denote for which mode the given attribute will be present:
|
||||
|
||||
```
|
||||
mode: int
|
||||
Gamemode of the map, 0=O, 1=T, 2=C, 3=M. (O/T/C/M)
|
||||
stars: float
|
||||
Star rating of the map. (O/T/C/M)
|
||||
pp: float
|
||||
Performance points of the score. (O/T/C/M)
|
||||
ppAcc: Optional[float]
|
||||
Accuracy based portion of the performance points. (O/T/M)
|
||||
ppAim: Optional[float]
|
||||
Aim based portion of the performance points. (O)
|
||||
ppFlashlight: Optional[float]
|
||||
Flashlight based portion of the performance points. (O)
|
||||
ppSpeed: Optional[float]
|
||||
Speed based portion of the performance points. (O)
|
||||
ppStrain: Optional[float]
|
||||
Strain based portion of the performance points. (T/M)
|
||||
nFruits: Optional[int]
|
||||
The amount of fruits in the map. (C)
|
||||
nDroplets: Optional[int]
|
||||
The amount of droplets in the map. (C)
|
||||
nTinyDroplets: Optional[int]
|
||||
The amount of tiny droplets in the map. (C)
|
||||
aimStrain: Optional[float]
|
||||
Aim based portion of the star rating. (O)
|
||||
speedStrain: Optional[float]
|
||||
Speed based portion of the star rating. (O)
|
||||
flashlightRating: Optional[float]
|
||||
Flashlight based portion of the star rating. (O)
|
||||
sliderFactor: Optional[float]
|
||||
Nerf factor for sliders. (O)
|
||||
ar: float
|
||||
Approach rate of the map. (O/T/C/M)
|
||||
cs: float
|
||||
Circle size of the map. (O/T/C/M)
|
||||
hp: float
|
||||
Health drain rate of the map. (O/T/C/M)
|
||||
od: float
|
||||
Overall difficulty of the map. (O/T/C/M)
|
||||
bpm: float
|
||||
Beats per minute of the map. (O/T/C/M)
|
||||
clockRate: float
|
||||
Clock rate used in calculation i.e. 1.5 for DT, 0.75 for HT, 1.0 for NM or one that was specified (O/T/C/M)
|
||||
timePreempt: Optional[float]
|
||||
The time in milliseconds in which the circles is visible before being clicked. (O)
|
||||
greatHitWindow: Optional[float]
|
||||
The time in milliseconds in which a 300 ("great") is achievable. (O/T/M)
|
||||
nCircles: Optional[int]
|
||||
The amount of circles in the map. (O/T/M)
|
||||
nSliders: Optional[int]
|
||||
The amount of sliders in the map. (O/T/M)
|
||||
nSpinners: Optional[int]
|
||||
The amount of spinners in the map. (O/T/C)
|
||||
maxCombo: Optional[int]
|
||||
The max combo of the map. (O/T/C)
|
||||
```
|
||||
|
||||
## Calculating strains
|
||||
|
||||
If you want to plot the difficulty of a map over time, you can calculate the strain values.
|
||||
The return type of `Calculator::strains` is an instance of the `Strains` class.
|
||||
Its attributes depend on the map's game mode again and look as follows:
|
||||
```
|
||||
sectionLength: float
|
||||
The time in milliseconds between two strain values. (O/T/C/M)
|
||||
aim: List[float]
|
||||
Strain values for the aim skill (O)
|
||||
aimNoSliders: List[float]
|
||||
Strain values for the aim skill without sliders (O)
|
||||
speed: List[float]
|
||||
Strain values for the speed skill (O)
|
||||
flashlight: List[float]
|
||||
Strain values for the flashlight skill (O)
|
||||
color: List[float]
|
||||
Strain values for the color skill (T)
|
||||
rhythm: List[float]
|
||||
Strain values for the rhythm skill (T)
|
||||
staminaLeft: List[float]
|
||||
Strain values for the left-stamina skill (T)
|
||||
staminaRight: List[float]
|
||||
Strain values for the right-stamina skill (T)
|
||||
strains: List[float]
|
||||
Strain values for the strain skill (M)
|
||||
movement: List[float]
|
||||
Strain values for the movement skill (C)
|
||||
```
|
||||
Here's a small example
|
||||
```py
|
||||
from rosu_pp_py import Calculator
|
||||
|
||||
calculator = Calculator('./maps/1980365.osu')
|
||||
strains = calculator.strains(8 + 16) # HDHR
|
||||
for i,strain in enumerate(strains.aim):
|
||||
currTime = i * strains.sectionLength
|
||||
print(f'Aim strain at {currTime}ms: {strain}')
|
||||
strains = calc.strains(map)
|
||||
print(f'Maximum aim strain: {max(strains.aim)}')
|
||||
```
|
||||
|
||||
## Installing rosu-pp-py
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ class Calculator:
|
||||
`'clock_rate': float`
|
||||
Specify a custom clock rate
|
||||
`'difficulty': DifficultyAttributes`
|
||||
If you perform multiple calculations and neither map, mode, mods, nor passed objects change,
|
||||
If you perform multiple calculations and neither map, mode, mods, nor passed objects amount change,
|
||||
pass the difficulty attributes from a previous calculation so that they don't have to be recalculated
|
||||
|
||||
## Raises
|
||||
|
||||
Reference in New Issue
Block a user