feat(multiplayer): support leaderboard
This commit is contained in:
@@ -16,12 +16,15 @@ from .lazer_user import (
|
||||
UserResp,
|
||||
)
|
||||
from .playlist_attempts import ItemAttemptsCount
|
||||
from .playlist_best_score import PlaylistBestScore
|
||||
from .playlists import Playlist, PlaylistResp
|
||||
from .pp_best_score import PPBestScore
|
||||
from .relationship import Relationship, RelationshipResp, RelationshipType
|
||||
from .room import Room, RoomResp
|
||||
from .score import (
|
||||
MultiplayerScores,
|
||||
Score,
|
||||
ScoreAround,
|
||||
ScoreBase,
|
||||
ScoreResp,
|
||||
ScoreStatistics,
|
||||
@@ -47,9 +50,11 @@ __all__ = [
|
||||
"DailyChallengeStatsResp",
|
||||
"FavouriteBeatmapset",
|
||||
"ItemAttemptsCount",
|
||||
"MultiplayerScores",
|
||||
"OAuthToken",
|
||||
"PPBestScore",
|
||||
"Playlist",
|
||||
"PlaylistBestScore",
|
||||
"PlaylistResp",
|
||||
"Relationship",
|
||||
"RelationshipResp",
|
||||
@@ -57,6 +62,7 @@ __all__ = [
|
||||
"Room",
|
||||
"RoomResp",
|
||||
"Score",
|
||||
"ScoreAround",
|
||||
"ScoreBase",
|
||||
"ScoreResp",
|
||||
"ScoreStatistics",
|
||||
|
||||
107
app/database/playlist_best_score.py
Normal file
107
app/database/playlist_best_score.py
Normal file
@@ -0,0 +1,107 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .lazer_user import User
|
||||
|
||||
from redis.asyncio import Redis
|
||||
from sqlmodel import (
|
||||
BigInteger,
|
||||
Column,
|
||||
Field,
|
||||
ForeignKey,
|
||||
Relationship,
|
||||
SQLModel,
|
||||
col,
|
||||
func,
|
||||
select,
|
||||
)
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .score import Score
|
||||
|
||||
|
||||
class PlaylistBestScore(SQLModel, table=True):
|
||||
__tablename__ = "playlist_best_scores" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
user_id: int = Field(
|
||||
sa_column=Column(BigInteger, ForeignKey("lazer_users.id"), index=True)
|
||||
)
|
||||
score_id: int = Field(
|
||||
sa_column=Column(BigInteger, ForeignKey("scores.id"), primary_key=True)
|
||||
)
|
||||
room_id: int = Field(foreign_key="rooms.id", index=True)
|
||||
playlist_id: int = Field(foreign_key="room_playlists.id", index=True)
|
||||
total_score: int = Field(default=0, sa_column=Column(BigInteger))
|
||||
|
||||
user: User = Relationship()
|
||||
score: "Score" = Relationship(
|
||||
sa_relationship_kwargs={
|
||||
"foreign_keys": "[PlaylistBestScore.score_id]",
|
||||
"lazy": "joined",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def process_playlist_best_score(
|
||||
room_id: int,
|
||||
playlist_id: int,
|
||||
user_id: int,
|
||||
score_id: int,
|
||||
total_score: int,
|
||||
session: AsyncSession,
|
||||
redis: Redis,
|
||||
):
|
||||
previous = (
|
||||
await session.exec(
|
||||
select(PlaylistBestScore).where(
|
||||
PlaylistBestScore.room_id == room_id,
|
||||
PlaylistBestScore.playlist_id == playlist_id,
|
||||
PlaylistBestScore.user_id == user_id,
|
||||
)
|
||||
)
|
||||
).first()
|
||||
if previous is None:
|
||||
score = PlaylistBestScore(
|
||||
user_id=user_id,
|
||||
score_id=score_id,
|
||||
room_id=room_id,
|
||||
playlist_id=playlist_id,
|
||||
total_score=total_score,
|
||||
)
|
||||
session.add(score)
|
||||
else:
|
||||
previous.score_id = score_id
|
||||
previous.total_score = total_score
|
||||
await session.commit()
|
||||
await redis.decr(f"multiplayer:{room_id}:gameplay:players")
|
||||
|
||||
|
||||
async def get_position(
|
||||
room_id: int,
|
||||
playlist_id: int,
|
||||
score_id: int,
|
||||
session: AsyncSession,
|
||||
) -> int:
|
||||
rownum = (
|
||||
func.row_number()
|
||||
.over(
|
||||
partition_by=(
|
||||
col(PlaylistBestScore.playlist_id),
|
||||
col(PlaylistBestScore.room_id),
|
||||
),
|
||||
order_by=col(PlaylistBestScore.total_score).desc(),
|
||||
)
|
||||
.label("row_number")
|
||||
)
|
||||
subq = (
|
||||
select(PlaylistBestScore, rownum)
|
||||
.where(
|
||||
PlaylistBestScore.playlist_id == playlist_id,
|
||||
PlaylistBestScore.room_id == room_id,
|
||||
)
|
||||
.subquery()
|
||||
)
|
||||
stmt = select(subq.c.row_number).where(subq.c.score_id == score_id)
|
||||
result = await session.exec(stmt)
|
||||
s = result.one_or_none()
|
||||
return s if s else 0
|
||||
@@ -26,7 +26,7 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class PlaylistBase(SQLModel, UTCBaseModel):
|
||||
id: int = 0
|
||||
id: int = Field(index=True)
|
||||
owner_id: int = Field(sa_column=Column(BigInteger, ForeignKey("lazer_users.id")))
|
||||
ruleset_id: int = Field(ge=0, le=3)
|
||||
expired: bool = Field(default=False)
|
||||
|
||||
@@ -3,7 +3,7 @@ from collections.abc import Sequence
|
||||
from datetime import UTC, date, datetime
|
||||
import json
|
||||
import math
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from app.calculator import (
|
||||
calculate_pp,
|
||||
@@ -14,7 +14,7 @@ from app.calculator import (
|
||||
clamp,
|
||||
)
|
||||
from app.database.team import TeamMember
|
||||
from app.models.model import UTCBaseModel
|
||||
from app.models.model import RespWithCursor, UTCBaseModel
|
||||
from app.models.mods import APIMod, mods_can_get_pp
|
||||
from app.models.score import (
|
||||
INT_TO_MODE,
|
||||
@@ -88,6 +88,7 @@ class ScoreBase(AsyncAttrs, SQLModel, UTCBaseModel):
|
||||
default=0, sa_column=Column(BigInteger), exclude=True
|
||||
)
|
||||
type: str
|
||||
beatmap_id: int = Field(index=True, foreign_key="beatmaps.id")
|
||||
|
||||
# optional
|
||||
# TODO: current_user_attributes
|
||||
@@ -99,7 +100,6 @@ class Score(ScoreBase, table=True):
|
||||
id: int | None = Field(
|
||||
default=None, sa_column=Column(BigInteger, autoincrement=True, primary_key=True)
|
||||
)
|
||||
beatmap_id: int = Field(index=True, foreign_key="beatmaps.id")
|
||||
user_id: int = Field(
|
||||
default=None,
|
||||
sa_column=Column(
|
||||
@@ -162,7 +162,8 @@ class ScoreResp(ScoreBase):
|
||||
maximum_statistics: ScoreStatistics | None = None
|
||||
rank_global: int | None = None
|
||||
rank_country: int | None = None
|
||||
position: int = 1 # TODO
|
||||
position: int | None = None
|
||||
scores_around: "ScoreAround | None" = None
|
||||
|
||||
@classmethod
|
||||
async def from_db(cls, session: AsyncSession, score: Score) -> "ScoreResp":
|
||||
@@ -234,6 +235,16 @@ class ScoreResp(ScoreBase):
|
||||
return s
|
||||
|
||||
|
||||
class MultiplayerScores(RespWithCursor):
|
||||
scores: list[ScoreResp] = Field(default_factory=list)
|
||||
params: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class ScoreAround(SQLModel):
|
||||
higher: MultiplayerScores | None = None
|
||||
lower: MultiplayerScores | None = None
|
||||
|
||||
|
||||
async def get_best_id(session: AsyncSession, score_id: int) -> None:
|
||||
rownum = (
|
||||
func.row_number()
|
||||
|
||||
Reference in New Issue
Block a user