Files
g0v0-server/app/calculators/performance/performance_server.py
MingxuanGame 8790ccad64 feat(pp-calculator): support other pp calculators (#57)
New configurations:

- CALCULATOR="rosu": specific pp calculator
- CALCULATOR_CONFIG='{}': argument passed through into calculator
2025-10-18 19:10:53 +08:00

89 lines
3.7 KiB
Python

from typing import TYPE_CHECKING
from app.models.mods import APIMod
from app.models.performance import (
DIFFICULTY_CLASS,
PERFORMANCE_CLASS,
BeatmapAttributes,
PerformanceAttributes,
)
from app.models.score import GameMode
from ._base import (
CalculateError,
DifficultyError,
PerformanceCalculator as BasePerformanceCalculator,
PerformanceError,
)
from httpx import AsyncClient, HTTPError
if TYPE_CHECKING:
from app.database.score import Score
class PerformanceCalculator(BasePerformanceCalculator):
def __init__(self, server_url: str = "http://localhost:5225") -> None:
self.server_url = server_url
async def calculate_performance(self, beatmap_raw: str, score: "Score") -> PerformanceAttributes:
# https://github.com/GooGuTeam/osu-performance-server#post-performance
async with AsyncClient() as client:
try:
resp = await client.post(
f"{self.server_url}/performance",
json={
"beatmap_id": score.beatmap_id,
"beatmap_file": beatmap_raw,
"checksum": score.map_md5,
"accuracy": score.accuracy,
"combo": score.max_combo,
"mods": score.mods,
"statistics": {
"great": score.n300,
"ok": score.n100,
"meh": score.n50,
"miss": score.nmiss,
"perfect": score.ngeki,
"good": score.nkatu,
"large_tick_hit": score.nlarge_tick_hit or 0,
"large_tick_miss": score.nlarge_tick_miss or 0,
"small_tick_hit": score.nsmall_tick_hit or 0,
"slider_tail_hit": score.nslider_tail_hit or 0,
},
"ruleset": score.gamemode.value,
},
)
if resp.status_code != 200:
raise PerformanceError(f"Failed to calculate performance: {resp.text}")
data = resp.json()
return PERFORMANCE_CLASS.get(score.gamemode, PerformanceAttributes).model_validate(data)
except HTTPError as e:
raise PerformanceError(f"Failed to calculate performance: {e}") from e
except Exception as e:
raise CalculateError(f"Unknown error: {e}") from e
async def calculate_difficulty(
self, beatmap_raw: str, mods: list[APIMod] | None = None, gamemode: GameMode | None = None
) -> BeatmapAttributes:
# https://github.com/GooGuTeam/osu-performance-server#post-difficulty
async with AsyncClient() as client:
try:
resp = await client.post(
f"{self.server_url}/difficulty",
json={
"beatmap_file": beatmap_raw,
"mods": mods or [],
"ruleset": int(gamemode) if gamemode else None,
},
)
if resp.status_code != 200:
raise DifficultyError(f"Failed to calculate difficulty: {resp.text}")
data = resp.json()
ruleset_id = data.pop("ruleset", "osu")
return DIFFICULTY_CLASS.get(GameMode(ruleset_id), BeatmapAttributes).model_validate(data)
except HTTPError as e:
raise DifficultyError(f"Failed to calculate difficulty: {e}") from e
except Exception as e:
raise DifficultyError(f"Unknown error: {e}") from e