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:
@@ -3,6 +3,7 @@ import hashlib
|
||||
import json
|
||||
from typing import Annotated
|
||||
|
||||
from app.calculator import get_calculator
|
||||
from app.calculators.performance import ConvertError
|
||||
from app.database import Beatmap, BeatmapResp, User
|
||||
from app.database.beatmap import calculate_beatmap_attributes
|
||||
@@ -147,7 +148,7 @@ async def get_beatmap_attributes(
|
||||
redis: Redis,
|
||||
fetcher: Fetcher,
|
||||
ruleset: Annotated[GameMode | None, Query(description="指定 ruleset;为空则使用谱面自身模式")] = None,
|
||||
ruleset_id: Annotated[int | None, Query(description="以数字指定 ruleset (与 ruleset 二选一)", ge=0, le=3)] = None,
|
||||
ruleset_id: Annotated[int | None, Query(description="以数字指定 ruleset (与 ruleset 二选一)")] = None,
|
||||
):
|
||||
mods_ = []
|
||||
if mods and mods[0].isdigit():
|
||||
@@ -170,6 +171,10 @@ async def get_beatmap_attributes(
|
||||
)
|
||||
if await redis.exists(key):
|
||||
return DifficultyAttributes.model_validate_json(await redis.get(key)) # pyright: ignore[reportArgumentType]
|
||||
|
||||
if await get_calculator().can_calculate_difficulty(ruleset) is False:
|
||||
raise HTTPException(status_code=422, detail="Cannot calculate difficulty for the specified ruleset")
|
||||
|
||||
try:
|
||||
return await calculate_beatmap_attributes(beatmap_id, ruleset, mods_, redis, fetcher)
|
||||
except HTTPStatusError:
|
||||
|
||||
@@ -374,13 +374,29 @@ async def create_solo_score(
|
||||
db: Database,
|
||||
beatmap_id: Annotated[int, Path(description="谱面 ID")],
|
||||
beatmap_hash: Annotated[str, Form(description="谱面文件哈希")],
|
||||
ruleset_id: Annotated[int, Form(..., ge=0, le=3, description="ruleset 数字 ID (0-3)")],
|
||||
ruleset_id: Annotated[int, Form(..., description="ruleset 数字 ID (0-3)")],
|
||||
current_user: ClientUser,
|
||||
version_hash: Annotated[str, Form(description="游戏版本哈希")] = "",
|
||||
ruleset_hash: Annotated[str, Form(description="ruleset 版本哈希")] = "",
|
||||
):
|
||||
# 立即获取用户ID,避免懒加载问题
|
||||
user_id = current_user.id
|
||||
|
||||
try:
|
||||
gamemode = GameMode.from_int(ruleset_id)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid ruleset ID")
|
||||
|
||||
if not (result := gamemode.check_ruleset_version(ruleset_hash)):
|
||||
logger.info(
|
||||
f"Ruleset version check failed for user {current_user.id} on beatmap {beatmap_id} "
|
||||
f"(ruleset: {ruleset_id}, hash: {ruleset_hash})"
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail=result.error_msg or "Ruleset version check failed",
|
||||
)
|
||||
|
||||
background_task.add_task(_preload_beatmap_for_pp_calculation, beatmap_id)
|
||||
async with db:
|
||||
score_token = ScoreToken(
|
||||
@@ -428,10 +444,26 @@ async def create_playlist_score(
|
||||
playlist_id: int,
|
||||
beatmap_id: Annotated[int, Form(description="谱面 ID")],
|
||||
beatmap_hash: Annotated[str, Form(description="游戏版本哈希")],
|
||||
ruleset_id: Annotated[int, Form(..., ge=0, le=3, description="ruleset 数字 ID (0-3)")],
|
||||
ruleset_id: Annotated[int, Form(..., description="ruleset 数字 ID (0-3)")],
|
||||
current_user: ClientUser,
|
||||
version_hash: Annotated[str, Form(description="谱面版本哈希")] = "",
|
||||
ruleset_hash: Annotated[str, Form(description="ruleset 版本哈希")] = "",
|
||||
):
|
||||
try:
|
||||
gamemode = GameMode.from_int(ruleset_id)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid ruleset ID")
|
||||
|
||||
if not (result := gamemode.check_ruleset_version(ruleset_hash)):
|
||||
logger.info(
|
||||
f"Ruleset version check failed for user {current_user.id} on room {room_id}, playlist {playlist_id},"
|
||||
f" (ruleset: {ruleset_id}, hash: {ruleset_hash})"
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail=result.error_msg or "Ruleset version check failed",
|
||||
)
|
||||
|
||||
if await current_user.is_restricted(session):
|
||||
raise HTTPException(status_code=403, detail="You are restricted from submitting multiplayer scores")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user