bacommon.servermanager
Functionality related to the server manager script.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Functionality related to the server manager script.""" 4from __future__ import annotations 5 6from enum import Enum 7from dataclasses import field, dataclass 8from typing import TYPE_CHECKING, Any 9 10from efro.dataclassio import ioprepped 11 12if TYPE_CHECKING: 13 pass 14 15 16@ioprepped 17@dataclass 18class ServerConfig: 19 """Configuration for the server manager app (<appname>_server).""" 20 21 # Name of our server in the public parties list. 22 party_name: str = 'FFA' 23 24 # If True, your party will show up in the global public party list 25 # Otherwise it will still be joinable via LAN or connecting by IP address. 26 party_is_public: bool = True 27 28 # If True, all connecting clients will be authenticated through the master 29 # server to screen for fake account info. Generally this should always 30 # be enabled unless you are hosting on a LAN with no internet connection. 31 authenticate_clients: bool = True 32 33 # IDs of server admins. Server admins are not kickable through the default 34 # kick vote system and they are able to kick players without a vote. To get 35 # your account id, enter 'getaccountid' in settings->advanced->enter-code. 36 admins: list[str] = field(default_factory=list) 37 38 # Whether the default kick-voting system is enabled. 39 enable_default_kick_voting: bool = True 40 41 # UDP port to host on. Change this to work around firewalls or run multiple 42 # servers on one machine. 43 # 43210 is the default and the only port that will show up in the LAN 44 # browser tab. 45 port: int = 43210 46 47 # Max devices in the party. Note that this does *NOT* mean max players. 48 # Any device in the party can have more than one player on it if they have 49 # multiple controllers. Also, this number currently includes the server so 50 # generally make it 1 bigger than you need. Max-players is not currently 51 # exposed but I'll try to add that soon. 52 max_party_size: int = 6 53 54 # Options here are 'ffa' (free-for-all), 'teams' and 'coop' (cooperative) 55 # This value is ignored if you supply a playlist_code (see below). 56 session_type: str = 'ffa' 57 58 # Playlist-code for teams or free-for-all mode sessions. 59 # To host your own custom playlists, use the 'share' functionality in the 60 # playlist editor in the regular version of the game. 61 # This will give you a numeric code you can enter here to host that 62 # playlist. 63 playlist_code: int | None = None 64 65 # Alternately, you can embed playlist data here instead of using codes. 66 # Make sure to set session_type to the correct type for the data here. 67 playlist_inline: list[dict[str, Any]] | None = None 68 69 # Whether to shuffle the playlist or play its games in designated order. 70 playlist_shuffle: bool = True 71 72 # If True, keeps team sizes equal by disallowing joining the largest team 73 # (teams mode only). 74 auto_balance_teams: bool = True 75 76 # The campaign used when in co-op session mode. 77 # Do print(ba.app.campaigns) to see available campaign names. 78 coop_campaign: str = 'Easy' 79 80 # The level name within the campaign used in co-op session mode. 81 # For campaign name FOO, do print(ba.app.campaigns['FOO'].levels) to see 82 # available level names. 83 coop_level: str = 'Onslaught Training' 84 85 # Whether to enable telnet access. 86 # IMPORTANT: This option is no longer available, as it was being used 87 # for exploits. Live access to the running server is still possible through 88 # the mgr.cmd() function in the server script. Run your server through 89 # tools such as 'screen' or 'tmux' and you can reconnect to it remotely 90 # over a secure ssh connection. 91 enable_telnet: bool = False 92 93 # Series length in teams mode (7 == 'best-of-7' series; a team must 94 # get 4 wins) 95 teams_series_length: int = 7 96 97 # Points to win in free-for-all mode (Points are awarded per game based on 98 # performance) 99 ffa_series_length: int = 24 100 101 # If you have a custom stats webpage for your server, you can use this 102 # to provide a convenient in-game link to it in the server-browser 103 # alongside the server name. 104 # if ${ACCOUNT} is present in the string, it will be replaced by the 105 # currently-signed-in account's id. To fetch info about an account, 106 # your back-end server can use the following url: 107 # https://legacy.ballistica.net/accountquery?id=ACCOUNT_ID_HERE 108 stats_url: str | None = None 109 110 # If present, the server subprocess will attempt to gracefully exit after 111 # this amount of time. A graceful exit can occur at the end of a series 112 # or other opportune time. Server-managers set to auto-restart (the 113 # default) will then spin up a fresh subprocess. This mechanism can be 114 # useful to clear out any memory leaks or other accumulated bad state 115 # in the server subprocess. 116 clean_exit_minutes: float | None = None 117 118 # If present, the server subprocess will shut down immediately after this 119 # amount of time. This can be useful as a fallback for clean_exit_time. 120 # The server manager will then spin up a fresh server subprocess if 121 # auto-restart is enabled (the default). 122 unclean_exit_minutes: float | None = None 123 124 # If present, the server subprocess will shut down immediately if this 125 # amount of time passes with no activity from any players. The server 126 # manager will then spin up a fresh server subprocess if auto-restart is 127 # enabled (the default). 128 idle_exit_minutes: float | None = None 129 130 # Should the tutorial be shown at the beginning of games? 131 show_tutorial: bool = False 132 133 # Team names (teams mode only). 134 team_names: tuple[str, str] | None = None 135 136 # Team colors (teams mode only). 137 team_colors: tuple[ 138 tuple[float, float, float], tuple[float, float, float] 139 ] | None = None 140 141 # Whether to enable the queue where players can line up before entering 142 # your server. Disabling this can be used as a workaround to deal with 143 # queue spamming attacks. 144 enable_queue: bool = True 145 146 # (internal) stress-testing mode. 147 stress_test_players: int | None = None 148 149 150# NOTE: as much as possible, communication from the server-manager to the 151# child-process should go through these and not ad-hoc Python string commands 152# since this way is type safe. 153class ServerCommand: 154 """Base class for commands that can be sent to the server.""" 155 156 157@dataclass 158class StartServerModeCommand(ServerCommand): 159 """Tells the app to switch into 'server' mode.""" 160 161 config: ServerConfig 162 163 164class ShutdownReason(Enum): 165 """Reason a server is shutting down.""" 166 167 NONE = 'none' 168 RESTARTING = 'restarting' 169 170 171@dataclass 172class ShutdownCommand(ServerCommand): 173 """Tells the server to shut down.""" 174 175 reason: ShutdownReason 176 immediate: bool 177 178 179@dataclass 180class ChatMessageCommand(ServerCommand): 181 """Chat message from the server.""" 182 183 message: str 184 clients: list[int] | None 185 186 187@dataclass 188class ScreenMessageCommand(ServerCommand): 189 """Screen-message from the server.""" 190 191 message: str 192 color: tuple[float, float, float] | None 193 clients: list[int] | None 194 195 196@dataclass 197class ClientListCommand(ServerCommand): 198 """Print a list of clients.""" 199 200 201@dataclass 202class KickCommand(ServerCommand): 203 """Kick a client.""" 204 205 client_id: int 206 ban_time: int | None
@ioprepped
@dataclass
class
ServerConfig:
17@ioprepped 18@dataclass 19class ServerConfig: 20 """Configuration for the server manager app (<appname>_server).""" 21 22 # Name of our server in the public parties list. 23 party_name: str = 'FFA' 24 25 # If True, your party will show up in the global public party list 26 # Otherwise it will still be joinable via LAN or connecting by IP address. 27 party_is_public: bool = True 28 29 # If True, all connecting clients will be authenticated through the master 30 # server to screen for fake account info. Generally this should always 31 # be enabled unless you are hosting on a LAN with no internet connection. 32 authenticate_clients: bool = True 33 34 # IDs of server admins. Server admins are not kickable through the default 35 # kick vote system and they are able to kick players without a vote. To get 36 # your account id, enter 'getaccountid' in settings->advanced->enter-code. 37 admins: list[str] = field(default_factory=list) 38 39 # Whether the default kick-voting system is enabled. 40 enable_default_kick_voting: bool = True 41 42 # UDP port to host on. Change this to work around firewalls or run multiple 43 # servers on one machine. 44 # 43210 is the default and the only port that will show up in the LAN 45 # browser tab. 46 port: int = 43210 47 48 # Max devices in the party. Note that this does *NOT* mean max players. 49 # Any device in the party can have more than one player on it if they have 50 # multiple controllers. Also, this number currently includes the server so 51 # generally make it 1 bigger than you need. Max-players is not currently 52 # exposed but I'll try to add that soon. 53 max_party_size: int = 6 54 55 # Options here are 'ffa' (free-for-all), 'teams' and 'coop' (cooperative) 56 # This value is ignored if you supply a playlist_code (see below). 57 session_type: str = 'ffa' 58 59 # Playlist-code for teams or free-for-all mode sessions. 60 # To host your own custom playlists, use the 'share' functionality in the 61 # playlist editor in the regular version of the game. 62 # This will give you a numeric code you can enter here to host that 63 # playlist. 64 playlist_code: int | None = None 65 66 # Alternately, you can embed playlist data here instead of using codes. 67 # Make sure to set session_type to the correct type for the data here. 68 playlist_inline: list[dict[str, Any]] | None = None 69 70 # Whether to shuffle the playlist or play its games in designated order. 71 playlist_shuffle: bool = True 72 73 # If True, keeps team sizes equal by disallowing joining the largest team 74 # (teams mode only). 75 auto_balance_teams: bool = True 76 77 # The campaign used when in co-op session mode. 78 # Do print(ba.app.campaigns) to see available campaign names. 79 coop_campaign: str = 'Easy' 80 81 # The level name within the campaign used in co-op session mode. 82 # For campaign name FOO, do print(ba.app.campaigns['FOO'].levels) to see 83 # available level names. 84 coop_level: str = 'Onslaught Training' 85 86 # Whether to enable telnet access. 87 # IMPORTANT: This option is no longer available, as it was being used 88 # for exploits. Live access to the running server is still possible through 89 # the mgr.cmd() function in the server script. Run your server through 90 # tools such as 'screen' or 'tmux' and you can reconnect to it remotely 91 # over a secure ssh connection. 92 enable_telnet: bool = False 93 94 # Series length in teams mode (7 == 'best-of-7' series; a team must 95 # get 4 wins) 96 teams_series_length: int = 7 97 98 # Points to win in free-for-all mode (Points are awarded per game based on 99 # performance) 100 ffa_series_length: int = 24 101 102 # If you have a custom stats webpage for your server, you can use this 103 # to provide a convenient in-game link to it in the server-browser 104 # alongside the server name. 105 # if ${ACCOUNT} is present in the string, it will be replaced by the 106 # currently-signed-in account's id. To fetch info about an account, 107 # your back-end server can use the following url: 108 # https://legacy.ballistica.net/accountquery?id=ACCOUNT_ID_HERE 109 stats_url: str | None = None 110 111 # If present, the server subprocess will attempt to gracefully exit after 112 # this amount of time. A graceful exit can occur at the end of a series 113 # or other opportune time. Server-managers set to auto-restart (the 114 # default) will then spin up a fresh subprocess. This mechanism can be 115 # useful to clear out any memory leaks or other accumulated bad state 116 # in the server subprocess. 117 clean_exit_minutes: float | None = None 118 119 # If present, the server subprocess will shut down immediately after this 120 # amount of time. This can be useful as a fallback for clean_exit_time. 121 # The server manager will then spin up a fresh server subprocess if 122 # auto-restart is enabled (the default). 123 unclean_exit_minutes: float | None = None 124 125 # If present, the server subprocess will shut down immediately if this 126 # amount of time passes with no activity from any players. The server 127 # manager will then spin up a fresh server subprocess if auto-restart is 128 # enabled (the default). 129 idle_exit_minutes: float | None = None 130 131 # Should the tutorial be shown at the beginning of games? 132 show_tutorial: bool = False 133 134 # Team names (teams mode only). 135 team_names: tuple[str, str] | None = None 136 137 # Team colors (teams mode only). 138 team_colors: tuple[ 139 tuple[float, float, float], tuple[float, float, float] 140 ] | None = None 141 142 # Whether to enable the queue where players can line up before entering 143 # your server. Disabling this can be used as a workaround to deal with 144 # queue spamming attacks. 145 enable_queue: bool = True 146 147 # (internal) stress-testing mode. 148 stress_test_players: int | None = None
Configuration for the server manager app (
ServerConfig( party_name: str = 'FFA', party_is_public: bool = True, authenticate_clients: bool = True, admins: list[str] = <factory>, enable_default_kick_voting: bool = True, port: int = 43210, max_party_size: int = 6, session_type: str = 'ffa', playlist_code: int | None = None, playlist_inline: list[dict[str, typing.Any]] | None = None, playlist_shuffle: bool = True, auto_balance_teams: bool = True, coop_campaign: str = 'Easy', coop_level: str = 'Onslaught Training', enable_telnet: bool = False, teams_series_length: int = 7, ffa_series_length: int = 24, stats_url: str | None = None, clean_exit_minutes: float | None = None, unclean_exit_minutes: float | None = None, idle_exit_minutes: float | None = None, show_tutorial: bool = False, team_names: tuple[str, str] | None = None, team_colors: tuple[tuple[float, float, float], tuple[float, float, float]] | None = None, enable_queue: bool = True, stress_test_players: int | None = None)
class
ServerCommand:
Base class for commands that can be sent to the server.
158@dataclass 159class StartServerModeCommand(ServerCommand): 160 """Tells the app to switch into 'server' mode.""" 161 162 config: ServerConfig
Tells the app to switch into 'server' mode.
StartServerModeCommand(config: ServerConfig)
config: ServerConfig
class
ShutdownReason(enum.Enum):
165class ShutdownReason(Enum): 166 """Reason a server is shutting down.""" 167 168 NONE = 'none' 169 RESTARTING = 'restarting'
Reason a server is shutting down.
NONE =
<ShutdownReason.NONE: 'none'>
RESTARTING =
<ShutdownReason.RESTARTING: 'restarting'>
Inherited Members
- enum.Enum
- name
- value
172@dataclass 173class ShutdownCommand(ServerCommand): 174 """Tells the server to shut down.""" 175 176 reason: ShutdownReason 177 immediate: bool
Tells the server to shut down.
ShutdownCommand(reason: ShutdownReason, immediate: bool)
reason: ShutdownReason
180@dataclass 181class ChatMessageCommand(ServerCommand): 182 """Chat message from the server.""" 183 184 message: str 185 clients: list[int] | None
Chat message from the server.
188@dataclass 189class ScreenMessageCommand(ServerCommand): 190 """Screen-message from the server.""" 191 192 message: str 193 color: tuple[float, float, float] | None 194 clients: list[int] | None
Screen-message from the server.
Print a list of clients.
202@dataclass 203class KickCommand(ServerCommand): 204 """Kick a client.""" 205 206 client_id: int 207 ban_time: int | None
Kick a client.