Source code for bacommon.docui.routes.classichost
# Released under the MIT License. See LICENSE for details.
#
"""Routes for the classic host doc-ui domain.
Pages a game host serves its connected clients over the game
connection (see ``bacommon.hostui``): the defaults classic hosts ship
with. Hosts may serve other routes of their own alongside these.
"""
from __future__ import annotations # Docs-generation hack.
from dataclasses import dataclass
from typing import Annotated, override
from efro.dataclassio import ioprepped, IOAttrs
import bacommon.docui.v2 as dui2
from bacommon.docui.routes import (
DocUIRoute,
DocUILocalActionBase,
family_members,
)
[docs]
class ClassicHostRoute(DocUIRoute):
"""Family class for classic host routes."""
[docs]
@override
@classmethod
def get_route_types(cls) -> tuple[type[DocUIRoute], ...]:
return family_members(AnyClassicHostRoute)
# (Window layout: the route default, wide.)
[docs]
@ioprepped
@dataclass
class Stats(ClassicHostRoute, path='/stats'):
"""Who is in the party and how their connections are doing."""
[docs]
@ioprepped
@dataclass
class Client(ClassicHostRoute, path='/client'):
"""One party member (a connected client, or -1 for the host).
Their account, connection and players, and kicking them. Party
windows open this when a member's name is pressed.
"""
client_id: int
[docs]
@override
@classmethod
def get_window_layout(cls) -> dui2.WindowLayout:
# (A few labeled values and a row of players; a narrow window
# keeps labels near their values.)
return dui2.WindowLayout.SMALL_TALL
[docs]
@ioprepped
@dataclass
class Player(ClassicHostRoute, path='/player'):
"""One player in the game, by their session player id.
How they're doing, who they're playing as, and kicking them.
"""
player_id: int
[docs]
@override
@classmethod
def get_window_layout(cls) -> dui2.WindowLayout:
# (A big icon and name over a wide row of columns.)
return dui2.WindowLayout.WIDE_TALL
[docs]
@ioprepped
@dataclass
class KickVote(ClassicHostRoute, path='/kickvote'):
"""Start or follow a vote to kick someone.
Clients open this with ``client_id`` set to whoever they chose to
kick (and ``player_id`` to the player they chose them by, if any);
without one it shows any vote in progress.
"""
client_id: int | None = None
player_id: int | None = None
[docs]
@override
@classmethod
def get_window_layout(cls) -> dui2.WindowLayout:
# (Its most is a question, a couple of lines, Yes/No buttons and
# a countdown.)
return dui2.WindowLayout.SMALL
[docs]
@ioprepped
@dataclass
class KickVoteStart(
ClassicHostRoute, path='/kickvote', method=dui2.RequestMethod.POST
):
"""Start a vote to kick a client."""
client_id: int
player_id: int | None = None
[docs]
@ioprepped
@dataclass
class KickVoteCast(
ClassicHostRoute, path='/kickvote/vote', method=dui2.RequestMethod.POST
):
"""Vote in the kick vote in progress."""
yes: bool
AnyClassicHostRoute = (
Stats | Client | Player | KickVote | KickVoteStart | KickVoteCast
)
#: First engine build that runs :class:`ShowAccount` from host pages
#: (older ones ignore host local-actions; the button then does nothing).
SHOW_ACCOUNT_BUILD = 23075
[docs]
class ClassicHostLocalAction(DocUILocalActionBase):
"""Family class for local-actions host pages may use.
Hosts are untrusted, so these are the only things a host page can
make a client do locally: each is safe whatever a host passes it
(its fields arrive from the host as-is). Clients ignore anything
else.
"""
[docs]
@override
@classmethod
def get_action_types(cls) -> tuple[type[DocUILocalActionBase], ...]:
return family_members(AnyClassicHostLocalAction)
[docs]
@ioprepped
@dataclass
class ShowAccount(ClassicHostLocalAction, name='show_account'):
"""Open an account's page from the master server, over this one.
(See :class:`bacommon.docui.routes.classicaccount.Account`; backing
out of it returns to the host page.) Only presses can do it.
Builds before :data:`SHOW_ACCOUNT_BUILD` ignore it.
"""
#: The account (``a-XXX``).
account_id: str
# All local-actions in the family.
AnyClassicHostLocalAction = ShowAccount
# Docs-generation hack; import some stuff that we likely only forward-declared
# in our actual source code so that docs tools can find it.
from typing import (Coroutine, Any, Literal, Callable,
Generator, Awaitable, Sequence, Self, TypeIs)
import asyncio
from concurrent.futures import Future
from pathlib import Path
from enum import Enum