Files
g0v0-server/app/models/multiplayer_hub.py
2025-08-01 11:08:59 +00:00

169 lines
4.3 KiB
Python

from __future__ import annotations
import datetime
from typing import Annotated, Any, Literal
from .room import (
DownloadState,
MatchType,
MultiplayerRoomState,
MultiplayerUserState,
QueueMode,
RoomCategory,
RoomStatus,
)
from .signalr import (
EnumByIndex,
MessagePackArrayModel,
UserState,
msgpack_union,
msgpack_union_dump,
)
from msgpack_lazer_api import APIMod
from pydantic import BaseModel, Field, field_serializer, field_validator
class MultiplayerClientState(UserState):
room_id: int = 0
class MultiplayerRoomSettings(MessagePackArrayModel):
name: str = "Unnamed Room"
playlist_item_id: int = 0
password: str = ""
match_type: Annotated[MatchType, EnumByIndex(MatchType)] = MatchType.HEAD_TO_HEAD
queue_mode: Annotated[QueueMode, EnumByIndex(QueueMode)] = QueueMode.HOST_ONLY
auto_start_duration: int = 0
auto_skip: bool = False
class BeatmapAvailability(MessagePackArrayModel):
state: Annotated[DownloadState, EnumByIndex(DownloadState)] = DownloadState.UNKNOWN
progress: float | None = None
class _MatchUserState(MessagePackArrayModel): ...
class TeamVersusUserState(_MatchUserState):
team_id: int
type: Literal[0] = Field(0, exclude=True)
MatchUserState = TeamVersusUserState
class _MatchRoomState(MessagePackArrayModel): ...
class MultiplayerTeam(MessagePackArrayModel):
id: int
name: str
class TeamVersusRoomState(_MatchRoomState):
teams: list[MultiplayerTeam] = Field(
default_factory=lambda: [
MultiplayerTeam(id=0, name="Team Red"),
MultiplayerTeam(id=1, name="Team Blue"),
]
)
type: Literal[0] = Field(0, exclude=True)
MatchRoomState = TeamVersusRoomState
class PlaylistItem(MessagePackArrayModel):
id: int
owner_id: int
beatmap_id: int
checksum: str
ruleset_id: int
required_mods: list[APIMod] = Field(default_factory=list)
allowed_mods: list[APIMod] = Field(default_factory=list)
expired: bool
order: int
played_at: datetime.datetime | None = None
star: float
freestyle: bool
class _MultiplayerCountdown(MessagePackArrayModel):
id: int
remaining: int
is_exclusive: bool
class MatchStartCountdown(_MultiplayerCountdown):
type: Literal[0] = Field(0, exclude=True)
class ForceGameplayStartCountdown(_MultiplayerCountdown):
type: Literal[1] = Field(1, exclude=True)
class ServerShuttingDownCountdown(_MultiplayerCountdown):
type: Literal[2] = Field(2, exclude=True)
MultiplayerCountdown = (
MatchStartCountdown | ForceGameplayStartCountdown | ServerShuttingDownCountdown
)
class MultiplayerRoomUser(MessagePackArrayModel):
user_id: int
state: Annotated[MultiplayerUserState, EnumByIndex(MultiplayerUserState)] = (
MultiplayerUserState.IDLE
)
availability: BeatmapAvailability = BeatmapAvailability(
state=DownloadState.UNKNOWN, progress=None
)
mods: list[APIMod] = Field(default_factory=list)
match_state: MatchUserState | None = None
ruleset_id: int | None = None # freestyle
beatmap_id: int | None = None # freestyle
@field_validator("match_state", mode="before")
def union_validate(v: Any):
if isinstance(v, list):
return msgpack_union(v)
return v
@field_serializer("match_state")
def union_serialize(v: Any):
return msgpack_union_dump(v)
class MultiplayerRoom(MessagePackArrayModel):
room_id: int
state: Annotated[MultiplayerRoomState, EnumByIndex(MultiplayerRoomState)]
settings: MultiplayerRoomSettings
users: list[MultiplayerRoomUser] = Field(default_factory=list)
host: MultiplayerRoomUser | None = None
match_state: MatchRoomState | None = None
playlist: list[PlaylistItem] = Field(default_factory=list)
active_cooldowns: list[MultiplayerCountdown] = Field(default_factory=list)
channel_id: int
@field_validator("match_state", mode="before")
def union_validate(v: Any):
if isinstance(v, list):
return msgpack_union(v)
return v
@field_serializer("match_state")
def union_serialize(v: Any):
return msgpack_union_dump(v)
class ServerMultiplayerRoom(BaseModel):
room: MultiplayerRoom
category: RoomCategory
status: RoomStatus
start_at: datetime.datetime