feat(multiplayer): support multiplayer events
This commit is contained in:
@@ -15,6 +15,7 @@ from .lazer_user import (
|
|||||||
User,
|
User,
|
||||||
UserResp,
|
UserResp,
|
||||||
)
|
)
|
||||||
|
from .multiplayer_event import MultiplayerEvent, MultiplayerEventResp
|
||||||
from .playlist_attempts import ItemAttemptsCount, ItemAttemptsResp
|
from .playlist_attempts import ItemAttemptsCount, ItemAttemptsResp
|
||||||
from .playlist_best_score import PlaylistBestScore
|
from .playlist_best_score import PlaylistBestScore
|
||||||
from .playlists import Playlist, PlaylistResp
|
from .playlists import Playlist, PlaylistResp
|
||||||
@@ -51,6 +52,8 @@ __all__ = [
|
|||||||
"FavouriteBeatmapset",
|
"FavouriteBeatmapset",
|
||||||
"ItemAttemptsCount",
|
"ItemAttemptsCount",
|
||||||
"ItemAttemptsResp",
|
"ItemAttemptsResp",
|
||||||
|
"MultiplayerEvent",
|
||||||
|
"MultiplayerEventResp",
|
||||||
"MultiplayerScores",
|
"MultiplayerScores",
|
||||||
"OAuthToken",
|
"OAuthToken",
|
||||||
"PPBestScore",
|
"PPBestScore",
|
||||||
|
|||||||
53
app/database/multiplayer_event.py
Normal file
53
app/database/multiplayer_event.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
from datetime import UTC, datetime
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from app.models.model import UTCBaseModel
|
||||||
|
|
||||||
|
from sqlmodel import (
|
||||||
|
JSON,
|
||||||
|
BigInteger,
|
||||||
|
Column,
|
||||||
|
DateTime,
|
||||||
|
Field,
|
||||||
|
ForeignKey,
|
||||||
|
SQLModel,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MultiplayerEventBase(SQLModel, UTCBaseModel):
|
||||||
|
playlist_item_id: int | None = None
|
||||||
|
user_id: int | None = Field(
|
||||||
|
default=None,
|
||||||
|
sa_column=Column(BigInteger, ForeignKey("lazer_users.id"), index=True),
|
||||||
|
)
|
||||||
|
created_at: datetime = Field(
|
||||||
|
sa_column=Column(
|
||||||
|
DateTime(timezone=True),
|
||||||
|
),
|
||||||
|
default=datetime.now(UTC),
|
||||||
|
)
|
||||||
|
event_type: str = Field(index=True)
|
||||||
|
|
||||||
|
|
||||||
|
class MultiplayerEvent(MultiplayerEventBase, table=True):
|
||||||
|
__tablename__ = "multiplayer_events" # pyright: ignore[reportAssignmentType]
|
||||||
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
|
room_id: int = Field(foreign_key="rooms.id", index=True)
|
||||||
|
updated_at: datetime = Field(
|
||||||
|
sa_column=Column(
|
||||||
|
DateTime(timezone=True),
|
||||||
|
),
|
||||||
|
default=datetime.now(UTC),
|
||||||
|
)
|
||||||
|
event_detail: dict[str, Any] | None = Field(
|
||||||
|
sa_column=Column(JSON),
|
||||||
|
default_factory=dict,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MultiplayerEventResp(MultiplayerEventBase):
|
||||||
|
id: int
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_db(cls, event: MultiplayerEvent) -> "MultiplayerEventResp":
|
||||||
|
return cls.model_validate(event)
|
||||||
@@ -29,9 +29,7 @@ class ItemAttemptsCountBase(SQLModel):
|
|||||||
|
|
||||||
class ItemAttemptsCount(ItemAttemptsCountBase, table=True):
|
class ItemAttemptsCount(ItemAttemptsCountBase, table=True):
|
||||||
__tablename__ = "item_attempts_count" # pyright: ignore[reportAssignmentType]
|
__tablename__ = "item_attempts_count" # pyright: ignore[reportAssignmentType]
|
||||||
id: int | None = Field(
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
default=None, foreign_key="room_playlists.db_id", primary_key=True
|
|
||||||
)
|
|
||||||
|
|
||||||
user: User = Relationship()
|
user: User = Relationship()
|
||||||
|
|
||||||
|
|||||||
@@ -133,8 +133,11 @@ class PlaylistResp(PlaylistBase):
|
|||||||
beatmap: BeatmapResp | None = None
|
beatmap: BeatmapResp | None = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def from_db(cls, playlist: Playlist) -> "PlaylistResp":
|
async def from_db(
|
||||||
|
cls, playlist: Playlist, include: list[str] = []
|
||||||
|
) -> "PlaylistResp":
|
||||||
data = playlist.model_dump()
|
data = playlist.model_dump()
|
||||||
data["beatmap"] = await BeatmapResp.from_db(playlist.beatmap, from_set=True)
|
if "beatmap" in include:
|
||||||
|
data["beatmap"] = await BeatmapResp.from_db(playlist.beatmap, from_set=True)
|
||||||
resp = cls.model_validate(data)
|
resp = cls.model_validate(data)
|
||||||
return resp
|
return resp
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class RoomResp(RoomBase):
|
|||||||
difficulty_range.max = max(
|
difficulty_range.max = max(
|
||||||
difficulty_range.max, playlist.beatmap.difficulty_rating
|
difficulty_range.max, playlist.beatmap.difficulty_rating
|
||||||
)
|
)
|
||||||
resp.playlist.append(await PlaylistResp.from_db(playlist))
|
resp.playlist.append(await PlaylistResp.from_db(playlist, ["beatmap"]))
|
||||||
stats.ruleset_ids = list(rulesets)
|
stats.ruleset_ids = list(rulesets)
|
||||||
resp.playlist_item_stats = stats
|
resp.playlist_item_stats = stats
|
||||||
resp.difficulty_range = difficulty_range
|
resp.difficulty_range = difficulty_range
|
||||||
|
|||||||
@@ -6,7 +6,16 @@ from collections.abc import Awaitable, Callable
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import UTC, datetime, timedelta
|
from datetime import UTC, datetime, timedelta
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from typing import TYPE_CHECKING, Annotated, Any, ClassVar, Literal, cast, override
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Annotated,
|
||||||
|
Any,
|
||||||
|
ClassVar,
|
||||||
|
Literal,
|
||||||
|
TypedDict,
|
||||||
|
cast,
|
||||||
|
override,
|
||||||
|
)
|
||||||
|
|
||||||
from app.database.beatmap import Beatmap
|
from app.database.beatmap import Beatmap
|
||||||
from app.dependencies.database import engine
|
from app.dependencies.database import engine
|
||||||
@@ -705,6 +714,9 @@ class MatchTypeHandler(ABC):
|
|||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def handle_leave(self, user: MultiplayerRoomUser): ...
|
async def handle_leave(self, user: MultiplayerRoomUser): ...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_details(self) -> MatchStartedEventDetail: ...
|
||||||
|
|
||||||
|
|
||||||
class HeadToHeadHandler(MatchTypeHandler):
|
class HeadToHeadHandler(MatchTypeHandler):
|
||||||
@override
|
@override
|
||||||
@@ -721,6 +733,11 @@ class HeadToHeadHandler(MatchTypeHandler):
|
|||||||
@override
|
@override
|
||||||
async def handle_leave(self, user: MultiplayerRoomUser): ...
|
async def handle_leave(self, user: MultiplayerRoomUser): ...
|
||||||
|
|
||||||
|
@override
|
||||||
|
def get_details(self) -> MatchStartedEventDetail:
|
||||||
|
detail = MatchStartedEventDetail(room_type="head_to_head", team=None)
|
||||||
|
return detail
|
||||||
|
|
||||||
|
|
||||||
class TeamVersusHandler(MatchTypeHandler):
|
class TeamVersusHandler(MatchTypeHandler):
|
||||||
@override
|
@override
|
||||||
@@ -780,6 +797,17 @@ class TeamVersusHandler(MatchTypeHandler):
|
|||||||
@override
|
@override
|
||||||
async def handle_leave(self, user: MultiplayerRoomUser): ...
|
async def handle_leave(self, user: MultiplayerRoomUser): ...
|
||||||
|
|
||||||
|
@override
|
||||||
|
def get_details(self) -> MatchStartedEventDetail:
|
||||||
|
teams: dict[int, Literal["blue", "red"]] = {}
|
||||||
|
for user in self.room.room.users:
|
||||||
|
if user.match_state is not None and isinstance(
|
||||||
|
user.match_state, TeamVersusUserState
|
||||||
|
):
|
||||||
|
teams[user.user_id] = "blue" if user.match_state.team_id == 1 else "red"
|
||||||
|
detail = MatchStartedEventDetail(room_type="team_versus", team=teams)
|
||||||
|
return detail
|
||||||
|
|
||||||
|
|
||||||
MATCH_TYPE_HANDLERS = {
|
MATCH_TYPE_HANDLERS = {
|
||||||
MatchType.HEAD_TO_HEAD: HeadToHeadHandler,
|
MatchType.HEAD_TO_HEAD: HeadToHeadHandler,
|
||||||
@@ -890,3 +918,8 @@ MatchServerEvent = CountdownStartedEvent | CountdownStoppedEvent
|
|||||||
class GameplayAbortReason(IntEnum):
|
class GameplayAbortReason(IntEnum):
|
||||||
LOAD_TOOK_TOO_LONG = 0
|
LOAD_TOOK_TOO_LONG = 0
|
||||||
HOST_ABORTED = 1
|
HOST_ABORTED = 1
|
||||||
|
|
||||||
|
|
||||||
|
class MatchStartedEventDetail(TypedDict):
|
||||||
|
room_type: Literal["playlists", "head_to_head", "team_versus"]
|
||||||
|
team: dict[int, Literal["blue", "red"]] | None
|
||||||
|
|||||||
@@ -3,10 +3,14 @@ from __future__ import annotations
|
|||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
from app.database.lazer_user import User
|
from app.database.beatmap import Beatmap, BeatmapResp
|
||||||
|
from app.database.beatmapset import BeatmapsetResp
|
||||||
|
from app.database.lazer_user import User, UserResp
|
||||||
|
from app.database.multiplayer_event import MultiplayerEvent, MultiplayerEventResp
|
||||||
from app.database.playlist_attempts import ItemAttemptsCount, ItemAttemptsResp
|
from app.database.playlist_attempts import ItemAttemptsCount, ItemAttemptsResp
|
||||||
from app.database.playlists import Playlist, PlaylistResp
|
from app.database.playlists import Playlist, PlaylistResp
|
||||||
from app.database.room import Room, RoomBase, RoomResp
|
from app.database.room import Room, RoomBase, RoomResp
|
||||||
|
from app.database.score import Score
|
||||||
from app.dependencies.database import get_db, get_redis
|
from app.dependencies.database import get_db, get_redis
|
||||||
from app.dependencies.fetcher import get_fetcher
|
from app.dependencies.fetcher import get_fetcher
|
||||||
from app.dependencies.user import get_current_user
|
from app.dependencies.user import get_current_user
|
||||||
@@ -140,7 +144,7 @@ async def add_user_to_room(room: int, user: int, db: AsyncSession = Depends(get_
|
|||||||
resp = await RoomResp.from_hub(server_room)
|
resp = await RoomResp.from_hub(server_room)
|
||||||
await db.refresh(db_room)
|
await db.refresh(db_room)
|
||||||
for item in db_room.playlist:
|
for item in db_room.playlist:
|
||||||
resp.playlist.append(await PlaylistResp.from_db(item))
|
resp.playlist.append(await PlaylistResp.from_db(item, ["beatmap"]))
|
||||||
return resp
|
return resp
|
||||||
else:
|
else:
|
||||||
raise HTTPException(404, "room not found0")
|
raise HTTPException(404, "room not found0")
|
||||||
@@ -178,3 +182,115 @@ async def get_room_leaderboard(
|
|||||||
leaderboard=aggs_resp,
|
leaderboard=aggs_resp,
|
||||||
user_score=user_agg,
|
user_score=user_agg,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RoomEvents(BaseModel):
|
||||||
|
beatmaps: list[BeatmapResp] = Field(default_factory=list)
|
||||||
|
beatmapsets: dict[int, BeatmapsetResp] = Field(default_factory=dict)
|
||||||
|
current_playlist_item_id: int = 0
|
||||||
|
events: list[MultiplayerEventResp] = Field(default_factory=list)
|
||||||
|
first_event_id: int = 0
|
||||||
|
last_event_id: int = 0
|
||||||
|
playlist_items: list[PlaylistResp] = Field(default_factory=list)
|
||||||
|
room: RoomResp
|
||||||
|
user: list[UserResp] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/rooms/{room_id}/events", response_model=RoomEvents, tags=["room"])
|
||||||
|
async def get_room_events(
|
||||||
|
room_id: int,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
limit: int = Query(100, ge=1, le=1000),
|
||||||
|
after: int | None = Query(None, ge=0),
|
||||||
|
before: int | None = Query(None, ge=0),
|
||||||
|
):
|
||||||
|
events = (
|
||||||
|
await db.exec(
|
||||||
|
select(MultiplayerEvent)
|
||||||
|
.where(
|
||||||
|
MultiplayerEvent.room_id == room_id,
|
||||||
|
col(MultiplayerEvent.id) > after if after is not None else True,
|
||||||
|
col(MultiplayerEvent.id) < before if before is not None else True,
|
||||||
|
)
|
||||||
|
.order_by(col(MultiplayerEvent.id).desc())
|
||||||
|
.limit(limit)
|
||||||
|
)
|
||||||
|
).all()
|
||||||
|
|
||||||
|
user_ids = set()
|
||||||
|
playlist_items = {}
|
||||||
|
beatmap_ids = set()
|
||||||
|
|
||||||
|
event_resps = []
|
||||||
|
first_event_id = 0
|
||||||
|
last_event_id = 0
|
||||||
|
|
||||||
|
current_playlist_item_id = 0
|
||||||
|
for event in events:
|
||||||
|
event_resps.append(MultiplayerEventResp.from_db(event))
|
||||||
|
|
||||||
|
if event.user_id:
|
||||||
|
user_ids.add(event.user_id)
|
||||||
|
|
||||||
|
if event.playlist_item_id is not None and (
|
||||||
|
playitem := (
|
||||||
|
await db.exec(
|
||||||
|
select(Playlist).where(
|
||||||
|
Playlist.id == event.playlist_item_id,
|
||||||
|
Playlist.room_id == room_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).first()
|
||||||
|
):
|
||||||
|
current_playlist_item_id = playitem.id
|
||||||
|
playlist_items[event.playlist_item_id] = playitem
|
||||||
|
beatmap_ids.add(playitem.beatmap_id)
|
||||||
|
scores = await db.exec(
|
||||||
|
select(Score).where(
|
||||||
|
Score.playlist_item_id == event.playlist_item_id,
|
||||||
|
Score.room_id == room_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for score in scores:
|
||||||
|
user_ids.add(score.user_id)
|
||||||
|
beatmap_ids.add(score.beatmap_id)
|
||||||
|
|
||||||
|
assert event.id is not None
|
||||||
|
first_event_id = min(first_event_id, event.id)
|
||||||
|
last_event_id = max(last_event_id, event.id)
|
||||||
|
|
||||||
|
if room := MultiplayerHubs.rooms.get(room_id):
|
||||||
|
current_playlist_item_id = room.queue.current_item.id
|
||||||
|
room_resp = await RoomResp.from_hub(room)
|
||||||
|
else:
|
||||||
|
room = (await db.exec(select(Room).where(Room.id == room_id))).first()
|
||||||
|
if room is None:
|
||||||
|
raise HTTPException(404, "Room not found")
|
||||||
|
room_resp = await RoomResp.from_db(room)
|
||||||
|
|
||||||
|
users = await db.exec(select(User).where(col(User.id).in_(user_ids)))
|
||||||
|
user_resps = [await UserResp.from_db(user, db) for user in users]
|
||||||
|
beatmaps = await db.exec(select(Beatmap).where(col(Beatmap.id).in_(beatmap_ids)))
|
||||||
|
beatmap_resps = [
|
||||||
|
await BeatmapResp.from_db(beatmap, session=db) for beatmap in beatmaps
|
||||||
|
]
|
||||||
|
beatmapset_resps = {}
|
||||||
|
for beatmap_resp in beatmap_resps:
|
||||||
|
beatmapset_resps[beatmap_resp.beatmapset_id] = beatmap_resp.beatmapset
|
||||||
|
|
||||||
|
playlist_items_resps = [
|
||||||
|
await PlaylistResp.from_db(item) for item in playlist_items.values()
|
||||||
|
]
|
||||||
|
|
||||||
|
return RoomEvents(
|
||||||
|
beatmaps=beatmap_resps,
|
||||||
|
beatmapsets=beatmapset_resps,
|
||||||
|
current_playlist_item_id=current_playlist_item_id,
|
||||||
|
events=event_resps,
|
||||||
|
first_event_id=first_event_id,
|
||||||
|
last_event_id=last_event_id,
|
||||||
|
playlist_items=playlist_items_resps,
|
||||||
|
room=room_resp,
|
||||||
|
user=user_resps,
|
||||||
|
)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from typing import override
|
|||||||
from app.database import Room
|
from app.database import Room
|
||||||
from app.database.beatmap import Beatmap
|
from app.database.beatmap import Beatmap
|
||||||
from app.database.lazer_user import User
|
from app.database.lazer_user import User
|
||||||
|
from app.database.multiplayer_event import MultiplayerEvent
|
||||||
from app.database.playlists import Playlist
|
from app.database.playlists import Playlist
|
||||||
from app.database.relationship import Relationship, RelationshipType
|
from app.database.relationship import Relationship, RelationshipType
|
||||||
from app.dependencies.database import engine, get_redis
|
from app.dependencies.database import engine, get_redis
|
||||||
@@ -20,6 +21,7 @@ from app.models.multiplayer_hub import (
|
|||||||
MatchRequest,
|
MatchRequest,
|
||||||
MatchServerEvent,
|
MatchServerEvent,
|
||||||
MatchStartCountdown,
|
MatchStartCountdown,
|
||||||
|
MatchStartedEventDetail,
|
||||||
MultiplayerClientState,
|
MultiplayerClientState,
|
||||||
MultiplayerRoom,
|
MultiplayerRoom,
|
||||||
MultiplayerRoomSettings,
|
MultiplayerRoomSettings,
|
||||||
@@ -49,11 +51,100 @@ from sqlmodel.ext.asyncio.session import AsyncSession
|
|||||||
GAMEPLAY_LOAD_TIMEOUT = 30
|
GAMEPLAY_LOAD_TIMEOUT = 30
|
||||||
|
|
||||||
|
|
||||||
|
class MultiplayerEventLogger:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def log_event(self, event: MultiplayerEvent):
|
||||||
|
try:
|
||||||
|
async with AsyncSession(engine) as session:
|
||||||
|
session.add(event)
|
||||||
|
await session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to log multiplayer room event to database: {e}")
|
||||||
|
|
||||||
|
async def room_created(self, room_id: int, user_id: int):
|
||||||
|
event = MultiplayerEvent(
|
||||||
|
room_id=room_id,
|
||||||
|
user_id=user_id,
|
||||||
|
event_type="room_created",
|
||||||
|
)
|
||||||
|
await self.log_event(event)
|
||||||
|
|
||||||
|
async def room_disbanded(self, room_id: int, user_id: int):
|
||||||
|
event = MultiplayerEvent(
|
||||||
|
room_id=room_id,
|
||||||
|
user_id=user_id,
|
||||||
|
event_type="room_disbanded",
|
||||||
|
)
|
||||||
|
await self.log_event(event)
|
||||||
|
|
||||||
|
async def player_joined(self, room_id: int, user_id: int):
|
||||||
|
event = MultiplayerEvent(
|
||||||
|
room_id=room_id,
|
||||||
|
user_id=user_id,
|
||||||
|
event_type="player_joined",
|
||||||
|
)
|
||||||
|
await self.log_event(event)
|
||||||
|
|
||||||
|
async def player_left(self, room_id: int, user_id: int):
|
||||||
|
event = MultiplayerEvent(
|
||||||
|
room_id=room_id,
|
||||||
|
user_id=user_id,
|
||||||
|
event_type="player_left",
|
||||||
|
)
|
||||||
|
await self.log_event(event)
|
||||||
|
|
||||||
|
async def player_kicked(self, room_id: int, user_id: int):
|
||||||
|
event = MultiplayerEvent(
|
||||||
|
room_id=room_id,
|
||||||
|
user_id=user_id,
|
||||||
|
event_type="player_kicked",
|
||||||
|
)
|
||||||
|
await self.log_event(event)
|
||||||
|
|
||||||
|
async def host_changed(self, room_id: int, user_id: int):
|
||||||
|
event = MultiplayerEvent(
|
||||||
|
room_id=room_id,
|
||||||
|
user_id=user_id,
|
||||||
|
event_type="host_changed",
|
||||||
|
)
|
||||||
|
await self.log_event(event)
|
||||||
|
|
||||||
|
async def game_started(
|
||||||
|
self, room_id: int, playlist_item_id: int, details: MatchStartedEventDetail
|
||||||
|
):
|
||||||
|
event = MultiplayerEvent(
|
||||||
|
room_id=room_id,
|
||||||
|
playlist_item_id=playlist_item_id,
|
||||||
|
event_type="game_started",
|
||||||
|
event_detail=details, # pyright: ignore[reportArgumentType]
|
||||||
|
)
|
||||||
|
await self.log_event(event)
|
||||||
|
|
||||||
|
async def game_aborted(self, room_id: int, playlist_item_id: int):
|
||||||
|
event = MultiplayerEvent(
|
||||||
|
room_id=room_id,
|
||||||
|
playlist_item_id=playlist_item_id,
|
||||||
|
event_type="game_aborted",
|
||||||
|
)
|
||||||
|
await self.log_event(event)
|
||||||
|
|
||||||
|
async def game_completed(self, room_id: int, playlist_item_id: int):
|
||||||
|
event = MultiplayerEvent(
|
||||||
|
room_id=room_id,
|
||||||
|
playlist_item_id=playlist_item_id,
|
||||||
|
event_type="game_completed",
|
||||||
|
)
|
||||||
|
await self.log_event(event)
|
||||||
|
|
||||||
|
|
||||||
class MultiplayerHub(Hub[MultiplayerClientState]):
|
class MultiplayerHub(Hub[MultiplayerClientState]):
|
||||||
@override
|
@override
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.rooms: dict[int, ServerMultiplayerRoom] = {}
|
self.rooms: dict[int, ServerMultiplayerRoom] = {}
|
||||||
|
self.event_logger = MultiplayerEventLogger()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def group_id(room: int) -> str:
|
def group_id(room: int) -> str:
|
||||||
@@ -113,6 +204,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
)
|
)
|
||||||
self.rooms[room.room_id] = server_room
|
self.rooms[room.room_id] = server_room
|
||||||
await server_room.set_handler()
|
await server_room.set_handler()
|
||||||
|
await self.event_logger.room_created(room.room_id, client.user_id)
|
||||||
return await self.JoinRoomWithPassword(
|
return await self.JoinRoomWithPassword(
|
||||||
client, room.room_id, room.settings.password
|
client, room.room_id, room.settings.password
|
||||||
)
|
)
|
||||||
@@ -143,6 +235,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
room.users.append(user)
|
room.users.append(user)
|
||||||
self.add_to_group(client, self.group_id(room_id))
|
self.add_to_group(client, self.group_id(room_id))
|
||||||
await server_room.match_type_handler.handle_join(user)
|
await server_room.match_type_handler.handle_join(user)
|
||||||
|
await self.event_logger.player_joined(room_id, user.user_id)
|
||||||
return room
|
return room
|
||||||
|
|
||||||
async def ChangeBeatmapAvailability(
|
async def ChangeBeatmapAvailability(
|
||||||
@@ -550,10 +643,12 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
if all(
|
if all(
|
||||||
u.state != MultiplayerUserState.PLAYING for u in room.room.users
|
u.state != MultiplayerUserState.PLAYING for u in room.room.users
|
||||||
):
|
):
|
||||||
|
any_user_finished_playing = False
|
||||||
for u in filter(
|
for u in filter(
|
||||||
lambda u: u.state == MultiplayerUserState.FINISHED_PLAY,
|
lambda u: u.state == MultiplayerUserState.FINISHED_PLAY,
|
||||||
room.room.users,
|
room.room.users,
|
||||||
):
|
):
|
||||||
|
any_user_finished_playing = True
|
||||||
await self.change_user_state(
|
await self.change_user_state(
|
||||||
room, u, MultiplayerUserState.RESULTS
|
room, u, MultiplayerUserState.RESULTS
|
||||||
)
|
)
|
||||||
@@ -562,6 +657,16 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
self.group_id(room.room.room_id),
|
self.group_id(room.room.room_id),
|
||||||
"ResultsReady",
|
"ResultsReady",
|
||||||
)
|
)
|
||||||
|
if any_user_finished_playing:
|
||||||
|
await self.event_logger.game_completed(
|
||||||
|
room.room.room_id,
|
||||||
|
room.queue.current_item.id,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await self.event_logger.game_aborted(
|
||||||
|
room.room.room_id,
|
||||||
|
room.queue.current_item.id,
|
||||||
|
)
|
||||||
await room.queue.finish_current_item()
|
await room.queue.finish_current_item()
|
||||||
|
|
||||||
async def change_room_state(
|
async def change_room_state(
|
||||||
@@ -635,6 +740,11 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
),
|
),
|
||||||
self.start_gameplay,
|
self.start_gameplay,
|
||||||
)
|
)
|
||||||
|
await self.event_logger.game_started(
|
||||||
|
room.room.room_id,
|
||||||
|
room.queue.current_item.id,
|
||||||
|
details=room.match_type_handler.get_details(),
|
||||||
|
)
|
||||||
|
|
||||||
async def start_gameplay(self, room: ServerMultiplayerRoom):
|
async def start_gameplay(self, room: ServerMultiplayerRoom):
|
||||||
if room.room.state != MultiplayerRoomState.WAITING_FOR_LOAD:
|
if room.room.state != MultiplayerRoomState.WAITING_FOR_LOAD:
|
||||||
@@ -737,6 +847,10 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
host_id=room.room.host.user_id,
|
host_id=room.room.host.user_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
await self.event_logger.room_disbanded(
|
||||||
|
room.room.room_id,
|
||||||
|
room.room.host.user_id,
|
||||||
|
)
|
||||||
del self.rooms[room.room.room_id]
|
del self.rooms[room.room.room_id]
|
||||||
|
|
||||||
async def LeaveRoom(self, client: Client):
|
async def LeaveRoom(self, client: Client):
|
||||||
@@ -751,6 +865,10 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
if user is None:
|
if user is None:
|
||||||
raise InvokeException("You are not in this room")
|
raise InvokeException("You are not in this room")
|
||||||
|
|
||||||
|
await self.event_logger.player_left(
|
||||||
|
room.room_id,
|
||||||
|
user.user_id,
|
||||||
|
)
|
||||||
await self.make_user_leave(client, server_room, user)
|
await self.make_user_leave(client, server_room, user)
|
||||||
|
|
||||||
async def KickUser(self, client: Client, user_id: int):
|
async def KickUser(self, client: Client, user_id: int):
|
||||||
@@ -772,6 +890,10 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
if user is None:
|
if user is None:
|
||||||
raise InvokeException("User not found in this room")
|
raise InvokeException("User not found in this room")
|
||||||
|
|
||||||
|
await self.event_logger.player_kicked(
|
||||||
|
room.room_id,
|
||||||
|
user.user_id,
|
||||||
|
)
|
||||||
target_client = self.get_client_by_id(str(user.user_id))
|
target_client = self.get_client_by_id(str(user.user_id))
|
||||||
if target_client is None:
|
if target_client is None:
|
||||||
return
|
return
|
||||||
@@ -800,6 +922,10 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
new_host = next((u for u in room.users if u.user_id == user_id), None)
|
new_host = next((u for u in room.users if u.user_id == user_id), None)
|
||||||
if new_host is None:
|
if new_host is None:
|
||||||
raise InvokeException("User not found in this room")
|
raise InvokeException("User not found in this room")
|
||||||
|
await self.event_logger.host_changed(
|
||||||
|
room.room_id,
|
||||||
|
new_host.user_id,
|
||||||
|
)
|
||||||
await self.set_host(server_room, new_host)
|
await self.set_host(server_room, new_host)
|
||||||
|
|
||||||
async def AbortGameplay(self, client: Client):
|
async def AbortGameplay(self, client: Client):
|
||||||
|
|||||||
Reference in New Issue
Block a user