158 lines
4.1 KiB
Python
158 lines
4.1 KiB
Python
from __future__ import annotations
|
|
|
|
from enum import Enum
|
|
from typing import TYPE_CHECKING, Literal, TypedDict
|
|
|
|
from .mods import API_MODS, APIMod, init_mods
|
|
|
|
from pydantic import BaseModel, Field, ValidationInfo, field_validator
|
|
|
|
if TYPE_CHECKING:
|
|
import rosu_pp_py as rosu
|
|
|
|
|
|
class GameMode(str, Enum):
|
|
OSU = "osu"
|
|
TAIKO = "taiko"
|
|
FRUITS = "fruits"
|
|
MANIA = "mania"
|
|
OSURX = "osurx"
|
|
OSUAP = "osuap"
|
|
|
|
def to_rosu(self) -> "rosu.GameMode":
|
|
import rosu_pp_py as rosu
|
|
|
|
return {
|
|
GameMode.OSU: rosu.GameMode.Osu,
|
|
GameMode.TAIKO: rosu.GameMode.Taiko,
|
|
GameMode.FRUITS: rosu.GameMode.Catch,
|
|
GameMode.MANIA: rosu.GameMode.Mania,
|
|
GameMode.OSURX: rosu.GameMode.Osu,
|
|
GameMode.OSUAP: rosu.GameMode.Osu,
|
|
}[self]
|
|
|
|
|
|
MODE_TO_INT = {
|
|
GameMode.OSU: 0,
|
|
GameMode.TAIKO: 1,
|
|
GameMode.FRUITS: 2,
|
|
GameMode.MANIA: 3,
|
|
GameMode.OSURX: 0,
|
|
GameMode.OSUAP: 0,
|
|
}
|
|
INT_TO_MODE = {v: k for k, v in MODE_TO_INT.items()}
|
|
INT_TO_MODE[0] = GameMode.OSU
|
|
|
|
|
|
class Rank(str, Enum):
|
|
X = "X"
|
|
XH = "XH"
|
|
S = "S"
|
|
SH = "SH"
|
|
A = "A"
|
|
B = "B"
|
|
C = "C"
|
|
D = "D"
|
|
F = "F"
|
|
|
|
@property
|
|
def in_statisctics(self):
|
|
return self in {
|
|
Rank.X,
|
|
Rank.XH,
|
|
Rank.S,
|
|
Rank.SH,
|
|
Rank.A,
|
|
}
|
|
|
|
|
|
# https://github.com/ppy/osu/blob/master/osu.Game/Rulesets/Scoring/HitResult.cs
|
|
class HitResult(str, Enum):
|
|
PERFECT = "perfect" # [Order(0)]
|
|
GREAT = "great" # [Order(1)]
|
|
GOOD = "good" # [Order(2)]
|
|
OK = "ok" # [Order(3)]
|
|
MEH = "meh" # [Order(4)]
|
|
MISS = "miss" # [Order(5)]
|
|
|
|
LARGE_TICK_HIT = "large_tick_hit" # [Order(6)]
|
|
SMALL_TICK_HIT = "small_tick_hit" # [Order(7)]
|
|
SLIDER_TAIL_HIT = "slider_tail_hit" # [Order(8)]
|
|
|
|
LARGE_BONUS = "large_bonus" # [Order(9)]
|
|
SMALL_BONUS = "small_bonus" # [Order(10)]
|
|
|
|
LARGE_TICK_MISS = "large_tick_miss" # [Order(11)]
|
|
SMALL_TICK_MISS = "small_tick_miss" # [Order(12)]
|
|
|
|
IGNORE_HIT = "ignore_hit" # [Order(13)]
|
|
IGNORE_MISS = "ignore_miss" # [Order(14)]
|
|
|
|
NONE = "none" # [Order(15)]
|
|
COMBO_BREAK = "combo_break" # [Order(16)]
|
|
|
|
LEGACY_COMBO_INCREASE = "legacy_combo_increase" # [Order(99)] @deprecated
|
|
|
|
def is_hit(self) -> bool:
|
|
return self not in (
|
|
HitResult.NONE,
|
|
HitResult.IGNORE_MISS,
|
|
HitResult.COMBO_BREAK,
|
|
HitResult.LARGE_TICK_MISS,
|
|
HitResult.SMALL_TICK_MISS,
|
|
HitResult.MISS,
|
|
)
|
|
|
|
|
|
class LeaderboardType(Enum):
|
|
GLOBAL = "global"
|
|
FRIENDS = "friend"
|
|
COUNTRY = "country"
|
|
TEAM = "team"
|
|
|
|
|
|
ScoreStatistics = dict[HitResult, int]
|
|
|
|
|
|
class SoloScoreSubmissionInfo(BaseModel):
|
|
rank: Rank
|
|
total_score: int = Field(ge=0, le=2**31 - 1)
|
|
total_score_without_mods: int = Field(ge=0, le=2**31 - 1)
|
|
accuracy: float = Field(ge=0, le=1)
|
|
pp: float = Field(default=0, ge=0, le=2**31 - 1)
|
|
max_combo: int = 0
|
|
ruleset_id: Literal[0, 1, 2, 3]
|
|
passed: bool = False
|
|
mods: list[APIMod] = Field(default_factory=list)
|
|
statistics: ScoreStatistics = Field(default_factory=dict)
|
|
maximum_statistics: ScoreStatistics = Field(default_factory=dict)
|
|
|
|
@field_validator("mods", mode="after")
|
|
@classmethod
|
|
def validate_mods(cls, mods: list[APIMod], info: ValidationInfo):
|
|
if not API_MODS:
|
|
init_mods()
|
|
incompatible_mods = set()
|
|
# check incompatible mods
|
|
for mod in mods:
|
|
if mod["acronym"] in incompatible_mods:
|
|
raise ValueError(
|
|
f"Mod {mod['acronym']} is incompatible with other mods"
|
|
)
|
|
setting_mods = API_MODS[info.data["ruleset_id"]].get(mod["acronym"])
|
|
if not setting_mods:
|
|
raise ValueError(f"Invalid mod: {mod['acronym']}")
|
|
incompatible_mods.update(setting_mods["IncompatibleMods"])
|
|
return mods
|
|
|
|
|
|
class LegacyReplaySoloScoreInfo(TypedDict):
|
|
online_id: int
|
|
mods: list[APIMod]
|
|
statistics: ScoreStatistics
|
|
maximum_statistics: ScoreStatistics
|
|
client_version: str
|
|
rank: Rank
|
|
user_id: int
|
|
total_score_without_mods: int
|