mirror of
https://github.com/hypervortex/VH-Bombsquad-Modded-Server-Files
synced 2026-08-15 13:04:50 +00:00
Initial commit
This commit is contained in:
parent
bc49523c99
commit
44d606cce7
1929 changed files with 612166 additions and 0 deletions
3
dist/ba_data/python/bacommon/__init__.py
vendored
Normal file
3
dist/ba_data/python/bacommon/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality and data common to ballistica client and server components."""
|
||||
BIN
dist/ba_data/python/bacommon/__pycache__/__init__.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bacommon/__pycache__/__init__.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bacommon/__pycache__/cloud.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bacommon/__pycache__/cloud.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bacommon/__pycache__/login.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bacommon/__pycache__/login.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bacommon/__pycache__/net.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bacommon/__pycache__/net.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bacommon/__pycache__/servermanager.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bacommon/__pycache__/servermanager.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python/bacommon/__pycache__/transfer.cpython-310.opt-1.pyc
vendored
Normal file
BIN
dist/ba_data/python/bacommon/__pycache__/transfer.cpython-310.opt-1.pyc
vendored
Normal file
Binary file not shown.
64
dist/ba_data/python/bacommon/assets.py
vendored
Normal file
64
dist/ba_data/python/bacommon/assets.py
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to cloud based assets."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING, Annotated
|
||||
from enum import Enum
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class AssetPackageFlavor(Enum):
|
||||
"""Flavors for asset package outputs for different platforms/etc."""
|
||||
|
||||
# DXT3/DXT5 textures
|
||||
DESKTOP = 'desktop'
|
||||
|
||||
# ASTC textures
|
||||
MOBILE = 'mobile'
|
||||
|
||||
|
||||
class AssetType(Enum):
|
||||
"""Types for individual assets within a package."""
|
||||
|
||||
TEXTURE = 'texture'
|
||||
CUBE_TEXTURE = 'cube_texture'
|
||||
SOUND = 'sound'
|
||||
DATA = 'data'
|
||||
MESH = 'mesh'
|
||||
COLLISION_MESH = 'collision_mesh'
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class AssetPackageFlavorManifest:
|
||||
"""A manifest of asset info for a specific flavor of an asset package."""
|
||||
|
||||
cloudfiles: Annotated[dict[str, str], IOAttrs('cloudfiles')] = field(
|
||||
default_factory=dict
|
||||
)
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class AssetPackageBuildState:
|
||||
"""Contains info about an in-progress asset cloud build."""
|
||||
|
||||
# Asset names still being built.
|
||||
in_progress_builds: Annotated[list[str], IOAttrs('b')] = field(
|
||||
default_factory=list
|
||||
)
|
||||
|
||||
# The initial number of assets needing to be built.
|
||||
initial_build_count: Annotated[int, IOAttrs('c')] = 0
|
||||
|
||||
# Build error string. If this is present, it should be presented
|
||||
# to the user and they should required to explicitly restart the build
|
||||
# in some way if desired.
|
||||
error: Annotated[str | None, IOAttrs('e')] = None
|
||||
106
dist/ba_data/python/bacommon/bacloud.py
vendored
Normal file
106
dist/ba_data/python/bacommon/bacloud.py
vendored
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to the bacloud tool."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Annotated
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
# Version is sent to the master-server with all commands. Can be incremented
|
||||
# if we need to change behavior server-side to go along with client changes.
|
||||
BACLOUD_VERSION = 8
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class RequestData:
|
||||
"""Request sent to bacloud server."""
|
||||
|
||||
command: Annotated[str, IOAttrs('c')]
|
||||
token: Annotated[str | None, IOAttrs('t')]
|
||||
payload: Annotated[dict, IOAttrs('p')]
|
||||
tzoffset: Annotated[float, IOAttrs('z')]
|
||||
isatty: Annotated[bool, IOAttrs('y')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ResponseData:
|
||||
# noinspection PyUnresolvedReferences
|
||||
"""Response sent from the bacloud server to the client.
|
||||
|
||||
Attributes:
|
||||
message: If present, client should print this message before any other
|
||||
response processing (including error handling) occurs.
|
||||
message_end: end arg for message print() call.
|
||||
error: If present, client should abort with this error message.
|
||||
delay_seconds: How long to wait before proceeding with remaining
|
||||
response (can be useful when waiting for server progress in a loop).
|
||||
login: If present, a token that should be stored client-side and passed
|
||||
with subsequent commands.
|
||||
logout: If True, any existing client-side token should be discarded.
|
||||
dir_manifest: If present, client should generate a manifest of this dir.
|
||||
It should be added to end_command args as 'manifest'.
|
||||
uploads: If present, client should upload the requested files (arg1)
|
||||
individually to a server command (arg2) with provided args (arg3).
|
||||
uploads_inline: If present, a list of pathnames that should be base64
|
||||
gzipped and uploaded to an 'uploads_inline' dict in end_command args.
|
||||
This should be limited to relatively small files.
|
||||
deletes: If present, file paths that should be deleted on the client.
|
||||
downloads_inline: If present, pathnames mapped to base64 gzipped data to
|
||||
be written to the client. This should only be used for relatively
|
||||
small files as they are all included inline as part of the response.
|
||||
dir_prune_empty: If present, all empty dirs under this one should be
|
||||
removed.
|
||||
open_url: If present, url to display to the user.
|
||||
input_prompt: If present, a line of input is read and placed into
|
||||
end_command args as 'input'. The first value is the prompt printed
|
||||
before reading and the second is whether it should be read as a
|
||||
password (without echoing to the terminal).
|
||||
end_message: If present, a message that should be printed after all other
|
||||
response processing is done.
|
||||
end_message_end: end arg for end_message print() call.
|
||||
end_command: If present, this command is run with these args at the end
|
||||
of response processing.
|
||||
"""
|
||||
message: Annotated[str | None, IOAttrs('m', store_default=False)] = None
|
||||
message_end: Annotated[str, IOAttrs('m_end', store_default=False)] = '\n'
|
||||
error: Annotated[str | None, IOAttrs('e', store_default=False)] = None
|
||||
delay_seconds: Annotated[float, IOAttrs('d', store_default=False)] = 0.0
|
||||
login: Annotated[str | None, IOAttrs('l', store_default=False)] = None
|
||||
logout: Annotated[bool, IOAttrs('lo', store_default=False)] = False
|
||||
dir_manifest: Annotated[
|
||||
str | None, IOAttrs('man', store_default=False)
|
||||
] = None
|
||||
uploads: Annotated[
|
||||
tuple[list[str], str, dict] | None, IOAttrs('u', store_default=False)
|
||||
] = None
|
||||
uploads_inline: Annotated[
|
||||
list[str] | None, IOAttrs('uinl', store_default=False)
|
||||
] = None
|
||||
deletes: Annotated[
|
||||
list[str] | None, IOAttrs('dlt', store_default=False)
|
||||
] = None
|
||||
downloads_inline: Annotated[
|
||||
dict[str, str] | None, IOAttrs('dinl', store_default=False)
|
||||
] = None
|
||||
dir_prune_empty: Annotated[
|
||||
str | None, IOAttrs('dpe', store_default=False)
|
||||
] = None
|
||||
open_url: Annotated[str | None, IOAttrs('url', store_default=False)] = None
|
||||
input_prompt: Annotated[
|
||||
tuple[str, bool] | None, IOAttrs('inp', store_default=False)
|
||||
] = None
|
||||
end_message: Annotated[
|
||||
str | None, IOAttrs('em', store_default=False)
|
||||
] = None
|
||||
end_message_end: Annotated[str, IOAttrs('eme', store_default=False)] = '\n'
|
||||
end_command: Annotated[
|
||||
tuple[str, dict] | None, IOAttrs('ec', store_default=False)
|
||||
] = None
|
||||
35
dist/ba_data/python/bacommon/build.py
vendored
Normal file
35
dist/ba_data/python/bacommon/build.py
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to game builds."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING, Annotated
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class BuildInfoSet:
|
||||
"""Set of build infos."""
|
||||
|
||||
@dataclass
|
||||
class Entry:
|
||||
"""Info about a particular build."""
|
||||
|
||||
filename: Annotated[str, IOAttrs('fname')]
|
||||
size: Annotated[int, IOAttrs('size')]
|
||||
version: Annotated[str, IOAttrs('version')]
|
||||
build_number: Annotated[int, IOAttrs('build')]
|
||||
checksum: Annotated[str, IOAttrs('checksum')]
|
||||
createtime: Annotated[datetime.datetime, IOAttrs('createtime')]
|
||||
|
||||
builds: Annotated[list[Entry], IOAttrs('builds')] = field(
|
||||
default_factory=list
|
||||
)
|
||||
218
dist/ba_data/python/bacommon/cloud.py
vendored
Normal file
218
dist/ba_data/python/bacommon/cloud.py
vendored
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to cloud functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING, Annotated
|
||||
from enum import Enum
|
||||
|
||||
from efro.message import Message, Response
|
||||
from efro.dataclassio import ioprepped, IOAttrs
|
||||
from bacommon.transfer import DirectoryManifest
|
||||
from bacommon.login import LoginType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class LoginProxyRequestMessage(Message):
|
||||
"""Request send to the cloud to ask for a login-proxy."""
|
||||
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [LoginProxyRequestResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class LoginProxyRequestResponse(Response):
|
||||
"""Response to a request for a login proxy."""
|
||||
|
||||
# URL to direct the user to for login.
|
||||
url: Annotated[str, IOAttrs('u')]
|
||||
|
||||
# Proxy-Login id for querying results.
|
||||
proxyid: Annotated[str, IOAttrs('p')]
|
||||
|
||||
# Proxy-Login key for querying results.
|
||||
proxykey: Annotated[str, IOAttrs('k')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class LoginProxyStateQueryMessage(Message):
|
||||
"""Soo.. how is that login proxy going?"""
|
||||
|
||||
proxyid: Annotated[str, IOAttrs('p')]
|
||||
proxykey: Annotated[str, IOAttrs('k')]
|
||||
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [LoginProxyStateQueryResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class LoginProxyStateQueryResponse(Response):
|
||||
"""Here's the info on that login-proxy you asked about, boss."""
|
||||
|
||||
class State(Enum):
|
||||
"""States a login-proxy can be in."""
|
||||
|
||||
WAITING = 'waiting'
|
||||
SUCCESS = 'success'
|
||||
FAIL = 'fail'
|
||||
|
||||
state: Annotated[State, IOAttrs('s')]
|
||||
|
||||
# On success, these will be filled out.
|
||||
credentials: Annotated[str | None, IOAttrs('tk')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class LoginProxyCompleteMessage(Message):
|
||||
"""Just so you know, we're done with this proxy."""
|
||||
|
||||
proxyid: Annotated[str, IOAttrs('p')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class PingMessage(Message):
|
||||
"""Standard ping."""
|
||||
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [PingResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class PingResponse(Response):
|
||||
"""pong."""
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class TestMessage(Message):
|
||||
"""Can I get some of that workspace action?"""
|
||||
|
||||
testfoo: Annotated[int, IOAttrs('f')]
|
||||
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [TestResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class TestResponse(Response):
|
||||
"""Here's that workspace you asked for, boss."""
|
||||
|
||||
testfoo: Annotated[int, IOAttrs('f')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class WorkspaceFetchState:
|
||||
"""Common state data for a workspace fetch."""
|
||||
|
||||
manifest: Annotated[DirectoryManifest, IOAttrs('m')]
|
||||
iteration: Annotated[int, IOAttrs('i')] = 0
|
||||
total_deletes: Annotated[int, IOAttrs('tdels')] = 0
|
||||
total_downloads: Annotated[int, IOAttrs('tdlds')] = 0
|
||||
total_up_to_date: Annotated[int | None, IOAttrs('tunmd')] = None
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class WorkspaceFetchMessage(Message):
|
||||
"""Can I get some of that workspace action?"""
|
||||
|
||||
workspaceid: Annotated[str, IOAttrs('w')]
|
||||
state: Annotated[WorkspaceFetchState, IOAttrs('s')]
|
||||
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [WorkspaceFetchResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class WorkspaceFetchResponse(Response):
|
||||
"""Here's that workspace you asked for, boss."""
|
||||
|
||||
state: Annotated[WorkspaceFetchState, IOAttrs('s')]
|
||||
deletes: Annotated[list[str], IOAttrs('dlt', store_default=False)] = field(
|
||||
default_factory=list
|
||||
)
|
||||
downloads_inline: Annotated[
|
||||
dict[str, bytes], IOAttrs('dinl', store_default=False)
|
||||
] = field(default_factory=dict)
|
||||
|
||||
done: Annotated[bool, IOAttrs('d')] = False
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class MerchAvailabilityMessage(Message):
|
||||
"""Can we show merch link?"""
|
||||
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [MerchAvailabilityResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class MerchAvailabilityResponse(Response):
|
||||
"""About that merch..."""
|
||||
|
||||
url: Annotated[str | None, IOAttrs('u')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class SignInMessage(Message):
|
||||
"""Can I sign in please?"""
|
||||
|
||||
login_type: Annotated[LoginType, IOAttrs('l')]
|
||||
sign_in_token: Annotated[str, IOAttrs('t')]
|
||||
|
||||
# For debugging. Can remove soft_default once build 20988+ is ubiquitous.
|
||||
description: Annotated[str, IOAttrs('d', soft_default='-')]
|
||||
apptime: Annotated[float, IOAttrs('at', soft_default=-1.0)]
|
||||
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [SignInResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class SignInResponse(Response):
|
||||
"""Here's that sign-in result you asked for, boss."""
|
||||
|
||||
credentials: Annotated[str | None, IOAttrs('c')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ManageAccountMessage(Message):
|
||||
"""Message asking for a manage-account url."""
|
||||
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [ManageAccountResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ManageAccountResponse(Response):
|
||||
"""Here's that sign-in result you asked for, boss."""
|
||||
|
||||
url: Annotated[str | None, IOAttrs('u')]
|
||||
18
dist/ba_data/python/bacommon/err.py
vendored
Normal file
18
dist/ba_data/python/bacommon/err.py
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Error related functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class RemoteError(Exception):
|
||||
"""An error occurred on the other end of some connection."""
|
||||
|
||||
def __str__(self) -> str:
|
||||
s = ''.join(str(arg) for arg in self.args)
|
||||
return f'Remote Exception Follows:\n{s}'
|
||||
31
dist/ba_data/python/bacommon/login.py
vendored
Normal file
31
dist/ba_data/python/bacommon/login.py
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to cloud based assets."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class LoginType(Enum):
|
||||
"""Types of logins available."""
|
||||
|
||||
# Email/password
|
||||
EMAIL = 'email'
|
||||
|
||||
# Google Play Game Services
|
||||
GPGS = 'gpgs'
|
||||
|
||||
@property
|
||||
def displayname(self) -> str:
|
||||
"""Human readable name for this value."""
|
||||
cls = type(self)
|
||||
match self:
|
||||
case cls.EMAIL:
|
||||
return 'Email/Password'
|
||||
case cls.GPGS:
|
||||
return 'Google Play Games'
|
||||
83
dist/ba_data/python/bacommon/net.py
vendored
Normal file
83
dist/ba_data/python/bacommon/net.py
vendored
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Network related data and functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from typing import TYPE_CHECKING, Any, Annotated
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ServerNodeEntry:
|
||||
"""Information about a specific server."""
|
||||
|
||||
zone: Annotated[str, IOAttrs('r')]
|
||||
address: Annotated[str, IOAttrs('a')]
|
||||
port: Annotated[int, IOAttrs('p')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ServerNodeQueryResponse:
|
||||
"""A response to a query about server-nodes."""
|
||||
|
||||
# The current utc time on the master server.
|
||||
time: Annotated[datetime.datetime, IOAttrs('t')]
|
||||
|
||||
# If present, something went wrong, and this describes it.
|
||||
error: Annotated[str | None, IOAttrs('e', store_default=False)] = None
|
||||
|
||||
# The set of servernodes.
|
||||
servers: Annotated[
|
||||
list[ServerNodeEntry], IOAttrs('s', store_default=False)
|
||||
] = field(default_factory=list)
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class PrivateHostingState:
|
||||
"""Combined state of whether we're hosting, whether we can, etc."""
|
||||
|
||||
unavailable_error: str | None = None
|
||||
party_code: str | None = None
|
||||
tickets_to_host_now: int = 0
|
||||
minutes_until_free_host: float | None = None
|
||||
free_host_minutes_remaining: float | None = None
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class PrivateHostingConfig:
|
||||
"""Config provided when hosting a private party."""
|
||||
|
||||
session_type: str = 'ffa'
|
||||
playlist_name: str = 'Unknown'
|
||||
randomize: bool = False
|
||||
tutorial: bool = False
|
||||
custom_team_names: tuple[str, str] | None = None
|
||||
custom_team_colors: tuple[
|
||||
tuple[float, float, float], tuple[float, float, float]
|
||||
] | None = None
|
||||
playlist: list[dict[str, Any]] | None = None
|
||||
exit_minutes: float = 120.0
|
||||
exit_minutes_unclean: float = 180.0
|
||||
exit_minutes_idle: float = 10.0
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class PrivatePartyConnectResult:
|
||||
"""Info about a server we get back when connecting."""
|
||||
|
||||
error: str | None = None
|
||||
addr: str | None = None
|
||||
port: int | None = None
|
||||
password: str | None = None
|
||||
206
dist/ba_data/python/bacommon/servermanager.py
vendored
Normal file
206
dist/ba_data/python/bacommon/servermanager.py
vendored
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to the server manager script."""
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from dataclasses import field, dataclass
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from efro.dataclassio import ioprepped
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ServerConfig:
|
||||
"""Configuration for the server manager app (<appname>_server)."""
|
||||
|
||||
# Name of our server in the public parties list.
|
||||
party_name: str = 'FFA'
|
||||
|
||||
# If True, your party will show up in the global public party list
|
||||
# Otherwise it will still be joinable via LAN or connecting by IP address.
|
||||
party_is_public: bool = True
|
||||
|
||||
# If True, all connecting clients will be authenticated through the master
|
||||
# server to screen for fake account info. Generally this should always
|
||||
# be enabled unless you are hosting on a LAN with no internet connection.
|
||||
authenticate_clients: bool = True
|
||||
|
||||
# IDs of server admins. Server admins are not kickable through the default
|
||||
# kick vote system and they are able to kick players without a vote. To get
|
||||
# your account id, enter 'getaccountid' in settings->advanced->enter-code.
|
||||
admins: list[str] = field(default_factory=list)
|
||||
|
||||
# Whether the default kick-voting system is enabled.
|
||||
enable_default_kick_voting: bool = True
|
||||
|
||||
# UDP port to host on. Change this to work around firewalls or run multiple
|
||||
# servers on one machine.
|
||||
# 43210 is the default and the only port that will show up in the LAN
|
||||
# browser tab.
|
||||
port: int = 43222
|
||||
|
||||
# Max devices in the party. Note that this does *NOT* mean max players.
|
||||
# Any device in the party can have more than one player on it if they have
|
||||
# multiple controllers. Also, this number currently includes the server so
|
||||
# generally make it 1 bigger than you need. Max-players is not currently
|
||||
# exposed but I'll try to add that soon.
|
||||
max_party_size: int = 6
|
||||
|
||||
# Options here are 'ffa' (free-for-all), 'teams' and 'coop' (cooperative)
|
||||
# This value is ignored if you supply a playlist_code (see below).
|
||||
session_type: str = 'ffa'
|
||||
|
||||
# Playlist-code for teams or free-for-all mode sessions.
|
||||
# To host your own custom playlists, use the 'share' functionality in the
|
||||
# playlist editor in the regular version of the game.
|
||||
# This will give you a numeric code you can enter here to host that
|
||||
# playlist.
|
||||
playlist_code: int | None = None
|
||||
|
||||
# Alternately, you can embed playlist data here instead of using codes.
|
||||
# Make sure to set session_type to the correct type for the data here.
|
||||
playlist_inline: list[dict[str, Any]] | None = None
|
||||
|
||||
# Whether to shuffle the playlist or play its games in designated order.
|
||||
playlist_shuffle: bool = True
|
||||
|
||||
# If True, keeps team sizes equal by disallowing joining the largest team
|
||||
# (teams mode only).
|
||||
auto_balance_teams: bool = True
|
||||
|
||||
# The campaign used when in co-op session mode.
|
||||
# Do print(ba.app.campaigns) to see available campaign names.
|
||||
coop_campaign: str = 'Easy'
|
||||
|
||||
# The level name within the campaign used in co-op session mode.
|
||||
# For campaign name FOO, do print(ba.app.campaigns['FOO'].levels) to see
|
||||
# available level names.
|
||||
coop_level: str = 'Onslaught Training'
|
||||
|
||||
# Whether to enable telnet access.
|
||||
# IMPORTANT: This option is no longer available, as it was being used
|
||||
# for exploits. Live access to the running server is still possible through
|
||||
# the mgr.cmd() function in the server script. Run your server through
|
||||
# tools such as 'screen' or 'tmux' and you can reconnect to it remotely
|
||||
# over a secure ssh connection.
|
||||
enable_telnet: bool = False
|
||||
|
||||
# Series length in teams mode (7 == 'best-of-7' series; a team must
|
||||
# get 4 wins)
|
||||
teams_series_length: int = 7
|
||||
|
||||
# Points to win in free-for-all mode (Points are awarded per game based on
|
||||
# performance)
|
||||
ffa_series_length: int = 24
|
||||
|
||||
# If you have a custom stats webpage for your server, you can use this
|
||||
# to provide a convenient in-game link to it in the server-browser
|
||||
# alongside the server name.
|
||||
# if ${ACCOUNT} is present in the string, it will be replaced by the
|
||||
# currently-signed-in account's id. To fetch info about an account,
|
||||
# your back-end server can use the following url:
|
||||
# https://legacy.ballistica.net/accountquery?id=ACCOUNT_ID_HERE
|
||||
stats_url: str | None = None
|
||||
|
||||
# If present, the server subprocess will attempt to gracefully exit after
|
||||
# this amount of time. A graceful exit can occur at the end of a series
|
||||
# or other opportune time. Server-managers set to auto-restart (the
|
||||
# default) will then spin up a fresh subprocess. This mechanism can be
|
||||
# useful to clear out any memory leaks or other accumulated bad state
|
||||
# in the server subprocess.
|
||||
clean_exit_minutes: float | None = None
|
||||
|
||||
# If present, the server subprocess will shut down immediately after this
|
||||
# amount of time. This can be useful as a fallback for clean_exit_time.
|
||||
# The server manager will then spin up a fresh server subprocess if
|
||||
# auto-restart is enabled (the default).
|
||||
unclean_exit_minutes: float | None = None
|
||||
|
||||
# If present, the server subprocess will shut down immediately if this
|
||||
# amount of time passes with no activity from any players. The server
|
||||
# manager will then spin up a fresh server subprocess if auto-restart is
|
||||
# enabled (the default).
|
||||
idle_exit_minutes: float | None = None
|
||||
|
||||
# Should the tutorial be shown at the beginning of games?
|
||||
show_tutorial: bool = False
|
||||
|
||||
# Team names (teams mode only).
|
||||
team_names: tuple[str, str] | None = None
|
||||
|
||||
# Team colors (teams mode only).
|
||||
team_colors: tuple[
|
||||
tuple[float, float, float], tuple[float, float, float]
|
||||
] | None = None
|
||||
|
||||
# Whether to enable the queue where players can line up before entering
|
||||
# your server. Disabling this can be used as a workaround to deal with
|
||||
# queue spamming attacks.
|
||||
enable_queue: bool = True
|
||||
|
||||
# (internal) stress-testing mode.
|
||||
stress_test_players: int | None = None
|
||||
|
||||
|
||||
# NOTE: as much as possible, communication from the server-manager to the
|
||||
# child-process should go through these and not ad-hoc Python string commands
|
||||
# since this way is type safe.
|
||||
class ServerCommand:
|
||||
"""Base class for commands that can be sent to the server."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class StartServerModeCommand(ServerCommand):
|
||||
"""Tells the app to switch into 'server' mode."""
|
||||
|
||||
config: ServerConfig
|
||||
|
||||
|
||||
class ShutdownReason(Enum):
|
||||
"""Reason a server is shutting down."""
|
||||
|
||||
NONE = 'none'
|
||||
RESTARTING = 'restarting'
|
||||
|
||||
|
||||
@dataclass
|
||||
class ShutdownCommand(ServerCommand):
|
||||
"""Tells the server to shut down."""
|
||||
|
||||
reason: ShutdownReason
|
||||
immediate: bool
|
||||
|
||||
|
||||
@dataclass
|
||||
class ChatMessageCommand(ServerCommand):
|
||||
"""Chat message from the server."""
|
||||
|
||||
message: str
|
||||
clients: list[int] | None
|
||||
|
||||
|
||||
@dataclass
|
||||
class ScreenMessageCommand(ServerCommand):
|
||||
"""Screen-message from the server."""
|
||||
|
||||
message: str
|
||||
color: tuple[float, float, float] | None
|
||||
clients: list[int] | None
|
||||
|
||||
|
||||
@dataclass
|
||||
class ClientListCommand(ServerCommand):
|
||||
"""Print a list of clients."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class KickCommand(ServerCommand):
|
||||
"""Kick a client."""
|
||||
|
||||
client_id: int
|
||||
ban_time: int | None
|
||||
103
dist/ba_data/python/bacommon/transfer.py
vendored
Normal file
103
dist/ba_data/python/bacommon/transfer.py
vendored
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to transferring files/data."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Annotated
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class DirectoryManifestFile:
|
||||
"""Describes metadata and hashes for a file in a manifest."""
|
||||
|
||||
filehash: Annotated[str, IOAttrs('h')]
|
||||
filesize: Annotated[int, IOAttrs('s')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class DirectoryManifest:
|
||||
"""Contains a summary of files in a directory."""
|
||||
|
||||
files: Annotated[dict[str, DirectoryManifestFile], IOAttrs('f')]
|
||||
|
||||
_empty_hash: str | None = None
|
||||
|
||||
@classmethod
|
||||
def create_from_disk(cls, path: Path) -> DirectoryManifest:
|
||||
"""Create a manifest from a directory on disk."""
|
||||
import hashlib
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
pathstr = str(path)
|
||||
paths: list[str] = []
|
||||
|
||||
if path.is_dir():
|
||||
# Build the full list of relative paths.
|
||||
for basename, _dirnames, filenames in os.walk(path):
|
||||
for filename in filenames:
|
||||
fullname = os.path.join(basename, filename)
|
||||
assert fullname.startswith(pathstr)
|
||||
# Make sure we end up with forward slashes no matter
|
||||
# what the os.* stuff above here was using.
|
||||
paths.append(Path(fullname[len(pathstr) + 1 :]).as_posix())
|
||||
elif path.exists():
|
||||
# Just return a single file entry if path is not a dir.
|
||||
paths.append(path.as_posix())
|
||||
|
||||
def _get_file_info(filepath: str) -> tuple[str, DirectoryManifestFile]:
|
||||
sha = hashlib.sha256()
|
||||
fullfilepath = os.path.join(pathstr, filepath)
|
||||
if not os.path.isfile(fullfilepath):
|
||||
raise Exception(f'File not found: "{fullfilepath}"')
|
||||
with open(fullfilepath, 'rb') as infile:
|
||||
filebytes = infile.read()
|
||||
filesize = len(filebytes)
|
||||
sha.update(filebytes)
|
||||
return (
|
||||
filepath,
|
||||
DirectoryManifestFile(
|
||||
filehash=sha.hexdigest(), filesize=filesize
|
||||
),
|
||||
)
|
||||
|
||||
# Now use all procs to hash the files efficiently.
|
||||
cpus = os.cpu_count()
|
||||
if cpus is None:
|
||||
cpus = 4
|
||||
with ThreadPoolExecutor(max_workers=cpus) as executor:
|
||||
return cls(files=dict(executor.map(_get_file_info, paths)))
|
||||
|
||||
def validate(self) -> None:
|
||||
"""Log any odd data in the manifest; for debugging."""
|
||||
import logging
|
||||
|
||||
for fpath, _fentry in self.files.items():
|
||||
# We want to be dealing in only forward slashes; make sure
|
||||
# that's the case (wondering if we'll ever see backslashes
|
||||
# for escape purposes).
|
||||
if '\\' in fpath:
|
||||
logging.exception(
|
||||
"Found unusual path in manifest: '%s'.", fpath
|
||||
)
|
||||
break # 1 error is enough for now.
|
||||
|
||||
@classmethod
|
||||
def get_empty_hash(cls) -> str:
|
||||
"""Return the hash for an empty file."""
|
||||
if cls._empty_hash is None:
|
||||
import hashlib
|
||||
|
||||
sha = hashlib.sha256()
|
||||
cls._empty_hash = sha.hexdigest()
|
||||
return cls._empty_hash
|
||||
Loading…
Add table
Add a link
Reference in a new issue