feat(multiplayer): 增加房间用户添加功能并优化房间模型
- 新增 APIUser 模型用于表示房间内的用户 - 扩展 MultiplayerRoom 模型以支持更多房间相关功能 - 添加用户加入房间的路由和相关逻辑 - 优化 Room 模型,增加从 MultiplayerRoom 转换的方法
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from app.database.beatmap import Beatmap
|
||||
from app.database.user import User
|
||||
from app.models.mods import APIMod
|
||||
from app.models.user import APIUser
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -40,6 +41,75 @@ class RoomStatus(str, Enum):
|
||||
PLAYING = "playing"
|
||||
|
||||
|
||||
class MultiplayerRoomState(str, Enum):
|
||||
OPEN = "open"
|
||||
WAITING_FOR_LOAD = "waiting_for_load"
|
||||
PLAYING = "playing"
|
||||
CLOSED = "closed"
|
||||
|
||||
|
||||
class MultiplayerUserState(str, Enum):
|
||||
IDLE = "idle"
|
||||
READY = "ready"
|
||||
WAITING_FOR_LOAD = "waiting_for_load"
|
||||
LOADED = "loaded"
|
||||
READY_FOR_GAMEPLAY = "ready_for_gameplay"
|
||||
PLAYING = "playing"
|
||||
FINISHED_PLAY = "finished_play"
|
||||
RESULTS = "results"
|
||||
SPECTATING = "spectating"
|
||||
|
||||
|
||||
class DownloadState(str, Enum):
|
||||
UNKONWN = "unkown"
|
||||
NOT_DOWNLOADED = "not_downloaded"
|
||||
DOWNLOADING = "downloading"
|
||||
IMPORTING = "importing"
|
||||
LOCALLY_AVAILABLE = "locally_available"
|
||||
|
||||
|
||||
class BeatmapAvailability(BaseModel):
|
||||
state: DownloadState
|
||||
downloadProgress: float | None
|
||||
|
||||
|
||||
class MultiplayerPlaylistItem(BaseModel):
|
||||
id: int = 0
|
||||
ownerId: int = 0
|
||||
beatmapId: int = 0
|
||||
beatmapChecksum: str = ""
|
||||
rulesetId: int = 0
|
||||
requiredMods: list[APIMod] = []
|
||||
allowedMods: list[APIMod] = []
|
||||
expired: bool = False
|
||||
playlistOrder: int = 0
|
||||
playedAt: datetime | None
|
||||
starRating: float = 0.0
|
||||
freestyle: bool = False
|
||||
|
||||
|
||||
class MultiplayerRoomSettings(BaseModel):
|
||||
name: str = "Unnamed room"
|
||||
playlistItemId: int = 0
|
||||
password: str = ""
|
||||
matchType: MatchType = MatchType.HEAD_TO_HEAD
|
||||
queueMode: QueueMode = QueueMode.HOST_ONLY
|
||||
autoStartDuration: timedelta = timedelta(0)
|
||||
autoSkip: bool = False
|
||||
|
||||
|
||||
class MultiplayerRoomUser(BaseModel):
|
||||
id: int
|
||||
state: MultiplayerUserState = MultiplayerUserState.IDLE
|
||||
beatmapAvailability: BeatmapAvailability = BeatmapAvailability(
|
||||
state=DownloadState.UNKONWN, downloadProgress=None
|
||||
)
|
||||
mods: list[APIMod] = []
|
||||
matchState: dict[str, Any] | None
|
||||
rulesetId: int | None
|
||||
beatmapId: int | None
|
||||
|
||||
|
||||
class PlaylistItem(BaseModel):
|
||||
id: int | None
|
||||
owner_id: int
|
||||
@@ -75,18 +145,23 @@ class PlaylistAggregateScore(BaseModel):
|
||||
playlist_item_attempts: list[ItemAttemptsCount]
|
||||
|
||||
|
||||
class MultiplayerCountdown(BaseModel):
|
||||
id: int
|
||||
timeRemaining: timedelta
|
||||
|
||||
|
||||
class Room(BaseModel):
|
||||
id: int | None
|
||||
name: str = ""
|
||||
password: str | None
|
||||
has_password: bool = False
|
||||
host: User | None
|
||||
host: APIUser
|
||||
category: RoomCategory = RoomCategory.NORMAL
|
||||
duration: int | None
|
||||
starts_at: datetime | None
|
||||
ends_at: datetime | None
|
||||
participant_count: int = 0
|
||||
recent_participants: list[User] = []
|
||||
recent_participants: list[APIUser] = []
|
||||
max_attempts: int | None
|
||||
playlist: list[PlaylistItem] = []
|
||||
playlist_item_stats: RoomPlaylistItemStats | None
|
||||
@@ -101,3 +176,60 @@ class Room(BaseModel):
|
||||
status: RoomStatus = RoomStatus.IDLE
|
||||
# availability 字段在当前序列化中未包含,但可能在某些场景下需要
|
||||
availability: RoomAvailability | None
|
||||
|
||||
@classmethod
|
||||
def from_MultiplayerRoom(cls, room: MultiplayerRoom):
|
||||
r = cls.model_validate(room.model_dump())
|
||||
r.id = room.roomId
|
||||
r.name = room.settings.name
|
||||
r.password = room.settings.password
|
||||
r.has_password = bool(room.settings.password)
|
||||
if room.host:
|
||||
r.host.id = room.host.id
|
||||
r.type = room.settings.matchType
|
||||
r.queue_mode = room.settings.queueMode
|
||||
r.auto_start_duration = room.settings.autoStartDuration.seconds
|
||||
r.auto_skip = room.settings.autoSkip
|
||||
r.channel_id = room.channelId
|
||||
if room.state == MultiplayerRoomState.OPEN:
|
||||
r.status = RoomStatus.IDLE
|
||||
elif (
|
||||
room.state == MultiplayerRoomState.WAITING_FOR_LOAD
|
||||
or room.state == MultiplayerRoomState.PLAYING
|
||||
):
|
||||
r.status = RoomStatus.PLAYING
|
||||
elif room.state == MultiplayerRoomState.CLOSED:
|
||||
r.status = RoomStatus.IDLE
|
||||
r.ends_at = datetime.utcnow()
|
||||
playlist_items = []
|
||||
for multiplayer_item in room.playlist:
|
||||
playlist_item = PlaylistItem(
|
||||
id=multiplayer_item.id,
|
||||
owner_id=multiplayer_item.ownerId,
|
||||
ruleset_id=multiplayer_item.rulesetId,
|
||||
expired=multiplayer_item.expired,
|
||||
playlist_order=multiplayer_item.playlistOrder,
|
||||
played_at=multiplayer_item.playedAt,
|
||||
freestyle=multiplayer_item.freestyle,
|
||||
beatmap_id=multiplayer_item.beatmapId,
|
||||
beatmap=None,
|
||||
)
|
||||
playlist_items.append(playlist_item)
|
||||
r.playlist = playlist_items
|
||||
r.participant_count = len(playlist_items)
|
||||
return r
|
||||
|
||||
|
||||
class MultiplayerRoom(BaseModel):
|
||||
roomId: int
|
||||
state: MultiplayerRoomState = MultiplayerRoomState.OPEN
|
||||
settings: MultiplayerRoomSettings = MultiplayerRoomSettings()
|
||||
users: list[MultiplayerRoomUser] = []
|
||||
host: MultiplayerRoomUser | None
|
||||
matchState: dict[str, Any] | None
|
||||
playlist: list[MultiplayerPlaylistItem] = []
|
||||
activeCountdowns: list[MultiplayerCountdown] = []
|
||||
channelId: int = 0
|
||||
|
||||
def __init__(self, roomId: int, **data):
|
||||
super().__init__(roomId=roomId, **data)
|
||||
|
||||
@@ -209,3 +209,7 @@ class User(BaseModel):
|
||||
replays_watched_counts: list[dict] = []
|
||||
team: Team | None = None
|
||||
user_achievements: list[UserAchievement] = []
|
||||
|
||||
|
||||
class APIUser(BaseModel):
|
||||
id: int
|
||||
|
||||
@@ -2,7 +2,11 @@ from __future__ import annotations
|
||||
|
||||
from app.database.room import RoomIndex
|
||||
from app.dependencies.database import get_db, get_redis
|
||||
from app.models.room import Room
|
||||
from app.models.room import (
|
||||
MultiplayerRoom,
|
||||
MultiplayerRoomUser,
|
||||
Room,
|
||||
)
|
||||
|
||||
from .api_router import router
|
||||
|
||||
@@ -27,9 +31,37 @@ async def get_all_rooms(
|
||||
for room_index in all_room_ids:
|
||||
dumped_room = redis.get(str(room_index.id))
|
||||
if dumped_room:
|
||||
actual_room = Room.model_validate_json(str(dumped_room))
|
||||
actual_room = MultiplayerRoom.model_validate_json(str(dumped_room))
|
||||
actual_room = Room.from_MultiplayerRoom(actual_room)
|
||||
if actual_room.status == status and actual_room.category == category:
|
||||
roomsList.append(actual_room)
|
||||
return roomsList
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail="Redis Error")
|
||||
|
||||
|
||||
@router.put("/rooms/{room}/users/{user}", tags=["rooms"], response_model=Room)
|
||||
async def add_user_to_room(
|
||||
room: int, user: int, password: str, db: AsyncSession = Depends(dependency=get_db)
|
||||
):
|
||||
redis = get_redis()
|
||||
if redis:
|
||||
dumped_room = redis.get(str(room))
|
||||
if not dumped_room:
|
||||
raise HTTPException(status_code=404, detail="房间不存在")
|
||||
actual_room = MultiplayerRoom.model_validate_json(str(dumped_room))
|
||||
|
||||
# 验证密码
|
||||
if password != actual_room.settings.password:
|
||||
raise HTTPException(status_code=403, detail="Invalid password")
|
||||
|
||||
# 继续处理加入房间的逻辑
|
||||
actual_room.users.append(
|
||||
MultiplayerRoomUser(
|
||||
id=user, matchState=None, rulesetId=None, beatmapId=None
|
||||
)
|
||||
)
|
||||
actual_room = Room.from_MultiplayerRoom(actual_room)
|
||||
return actual_room
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail="Redis Error")
|
||||
|
||||
Reference in New Issue
Block a user