mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-02-03 14:03:18 +08:00
Added 'clientlist' and 'kick' server commands
This commit is contained in:
parent
c4f06f5f96
commit
acaa9a90eb
@ -4132,16 +4132,16 @@
|
|||||||
"assets/build/windows/x64/python.exe": "https://files.ballistica.net/cache/ba1/25/a7/dc87c1be41605eb6fefd0145144c",
|
"assets/build/windows/x64/python.exe": "https://files.ballistica.net/cache/ba1/25/a7/dc87c1be41605eb6fefd0145144c",
|
||||||
"assets/build/windows/x64/python37.dll": "https://files.ballistica.net/cache/ba1/b9/e4/d912f56e42e9991bcbb4c804cfcb",
|
"assets/build/windows/x64/python37.dll": "https://files.ballistica.net/cache/ba1/b9/e4/d912f56e42e9991bcbb4c804cfcb",
|
||||||
"assets/build/windows/x64/pythonw.exe": "https://files.ballistica.net/cache/ba1/6c/bb/b6f52c306aa4e88061510e96cefe",
|
"assets/build/windows/x64/pythonw.exe": "https://files.ballistica.net/cache/ba1/6c/bb/b6f52c306aa4e88061510e96cefe",
|
||||||
"build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/fe/a7/f1a153619272b4882dc0d1fcf228",
|
"build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a2/5b/8598da1884bbe060f495d0273d7d",
|
||||||
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/bd/08/3a948bec6a0553109361ab201b7f",
|
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/83/95/5e30fd47e6befa8f9a370a52be21",
|
||||||
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/69/4e/34a3913539ae323f32c6029aee61",
|
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/60/3a/1edfbfc7adcbda3f6734f9282756",
|
||||||
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/c7/2c/3b0f126c249753469779ac7abd99",
|
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/34/a1/f7d0bf6b709a757418825a1cc24f",
|
||||||
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/9a/f5/252523aff43024c6ced461abcc04",
|
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/59/e9/8a29cf17f4434f783fab73afc349",
|
||||||
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/12/f5/35f0e8d8259c37389fe2d60becbc",
|
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/71/9d/02c9e2ba7c91941991552228c66c",
|
||||||
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ca/ad/c4e22ab2e7de1356f47aa4f8dc07",
|
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/9b/35/42e8f879ac5b07ba2d8caee897dc",
|
||||||
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/02/dc/bea2f3a3a862b9e587043f6bd73f",
|
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/3f/3e/20eb6ac1043ac0417c2b7728f7bd",
|
||||||
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/d7/df/80ceb60dc041e2b9a69f0a3da9ea",
|
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/b9/75/6b1523ec9b0d510870b4695a6613",
|
||||||
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/b1/86/dddc66545c917e3a028fc6a6caea",
|
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/7d/04/b882bbfd0197248bca49016356c1",
|
||||||
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a2/69/b183a77cd9e8a91ef550c4e7bcec",
|
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/13/50/1fb612a251431229703317bd81e5",
|
||||||
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a0/17/d7aca9e4c6e604993328cd389356"
|
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/ec/3c/ce84db1040251fb5a11b0c7dfd7b"
|
||||||
}
|
}
|
||||||
2
.idea/dictionaries/ericf.xml
generated
2
.idea/dictionaries/ericf.xml
generated
@ -296,6 +296,7 @@
|
|||||||
<w>cleancheck</w>
|
<w>cleancheck</w>
|
||||||
<w>cleanlist</w>
|
<w>cleanlist</w>
|
||||||
<w>clientid</w>
|
<w>clientid</w>
|
||||||
|
<w>clientlist</w>
|
||||||
<w>clionbin</w>
|
<w>clionbin</w>
|
||||||
<w>clioncode</w>
|
<w>clioncode</w>
|
||||||
<w>clionroot</w>
|
<w>clionroot</w>
|
||||||
@ -1956,6 +1957,7 @@
|
|||||||
<w>vartype</w>
|
<w>vartype</w>
|
||||||
<w>vcxproj</w>
|
<w>vcxproj</w>
|
||||||
<w>venv</w>
|
<w>venv</w>
|
||||||
|
<w>versioning</w>
|
||||||
<w>versionpredicate</w>
|
<w>versionpredicate</w>
|
||||||
<w>vert</w>
|
<w>vert</w>
|
||||||
<w>verts</w>
|
<w>verts</w>
|
||||||
|
|||||||
@ -30,7 +30,9 @@ from ba._enums import TimeType
|
|||||||
from ba._freeforallsession import FreeForAllSession
|
from ba._freeforallsession import FreeForAllSession
|
||||||
from ba._dualteamsession import DualTeamSession
|
from ba._dualteamsession import DualTeamSession
|
||||||
from bacommon.servermanager import (ServerCommand, StartServerModeCommand,
|
from bacommon.servermanager import (ServerCommand, StartServerModeCommand,
|
||||||
ShutdownCommand, ShutdownReason)
|
ShutdownCommand, ShutdownReason,
|
||||||
|
BroadcastCommand, ClientListCommand,
|
||||||
|
KickCommand)
|
||||||
import _ba
|
import _ba
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -56,6 +58,22 @@ def _cmd(command_data: bytes) -> None:
|
|||||||
immediate=command.immediate)
|
immediate=command.immediate)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if isinstance(command, BroadcastCommand):
|
||||||
|
assert _ba.app.server is not None
|
||||||
|
_ba.app.server.broadcast_message(command.message)
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(command, ClientListCommand):
|
||||||
|
assert _ba.app.server is not None
|
||||||
|
_ba.app.server.print_client_list()
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(command, KickCommand):
|
||||||
|
assert _ba.app.server is not None
|
||||||
|
_ba.app.server.kick(client_id=command.client_id,
|
||||||
|
ban_time=command.ban_time)
|
||||||
|
return
|
||||||
|
|
||||||
print(f'{Clr.SRED}ERROR: server process'
|
print(f'{Clr.SRED}ERROR: server process'
|
||||||
f' got unknown command: {type(command)}{Clr.RST}')
|
f' got unknown command: {type(command)}{Clr.RST}')
|
||||||
|
|
||||||
@ -93,6 +111,48 @@ class ServerController:
|
|||||||
timetype=TimeType.REAL,
|
timetype=TimeType.REAL,
|
||||||
repeat=True)
|
repeat=True)
|
||||||
|
|
||||||
|
def broadcast_message(self, message: str) -> None:
|
||||||
|
"""Broadcast a message to all connected clients."""
|
||||||
|
# FIXME: Should add a proper call for this, which would allow
|
||||||
|
# us to use Lstr values and colors and whatnot.
|
||||||
|
_ba.chat_message(message)
|
||||||
|
|
||||||
|
def print_client_list(self) -> None:
|
||||||
|
"""Print info about all connected clients."""
|
||||||
|
import json
|
||||||
|
roster = _ba.get_game_roster()
|
||||||
|
title1 = 'Client ID'
|
||||||
|
title2 = 'Account Name'
|
||||||
|
title3 = 'Players'
|
||||||
|
col1 = 10
|
||||||
|
col2 = 16
|
||||||
|
out = (f'{Clr.BLD}'
|
||||||
|
f'{title1:<{col1}} {title2:<{col2}} {title3}'
|
||||||
|
f'{Clr.RST}')
|
||||||
|
for client in roster:
|
||||||
|
if client['client_id'] == -1:
|
||||||
|
continue
|
||||||
|
spec = json.loads(client['specString'])
|
||||||
|
name = spec['n']
|
||||||
|
players = ', '.join(n['name'] for n in client['players'])
|
||||||
|
clientid = client['client_id']
|
||||||
|
out += f'\n{clientid:<{col1}} {name:<{col2}} {players}'
|
||||||
|
print(out)
|
||||||
|
|
||||||
|
def kick(self, client_id: int, ban_time: Optional[int]) -> None:
|
||||||
|
"""Kick the provided client id.
|
||||||
|
|
||||||
|
ban_time is provided in seconds.
|
||||||
|
If ban_time is None, ban duration will be determined automatically.
|
||||||
|
Pass 0 or a negative number for no ban time.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# FIXME: this case should be handled under the hood.
|
||||||
|
if ban_time is None:
|
||||||
|
ban_time = 300
|
||||||
|
|
||||||
|
_ba.disconnect_client(client_id=client_id, ban_time=ban_time)
|
||||||
|
|
||||||
def shutdown(self, reason: ShutdownReason, immediate: bool) -> None:
|
def shutdown(self, reason: ShutdownReason, immediate: bool) -> None:
|
||||||
"""Set the app to quit either now or at the next clean opportunity."""
|
"""Set the app to quit either now or at the next clean opportunity."""
|
||||||
self._shutdown_reason = reason
|
self._shutdown_reason = reason
|
||||||
@ -124,19 +184,15 @@ class ServerController:
|
|||||||
self._executing_shutdown = True
|
self._executing_shutdown = True
|
||||||
timestrval = time.strftime('%c')
|
timestrval = time.strftime('%c')
|
||||||
if self._shutdown_reason is ShutdownReason.RESTARTING:
|
if self._shutdown_reason is ShutdownReason.RESTARTING:
|
||||||
# FIXME: Should add a server-screen-message call.
|
self.broadcast_message(
|
||||||
# (so we could send this an an Lstr)
|
|
||||||
_ba.chat_message(
|
|
||||||
Lstr(resource='internal.serverRestartingText').evaluate())
|
Lstr(resource='internal.serverRestartingText').evaluate())
|
||||||
print(f'{Clr.SBLU}Exiting for server-restart'
|
print(f'{Clr.SBLU}Exiting for server-restart'
|
||||||
f' at {timestrval}{Clr.RST}')
|
f' at {timestrval}{Clr.RST}')
|
||||||
else:
|
else:
|
||||||
# FIXME: Should add a server-screen-message call.
|
self.broadcast_message(
|
||||||
# (so we could send this an an Lstr)
|
Lstr(resource='internal.serverShuttingDownText').evaluate())
|
||||||
print(f'{Clr.SBLU}Exiting for server-shutdown'
|
print(f'{Clr.SBLU}Exiting for server-shutdown'
|
||||||
f' at {timestrval}{Clr.RST}')
|
f' at {timestrval}{Clr.RST}')
|
||||||
_ba.chat_message(
|
|
||||||
Lstr(resource='internal.serverShuttingDownText').evaluate())
|
|
||||||
with _ba.Context('ui'):
|
with _ba.Context('ui'):
|
||||||
_ba.timer(2.0, _ba.quit, timetype=TimeType.REAL)
|
_ba.timer(2.0, _ba.quit, timetype=TimeType.REAL)
|
||||||
|
|
||||||
@ -153,18 +209,22 @@ class ServerController:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _access_check_response(self, data: Optional[Dict[str, Any]]) -> None:
|
def _access_check_response(self, data: Optional[Dict[str, Any]]) -> None:
|
||||||
gameport = _ba.get_game_port()
|
# gameport = _ba.get_game_port()
|
||||||
if data is None:
|
if data is None:
|
||||||
print('error on UDP port access check (internet down?)')
|
print('error on UDP port access check (internet down?)')
|
||||||
else:
|
else:
|
||||||
|
addr = data['address']
|
||||||
|
port = data['port']
|
||||||
if data['accessible']:
|
if data['accessible']:
|
||||||
print(f'{Clr.SBLU}UDP port {gameport} access check successful.'
|
print(f'{Clr.SBLU}Master server access check of {addr}'
|
||||||
f' Your server appears to be joinable from the'
|
f' udp port {port} succeeded.\n'
|
||||||
f' internet.{Clr.RST}')
|
f'Your server appears to be'
|
||||||
|
f' joinable from the internet.{Clr.RST}')
|
||||||
else:
|
else:
|
||||||
print(f'{Clr.SRED}UDP port {gameport} access check failed.'
|
print(f'{Clr.SRED}Master server access check of {addr}'
|
||||||
f' Your server does not appear to be joinable'
|
f' udp port {port} failed.\n'
|
||||||
f' from the internet.{Clr.RST}')
|
f'Your server does not appear to be'
|
||||||
|
f' joinable from the internet.{Clr.RST}')
|
||||||
|
|
||||||
def _prepare_to_serve(self) -> None:
|
def _prepare_to_serve(self) -> None:
|
||||||
signed_in = _ba.get_account_state() == 'signed_in'
|
signed_in = _ba.get_account_state() == 'signed_in'
|
||||||
|
|||||||
@ -48,6 +48,10 @@ if TYPE_CHECKING:
|
|||||||
from types import FrameType
|
from types import FrameType
|
||||||
from bacommon.servermanager import ServerCommand
|
from bacommon.servermanager import ServerCommand
|
||||||
|
|
||||||
|
# Not sure how much versioning we'll do with this, but this will get
|
||||||
|
# printed at startup in case we need it.
|
||||||
|
VERSION_STR = '1.0'
|
||||||
|
|
||||||
|
|
||||||
class ServerManagerApp:
|
class ServerManagerApp:
|
||||||
"""An app which manages BallisticaCore server execution.
|
"""An app which manages BallisticaCore server execution.
|
||||||
@ -101,7 +105,8 @@ class ServerManagerApp:
|
|||||||
|
|
||||||
# Print basic usage info in interactive mode.
|
# Print basic usage info in interactive mode.
|
||||||
if sys.stdin.isatty():
|
if sys.stdin.isatty():
|
||||||
print(f'{Clr.SMAG}BallisticaCore server manager starting up...\n'
|
print(f'{Clr.SMAG}BallisticaCore server manager {VERSION_STR}'
|
||||||
|
f' starting up...\n'
|
||||||
f'Use the "mgr" object to make live server adjustments.\n'
|
f'Use the "mgr" object to make live server adjustments.\n'
|
||||||
f'Type "help(mgr)" for more information.{Clr.RST}')
|
f'Type "help(mgr)" for more information.{Clr.RST}')
|
||||||
|
|
||||||
@ -115,22 +120,19 @@ class ServerManagerApp:
|
|||||||
self._process_thread = Thread(target=self._bg_thread_main)
|
self._process_thread = Thread(target=self._bg_thread_main)
|
||||||
self._process_thread.start()
|
self._process_thread.start()
|
||||||
|
|
||||||
# According to Python docs, default locals dict has __name__ set
|
context = {'__name__': '__console__', '__doc__': None, 'mgr': self}
|
||||||
# to __console__ and __doc__ set to None; using that as start point.
|
|
||||||
# https://docs.python.org/3/library/code.html
|
|
||||||
locs = {'__name__': '__console__', '__doc__': None, 'mgr': self}
|
|
||||||
|
|
||||||
# Enable tab-completion if possible.
|
# Enable tab-completion if possible.
|
||||||
self._enable_tab_completion(locs)
|
self._enable_tab_completion(context)
|
||||||
|
|
||||||
# Now just sit in an interpreter.
|
# Now just sit in an interpreter.
|
||||||
# TODO: make it possible to use IPython if the user has it available.
|
# TODO: make it possible to use IPython if the user has it available.
|
||||||
try:
|
try:
|
||||||
code.interact(local=locs, banner='', exitmsg='')
|
code.interact(local=context, banner='', exitmsg='')
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
# We get this from the builtin quit(), etc.
|
# We get this from the builtin quit(), etc.
|
||||||
# Need to catch this so we can clean up, otherwise we'll be
|
# Need to catch this so we can clean up, otherwise we'll be
|
||||||
# left in limbo with our BG thread still running.
|
# left in limbo with our process thread still running.
|
||||||
pass
|
pass
|
||||||
except BaseException as exc:
|
except BaseException as exc:
|
||||||
print(f'{Clr.SRED}Unexpected interpreter exception:'
|
print(f'{Clr.SRED}Unexpected interpreter exception:'
|
||||||
@ -150,7 +152,9 @@ class ServerManagerApp:
|
|||||||
raise TypeError(f'Expected a string arg; got {type(statement)}')
|
raise TypeError(f'Expected a string arg; got {type(statement)}')
|
||||||
with self._process_commands_lock:
|
with self._process_commands_lock:
|
||||||
self._process_commands.append(statement)
|
self._process_commands.append(statement)
|
||||||
|
self._block_for_command_completion()
|
||||||
|
|
||||||
|
def _block_for_command_completion(self) -> None:
|
||||||
# Ideally we'd block here until the command was run so our prompt would
|
# Ideally we'd block here until the command was run so our prompt would
|
||||||
# print after it's results. We currently don't get any response from
|
# print after it's results. We currently don't get any response from
|
||||||
# the app so the best we can do is block until our bg thread has sent
|
# the app so the best we can do is block until our bg thread has sent
|
||||||
@ -166,6 +170,29 @@ class ServerManagerApp:
|
|||||||
# we'll hopefully still give it enough time to process/print.
|
# we'll hopefully still give it enough time to process/print.
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
def broadcast(self, message: str) -> None:
|
||||||
|
"""Broadcast a message to all connected clients."""
|
||||||
|
from bacommon.servermanager import BroadcastCommand
|
||||||
|
self._enqueue_server_command(BroadcastCommand(message=message))
|
||||||
|
|
||||||
|
def clientlist(self) -> None:
|
||||||
|
"""Print a list of connected clients."""
|
||||||
|
from bacommon.servermanager import ClientListCommand
|
||||||
|
self._enqueue_server_command(ClientListCommand())
|
||||||
|
self._block_for_command_completion()
|
||||||
|
|
||||||
|
def kick(self, client_id: int, ban_time: Optional[int] = None) -> None:
|
||||||
|
"""Kick the client with the provided id.
|
||||||
|
|
||||||
|
If ban_time is provided, the client will be banned for that
|
||||||
|
length of time in seconds. If it is None, ban duration will
|
||||||
|
be determined automatically. Pass 0 or a negative number for no
|
||||||
|
ban time.
|
||||||
|
"""
|
||||||
|
from bacommon.servermanager import KickCommand
|
||||||
|
self._enqueue_server_command(
|
||||||
|
KickCommand(client_id=client_id, ban_time=ban_time))
|
||||||
|
|
||||||
def restart(self, immediate: bool = False) -> None:
|
def restart(self, immediate: bool = False) -> None:
|
||||||
"""Restart the server child-process.
|
"""Restart the server child-process.
|
||||||
|
|
||||||
@ -360,10 +387,15 @@ class ServerManagerApp:
|
|||||||
print(f'{Clr.SMAG}Server process stopped.{Clr.RST}')
|
print(f'{Clr.SMAG}Server process stopped.{Clr.RST}')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
def main() -> None:
|
||||||
|
"""Run a BallisticaCore server manager in interactive mode."""
|
||||||
try:
|
try:
|
||||||
ServerManagerApp().run_interactive()
|
ServerManagerApp().run_interactive()
|
||||||
except CleanError as clean_exc:
|
except CleanError as exc:
|
||||||
# For clean errors, do a simple print and fail; no tracebacks/etc.
|
# For clean errors, do a simple print and fail; no tracebacks/etc.
|
||||||
clean_exc.pretty_print()
|
exc.pretty_print()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|||||||
@ -3988,11 +3988,17 @@ cause the powerup box to make a sound and disappear or whatnot.</p>
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3>Methods:</h3>
|
<h3>Methods:</h3>
|
||||||
<h5><a href="#method_ba_ServerController____init__"><constructor></a>, <a href="#method_ba_ServerController__handle_transition">handle_transition()</a>, <a href="#method_ba_ServerController__shutdown">shutdown()</a></h5>
|
<h5><a href="#method_ba_ServerController____init__"><constructor></a>, <a href="#method_ba_ServerController__broadcast_message">broadcast_message()</a>, <a href="#method_ba_ServerController__handle_transition">handle_transition()</a>, <a href="#method_ba_ServerController__kick">kick()</a>, <a href="#method_ba_ServerController__print_client_list">print_client_list()</a>, <a href="#method_ba_ServerController__shutdown">shutdown()</a></h5>
|
||||||
<dl>
|
<dl>
|
||||||
<dt><h4><a name="method_ba_ServerController____init__"><constructor></a></dt></h4><dd>
|
<dt><h4><a name="method_ba_ServerController____init__"><constructor></a></dt></h4><dd>
|
||||||
<p><span>ba.ServerController(config: ServerConfig)</span></p>
|
<p><span>ba.ServerController(config: ServerConfig)</span></p>
|
||||||
|
|
||||||
|
</dd>
|
||||||
|
<dt><h4><a name="method_ba_ServerController__broadcast_message">broadcast_message()</a></dt></h4><dd>
|
||||||
|
<p><span>broadcast_message(self, message: str) -> None</span></p>
|
||||||
|
|
||||||
|
<p>Broadcast a message to all connected clients.</p>
|
||||||
|
|
||||||
</dd>
|
</dd>
|
||||||
<dt><h4><a name="method_ba_ServerController__handle_transition">handle_transition()</a></dt></h4><dd>
|
<dt><h4><a name="method_ba_ServerController__handle_transition">handle_transition()</a></dt></h4><dd>
|
||||||
<p><span>handle_transition(self) -> bool</span></p>
|
<p><span>handle_transition(self) -> bool</span></p>
|
||||||
@ -4004,6 +4010,22 @@ a good 'end-point' (such as a final score screen).
|
|||||||
Should return True if action will be handled by us; False if the
|
Should return True if action will be handled by us; False if the
|
||||||
session should just continue on it's merry way.</p>
|
session should just continue on it's merry way.</p>
|
||||||
|
|
||||||
|
</dd>
|
||||||
|
<dt><h4><a name="method_ba_ServerController__kick">kick()</a></dt></h4><dd>
|
||||||
|
<p><span>kick(self, client_id: int, ban_time: Optional[int]) -> None</span></p>
|
||||||
|
|
||||||
|
<p>Kick the provided client id.</p>
|
||||||
|
|
||||||
|
<p>ban_time is provided in seconds.
|
||||||
|
If ban_time is None, ban duration will be determined automatically.
|
||||||
|
Pass 0 or a negative number for no ban time.</p>
|
||||||
|
|
||||||
|
</dd>
|
||||||
|
<dt><h4><a name="method_ba_ServerController__print_client_list">print_client_list()</a></dt></h4><dd>
|
||||||
|
<p><span>print_client_list(self) -> None</span></p>
|
||||||
|
|
||||||
|
<p>Print info about all connected clients.</p>
|
||||||
|
|
||||||
</dd>
|
</dd>
|
||||||
<dt><h4><a name="method_ba_ServerController__shutdown">shutdown()</a></dt></h4><dd>
|
<dt><h4><a name="method_ba_ServerController__shutdown">shutdown()</a></dt></h4><dd>
|
||||||
<p><span>shutdown(self, reason: ShutdownReason, immediate: bool) -> None</span></p>
|
<p><span>shutdown(self, reason: ShutdownReason, immediate: bool) -> None</span></p>
|
||||||
|
|||||||
@ -121,3 +121,21 @@ class ShutdownCommand(ServerCommand):
|
|||||||
"""Tells the server to shut down."""
|
"""Tells the server to shut down."""
|
||||||
reason: ShutdownReason
|
reason: ShutdownReason
|
||||||
immediate: bool
|
immediate: bool
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BroadcastCommand(ServerCommand):
|
||||||
|
"""Broadcast a message to all clients."""
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ClientListCommand(ServerCommand):
|
||||||
|
"""Print a list of clients."""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class KickCommand(ServerCommand):
|
||||||
|
"""Kick a client."""
|
||||||
|
client_id: int
|
||||||
|
ban_time: Optional[int]
|
||||||
|
|||||||
@ -69,7 +69,7 @@ def dataclass_assign(instance: Any, values: Dict[str, Any]) -> None:
|
|||||||
for key, value in values.items():
|
for key, value in values.items():
|
||||||
if key not in fieldsdict:
|
if key not in fieldsdict:
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
f"'{type(instance).__name__}' has no '{key}' field")
|
f"'{type(instance).__name__}' has no '{key}' field.")
|
||||||
field = fieldsdict[key]
|
field = fieldsdict[key]
|
||||||
|
|
||||||
# We expect to be operating under 'from __future__ import annotations'
|
# We expect to be operating under 'from __future__ import annotations'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user