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 26 # address. 27 party_is_public: bool = True 28 29 # If True, all connecting clients will be authenticated through the 30 # master server to screen for fake account info. Generally this 31 # should always be enabled unless you are hosting on a LAN with no 32 # internet connection. 33 authenticate_clients: bool = True 34 35 # IDs of server admins. Server admins are not kickable through the 36 # default kick vote system and they are able to kick players without 37 # a vote. To get your account id, enter 'getaccountid' in 38 # settings->advanced->enter-code. 39 admins: list[str] = field(default_factory=list) 40 41 # Whether the default kick-voting system is enabled. 42 enable_default_kick_voting: bool = True 43 44 # To be included in the public server list, your server MUST be 45 # accessible via an ipv4 address. By default, the master server will 46 # try to use the address your server contacts it from, but this may 47 # be an ipv6 address these days so you may need to provide an ipv4 48 # address explicitly. 49 public_ipv4_address: str | None = None 50 51 # You can optionally provide an ipv6 address for your server for the 52 # public server list. Unlike ipv4, a server is not required to have 53 # an ipv6 address to appear in the list, but is still good to 54 # provide when available since more and more devices are using ipv6 55 # these days. Your server's ipv6 address will be autodetected if 56 # your server uses ipv6 when communicating with the master server. You 57 # can pass an empty string here to explicitly disable the ipv6 58 # address. 59 public_ipv6_address: str | None = None 60 61 # UDP port to host on. Change this to work around firewalls or run 62 # multiple servers on one machine. 63 # 64 # 43210 is the default and the only port that will show up in the 65 # LAN browser tab. 66 port: int = 43210 67 68 # Max devices in the party. Note that this does *NOT* mean max 69 # players. Any device in the party can have more than one player on 70 # it if they have multiple controllers. Also, this number currently 71 # includes the server so generally make it 1 bigger than you need. 72 max_party_size: int = 6 73 74 # Max players that can join a session. If present this will override 75 # the session's preferred max_players. if a value below 0 is given 76 # player limit will be removed. 77 session_max_players_override: int | None = None 78 79 # Options here are 'ffa' (free-for-all), 'teams' and 'coop' 80 # (cooperative) This value is ignored if you supply a playlist_code 81 # (see below). 82 session_type: str = 'ffa' 83 84 # Playlist-code for teams or free-for-all mode sessions. To host 85 # your own custom playlists, use the 'share' functionality in the 86 # playlist editor in the regular version of the game. This will give 87 # you a numeric code you can enter here to host that playlist. 88 playlist_code: int | None = None 89 90 # Alternately, you can embed playlist data here instead of using 91 # codes. Make sure to set session_type to the correct type for the 92 # data here. 93 playlist_inline: list[dict[str, Any]] | None = None 94 95 # Whether to shuffle the playlist or play its games in designated 96 # order. 97 playlist_shuffle: bool = True 98 99 # If True, keeps team sizes equal by disallowing joining the largest 100 # team (teams mode only). 101 auto_balance_teams: bool = True 102 103 # The campaign used when in co-op session mode. Do 104 # print(ba.app.campaigns) to see available campaign names. 105 coop_campaign: str = 'Easy' 106 107 # The level name within the campaign used in co-op session mode. For 108 # campaign name FOO, do print(ba.app.campaigns['FOO'].levels) to see 109 # available level names. 110 coop_level: str = 'Onslaught Training' 111 112 # Whether to enable telnet access. 113 # 114 # IMPORTANT: This option is no longer available, as it was being 115 # used for exploits. Live access to the running server is still 116 # possible through the mgr.cmd() function in the server script. Run 117 # your server through tools such as 'screen' or 'tmux' and you can 118 # reconnect to it remotely over a secure ssh connection. 119 enable_telnet: bool = False 120 121 # Series length in teams mode (7 == 'best-of-7' series; a team must 122 # get 4 wins) 123 teams_series_length: int = 7 124 125 # Points to win in free-for-all mode (Points are awarded per game 126 # based on performance) 127 ffa_series_length: int = 24 128 129 # If you have a custom stats webpage for your server, you can use 130 # this to provide a convenient in-game link to it in the 131 # server-browser alongside the server name. 132 # 133 # if ${ACCOUNT} is present in the string, it will be replaced by the 134 # currently-signed-in account's id. To fetch info about an account, 135 # your back-end server can use the following url: 136 # https://legacy.ballistica.net/accountquery?id=ACCOUNT_ID_HERE 137 stats_url: str | None = None 138 139 # If present, the server subprocess will attempt to gracefully exit 140 # after this amount of time. A graceful exit can occur at the end of 141 # a series or other opportune time. Server-managers set to 142 # auto-restart (the default) will then spin up a fresh subprocess. 143 # This mechanism can be useful to clear out any memory leaks or 144 # other accumulated bad state in the server subprocess. 145 clean_exit_minutes: float | None = None 146 147 # If present, the server subprocess will shut down immediately after 148 # this amount of time. This can be useful as a fallback for 149 # clean_exit_time. The server manager will then spin up a fresh 150 # server subprocess if auto-restart is enabled (the default). 151 unclean_exit_minutes: float | None = None 152 153 # If present, the server subprocess will shut down immediately if 154 # this amount of time passes with no activity from any players. The 155 # server manager will then spin up a fresh server subprocess if 156 # auto-restart is enabled (the default). 157 idle_exit_minutes: float | None = None 158 159 # Should the tutorial be shown at the beginning of games? 160 show_tutorial: bool = False 161 162 # Team names (teams mode only). 163 team_names: tuple[str, str] | None = None 164 165 # Team colors (teams mode only). 166 team_colors: ( 167 tuple[tuple[float, float, float], tuple[float, float, float]] | None 168 ) = None 169 170 # Whether to enable the queue where players can line up before 171 # entering your server. Disabling this can be used as a workaround 172 # to deal with queue spamming attacks. 173 enable_queue: bool = True 174 175 # Protocol version we host with. Currently the default is 33 which 176 # still allows older 1.4 game clients to connect. Explicitly setting 177 # to 35 no longer allows those clients but adds/fixes a few things 178 # such as making camera shake properly work in net games. 179 protocol_version: int | None = None 180 181 # (internal) stress-testing mode. 182 stress_test_players: int | None = None 183 184 # How many seconds individual players from a given account must wait 185 # before rejoining the game. This can help suppress exploits 186 # involving leaving and rejoining or switching teams rapidly. 187 player_rejoin_cooldown: float = 10.0 188 189 # Log levels for particular loggers, overriding the engine's 190 # defaults. Valid values are NOTSET, DEBUG, INFO, WARNING, ERROR, or 191 # CRITICAL. 192 log_levels: dict[str, str] | None = None 193 194 195# NOTE: as much as possible, communication from the server-manager to 196# the child-process should go through these and not ad-hoc Python string 197# commands since this way is type safe. 198class ServerCommand: 199 """Base class for commands that can be sent to the server.""" 200 201 202@dataclass 203class StartServerModeCommand(ServerCommand): 204 """Tells the app to switch into 'server' mode.""" 205 206 config: ServerConfig 207 208 209class ShutdownReason(Enum): 210 """Reason a server is shutting down.""" 211 212 NONE = 'none' 213 RESTARTING = 'restarting' 214 215 216@dataclass 217class ShutdownCommand(ServerCommand): 218 """Tells the server to shut down.""" 219 220 reason: ShutdownReason 221 immediate: bool 222 223 224@dataclass 225class ChatMessageCommand(ServerCommand): 226 """Chat message from the server.""" 227 228 message: str 229 clients: list[int] | None 230 231 232@dataclass 233class ScreenMessageCommand(ServerCommand): 234 """Screen-message from the server.""" 235 236 message: str 237 color: tuple[float, float, float] | None 238 clients: list[int] | None 239 240 241@dataclass 242class ClientListCommand(ServerCommand): 243 """Print a list of clients.""" 244 245 246@dataclass 247class KickCommand(ServerCommand): 248 """Kick a client.""" 249 250 client_id: int 251 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 27 # address. 28 party_is_public: bool = True 29 30 # If True, all connecting clients will be authenticated through the 31 # master server to screen for fake account info. Generally this 32 # should always be enabled unless you are hosting on a LAN with no 33 # internet connection. 34 authenticate_clients: bool = True 35 36 # IDs of server admins. Server admins are not kickable through the 37 # default kick vote system and they are able to kick players without 38 # a vote. To get your account id, enter 'getaccountid' in 39 # settings->advanced->enter-code. 40 admins: list[str] = field(default_factory=list) 41 42 # Whether the default kick-voting system is enabled. 43 enable_default_kick_voting: bool = True 44 45 # To be included in the public server list, your server MUST be 46 # accessible via an ipv4 address. By default, the master server will 47 # try to use the address your server contacts it from, but this may 48 # be an ipv6 address these days so you may need to provide an ipv4 49 # address explicitly. 50 public_ipv4_address: str | None = None 51 52 # You can optionally provide an ipv6 address for your server for the 53 # public server list. Unlike ipv4, a server is not required to have 54 # an ipv6 address to appear in the list, but is still good to 55 # provide when available since more and more devices are using ipv6 56 # these days. Your server's ipv6 address will be autodetected if 57 # your server uses ipv6 when communicating with the master server. You 58 # can pass an empty string here to explicitly disable the ipv6 59 # address. 60 public_ipv6_address: str | None = None 61 62 # UDP port to host on. Change this to work around firewalls or run 63 # multiple servers on one machine. 64 # 65 # 43210 is the default and the only port that will show up in the 66 # LAN browser tab. 67 port: int = 43210 68 69 # Max devices in the party. Note that this does *NOT* mean max 70 # players. Any device in the party can have more than one player on 71 # it if they have multiple controllers. Also, this number currently 72 # includes the server so generally make it 1 bigger than you need. 73 max_party_size: int = 6 74 75 # Max players that can join a session. If present this will override 76 # the session's preferred max_players. if a value below 0 is given 77 # player limit will be removed. 78 session_max_players_override: int | None = None 79 80 # Options here are 'ffa' (free-for-all), 'teams' and 'coop' 81 # (cooperative) This value is ignored if you supply a playlist_code 82 # (see below). 83 session_type: str = 'ffa' 84 85 # Playlist-code for teams or free-for-all mode sessions. To host 86 # your own custom playlists, use the 'share' functionality in the 87 # playlist editor in the regular version of the game. This will give 88 # you a numeric code you can enter here to host that playlist. 89 playlist_code: int | None = None 90 91 # Alternately, you can embed playlist data here instead of using 92 # codes. Make sure to set session_type to the correct type for the 93 # data here. 94 playlist_inline: list[dict[str, Any]] | None = None 95 96 # Whether to shuffle the playlist or play its games in designated 97 # order. 98 playlist_shuffle: bool = True 99 100 # If True, keeps team sizes equal by disallowing joining the largest 101 # team (teams mode only). 102 auto_balance_teams: bool = True 103 104 # The campaign used when in co-op session mode. Do 105 # print(ba.app.campaigns) to see available campaign names. 106 coop_campaign: str = 'Easy' 107 108 # The level name within the campaign used in co-op session mode. For 109 # campaign name FOO, do print(ba.app.campaigns['FOO'].levels) to see 110 # available level names. 111 coop_level: str = 'Onslaught Training' 112 113 # Whether to enable telnet access. 114 # 115 # IMPORTANT: This option is no longer available, as it was being 116 # used for exploits. Live access to the running server is still 117 # possible through the mgr.cmd() function in the server script. Run 118 # your server through tools such as 'screen' or 'tmux' and you can 119 # reconnect to it remotely over a secure ssh connection. 120 enable_telnet: bool = False 121 122 # Series length in teams mode (7 == 'best-of-7' series; a team must 123 # get 4 wins) 124 teams_series_length: int = 7 125 126 # Points to win in free-for-all mode (Points are awarded per game 127 # based on performance) 128 ffa_series_length: int = 24 129 130 # If you have a custom stats webpage for your server, you can use 131 # this to provide a convenient in-game link to it in the 132 # server-browser alongside the server name. 133 # 134 # if ${ACCOUNT} is present in the string, it will be replaced by the 135 # currently-signed-in account's id. To fetch info about an account, 136 # your back-end server can use the following url: 137 # https://legacy.ballistica.net/accountquery?id=ACCOUNT_ID_HERE 138 stats_url: str | None = None 139 140 # If present, the server subprocess will attempt to gracefully exit 141 # after this amount of time. A graceful exit can occur at the end of 142 # a series or other opportune time. Server-managers set to 143 # auto-restart (the default) will then spin up a fresh subprocess. 144 # This mechanism can be useful to clear out any memory leaks or 145 # other accumulated bad state in the server subprocess. 146 clean_exit_minutes: float | None = None 147 148 # If present, the server subprocess will shut down immediately after 149 # this amount of time. This can be useful as a fallback for 150 # clean_exit_time. The server manager will then spin up a fresh 151 # server subprocess if auto-restart is enabled (the default). 152 unclean_exit_minutes: float | None = None 153 154 # If present, the server subprocess will shut down immediately if 155 # this amount of time passes with no activity from any players. The 156 # server manager will then spin up a fresh server subprocess if 157 # auto-restart is enabled (the default). 158 idle_exit_minutes: float | None = None 159 160 # Should the tutorial be shown at the beginning of games? 161 show_tutorial: bool = False 162 163 # Team names (teams mode only). 164 team_names: tuple[str, str] | None = None 165 166 # Team colors (teams mode only). 167 team_colors: ( 168 tuple[tuple[float, float, float], tuple[float, float, float]] | None 169 ) = None 170 171 # Whether to enable the queue where players can line up before 172 # entering your server. Disabling this can be used as a workaround 173 # to deal with queue spamming attacks. 174 enable_queue: bool = True 175 176 # Protocol version we host with. Currently the default is 33 which 177 # still allows older 1.4 game clients to connect. Explicitly setting 178 # to 35 no longer allows those clients but adds/fixes a few things 179 # such as making camera shake properly work in net games. 180 protocol_version: int | None = None 181 182 # (internal) stress-testing mode. 183 stress_test_players: int | None = None 184 185 # How many seconds individual players from a given account must wait 186 # before rejoining the game. This can help suppress exploits 187 # involving leaving and rejoining or switching teams rapidly. 188 player_rejoin_cooldown: float = 10.0 189 190 # Log levels for particular loggers, overriding the engine's 191 # defaults. Valid values are NOTSET, DEBUG, INFO, WARNING, ERROR, or 192 # CRITICAL. 193 log_levels: dict[str, str] | 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, public_ipv4_address: str | None = None, public_ipv6_address: str | None = None, port: int = 43210, max_party_size: int = 6, session_max_players_override: int | None = None, 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, protocol_version: int | None = None, stress_test_players: int | None = None, player_rejoin_cooldown: float = 10.0, log_levels: dict[str, str] | None = None)
class
ServerCommand:
Base class for commands that can be sent to the server.
203@dataclass 204class StartServerModeCommand(ServerCommand): 205 """Tells the app to switch into 'server' mode.""" 206 207 config: ServerConfig
Tells the app to switch into 'server' mode.
StartServerModeCommand(config: ServerConfig)
config: ServerConfig
class
ShutdownReason(enum.Enum):
210class ShutdownReason(Enum): 211 """Reason a server is shutting down.""" 212 213 NONE = 'none' 214 RESTARTING = 'restarting'
Reason a server is shutting down.
NONE =
<ShutdownReason.NONE: 'none'>
RESTARTING =
<ShutdownReason.RESTARTING: 'restarting'>
217@dataclass 218class ShutdownCommand(ServerCommand): 219 """Tells the server to shut down.""" 220 221 reason: ShutdownReason 222 immediate: bool
Tells the server to shut down.
ShutdownCommand(reason: ShutdownReason, immediate: bool)
reason: ShutdownReason
225@dataclass 226class ChatMessageCommand(ServerCommand): 227 """Chat message from the server.""" 228 229 message: str 230 clients: list[int] | None
Chat message from the server.
233@dataclass 234class ScreenMessageCommand(ServerCommand): 235 """Screen-message from the server.""" 236 237 message: str 238 color: tuple[float, float, float] | None 239 clients: list[int] | None
Screen-message from the server.
Print a list of clients.
247@dataclass 248class KickCommand(ServerCommand): 249 """Kick a client.""" 250 251 client_id: int 252 ban_time: int | None
Kick a client.