feat(custom-rulesets): support custom rulesets (#23)
* feat(custom_ruleset): add custom rulesets support * feat(custom-ruleset): add version check * feat(custom-ruleset): add LegacyIO API to get ruleset hashes * feat(pp): add check for rulesets whose pp cannot be calculated * docs(readme): update README to include support for custom rulesets * fix(custom-ruleset): make `rulesets` empty instead of throw a error when version check is disabled Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore(custom-ruleset): apply the latest changes of generatorc891bcd159ande25041ad3b* feat(calculator): add fallback performance calculation for unsupported modes * fix(calculator): remove debug print * fix: resolve reviews * feat(calculator): add difficulty calculation checks --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
from typing import TYPE_CHECKING
|
||||
import asyncio
|
||||
import datetime
|
||||
from typing import TYPE_CHECKING, TypedDict, cast
|
||||
|
||||
from app.models.mods import APIMod
|
||||
from app.models.performance import (
|
||||
@@ -10,6 +12,7 @@ from app.models.performance import (
|
||||
from app.models.score import GameMode
|
||||
|
||||
from ._base import (
|
||||
AvailableModes,
|
||||
CalculateError,
|
||||
DifficultyError,
|
||||
PerformanceCalculator as BasePerformanceCalculator,
|
||||
@@ -23,10 +26,61 @@ if TYPE_CHECKING:
|
||||
from app.database.score import Score
|
||||
|
||||
|
||||
class PerformanceCalculator(BasePerformanceCalculator):
|
||||
class AvailableRulesetResp(TypedDict):
|
||||
has_performance_calculator: list[str]
|
||||
has_difficulty_calculator: list[str]
|
||||
loaded_rulesets: list[str]
|
||||
|
||||
|
||||
class PerformanceServerPerformanceCalculator(BasePerformanceCalculator):
|
||||
def __init__(self, server_url: str = "http://localhost:5225") -> None:
|
||||
self.server_url = server_url
|
||||
|
||||
self._available_modes: AvailableModes | None = None
|
||||
self._modes_lock = asyncio.Lock()
|
||||
self._today = datetime.date.today()
|
||||
|
||||
async def init(self):
|
||||
await self.get_available_modes()
|
||||
|
||||
def _process_modes(self, modes: AvailableRulesetResp) -> AvailableModes:
|
||||
performance_modes = {
|
||||
m for mode in modes["has_performance_calculator"] if (m := GameMode.parse(mode)) is not None
|
||||
}
|
||||
difficulty_modes = {m for mode in modes["has_difficulty_calculator"] if (m := GameMode.parse(mode)) is not None}
|
||||
if GameMode.OSU in performance_modes:
|
||||
performance_modes.add(GameMode.OSURX)
|
||||
performance_modes.add(GameMode.OSUAP)
|
||||
if GameMode.TAIKO in performance_modes:
|
||||
performance_modes.add(GameMode.TAIKORX)
|
||||
if GameMode.FRUITS in performance_modes:
|
||||
performance_modes.add(GameMode.FRUITSRX)
|
||||
|
||||
return AvailableModes(
|
||||
has_performance_calculator=performance_modes,
|
||||
has_difficulty_calculator=difficulty_modes,
|
||||
)
|
||||
|
||||
async def get_available_modes(self) -> AvailableModes:
|
||||
# https://github.com/GooGuTeam/osu-performance-server#get-available_rulesets
|
||||
if self._available_modes is not None and self._today == datetime.date.today():
|
||||
return self._available_modes
|
||||
async with self._modes_lock, AsyncClient() as client:
|
||||
try:
|
||||
resp = await client.get(f"{self.server_url}/available_rulesets")
|
||||
if resp.status_code != 200:
|
||||
raise CalculateError(f"Failed to get available modes: {resp.text}")
|
||||
modes = cast(AvailableRulesetResp, resp.json())
|
||||
result = self._process_modes(modes)
|
||||
|
||||
self._available_modes = result
|
||||
self._today = datetime.date.today()
|
||||
return result
|
||||
except HTTPError as e:
|
||||
raise CalculateError(f"Failed to get available modes: {e}") from e
|
||||
except Exception as e:
|
||||
raise CalculateError(f"Unknown error: {e}") from e
|
||||
|
||||
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:
|
||||
@@ -74,7 +128,7 @@ class PerformanceCalculator(BasePerformanceCalculator):
|
||||
json={
|
||||
"beatmap_file": beatmap_raw,
|
||||
"mods": mods or [],
|
||||
"ruleset": int(gamemode) if gamemode else None,
|
||||
"ruleset": gamemode.value if gamemode else None,
|
||||
},
|
||||
)
|
||||
if resp.status_code != 200:
|
||||
@@ -84,3 +138,6 @@ class PerformanceCalculator(BasePerformanceCalculator):
|
||||
raise DifficultyError(f"Failed to calculate difficulty: {e}") from e
|
||||
except Exception as e:
|
||||
raise DifficultyError(f"Unknown error: {e}") from e
|
||||
|
||||
|
||||
PerformanceCalculator = PerformanceServerPerformanceCalculator
|
||||
|
||||
Reference in New Issue
Block a user