mirror of
https://github.com/imayushsaini/Bombsquad-Ballistica-Modded-Server.git
synced 2025-10-20 00:00:39 +00:00
syncing with ballisitca/master
This commit is contained in:
parent
9c57ee13f5
commit
a748699245
132 changed files with 2955 additions and 2192 deletions
34
dist/ba_data/python/babase/__init__.py
vendored
34
dist/ba_data/python/babase/__init__.py
vendored
|
|
@ -2,10 +2,10 @@
|
||||||
#
|
#
|
||||||
"""Common shared Ballistica components.
|
"""Common shared Ballistica components.
|
||||||
|
|
||||||
For modding purposes, this package should generally not be used directly.
|
For modding purposes, this package should generally not be used
|
||||||
Instead one should use purpose-built packages such as bascenev1 or bauiv1
|
directly. Instead one should use purpose-built packages such as
|
||||||
which themselves import various functionality from here and reexpose it in
|
:mod:`bascenev1` or :mod:`bauiv1` which themselves import various
|
||||||
a more focused way.
|
functionality from here and reexpose it in a more focused way.
|
||||||
"""
|
"""
|
||||||
# pylint: disable=redefined-builtin
|
# pylint: disable=redefined-builtin
|
||||||
|
|
||||||
|
|
@ -35,6 +35,7 @@ from _babase import (
|
||||||
fullscreen_control_get,
|
fullscreen_control_get,
|
||||||
fullscreen_control_key_shortcut,
|
fullscreen_control_key_shortcut,
|
||||||
fullscreen_control_set,
|
fullscreen_control_set,
|
||||||
|
can_display_chars,
|
||||||
charstr,
|
charstr,
|
||||||
clipboard_get_text,
|
clipboard_get_text,
|
||||||
clipboard_has_text,
|
clipboard_has_text,
|
||||||
|
|
@ -64,7 +65,6 @@ from _babase import (
|
||||||
get_virtual_screen_size,
|
get_virtual_screen_size,
|
||||||
getsimplesound,
|
getsimplesound,
|
||||||
has_user_run_commands,
|
has_user_run_commands,
|
||||||
have_chars,
|
|
||||||
have_permission,
|
have_permission,
|
||||||
in_logic_thread,
|
in_logic_thread,
|
||||||
in_main_menu,
|
in_main_menu,
|
||||||
|
|
@ -123,7 +123,7 @@ from _babase import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from babase._accountv2 import AccountV2Handle, AccountV2Subsystem
|
from babase._accountv2 import AccountV2Handle, AccountV2Subsystem
|
||||||
from babase._app import App
|
from babase._app import App, AppState
|
||||||
from babase._appconfig import commit_app_config
|
from babase._appconfig import commit_app_config
|
||||||
from babase._appintent import AppIntent, AppIntentDefault, AppIntentExec
|
from babase._appintent import AppIntent, AppIntentDefault, AppIntentExec
|
||||||
from babase._appmode import AppMode
|
from babase._appmode import AppMode
|
||||||
|
|
@ -135,7 +135,7 @@ from babase._apputils import (
|
||||||
is_browser_likely_available,
|
is_browser_likely_available,
|
||||||
garbage_collect,
|
garbage_collect,
|
||||||
get_remote_app_name,
|
get_remote_app_name,
|
||||||
AppHealthMonitor,
|
AppHealthSubsystem,
|
||||||
utc_now_cloud,
|
utc_now_cloud,
|
||||||
)
|
)
|
||||||
from babase._cloud import CloudSubscription
|
from babase._cloud import CloudSubscription
|
||||||
|
|
@ -146,8 +146,6 @@ from babase._devconsole import (
|
||||||
)
|
)
|
||||||
from babase._emptyappmode import EmptyAppMode
|
from babase._emptyappmode import EmptyAppMode
|
||||||
from babase._error import (
|
from babase._error import (
|
||||||
print_exception,
|
|
||||||
print_error,
|
|
||||||
ContextError,
|
ContextError,
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
PlayerNotFoundError,
|
PlayerNotFoundError,
|
||||||
|
|
@ -164,7 +162,6 @@ from babase._error import (
|
||||||
DelegateNotFoundError,
|
DelegateNotFoundError,
|
||||||
)
|
)
|
||||||
from babase._general import (
|
from babase._general import (
|
||||||
utf8_all,
|
|
||||||
DisplayTime,
|
DisplayTime,
|
||||||
AppTime,
|
AppTime,
|
||||||
WeakCall,
|
WeakCall,
|
||||||
|
|
@ -189,12 +186,15 @@ from babase._mgen.enums import (
|
||||||
)
|
)
|
||||||
from babase._math import normalized_color, is_point_in_box, vec3validate
|
from babase._math import normalized_color, is_point_in_box, vec3validate
|
||||||
from babase._meta import MetadataSubsystem
|
from babase._meta import MetadataSubsystem
|
||||||
from babase._net import get_ip_address_type, DEFAULT_REQUEST_TIMEOUT_SECONDS
|
from babase._net import (
|
||||||
|
get_ip_address_type,
|
||||||
|
DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
||||||
|
NetworkSubsystem,
|
||||||
|
)
|
||||||
from babase._plugin import PluginSpec, Plugin, PluginSubsystem
|
from babase._plugin import PluginSpec, Plugin, PluginSubsystem
|
||||||
from babase._stringedit import StringEditAdapter, StringEditSubsystem
|
from babase._stringedit import StringEditAdapter, StringEditSubsystem
|
||||||
from babase._text import timestring
|
from babase._text import timestring
|
||||||
|
|
||||||
|
|
||||||
_babase.app = app = App()
|
_babase.app = app = App()
|
||||||
app.postinit()
|
app.postinit()
|
||||||
|
|
||||||
|
|
@ -207,14 +207,14 @@ __all__ = [
|
||||||
'add_clean_frame_callback',
|
'add_clean_frame_callback',
|
||||||
'android_get_external_files_dir',
|
'android_get_external_files_dir',
|
||||||
'app',
|
'app',
|
||||||
'app',
|
|
||||||
'App',
|
'App',
|
||||||
'AppConfig',
|
'AppConfig',
|
||||||
'AppHealthMonitor',
|
'AppHealthSubsystem',
|
||||||
'AppIntent',
|
'AppIntent',
|
||||||
'AppIntentDefault',
|
'AppIntentDefault',
|
||||||
'AppIntentExec',
|
'AppIntentExec',
|
||||||
'AppMode',
|
'AppMode',
|
||||||
|
'AppState',
|
||||||
'app_instance_uuid',
|
'app_instance_uuid',
|
||||||
'applog',
|
'applog',
|
||||||
'appname',
|
'appname',
|
||||||
|
|
@ -233,6 +233,7 @@ __all__ = [
|
||||||
'fullscreen_control_get',
|
'fullscreen_control_get',
|
||||||
'fullscreen_control_key_shortcut',
|
'fullscreen_control_key_shortcut',
|
||||||
'fullscreen_control_set',
|
'fullscreen_control_set',
|
||||||
|
'can_display_chars',
|
||||||
'charstr',
|
'charstr',
|
||||||
'clipboard_get_text',
|
'clipboard_get_text',
|
||||||
'clipboard_has_text',
|
'clipboard_has_text',
|
||||||
|
|
@ -279,7 +280,6 @@ __all__ = [
|
||||||
'getsimplesound',
|
'getsimplesound',
|
||||||
'handle_leftover_v1_cloud_log_file',
|
'handle_leftover_v1_cloud_log_file',
|
||||||
'has_user_run_commands',
|
'has_user_run_commands',
|
||||||
'have_chars',
|
|
||||||
'have_permission',
|
'have_permission',
|
||||||
'in_logic_thread',
|
'in_logic_thread',
|
||||||
'in_main_menu',
|
'in_main_menu',
|
||||||
|
|
@ -313,6 +313,7 @@ __all__ = [
|
||||||
'native_review_request',
|
'native_review_request',
|
||||||
'native_review_request_supported',
|
'native_review_request_supported',
|
||||||
'native_stack_trace',
|
'native_stack_trace',
|
||||||
|
'NetworkSubsystem',
|
||||||
'NodeNotFoundError',
|
'NodeNotFoundError',
|
||||||
'normalized_color',
|
'normalized_color',
|
||||||
'NotFoundError',
|
'NotFoundError',
|
||||||
|
|
@ -327,8 +328,6 @@ __all__ = [
|
||||||
'Plugin',
|
'Plugin',
|
||||||
'PluginSubsystem',
|
'PluginSubsystem',
|
||||||
'PluginSpec',
|
'PluginSpec',
|
||||||
'print_error',
|
|
||||||
'print_exception',
|
|
||||||
'print_load_info',
|
'print_load_info',
|
||||||
'push_back_press',
|
'push_back_press',
|
||||||
'pushcall',
|
'pushcall',
|
||||||
|
|
@ -367,7 +366,6 @@ __all__ = [
|
||||||
'user_agent_string',
|
'user_agent_string',
|
||||||
'user_ran_commands',
|
'user_ran_commands',
|
||||||
'utc_now_cloud',
|
'utc_now_cloud',
|
||||||
'utf8_all',
|
|
||||||
'Vec3',
|
'Vec3',
|
||||||
'vec3validate',
|
'vec3validate',
|
||||||
'verify_object_death',
|
'verify_object_death',
|
||||||
|
|
|
||||||
65
dist/ba_data/python/babase/_accountv2.py
vendored
65
dist/ba_data/python/babase/_accountv2.py
vendored
|
|
@ -25,9 +25,9 @@ logger = logging.getLogger('ba.accountv2')
|
||||||
class AccountV2Subsystem:
|
class AccountV2Subsystem:
|
||||||
"""Subsystem for modern account handling in the app.
|
"""Subsystem for modern account handling in the app.
|
||||||
|
|
||||||
Category: **App Classes**
|
Access the single shared instance of this class via the
|
||||||
|
:attr:`~baplus.PlusAppSubsystem.accounts` attr on the
|
||||||
Access the single shared instance of this class at 'ba.app.plus.accounts'.
|
:class:`~baplus.PlusAppSubsystem` class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
|
@ -71,8 +71,10 @@ class AccountV2Subsystem:
|
||||||
self.login_adapters[adapter.login_type] = adapter
|
self.login_adapters[adapter.login_type] = adapter
|
||||||
|
|
||||||
def on_app_loading(self) -> None:
|
def on_app_loading(self) -> None:
|
||||||
"""Should be called at standard on_app_loading time."""
|
"""Internal; Called at standard on_app_loading time.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
for adapter in self.login_adapters.values():
|
for adapter in self.login_adapters.values():
|
||||||
adapter.on_app_loading()
|
adapter.on_app_loading()
|
||||||
|
|
||||||
|
|
@ -81,7 +83,7 @@ class AccountV2Subsystem:
|
||||||
|
|
||||||
Note that this does not mean these credentials have been checked
|
Note that this does not mean these credentials have been checked
|
||||||
for validity; only that they exist. If/when credentials are
|
for validity; only that they exist. If/when credentials are
|
||||||
validated, the 'primary' account handle will be set.
|
validated, the :attr:`primary` account handle will be set.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -97,6 +99,8 @@ class AccountV2Subsystem:
|
||||||
|
|
||||||
Will be called with None on log-outs and when new credentials
|
Will be called with None on log-outs and when new credentials
|
||||||
are set but have not yet been verified.
|
are set but have not yet been verified.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
"""
|
"""
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
|
|
||||||
|
|
@ -144,15 +148,20 @@ class AccountV2Subsystem:
|
||||||
_babase.app.on_initial_sign_in_complete()
|
_babase.app.on_initial_sign_in_complete()
|
||||||
|
|
||||||
def on_active_logins_changed(self, logins: dict[LoginType, str]) -> None:
|
def on_active_logins_changed(self, logins: dict[LoginType, str]) -> None:
|
||||||
"""Should be called when logins for the active account change."""
|
"""Called when logins for the active account change.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
for adapter in self.login_adapters.values():
|
for adapter in self.login_adapters.values():
|
||||||
adapter.set_active_logins(logins)
|
adapter.set_active_logins(logins)
|
||||||
|
|
||||||
def on_implicit_sign_in(
|
def on_implicit_sign_in(
|
||||||
self, login_type: LoginType, login_id: str, display_name: str
|
self, login_type: LoginType, login_id: str, display_name: str
|
||||||
) -> None:
|
) -> None:
|
||||||
"""An implicit sign-in happened (called by native layer)."""
|
"""An implicit sign-in happened (called by native layer).
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
from babase._login import LoginAdapter
|
from babase._login import LoginAdapter
|
||||||
|
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
|
|
@ -165,17 +174,22 @@ class AccountV2Subsystem:
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_implicit_sign_out(self, login_type: LoginType) -> None:
|
def on_implicit_sign_out(self, login_type: LoginType) -> None:
|
||||||
"""An implicit sign-out happened (called by native layer)."""
|
"""An implicit sign-out happened (called by native layer).
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
with _babase.ContextRef.empty():
|
with _babase.ContextRef.empty():
|
||||||
self.login_adapters[login_type].set_implicit_login_state(None)
|
self.login_adapters[login_type].set_implicit_login_state(None)
|
||||||
|
|
||||||
def on_no_initial_primary_account(self) -> None:
|
def on_no_initial_primary_account(self) -> None:
|
||||||
"""Callback run if the app has no primary account after launch.
|
"""Internal; run if the app has no primary account after launch.
|
||||||
|
|
||||||
Either this callback or on_primary_account_changed will be called
|
Either this callback or on_primary_account_changed will be
|
||||||
within a few seconds of app launch; the app can move forward
|
called within a few seconds of app launch; the app can move
|
||||||
with the startup sequence at that point.
|
forward with the startup sequence at that point.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
"""
|
"""
|
||||||
if not self._initial_sign_in_completed:
|
if not self._initial_sign_in_completed:
|
||||||
self._initial_sign_in_completed = True
|
self._initial_sign_in_completed = True
|
||||||
|
|
@ -192,13 +206,15 @@ class AccountV2Subsystem:
|
||||||
login_type: LoginType,
|
login_type: LoginType,
|
||||||
state: LoginAdapter.ImplicitLoginState | None,
|
state: LoginAdapter.ImplicitLoginState | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Called when implicit login state changes.
|
"""Internal; Called when implicit login state changes.
|
||||||
|
|
||||||
Login systems that tend to sign themselves in/out in the
|
Login systems that tend to sign themselves in/out in the
|
||||||
background are considered implicit. We may choose to honor or
|
background are considered implicit. We may choose to honor or
|
||||||
ignore their states, allowing the user to opt for other login
|
ignore their states, allowing the user to opt for other login
|
||||||
types even if the default implicit one can't be explicitly
|
types even if the default implicit one can't be explicitly
|
||||||
logged out or otherwise controlled.
|
logged out or otherwise controlled.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
"""
|
"""
|
||||||
from babase._language import Lstr
|
from babase._language import Lstr
|
||||||
|
|
||||||
|
|
@ -284,11 +300,19 @@ class AccountV2Subsystem:
|
||||||
self._update_auto_sign_in()
|
self._update_auto_sign_in()
|
||||||
|
|
||||||
def do_get_primary(self) -> AccountV2Handle | None:
|
def do_get_primary(self) -> AccountV2Handle | None:
|
||||||
"""Internal - should be overridden by subclass."""
|
"""Internal; should be overridden by subclass.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def set_primary_credentials(self, credentials: str | None) -> None:
|
def set_primary_credentials(self, credentials: str | None) -> None:
|
||||||
"""Set credentials for the primary app account."""
|
"""Set credentials for the primary app account.
|
||||||
|
|
||||||
|
Once credentials are set, they will be verified in the cloud
|
||||||
|
asynchronously. If verification is successful, the
|
||||||
|
:attr:`primary` attr will be set to the resulting account.
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def _update_auto_sign_in(self) -> None:
|
def _update_auto_sign_in(self) -> None:
|
||||||
|
|
@ -441,14 +465,23 @@ class AccountV2Subsystem:
|
||||||
class AccountV2Handle:
|
class AccountV2Handle:
|
||||||
"""Handle for interacting with a V2 account.
|
"""Handle for interacting with a V2 account.
|
||||||
|
|
||||||
This class supports the 'with' statement, which is how it is
|
This class supports the ``with`` statement, which is how it is
|
||||||
used with some operations such as cloud messaging.
|
used with some operations such as cloud messaging.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
#: The id of this account.
|
||||||
accountid: str
|
accountid: str
|
||||||
|
|
||||||
|
#: The last known tag for this account.
|
||||||
tag: str
|
tag: str
|
||||||
|
|
||||||
|
#: The name of the workspace being synced to this client.
|
||||||
workspacename: str | None
|
workspacename: str | None
|
||||||
|
|
||||||
|
#: The id of the workspace being synced to this client, if any.
|
||||||
workspaceid: str | None
|
workspaceid: str | None
|
||||||
|
|
||||||
|
#: Info about last known logins associated with this account.
|
||||||
logins: dict[LoginType, LoginInfo]
|
logins: dict[LoginType, LoginInfo]
|
||||||
|
|
||||||
def __enter__(self) -> None:
|
def __enter__(self) -> None:
|
||||||
|
|
|
||||||
412
dist/ba_data/python/babase/_app.py
vendored
412
dist/ba_data/python/babase/_app.py
vendored
|
|
@ -11,7 +11,7 @@ from functools import partial
|
||||||
from typing import TYPE_CHECKING, TypeVar, override
|
from typing import TYPE_CHECKING, TypeVar, override
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
|
|
||||||
from efro.threadpool import ThreadPoolExecutorPlus
|
from efro.threadpool import ThreadPoolExecutorEx
|
||||||
|
|
||||||
import _babase
|
import _babase
|
||||||
from babase._language import LanguageSubsystem
|
from babase._language import LanguageSubsystem
|
||||||
|
|
@ -34,7 +34,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
import babase
|
import babase
|
||||||
from babase import AppIntent, AppMode, AppSubsystem
|
from babase import AppIntent, AppMode, AppSubsystem
|
||||||
from babase._apputils import AppHealthMonitor
|
from babase._apputils import AppHealthSubsystem
|
||||||
|
|
||||||
# __FEATURESET_APP_SUBSYSTEM_IMPORTS_BEGIN__
|
# __FEATURESET_APP_SUBSYSTEM_IMPORTS_BEGIN__
|
||||||
# This section generated by batools.appmodule; do not edit.
|
# This section generated by batools.appmodule; do not edit.
|
||||||
|
|
@ -49,112 +49,34 @@ T = TypeVar('T')
|
||||||
|
|
||||||
|
|
||||||
class App:
|
class App:
|
||||||
"""A class for high level app functionality and state.
|
"""High level Ballistica app functionality and state.
|
||||||
|
|
||||||
Category: **App Classes**
|
Access the single shared instance of this class via the ``app`` attr
|
||||||
|
available on various high level modules such as :mod:`babase`,
|
||||||
Use babase.app to access the single shared instance of this class.
|
:mod:`bauiv1`, and :mod:`bascenev1`.
|
||||||
|
|
||||||
Note that properties not documented here should be considered internal
|
|
||||||
and subject to change without warning.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# pylint: disable=too-many-public-methods
|
# pylint: disable=too-many-public-methods
|
||||||
|
|
||||||
# A few things defined as non-optional values but not actually
|
# A few things defined as non-optional values but not actually
|
||||||
# available until the app starts.
|
# available until the app starts (so we need to predeclare them
|
||||||
|
# here).
|
||||||
|
|
||||||
|
#: Subsystem for wrangling plugins.
|
||||||
plugins: PluginSubsystem
|
plugins: PluginSubsystem
|
||||||
|
|
||||||
|
#: Language subsystem.
|
||||||
lang: LanguageSubsystem
|
lang: LanguageSubsystem
|
||||||
health_monitor: AppHealthMonitor
|
|
||||||
|
|
||||||
# How long we allow shutdown tasks to run before killing them.
|
#: Subsystem for keeping tabs on app health.
|
||||||
# Currently the entire app hard-exits if shutdown takes 15 seconds,
|
health: AppHealthSubsystem
|
||||||
# so we need to keep it under that. Staying above 10 should allow
|
|
||||||
# 10 second network timeouts to happen though.
|
#: How long we allow shutdown tasks to run before killing them.
|
||||||
|
#: Currently the entire app hard-exits if shutdown takes 15 seconds,
|
||||||
|
#: so we need to keep it under that. Staying above 10 should allow
|
||||||
|
#: 10 second network timeouts to happen though.
|
||||||
SHUTDOWN_TASK_TIMEOUT_SECONDS = 12
|
SHUTDOWN_TASK_TIMEOUT_SECONDS = 12
|
||||||
|
|
||||||
class State(Enum):
|
|
||||||
"""High level state the app can be in."""
|
|
||||||
|
|
||||||
# The app has not yet begun starting and should not be used in
|
|
||||||
# any way.
|
|
||||||
NOT_STARTED = 0
|
|
||||||
|
|
||||||
# The native layer is spinning up its machinery (screens,
|
|
||||||
# renderers, etc.). Nothing should happen in the Python layer
|
|
||||||
# until this completes.
|
|
||||||
NATIVE_BOOTSTRAPPING = 1
|
|
||||||
|
|
||||||
# Python app subsystems are being inited but should not yet
|
|
||||||
# interact or do any work.
|
|
||||||
INITING = 2
|
|
||||||
|
|
||||||
# Python app subsystems are inited and interacting, but the app
|
|
||||||
# has not yet embarked on a high level course of action. It is
|
|
||||||
# doing initial account logins, workspace & asset downloads,
|
|
||||||
# etc.
|
|
||||||
LOADING = 3
|
|
||||||
|
|
||||||
# All pieces are in place and the app is now doing its thing.
|
|
||||||
RUNNING = 4
|
|
||||||
|
|
||||||
# Used on platforms such as mobile where the app basically needs
|
|
||||||
# to shut down while backgrounded. In this state, all event
|
|
||||||
# loops are suspended and all graphics and audio must cease
|
|
||||||
# completely. Be aware that the suspended state can be entered
|
|
||||||
# from any other state including NATIVE_BOOTSTRAPPING and
|
|
||||||
# SHUTTING_DOWN.
|
|
||||||
SUSPENDED = 5
|
|
||||||
|
|
||||||
# The app is shutting down. This process may involve sending
|
|
||||||
# network messages or other things that can take up to a few
|
|
||||||
# seconds, so ideally graphics and audio should remain
|
|
||||||
# functional (with fades or spinners or whatever to show
|
|
||||||
# something is happening).
|
|
||||||
SHUTTING_DOWN = 6
|
|
||||||
|
|
||||||
# The app has completed shutdown. Any code running here should
|
|
||||||
# be basically immediate.
|
|
||||||
SHUTDOWN_COMPLETE = 7
|
|
||||||
|
|
||||||
class DefaultAppModeSelector(AppModeSelector):
|
|
||||||
"""Decides which AppModes to use to handle AppIntents.
|
|
||||||
|
|
||||||
This default version is generated by the project updater based
|
|
||||||
on the 'default_app_modes' value in the projectconfig.
|
|
||||||
|
|
||||||
It is also possible to modify app mode selection behavior by
|
|
||||||
setting app.mode_selector to an instance of a custom
|
|
||||||
AppModeSelector subclass. This is a good way to go if you are
|
|
||||||
modifying app behavior dynamically via a plugin instead of
|
|
||||||
statically in a spinoff project.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@override
|
|
||||||
def app_mode_for_intent(
|
|
||||||
self, intent: AppIntent
|
|
||||||
) -> type[AppMode] | None:
|
|
||||||
# pylint: disable=cyclic-import
|
|
||||||
|
|
||||||
# __DEFAULT_APP_MODE_SELECTION_BEGIN__
|
|
||||||
# This section generated by batools.appmodule; do not edit.
|
|
||||||
|
|
||||||
# Ask our default app modes to handle it.
|
|
||||||
# (generated from 'default_app_modes' in projectconfig).
|
|
||||||
import baclassic
|
|
||||||
import babase
|
|
||||||
|
|
||||||
for appmode in [
|
|
||||||
baclassic.ClassicAppMode,
|
|
||||||
babase.EmptyAppMode,
|
|
||||||
]:
|
|
||||||
if appmode.can_handle_intent(intent):
|
|
||||||
return appmode
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
# __DEFAULT_APP_MODE_SELECTION_END__
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""(internal)
|
"""(internal)
|
||||||
|
|
||||||
|
|
@ -168,34 +90,47 @@ class App:
|
||||||
if os.environ.get('BA_RUNNING_WITH_DUMMY_MODULES') == '1':
|
if os.environ.get('BA_RUNNING_WITH_DUMMY_MODULES') == '1':
|
||||||
return
|
return
|
||||||
|
|
||||||
# Wrap our raw app config in our special wrapper and pass it to
|
#: Config values for the app.
|
||||||
# the native layer.
|
self.config: AppConfig = AppConfig(_babase.get_initial_app_config())
|
||||||
self.config = AppConfig(_babase.get_initial_app_config())
|
|
||||||
_babase.set_app_config(self.config)
|
_babase.set_app_config(self.config)
|
||||||
|
|
||||||
|
#: Static environment values for the app.
|
||||||
self.env: babase.Env = _babase.Env()
|
self.env: babase.Env = _babase.Env()
|
||||||
self.state = self.State.NOT_STARTED
|
|
||||||
|
|
||||||
# Default executor which can be used for misc background
|
#: Current app state.
|
||||||
# processing. It should also be passed to any additional asyncio
|
self.state: AppState = AppState.NOT_STARTED
|
||||||
# loops we create so that everything shares the same single set
|
|
||||||
# of worker threads.
|
#: Default executor which can be used for misc background
|
||||||
self.threadpool = ThreadPoolExecutorPlus(
|
#: processing. It should also be passed to any additional asyncio
|
||||||
|
#: loops we create so that everything shares the same single set
|
||||||
|
#: of worker threads.
|
||||||
|
self.threadpool: ThreadPoolExecutorEx = ThreadPoolExecutorEx(
|
||||||
thread_name_prefix='baworker',
|
thread_name_prefix='baworker',
|
||||||
initializer=self._thread_pool_thread_init,
|
initializer=self._thread_pool_thread_init,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.meta = MetadataSubsystem()
|
#: Subsystem for wrangling metadata.
|
||||||
self.net = NetworkSubsystem()
|
self.meta: MetadataSubsystem = MetadataSubsystem()
|
||||||
self.workspaces = WorkspaceSubsystem()
|
|
||||||
self.components = AppComponentSubsystem()
|
|
||||||
self.stringedit = StringEditSubsystem()
|
|
||||||
self.devconsole = DevConsoleSubsystem()
|
|
||||||
|
|
||||||
# This is incremented any time the app is backgrounded or
|
#: Subsystem for network functionality.
|
||||||
# foregrounded; can be a simple way to determine if network data
|
self.net: NetworkSubsystem = NetworkSubsystem()
|
||||||
# should be refreshed/etc.
|
|
||||||
self.fg_state = 0
|
#: Subsystem for wrangling workspaces.
|
||||||
|
self.workspaces: WorkspaceSubsystem = WorkspaceSubsystem()
|
||||||
|
|
||||||
|
# (not actually in use yet)
|
||||||
|
self.components: AppComponentSubsystem = AppComponentSubsystem()
|
||||||
|
|
||||||
|
#: Subsystem for wrangling text input from various sources.
|
||||||
|
self.stringedit: StringEditSubsystem = StringEditSubsystem()
|
||||||
|
|
||||||
|
#: Subsystem for wrangling the dev-console UI.
|
||||||
|
self.devconsole: DevConsoleSubsystem = DevConsoleSubsystem()
|
||||||
|
|
||||||
|
#: Incremented each time the app leaves the
|
||||||
|
#: :attr:`~babase.AppState.SUSPENDED` state. This can be a simple
|
||||||
|
#: way to determine if network data should be refreshed/etc.
|
||||||
|
self.fg_state: int = 0
|
||||||
|
|
||||||
self._subsystems: list[AppSubsystem] = []
|
self._subsystems: list[AppSubsystem] = []
|
||||||
self._native_bootstrapping_completed = False
|
self._native_bootstrapping_completed = False
|
||||||
|
|
@ -235,9 +170,9 @@ class App:
|
||||||
self._subsystem_property_data: dict[str, AppSubsystem | bool] = {}
|
self._subsystem_property_data: dict[str, AppSubsystem | bool] = {}
|
||||||
|
|
||||||
def postinit(self) -> None:
|
def postinit(self) -> None:
|
||||||
"""Called after we've been inited and assigned to babase.app.
|
"""Called after we've been inited and assigned to ``babase.app``.
|
||||||
|
|
||||||
Anything that accesses babase.app as part of its init process
|
Anything that accesses ``babase.app`` as part of its init process
|
||||||
must go here instead of __init__.
|
must go here instead of __init__.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -267,26 +202,27 @@ class App:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def asyncio_loop(self) -> asyncio.AbstractEventLoop:
|
def asyncio_loop(self) -> asyncio.AbstractEventLoop:
|
||||||
"""The logic thread's asyncio event loop.
|
"""The logic thread's :mod:`asyncio` event-loop.
|
||||||
|
|
||||||
This allow async tasks to be run in the logic thread.
|
This allows :mod:`asyncio` tasks to be run in the logic thread.
|
||||||
|
|
||||||
Generally you should call App.create_async_task() to schedule
|
Generally you should call
|
||||||
async code to run instead of using this directly. That will
|
:meth:`~babase.App.create_async_task()` to schedule async code
|
||||||
handle retaining the task and logging errors automatically.
|
to run instead of using this directly. That will handle
|
||||||
Only schedule tasks onto asyncio_loop yourself when you intend
|
retaining the task and logging errors automatically. Only
|
||||||
to hold on to the returned task and await its results. Releasing
|
schedule tasks onto ``asyncio_loop`` yourself when you intend to
|
||||||
|
hold on to the returned task and await its results. Releasing
|
||||||
the task reference can lead to subtle bugs such as unreported
|
the task reference can lead to subtle bugs such as unreported
|
||||||
errors and garbage-collected tasks disappearing before their
|
errors and garbage-collected tasks disappearing before their
|
||||||
work is done.
|
work is done.
|
||||||
|
|
||||||
Note that, at this time, the asyncio loop is encapsulated
|
Note that, at this time, the asyncio loop is encapsulated and
|
||||||
and explicitly stepped by the engine's logic thread loop and
|
explicitly stepped by the engine's logic thread loop and thus
|
||||||
thus things like asyncio.get_running_loop() will unintuitively
|
things like :meth:`asyncio.get_running_loop()` will
|
||||||
*not* return this loop from most places in the logic thread;
|
unintuitively *not* return this loop from most places in the
|
||||||
only from within a task explicitly created in this loop.
|
logic thread; only from within a task explicitly created in this
|
||||||
Hopefully this situation will be improved in the future with a
|
loop. Hopefully this situation will be improved in the future
|
||||||
unified event loop.
|
with a unified event loop.
|
||||||
"""
|
"""
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
assert self._asyncio_loop is not None
|
assert self._asyncio_loop is not None
|
||||||
|
|
@ -295,12 +231,12 @@ class App:
|
||||||
def create_async_task(
|
def create_async_task(
|
||||||
self, coro: Coroutine[Any, Any, T], *, name: str | None = None
|
self, coro: Coroutine[Any, Any, T], *, name: str | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Create a fully managed async task.
|
"""Create a fully managed :mod:`asyncio` task.
|
||||||
|
|
||||||
This will automatically retain and release a reference to the task
|
This will automatically retain and release a reference to the task
|
||||||
and log any exceptions that occur in it. If you need to await a task
|
and log any exceptions that occur in it. If you need to await a task
|
||||||
or otherwise need more control, schedule a task directly using
|
or otherwise need more control, schedule a task directly using
|
||||||
App.asyncio_loop.
|
:attr:`asyncio_loop`.
|
||||||
"""
|
"""
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
|
|
||||||
|
|
@ -453,7 +389,10 @@ class App:
|
||||||
# __FEATURESET_APP_SUBSYSTEM_PROPERTIES_END__
|
# __FEATURESET_APP_SUBSYSTEM_PROPERTIES_END__
|
||||||
|
|
||||||
def register_subsystem(self, subsystem: AppSubsystem) -> None:
|
def register_subsystem(self, subsystem: AppSubsystem) -> None:
|
||||||
"""Called by the AppSubsystem class. Do not use directly."""
|
"""Called by the AppSubsystem class. Do not use directly.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
|
|
||||||
# We only allow registering new subsystems if we've not yet
|
# We only allow registering new subsystems if we've not yet
|
||||||
# reached the 'running' state. This ensures that all subsystems
|
# reached the 'running' state. This ensures that all subsystems
|
||||||
|
|
@ -470,11 +409,12 @@ class App:
|
||||||
"""Add a task to be run on app shutdown.
|
"""Add a task to be run on app shutdown.
|
||||||
|
|
||||||
Note that shutdown tasks will be canceled after
|
Note that shutdown tasks will be canceled after
|
||||||
App.SHUTDOWN_TASK_TIMEOUT_SECONDS if they are still running.
|
:py:const:`SHUTDOWN_TASK_TIMEOUT_SECONDS` if they are still
|
||||||
|
running.
|
||||||
"""
|
"""
|
||||||
if (
|
if (
|
||||||
self.state is self.State.SHUTTING_DOWN
|
self.state is AppState.SHUTTING_DOWN
|
||||||
or self.state is self.State.SHUTDOWN_COMPLETE
|
or self.state is AppState.SHUTDOWN_COMPLETE
|
||||||
):
|
):
|
||||||
stname = self.state.name
|
stname = self.state.name
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
|
|
@ -485,8 +425,8 @@ class App:
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
"""Run the app to completion.
|
"""Run the app to completion.
|
||||||
|
|
||||||
Note that this only works on builds where Ballistica manages
|
Note that this only works on builds/runs where Ballistica is
|
||||||
its own event loop.
|
managing its own event loop.
|
||||||
"""
|
"""
|
||||||
_babase.run_app()
|
_babase.run_app()
|
||||||
|
|
||||||
|
|
@ -495,9 +435,9 @@ class App:
|
||||||
|
|
||||||
Intent defines what the app is trying to do at a given time.
|
Intent defines what the app is trying to do at a given time.
|
||||||
This call is asynchronous; the intent switch will happen in the
|
This call is asynchronous; the intent switch will happen in the
|
||||||
logic thread in the near future. If set_intent is called
|
logic thread in the near future. If this is called repeatedly
|
||||||
repeatedly before the change takes place, the final intent to be
|
before the change takes place, the final intent to be set will
|
||||||
set will be used.
|
be used.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Mark this one as pending. We do this synchronously so that the
|
# Mark this one as pending. We do this synchronously so that the
|
||||||
|
|
@ -510,7 +450,10 @@ class App:
|
||||||
self.threadpool.submit_no_wait(self._set_intent, intent)
|
self.threadpool.submit_no_wait(self._set_intent, intent)
|
||||||
|
|
||||||
def push_apply_app_config(self) -> None:
|
def push_apply_app_config(self) -> None:
|
||||||
"""Internal. Use app.config.apply() to apply app config changes."""
|
"""Internal. Use app.config.apply() to apply app config changes.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
# To be safe, let's run this by itself in the event loop.
|
# To be safe, let's run this by itself in the event loop.
|
||||||
# This avoids potential trouble if this gets called mid-draw or
|
# This avoids potential trouble if this gets called mid-draw or
|
||||||
# something like that.
|
# something like that.
|
||||||
|
|
@ -518,47 +461,68 @@ class App:
|
||||||
_babase.pushcall(self._apply_app_config, raw=True)
|
_babase.pushcall(self._apply_app_config, raw=True)
|
||||||
|
|
||||||
def on_native_start(self) -> None:
|
def on_native_start(self) -> None:
|
||||||
"""Called by the native layer when the app is being started."""
|
"""Called by the native layer when the app is being started.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
assert not self._native_start_called
|
assert not self._native_start_called
|
||||||
self._native_start_called = True
|
self._native_start_called = True
|
||||||
self._update_state()
|
self._update_state()
|
||||||
|
|
||||||
def on_native_bootstrapping_complete(self) -> None:
|
def on_native_bootstrapping_complete(self) -> None:
|
||||||
"""Called by the native layer once its ready to rock."""
|
"""Called by the native layer once its ready to rock.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
assert not self._native_bootstrapping_completed
|
assert not self._native_bootstrapping_completed
|
||||||
self._native_bootstrapping_completed = True
|
self._native_bootstrapping_completed = True
|
||||||
self._update_state()
|
self._update_state()
|
||||||
|
|
||||||
def on_native_suspend(self) -> None:
|
def on_native_suspend(self) -> None:
|
||||||
"""Called by the native layer when the app is suspended."""
|
"""Called by the native layer when the app is suspended.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
assert not self._native_suspended # Should avoid redundant calls.
|
assert not self._native_suspended # Should avoid redundant calls.
|
||||||
self._native_suspended = True
|
self._native_suspended = True
|
||||||
self._update_state()
|
self._update_state()
|
||||||
|
|
||||||
def on_native_unsuspend(self) -> None:
|
def on_native_unsuspend(self) -> None:
|
||||||
"""Called by the native layer when the app suspension ends."""
|
"""Called by the native layer when the app suspension ends.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
assert self._native_suspended # Should avoid redundant calls.
|
assert self._native_suspended # Should avoid redundant calls.
|
||||||
self._native_suspended = False
|
self._native_suspended = False
|
||||||
self._update_state()
|
self._update_state()
|
||||||
|
|
||||||
def on_native_shutdown(self) -> None:
|
def on_native_shutdown(self) -> None:
|
||||||
"""Called by the native layer when the app starts shutting down."""
|
"""Called by the native layer when the app starts shutting down.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
self._native_shutdown_called = True
|
self._native_shutdown_called = True
|
||||||
self._update_state()
|
self._update_state()
|
||||||
|
|
||||||
def on_native_shutdown_complete(self) -> None:
|
def on_native_shutdown_complete(self) -> None:
|
||||||
"""Called by the native layer when the app is done shutting down."""
|
"""Called by the native layer when the app is done shutting down.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
self._native_shutdown_complete_called = True
|
self._native_shutdown_complete_called = True
|
||||||
self._update_state()
|
self._update_state()
|
||||||
|
|
||||||
def on_native_active_changed(self) -> None:
|
def on_native_active_changed(self) -> None:
|
||||||
"""Called by the native layer when the app active state changes."""
|
"""Called by the native layer when the app active state changes.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
if self._mode is not None:
|
if self._mode is not None:
|
||||||
self._mode.on_app_active_changed()
|
self._mode.on_app_active_changed()
|
||||||
|
|
@ -590,6 +554,8 @@ class App:
|
||||||
initial-sign-in process may include tasks such as syncing
|
initial-sign-in process may include tasks such as syncing
|
||||||
account workspaces or other data so it may take a substantial
|
account workspaces or other data so it may take a substantial
|
||||||
amount of time.
|
amount of time.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
"""
|
"""
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
assert not self._initial_sign_in_completed
|
assert not self._initial_sign_in_completed
|
||||||
|
|
@ -604,8 +570,11 @@ class App:
|
||||||
def set_ui_scale(self, scale: babase.UIScale) -> None:
|
def set_ui_scale(self, scale: babase.UIScale) -> None:
|
||||||
"""Change ui-scale on the fly.
|
"""Change ui-scale on the fly.
|
||||||
|
|
||||||
Currently this is mainly for debugging and will not be called as
|
Currently this is mainly for testing/debugging and will not be
|
||||||
part of normal app operation.
|
called as part of normal app operation, though this may change
|
||||||
|
in the future.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
"""
|
"""
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
|
|
||||||
|
|
@ -625,7 +594,10 @@ class App:
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_screen_size_change(self) -> None:
|
def on_screen_size_change(self) -> None:
|
||||||
"""Screen size has changed."""
|
"""Screen size has changed.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
|
|
||||||
# Inform all app subsystems in the same order they were inited.
|
# Inform all app subsystems in the same order they were inited.
|
||||||
# Operate on a copy of the list here because this can be called
|
# Operate on a copy of the list here because this can be called
|
||||||
|
|
@ -768,7 +740,7 @@ class App:
|
||||||
# pylint: disable=cyclic-import
|
# pylint: disable=cyclic-import
|
||||||
from babase import _asyncio
|
from babase import _asyncio
|
||||||
from babase import _appconfig
|
from babase import _appconfig
|
||||||
from babase._apputils import AppHealthMonitor
|
from babase._apputils import AppHealthSubsystem
|
||||||
from babase import _env
|
from babase import _env
|
||||||
|
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
|
|
@ -776,7 +748,7 @@ class App:
|
||||||
_env.on_app_state_initing()
|
_env.on_app_state_initing()
|
||||||
|
|
||||||
self._asyncio_loop = _asyncio.setup_asyncio()
|
self._asyncio_loop = _asyncio.setup_asyncio()
|
||||||
self.health_monitor = AppHealthMonitor()
|
self.health = AppHealthSubsystem()
|
||||||
|
|
||||||
# __FEATURESET_APP_SUBSYSTEM_CREATE_BEGIN__
|
# __FEATURESET_APP_SUBSYSTEM_CREATE_BEGIN__
|
||||||
# This section generated by batools.appmodule; do not edit.
|
# This section generated by batools.appmodule; do not edit.
|
||||||
|
|
@ -849,7 +821,7 @@ class App:
|
||||||
# Set a default app-mode-selector if none has been set yet
|
# Set a default app-mode-selector if none has been set yet
|
||||||
# by a plugin or whatnot.
|
# by a plugin or whatnot.
|
||||||
if self._mode_selector is None:
|
if self._mode_selector is None:
|
||||||
self._mode_selector = self.DefaultAppModeSelector()
|
self._mode_selector = DefaultAppModeSelector()
|
||||||
|
|
||||||
# Inform all app subsystems in the same order they were
|
# Inform all app subsystems in the same order they were
|
||||||
# registered. Operate on a copy here because subsystems can
|
# registered. Operate on a copy here because subsystems can
|
||||||
|
|
@ -913,8 +885,8 @@ class App:
|
||||||
|
|
||||||
# Shutdown-complete trumps absolutely all.
|
# Shutdown-complete trumps absolutely all.
|
||||||
if self._native_shutdown_complete_called:
|
if self._native_shutdown_complete_called:
|
||||||
if self.state is not self.State.SHUTDOWN_COMPLETE:
|
if self.state is not AppState.SHUTDOWN_COMPLETE:
|
||||||
self.state = self.State.SHUTDOWN_COMPLETE
|
self.state = AppState.SHUTDOWN_COMPLETE
|
||||||
lifecyclelog.info('app-state is now %s', self.state.name)
|
lifecyclelog.info('app-state is now %s', self.state.name)
|
||||||
self._on_shutdown_complete()
|
self._on_shutdown_complete()
|
||||||
|
|
||||||
|
|
@ -923,26 +895,26 @@ class App:
|
||||||
# the shutdown process.
|
# the shutdown process.
|
||||||
elif self._native_shutdown_called and self._init_completed:
|
elif self._native_shutdown_called and self._init_completed:
|
||||||
# Entering shutdown state:
|
# Entering shutdown state:
|
||||||
if self.state is not self.State.SHUTTING_DOWN:
|
if self.state is not AppState.SHUTTING_DOWN:
|
||||||
self.state = self.State.SHUTTING_DOWN
|
self.state = AppState.SHUTTING_DOWN
|
||||||
applog.info('Shutting down...')
|
applog.info('Shutting down...')
|
||||||
lifecyclelog.info('app-state is now %s', self.state.name)
|
lifecyclelog.info('app-state is now %s', self.state.name)
|
||||||
self._on_shutting_down()
|
self._on_shutting_down()
|
||||||
|
|
||||||
elif self._native_suspended:
|
elif self._native_suspended:
|
||||||
# Entering suspended state:
|
# Entering suspended state:
|
||||||
if self.state is not self.State.SUSPENDED:
|
if self.state is not AppState.SUSPENDED:
|
||||||
self.state = self.State.SUSPENDED
|
self.state = AppState.SUSPENDED
|
||||||
self._on_suspend()
|
self._on_suspend()
|
||||||
else:
|
else:
|
||||||
# Leaving suspended state:
|
# Leaving suspended state:
|
||||||
if self.state is self.State.SUSPENDED:
|
if self.state is AppState.SUSPENDED:
|
||||||
self._on_unsuspend()
|
self._on_unsuspend()
|
||||||
|
|
||||||
# Entering or returning to running state
|
# Entering or returning to running state
|
||||||
if self._initial_sign_in_completed and self._meta_scan_completed:
|
if self._initial_sign_in_completed and self._meta_scan_completed:
|
||||||
if self.state != self.State.RUNNING:
|
if self.state != AppState.RUNNING:
|
||||||
self.state = self.State.RUNNING
|
self.state = AppState.RUNNING
|
||||||
lifecyclelog.info('app-state is now %s', self.state.name)
|
lifecyclelog.info('app-state is now %s', self.state.name)
|
||||||
if not self._called_on_running:
|
if not self._called_on_running:
|
||||||
self._called_on_running = True
|
self._called_on_running = True
|
||||||
|
|
@ -950,8 +922,8 @@ class App:
|
||||||
|
|
||||||
# Entering or returning to loading state:
|
# Entering or returning to loading state:
|
||||||
elif self._init_completed:
|
elif self._init_completed:
|
||||||
if self.state is not self.State.LOADING:
|
if self.state is not AppState.LOADING:
|
||||||
self.state = self.State.LOADING
|
self.state = AppState.LOADING
|
||||||
lifecyclelog.info('app-state is now %s', self.state.name)
|
lifecyclelog.info('app-state is now %s', self.state.name)
|
||||||
if not self._called_on_loading:
|
if not self._called_on_loading:
|
||||||
self._called_on_loading = True
|
self._called_on_loading = True
|
||||||
|
|
@ -959,8 +931,8 @@ class App:
|
||||||
|
|
||||||
# Entering or returning to initing state:
|
# Entering or returning to initing state:
|
||||||
elif self._native_bootstrapping_completed:
|
elif self._native_bootstrapping_completed:
|
||||||
if self.state is not self.State.INITING:
|
if self.state is not AppState.INITING:
|
||||||
self.state = self.State.INITING
|
self.state = AppState.INITING
|
||||||
lifecyclelog.info('app-state is now %s', self.state.name)
|
lifecyclelog.info('app-state is now %s', self.state.name)
|
||||||
if not self._called_on_initing:
|
if not self._called_on_initing:
|
||||||
self._called_on_initing = True
|
self._called_on_initing = True
|
||||||
|
|
@ -968,8 +940,8 @@ class App:
|
||||||
|
|
||||||
# Entering or returning to native bootstrapping:
|
# Entering or returning to native bootstrapping:
|
||||||
elif self._native_start_called:
|
elif self._native_start_called:
|
||||||
if self.state is not self.State.NATIVE_BOOTSTRAPPING:
|
if self.state is not AppState.NATIVE_BOOTSTRAPPING:
|
||||||
self.state = self.State.NATIVE_BOOTSTRAPPING
|
self.state = AppState.NATIVE_BOOTSTRAPPING
|
||||||
lifecyclelog.info('app-state is now %s', self.state.name)
|
lifecyclelog.info('app-state is now %s', self.state.name)
|
||||||
else:
|
else:
|
||||||
# Only logical possibility left is NOT_STARTED, in which
|
# Only logical possibility left is NOT_STARTED, in which
|
||||||
|
|
@ -1065,6 +1037,19 @@ class App:
|
||||||
"""(internal)"""
|
"""(internal)"""
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
|
|
||||||
|
# Deactivate any active app-mode. This allows things like saving
|
||||||
|
# state to happen naturally without needing to handle
|
||||||
|
# app-shutdown as a special case.
|
||||||
|
if self._mode is not None:
|
||||||
|
try:
|
||||||
|
self._mode.on_deactivate()
|
||||||
|
except Exception:
|
||||||
|
logging.exception(
|
||||||
|
'Error deactivating app-mode %s at app shutdown.',
|
||||||
|
self._mode,
|
||||||
|
)
|
||||||
|
self._mode = None
|
||||||
|
|
||||||
# Inform app subsystems that we're done shutting down in the opposite
|
# Inform app subsystems that we're done shutting down in the opposite
|
||||||
# order they were inited.
|
# order they were inited.
|
||||||
for subsystem in reversed(self._subsystems):
|
for subsystem in reversed(self._subsystems):
|
||||||
|
|
@ -1124,3 +1109,86 @@ class App:
|
||||||
# Help keep things clear in profiling tools/etc.
|
# Help keep things clear in profiling tools/etc.
|
||||||
self._pool_thread_count += 1
|
self._pool_thread_count += 1
|
||||||
_babase.set_thread_name(f'ballistica worker-{self._pool_thread_count}')
|
_babase.set_thread_name(f'ballistica worker-{self._pool_thread_count}')
|
||||||
|
|
||||||
|
|
||||||
|
class AppState(Enum):
|
||||||
|
"""High level state the app can be in."""
|
||||||
|
|
||||||
|
#: The app has not yet begun starting and should not be used in
|
||||||
|
#: any way.
|
||||||
|
NOT_STARTED = 0
|
||||||
|
|
||||||
|
#: The native layer is spinning up its machinery (screens,
|
||||||
|
#: renderers, etc.). Nothing should happen in the Python layer
|
||||||
|
#: until this completes.
|
||||||
|
NATIVE_BOOTSTRAPPING = 1
|
||||||
|
|
||||||
|
#: Python app subsystems are being inited but should not yet
|
||||||
|
#: interact or do any work.
|
||||||
|
INITING = 2
|
||||||
|
|
||||||
|
#: Python app subsystems are inited and interacting, but the app
|
||||||
|
#: has not yet embarked on a high level course of action. It is
|
||||||
|
#: doing initial account logins, workspace & asset downloads,
|
||||||
|
#: etc.
|
||||||
|
LOADING = 3
|
||||||
|
|
||||||
|
#: All pieces are in place and the app is now doing its thing.
|
||||||
|
RUNNING = 4
|
||||||
|
|
||||||
|
#: Used on platforms such as mobile where the app basically needs
|
||||||
|
#: to shut down while backgrounded. In this state, all event
|
||||||
|
#: loops are suspended and all graphics and audio must cease
|
||||||
|
#: completely. Be aware that the suspended state can be entered
|
||||||
|
#: from any other state including :attr:`NATIVE_BOOTSTRAPPING` and
|
||||||
|
#: :attr:`SHUTTING_DOWN`.
|
||||||
|
SUSPENDED = 5
|
||||||
|
|
||||||
|
#: The app is shutting down. This process may involve sending
|
||||||
|
#: network messages or other things that can take up to a few
|
||||||
|
#: seconds, so ideally graphics and audio should remain
|
||||||
|
#: functional (with fades or spinners or whatever to show
|
||||||
|
#: something is happening).
|
||||||
|
SHUTTING_DOWN = 6
|
||||||
|
|
||||||
|
#: The app has completed shutdown. Any code running here should
|
||||||
|
#: be basically immediate.
|
||||||
|
SHUTDOWN_COMPLETE = 7
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultAppModeSelector(AppModeSelector):
|
||||||
|
"""Selects an :class:`AppMode` to handle an :class:`AppIntent`.
|
||||||
|
|
||||||
|
This default version is generated by the project updater based
|
||||||
|
on the 'default_app_modes' value in the projectconfig.
|
||||||
|
|
||||||
|
It is also possible to modify app mode selection behavior by
|
||||||
|
setting the app's :attr:`~babase.App.mode_selector` to an
|
||||||
|
instance of a custom :class:`~babase.AppModeSelector` subclass.
|
||||||
|
This is a good way to go if you are modifying app behavior
|
||||||
|
dynamically via a :class:`~babase.Plugin` instead of statically
|
||||||
|
in a spinoff project.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@override
|
||||||
|
def app_mode_for_intent(self, intent: AppIntent) -> type[AppMode] | None:
|
||||||
|
# pylint: disable=cyclic-import
|
||||||
|
|
||||||
|
# __DEFAULT_APP_MODE_SELECTION_BEGIN__
|
||||||
|
# This section generated by batools.appmodule; do not edit.
|
||||||
|
|
||||||
|
# Ask our default app modes to handle it.
|
||||||
|
# (generated from 'default_app_modes' in projectconfig).
|
||||||
|
import baclassic
|
||||||
|
import babase
|
||||||
|
|
||||||
|
for appmode in [
|
||||||
|
baclassic.ClassicAppMode,
|
||||||
|
babase.EmptyAppMode,
|
||||||
|
]:
|
||||||
|
if appmode.can_handle_intent(intent):
|
||||||
|
return appmode
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
# __DEFAULT_APP_MODE_SELECTION_END__
|
||||||
|
|
|
||||||
2
dist/ba_data/python/babase/_appcomponent.py
vendored
2
dist/ba_data/python/babase/_appcomponent.py
vendored
|
|
@ -16,8 +16,6 @@ T = TypeVar('T', bound=type)
|
||||||
class AppComponentSubsystem:
|
class AppComponentSubsystem:
|
||||||
"""Subsystem for wrangling AppComponents.
|
"""Subsystem for wrangling AppComponents.
|
||||||
|
|
||||||
Category: **App Classes**
|
|
||||||
|
|
||||||
This subsystem acts as a registry for classes providing particular
|
This subsystem acts as a registry for classes providing particular
|
||||||
functionality for the app, and allows plugins or other custom code
|
functionality for the app, and allows plugins or other custom code
|
||||||
to easily override said functionality.
|
to easily override said functionality.
|
||||||
|
|
|
||||||
70
dist/ba_data/python/babase/_appconfig.py
vendored
70
dist/ba_data/python/babase/_appconfig.py
vendored
|
|
@ -14,41 +14,42 @@ _g_pending_apply = False # pylint: disable=invalid-name
|
||||||
|
|
||||||
|
|
||||||
class AppConfig(dict):
|
class AppConfig(dict):
|
||||||
"""A special dict that holds the game's persistent configuration values.
|
"""A special dict that holds persistent app configuration values.
|
||||||
|
|
||||||
Category: **App Classes**
|
It also provides methods for fetching values with app-defined
|
||||||
|
fallback defaults, applying contained values to the game, and
|
||||||
|
committing the config to storage.
|
||||||
|
|
||||||
It also provides methods for fetching values with app-defined fallback
|
Access the single shared instance of this config via the
|
||||||
defaults, applying contained values to the game, and committing the
|
:attr:`~babase.App.config` attr on the :class:`~babase.App` class.
|
||||||
config to storage.
|
|
||||||
|
|
||||||
Call babase.appconfig() to get the single shared instance of this class.
|
App-config data is stored as json on disk on so make sure to only
|
||||||
|
place json-friendly values in it (``dict``, ``list``, ``str``,
|
||||||
AppConfig data is stored as json on disk on so make sure to only place
|
``float``, ``int``, ``bool``). Be aware that tuples will be quietly
|
||||||
json-friendly values in it (dict, list, str, float, int, bool).
|
converted to lists when stored.
|
||||||
Be aware that tuples will be quietly converted to lists when stored.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def resolve(self, key: str) -> Any:
|
def resolve(self, key: str) -> Any:
|
||||||
"""Given a string key, return a config value (type varies).
|
"""Given a string key, return a config value (type varies).
|
||||||
|
|
||||||
This will substitute application defaults for values not present in
|
This will substitute application defaults for values not present
|
||||||
the config dict, filter some invalid values, etc. Note that these
|
in the config dict, filter some invalid values, etc. Note that
|
||||||
values do not represent the state of the app; simply the state of its
|
these values do not represent the state of the app; simply the
|
||||||
config. Use babase.App to access actual live state.
|
state of its config. Use the :class:`~babase.App` class to
|
||||||
|
access actual live state.
|
||||||
|
|
||||||
Raises an Exception for unrecognized key names. To get the list of keys
|
Raises an :class:`KeyError` for unrecognized key names. To get
|
||||||
supported by this method, use babase.AppConfig.builtin_keys(). Note
|
the list of keys supported by this method, use
|
||||||
that it is perfectly legal to store other data in the config; it just
|
:meth:`builtin_keys()`. Note that it is perfectly legal to store
|
||||||
needs to be accessed through standard dict methods and missing values
|
other data in the config; it just needs to be accessed through
|
||||||
handled manually.
|
standard dict methods and missing values handled manually.
|
||||||
"""
|
"""
|
||||||
return _babase.resolve_appconfig_value(key)
|
return _babase.resolve_appconfig_value(key)
|
||||||
|
|
||||||
def default_value(self, key: str) -> Any:
|
def default_value(self, key: str) -> Any:
|
||||||
"""Given a string key, return its predefined default value.
|
"""Given a string key, return its predefined default value.
|
||||||
|
|
||||||
This is the value that will be returned by babase.AppConfig.resolve()
|
This is the value that will be returned by :meth:`resolve()`
|
||||||
if the key is not present in the config dict or of an incompatible
|
if the key is not present in the config dict or of an incompatible
|
||||||
type.
|
type.
|
||||||
|
|
||||||
|
|
@ -61,17 +62,19 @@ class AppConfig(dict):
|
||||||
return _babase.get_appconfig_default_value(key)
|
return _babase.get_appconfig_default_value(key)
|
||||||
|
|
||||||
def builtin_keys(self) -> list[str]:
|
def builtin_keys(self) -> list[str]:
|
||||||
"""Return the list of valid key names recognized by babase.AppConfig.
|
"""Return the list of valid key names recognized by this class.
|
||||||
|
|
||||||
This set of keys can be used with resolve(), default_value(), etc.
|
This set of keys can be used with :meth:`resolve()`,
|
||||||
It does not vary across platforms and may include keys that are
|
:meth:`default_value()`, etc. It does not vary across platforms
|
||||||
obsolete or not relevant on the current running version. (for instance,
|
and may include keys that are obsolete or not relevant on the
|
||||||
VR related keys on non-VR platforms). This is to minimize the amount
|
current running version. (for instance, VR related keys on
|
||||||
of platform checking necessary)
|
non-VR platforms). This is to minimize the amount of platform
|
||||||
|
checking necessary)
|
||||||
|
|
||||||
Note that it is perfectly legal to store arbitrary named data in the
|
Note that it is perfectly legal to store arbitrary named data in
|
||||||
config, but in that case it is up to the user to test for the existence
|
the config, but in that case it is up to the user to test for
|
||||||
of the key in the config dict, fall back to consistent defaults, etc.
|
the existence of the key in the config dict, fall back to
|
||||||
|
consistent defaults, etc.
|
||||||
"""
|
"""
|
||||||
return _babase.get_appconfig_builtin_keys()
|
return _babase.get_appconfig_builtin_keys()
|
||||||
|
|
||||||
|
|
@ -92,9 +95,10 @@ class AppConfig(dict):
|
||||||
commit_app_config()
|
commit_app_config()
|
||||||
|
|
||||||
def apply_and_commit(self) -> None:
|
def apply_and_commit(self) -> None:
|
||||||
"""Run apply() followed by commit(); for convenience.
|
"""Shortcut to run :meth:`apply()` followed by :meth:`commit()`.
|
||||||
|
|
||||||
(This way the commit() will not occur if apply() hits invalid data)
|
This way the :meth:`commit()` will not occur if :meth:`apply()`
|
||||||
|
hits invalid data, which is generally desirable.
|
||||||
"""
|
"""
|
||||||
self.apply()
|
self.apply()
|
||||||
self.commit()
|
self.commit()
|
||||||
|
|
@ -103,9 +107,7 @@ class AppConfig(dict):
|
||||||
def commit_app_config() -> None:
|
def commit_app_config() -> None:
|
||||||
"""Commit the config to persistent storage.
|
"""Commit the config to persistent storage.
|
||||||
|
|
||||||
Category: **General Utility Functions**
|
:meta private:
|
||||||
|
|
||||||
(internal)
|
|
||||||
"""
|
"""
|
||||||
# FIXME - this should not require plus.
|
# FIXME - this should not require plus.
|
||||||
plus = _babase.app.plus
|
plus = _babase.app.plus
|
||||||
|
|
|
||||||
5
dist/ba_data/python/babase/_appintent.py
vendored
5
dist/ba_data/python/babase/_appintent.py
vendored
|
|
@ -10,10 +10,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class AppIntent:
|
class AppIntent:
|
||||||
"""A high level directive given to the app.
|
"""Base class for high level directives given to the app."""
|
||||||
|
|
||||||
Category: **App Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class AppIntentDefault(AppIntent):
|
class AppIntentDefault(AppIntent):
|
||||||
|
|
|
||||||
131
dist/ba_data/python/babase/_appmode.py
vendored
131
dist/ba_data/python/babase/_appmode.py
vendored
|
|
@ -3,18 +3,19 @@
|
||||||
"""Provides AppMode functionality."""
|
"""Provides AppMode functionality."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, final
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from bacommon.app import AppExperience
|
from bacommon.app import AppExperience
|
||||||
from babase._appintent import AppIntent
|
from babase import AppIntent
|
||||||
|
|
||||||
|
|
||||||
class AppMode:
|
class AppMode:
|
||||||
"""A high level mode for the app.
|
"""A low level mode the app can be in.
|
||||||
|
|
||||||
Category: **App Classes**
|
|
||||||
|
|
||||||
|
App-modes fundamentally change app behavior related to input
|
||||||
|
handling, networking, graphics, and more. In a way, different
|
||||||
|
app-modes can almost be considered different apps.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -22,26 +23,27 @@ class AppMode:
|
||||||
"""Return the overall experience provided by this mode."""
|
"""Return the overall experience provided by this mode."""
|
||||||
raise NotImplementedError('AppMode subclasses must override this.')
|
raise NotImplementedError('AppMode subclasses must override this.')
|
||||||
|
|
||||||
|
@final
|
||||||
@classmethod
|
@classmethod
|
||||||
def can_handle_intent(cls, intent: AppIntent) -> bool:
|
def can_handle_intent(cls, intent: AppIntent) -> bool:
|
||||||
"""Return whether this mode can handle the provided intent.
|
"""Return whether this mode can handle the provided intent.
|
||||||
|
|
||||||
For this to return True, the AppMode must claim to support the
|
For this to return True, the app-mode must claim to support the
|
||||||
provided intent (via its _can_handle_intent() method) AND the
|
provided intent (via its :meth:`can_handle_intent_impl()`
|
||||||
AppExperience associated with the AppMode must be supported by
|
method) *AND* the :class:`~bacommon.app.AppExperience` associated
|
||||||
the current app and runtime environment.
|
with the app-mode must be supported by the current app and
|
||||||
|
runtime environment.
|
||||||
"""
|
"""
|
||||||
# TODO: check AppExperience against current environment.
|
# TODO: check AppExperience against current environment.
|
||||||
return cls._can_handle_intent(intent)
|
return cls.can_handle_intent_impl(intent)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _can_handle_intent(cls, intent: AppIntent) -> bool:
|
def can_handle_intent_impl(cls, intent: AppIntent) -> bool:
|
||||||
"""Return whether our mode can handle the provided intent.
|
"""Override this to define indent handling for an app-mode.
|
||||||
|
|
||||||
AppModes should override this to communicate what they can
|
Note that :class:`~bacommon.app.AppExperience` does not have to
|
||||||
handle. Note that AppExperience does not have to be considered
|
be considered here; that is handled automatically by the
|
||||||
here; that is handled automatically by the can_handle_intent()
|
:meth:`can_handle_intent()` call.
|
||||||
call.
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError('AppMode subclasses must override this.')
|
raise NotImplementedError('AppMode subclasses must override this.')
|
||||||
|
|
||||||
|
|
@ -50,14 +52,101 @@ class AppMode:
|
||||||
raise NotImplementedError('AppMode subclasses must override this.')
|
raise NotImplementedError('AppMode subclasses must override this.')
|
||||||
|
|
||||||
def on_activate(self) -> None:
|
def on_activate(self) -> None:
|
||||||
"""Called when the mode is being activated."""
|
"""Called when the mode is becoming the active one fro the app."""
|
||||||
|
|
||||||
def on_deactivate(self) -> None:
|
def on_deactivate(self) -> None:
|
||||||
"""Called when the mode is being deactivated."""
|
"""Called when the mode stops being the active one for the app.
|
||||||
|
|
||||||
|
On platforms where the app is explicitly exited (such as desktop
|
||||||
|
PC) this will also be called at app shutdown.
|
||||||
|
|
||||||
|
To best cover both mobile and desktop style platforms, actions
|
||||||
|
such as saving state should generally happen in response to both
|
||||||
|
:meth:`on_deactivate()` and :meth:`on_app_active_changed()`
|
||||||
|
(when active is False).
|
||||||
|
"""
|
||||||
|
|
||||||
def on_app_active_changed(self) -> None:
|
def on_app_active_changed(self) -> None:
|
||||||
"""Called when ba*.app.active changes while this mode is active.
|
"""Called when the app's active state changes while in this app-mode.
|
||||||
|
|
||||||
The app-mode may want to take action such as pausing a running
|
This corresponds to the app's :attr:`~babase.App.active` attr.
|
||||||
game in such cases.
|
App-active state becomes false when the app is hidden,
|
||||||
|
minimized, backgrounded, etc. The app-mode may want to take
|
||||||
|
action such as pausing a running game or saving state when this
|
||||||
|
occurs.
|
||||||
|
|
||||||
|
On platforms such as mobile where apps get suspended and later
|
||||||
|
silently terminated by the OS, this is likely to be the last
|
||||||
|
reliable place to save state/etc.
|
||||||
|
|
||||||
|
To best cover both mobile and desktop style platforms, actions
|
||||||
|
such as saving state should generally happen in response to both
|
||||||
|
:meth:`on_deactivate()` and :meth:`on_app_active_changed()`
|
||||||
|
(when active is False).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def on_purchase_process_begin(
|
||||||
|
self, item_id: str, user_initiated: bool
|
||||||
|
) -> None:
|
||||||
|
"""Called when in-app-purchase processing is beginning.
|
||||||
|
|
||||||
|
This call happens after a purchase has been completed locally
|
||||||
|
but before its receipt/info is sent to the master-server to
|
||||||
|
apply to the account.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
|
# pylint: disable=cyclic-import
|
||||||
|
import babase
|
||||||
|
|
||||||
|
del item_id # Unused.
|
||||||
|
|
||||||
|
# Show nothing for stuff not directly kicked off by the user.
|
||||||
|
if not user_initiated:
|
||||||
|
return
|
||||||
|
|
||||||
|
babase.screenmessage(
|
||||||
|
babase.Lstr(resource='updatingAccountText'),
|
||||||
|
color=(0, 1, 0),
|
||||||
|
)
|
||||||
|
# Ick; we can be called early in the bootstrapping process
|
||||||
|
# before we're allowed to load assets. Guard against that.
|
||||||
|
if babase.asset_loads_allowed():
|
||||||
|
babase.getsimplesound('click01').play()
|
||||||
|
|
||||||
|
def on_purchase_process_end(
|
||||||
|
self, item_id: str, user_initiated: bool, applied: bool
|
||||||
|
) -> None:
|
||||||
|
"""Called when in-app-purchase processing completes.
|
||||||
|
|
||||||
|
Each call to :meth:`on_purchase_process_begin()` will be
|
||||||
|
followed up by a call to this method. If the purchase was found
|
||||||
|
to be valid and was applied to the account, applied will be
|
||||||
|
True. In the case of redundant or invalid purchases or
|
||||||
|
communication failures it will be False.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
|
# pylint: disable=cyclic-import
|
||||||
|
import babase
|
||||||
|
|
||||||
|
# Ignore this; we want to announce newly applied stuff even if
|
||||||
|
# it was from a different launch or client or whatever.
|
||||||
|
del user_initiated
|
||||||
|
|
||||||
|
# If the purchase wasn't applied, do nothing. This likely means it
|
||||||
|
# was redundant or something else harmless.
|
||||||
|
if not applied:
|
||||||
|
return
|
||||||
|
|
||||||
|
# By default just announce the item id we got. Real app-modes
|
||||||
|
# probably want to do something more specific based on item-id.
|
||||||
|
babase.screenmessage(
|
||||||
|
babase.Lstr(
|
||||||
|
translate=('serverResponses', 'You got a ${ITEM}!'),
|
||||||
|
subs=[('${ITEM}', item_id)],
|
||||||
|
),
|
||||||
|
color=(0, 1, 0),
|
||||||
|
)
|
||||||
|
if babase.asset_loads_allowed():
|
||||||
|
babase.getsimplesound('cashRegister').play()
|
||||||
|
|
|
||||||
21
dist/ba_data/python/babase/_appmodeselector.py
vendored
21
dist/ba_data/python/babase/_appmodeselector.py
vendored
|
|
@ -6,25 +6,24 @@ from __future__ import annotations
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from babase._appintent import AppIntent
|
from babase import AppMode, AppIntent
|
||||||
from babase._appmode import AppMode
|
|
||||||
|
|
||||||
|
|
||||||
class AppModeSelector:
|
class AppModeSelector:
|
||||||
"""Defines which AppModes are available or used to handle given AppIntents.
|
"""Defines which app-modes should handle which app-intents.
|
||||||
|
|
||||||
Category: **App Classes**
|
The app calls an instance of this class when passed an
|
||||||
|
:class:`~babase.AppIntent` to determine which
|
||||||
The app calls an instance of this class when passed an AppIntent to
|
:class:`~babase.AppMode` to use to handle it. Plugins or spinoff
|
||||||
determine which AppMode to use to handle the intent. Plugins or
|
projects can modify high level app behavior by replacing or
|
||||||
spinoff projects can modify high level app behavior by replacing or
|
modifying the app's :attr:`~babase.App.mode_selector` attr or by
|
||||||
modifying the app's mode-selector.
|
modifying settings used to construct the default one.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def app_mode_for_intent(self, intent: AppIntent) -> type[AppMode] | None:
|
def app_mode_for_intent(self, intent: AppIntent) -> type[AppMode] | None:
|
||||||
"""Given an AppIntent, return the AppMode that should handle it.
|
"""Given an app-intent, return the app-mode that should handle it.
|
||||||
|
|
||||||
If None is returned, the AppIntent will be ignored.
|
If None is returned, the intent will be ignored.
|
||||||
|
|
||||||
This may be called in a background thread, so avoid any calls
|
This may be called in a background thread, so avoid any calls
|
||||||
limited to logic thread use/etc.
|
limited to logic thread use/etc.
|
||||||
|
|
|
||||||
16
dist/ba_data/python/babase/_appsubsystem.py
vendored
16
dist/ba_data/python/babase/_appsubsystem.py
vendored
|
|
@ -14,8 +14,6 @@ if TYPE_CHECKING:
|
||||||
class AppSubsystem:
|
class AppSubsystem:
|
||||||
"""Base class for an app subsystem.
|
"""Base class for an app subsystem.
|
||||||
|
|
||||||
Category: **App Classes**
|
|
||||||
|
|
||||||
An app 'subsystem' is a bit of a vague term, as pieces of the app
|
An app 'subsystem' is a bit of a vague term, as pieces of the app
|
||||||
can technically be any class and are not required to use this, but
|
can technically be any class and are not required to use this, but
|
||||||
building one out of this base class provides conveniences such as
|
building one out of this base class provides conveniences such as
|
||||||
|
|
@ -37,19 +35,19 @@ class AppSubsystem:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def on_app_running(self) -> None:
|
def on_app_running(self) -> None:
|
||||||
"""Called when the app reaches the running state."""
|
"""Called when app enters :attr:`~AppState.RUNNING` state."""
|
||||||
|
|
||||||
def on_app_suspend(self) -> None:
|
def on_app_suspend(self) -> None:
|
||||||
"""Called when the app enters the suspended state."""
|
"""Called when app enters :attr:`~AppState.SUSPENDED` state."""
|
||||||
|
|
||||||
def on_app_unsuspend(self) -> None:
|
def on_app_unsuspend(self) -> None:
|
||||||
"""Called when the app exits the suspended state."""
|
"""Called when app exits :attr:`~AppState.SUSPENDED` state."""
|
||||||
|
|
||||||
def on_app_shutdown(self) -> None:
|
def on_app_shutdown(self) -> None:
|
||||||
"""Called when the app begins shutting down."""
|
"""Called when app enters :attr:`~AppState.SHUTTING_DOWN` state."""
|
||||||
|
|
||||||
def on_app_shutdown_complete(self) -> None:
|
def on_app_shutdown_complete(self) -> None:
|
||||||
"""Called when the app completes shutting down."""
|
"""Called when app enters :attr:`~AppState.SHUTDOWN_COMPLETE` state."""
|
||||||
|
|
||||||
def do_apply_app_config(self) -> None:
|
def do_apply_app_config(self) -> None:
|
||||||
"""Called when the app config should be applied."""
|
"""Called when the app config should be applied."""
|
||||||
|
|
@ -69,6 +67,6 @@ class AppSubsystem:
|
||||||
def reset(self) -> None:
|
def reset(self) -> None:
|
||||||
"""Reset the subsystem to a default state.
|
"""Reset the subsystem to a default state.
|
||||||
|
|
||||||
This is called when switching app modes, but may be called
|
This is called when switching app modes, but may be called at
|
||||||
at other times too.
|
other times too.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
91
dist/ba_data/python/babase/_apputils.py
vendored
91
dist/ba_data/python/babase/_apputils.py
vendored
|
|
@ -5,7 +5,6 @@ from __future__ import annotations
|
||||||
|
|
||||||
import gc
|
import gc
|
||||||
import os
|
import os
|
||||||
import logging
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
@ -17,6 +16,7 @@ from efro.dataclassio import ioprepped, dataclass_to_json, dataclass_from_json
|
||||||
|
|
||||||
import _babase
|
import _babase
|
||||||
from babase._appsubsystem import AppSubsystem
|
from babase._appsubsystem import AppSubsystem
|
||||||
|
from babase._logging import balog
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import datetime
|
import datetime
|
||||||
|
|
@ -39,17 +39,16 @@ def utc_now_cloud() -> datetime.datetime:
|
||||||
def is_browser_likely_available() -> bool:
|
def is_browser_likely_available() -> bool:
|
||||||
"""Return whether a browser likely exists on the current device.
|
"""Return whether a browser likely exists on the current device.
|
||||||
|
|
||||||
category: General Utility Functions
|
If this returns False, you may want to avoid calling
|
||||||
|
:meth:`~babase.open_url()` with any lengthy addresses.
|
||||||
If this returns False you may want to avoid calling babase.open_url()
|
(:meth:`~babase.open_url()` will display an address as a
|
||||||
with any lengthy addresses. (babase.open_url() will display an address
|
string/qr-code in a window if unable to bring up a browser, but that
|
||||||
as a string in a window if unable to bring up a browser, but that
|
is only reasonable for small-ish URLs.)
|
||||||
is only useful for simple URLs.)
|
|
||||||
"""
|
"""
|
||||||
app = _babase.app
|
app = _babase.app
|
||||||
|
|
||||||
if app.classic is None:
|
if app.classic is None:
|
||||||
logging.warning(
|
balog.warning(
|
||||||
'is_browser_likely_available() needs to be updated'
|
'is_browser_likely_available() needs to be updated'
|
||||||
' to work without classic.'
|
' to work without classic.'
|
||||||
)
|
)
|
||||||
|
|
@ -70,14 +69,14 @@ def is_browser_likely_available() -> bool:
|
||||||
|
|
||||||
|
|
||||||
def get_remote_app_name() -> babase.Lstr:
|
def get_remote_app_name() -> babase.Lstr:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
from babase import _language
|
from babase import _language
|
||||||
|
|
||||||
return _language.Lstr(resource='remote_app.app_name')
|
return _language.Lstr(resource='remote_app.app_name')
|
||||||
|
|
||||||
|
|
||||||
def should_submit_debug_info() -> bool:
|
def should_submit_debug_info() -> bool:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
val = _babase.app.config.get('Submit Debug Info', True)
|
val = _babase.app.config.get('Submit Debug Info', True)
|
||||||
assert isinstance(val, bool)
|
assert isinstance(val, bool)
|
||||||
return val
|
return val
|
||||||
|
|
@ -96,7 +95,7 @@ def handle_v1_cloud_log() -> None:
|
||||||
|
|
||||||
if classic is None or plus is None:
|
if classic is None or plus is None:
|
||||||
if _babase.do_once():
|
if _babase.do_once():
|
||||||
logging.warning(
|
balog.warning(
|
||||||
'handle_v1_cloud_log should not be getting called'
|
'handle_v1_cloud_log should not be getting called'
|
||||||
' without classic and plus present.'
|
' without classic and plus present.'
|
||||||
)
|
)
|
||||||
|
|
@ -133,9 +132,8 @@ def handle_v1_cloud_log() -> None:
|
||||||
|
|
||||||
def response(data: Any) -> None:
|
def response(data: Any) -> None:
|
||||||
assert classic is not None
|
assert classic is not None
|
||||||
# A non-None response means success; lets
|
# A non-None response means success; lets take note that
|
||||||
# take note that we don't need to report further
|
# we don't need to report further log info this run
|
||||||
# log info this run
|
|
||||||
if data is not None:
|
if data is not None:
|
||||||
classic.log_have_new = False
|
classic.log_have_new = False
|
||||||
_babase.mark_log_sent()
|
_babase.mark_log_sent()
|
||||||
|
|
@ -144,8 +142,8 @@ def handle_v1_cloud_log() -> None:
|
||||||
|
|
||||||
classic.log_upload_timer_started = True
|
classic.log_upload_timer_started = True
|
||||||
|
|
||||||
# Delay our log upload slightly in case other
|
# Delay our log upload slightly in case other pertinent info
|
||||||
# pertinent info gets printed between now and then.
|
# gets printed between now and then.
|
||||||
with _babase.ContextRef.empty():
|
with _babase.ContextRef.empty():
|
||||||
_babase.apptimer(3.0, _put_log)
|
_babase.apptimer(3.0, _put_log)
|
||||||
|
|
||||||
|
|
@ -162,7 +160,10 @@ def handle_v1_cloud_log() -> None:
|
||||||
|
|
||||||
|
|
||||||
def handle_leftover_v1_cloud_log_file() -> None:
|
def handle_leftover_v1_cloud_log_file() -> None:
|
||||||
"""Handle an un-uploaded v1-cloud-log from a previous run."""
|
"""Handle an un-uploaded v1-cloud-log from a previous run.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
|
|
||||||
# Only applies with classic present.
|
# Only applies with classic present.
|
||||||
if _babase.app.classic is None:
|
if _babase.app.classic is None:
|
||||||
|
|
@ -180,8 +181,8 @@ def handle_leftover_v1_cloud_log_file() -> None:
|
||||||
if do_send:
|
if do_send:
|
||||||
|
|
||||||
def response(data: Any) -> None:
|
def response(data: Any) -> None:
|
||||||
# Non-None response means we were successful;
|
# Non-None response means we were successful; lets
|
||||||
# lets kill it.
|
# kill it.
|
||||||
if data is not None:
|
if data is not None:
|
||||||
try:
|
try:
|
||||||
os.remove(_babase.get_v1_cloud_log_file_path())
|
os.remove(_babase.get_v1_cloud_log_file_path())
|
||||||
|
|
@ -197,10 +198,9 @@ def handle_leftover_v1_cloud_log_file() -> None:
|
||||||
else:
|
else:
|
||||||
# If they don't want logs uploaded just kill it.
|
# If they don't want logs uploaded just kill it.
|
||||||
os.remove(_babase.get_v1_cloud_log_file_path())
|
os.remove(_babase.get_v1_cloud_log_file_path())
|
||||||
except Exception:
|
|
||||||
from babase import _error
|
|
||||||
|
|
||||||
_error.print_exception('Error handling leftover log file.')
|
except Exception:
|
||||||
|
balog.exception('Error handling leftover log file.')
|
||||||
|
|
||||||
|
|
||||||
def garbage_collect_session_end() -> None:
|
def garbage_collect_session_end() -> None:
|
||||||
|
|
@ -219,6 +219,7 @@ def garbage_collect_session_end() -> None:
|
||||||
# running them with an explicit flag passed, but we should never
|
# running them with an explicit flag passed, but we should never
|
||||||
# run them by default because gc.get_objects() can mess up the app.
|
# run them by default because gc.get_objects() can mess up the app.
|
||||||
# See notes at top of efro.debug.
|
# See notes at top of efro.debug.
|
||||||
|
|
||||||
# if bool(False):
|
# if bool(False):
|
||||||
# print_live_object_warnings('after session shutdown')
|
# print_live_object_warnings('after session shutdown')
|
||||||
|
|
||||||
|
|
@ -226,11 +227,11 @@ def garbage_collect_session_end() -> None:
|
||||||
def garbage_collect() -> None:
|
def garbage_collect() -> None:
|
||||||
"""Run an explicit pass of garbage collection.
|
"""Run an explicit pass of garbage collection.
|
||||||
|
|
||||||
category: General Utility Functions
|
|
||||||
|
|
||||||
May also print warnings/etc. if collection takes too long or if
|
May also print warnings/etc. if collection takes too long or if
|
||||||
uncollectible objects are found (so use this instead of simply
|
uncollectible objects are found (so use this instead of simply
|
||||||
gc.collect().
|
:meth:`gc.collect()`.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
"""
|
"""
|
||||||
gc.collect()
|
gc.collect()
|
||||||
|
|
||||||
|
|
@ -309,7 +310,7 @@ def dump_app_state(
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
# Abandon whole dump if we can't write metadata.
|
# Abandon whole dump if we can't write metadata.
|
||||||
logging.exception('Error writing app state dump metadata.')
|
balog.exception('Error writing app state dump metadata.')
|
||||||
return
|
return
|
||||||
|
|
||||||
tbpath = os.path.join(
|
tbpath = os.path.join(
|
||||||
|
|
@ -383,13 +384,18 @@ def log_dumped_app_state(from_previous_run: bool = False) -> None:
|
||||||
with open(tbpath, 'r', encoding='utf-8') as infile:
|
with open(tbpath, 'r', encoding='utf-8') as infile:
|
||||||
out += '\nPython tracebacks:\n' + infile.read()
|
out += '\nPython tracebacks:\n' + infile.read()
|
||||||
os.unlink(tbpath)
|
os.unlink(tbpath)
|
||||||
logging.log(metadata.log_level.python_logging_level, out)
|
balog.log(metadata.log_level.python_logging_level, out)
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.exception('Error logging dumped app state.')
|
balog.exception('Error logging dumped app state.')
|
||||||
|
|
||||||
|
|
||||||
class AppHealthMonitor(AppSubsystem):
|
class AppHealthSubsystem(AppSubsystem):
|
||||||
"""Logs things like app-not-responding issues."""
|
"""Subsystem for monitoring app health; logs not-responding issues, etc.
|
||||||
|
|
||||||
|
The single shared instance of this class can be found on the
|
||||||
|
:attr:`~babase.App.health` attr on the :class:`~babase.App`
|
||||||
|
class.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
|
|
@ -402,15 +408,28 @@ class AppHealthMonitor(AppSubsystem):
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def on_app_loading(self) -> None:
|
def on_app_loading(self) -> None:
|
||||||
|
""":meta private:"""
|
||||||
# If any traceback dumps happened last run, log and clear them.
|
# If any traceback dumps happened last run, log and clear them.
|
||||||
log_dumped_app_state(from_previous_run=True)
|
log_dumped_app_state(from_previous_run=True)
|
||||||
|
|
||||||
|
@override
|
||||||
|
def on_app_suspend(self) -> None:
|
||||||
|
""":meta private:"""
|
||||||
|
assert _babase.in_logic_thread()
|
||||||
|
self._running = False
|
||||||
|
|
||||||
|
@override
|
||||||
|
def on_app_unsuspend(self) -> None:
|
||||||
|
""":meta private:"""
|
||||||
|
assert _babase.in_logic_thread()
|
||||||
|
self._running = True
|
||||||
|
|
||||||
def _app_monitor_thread_main(self) -> None:
|
def _app_monitor_thread_main(self) -> None:
|
||||||
_babase.set_thread_name('ballistica app-monitor')
|
_babase.set_thread_name('ballistica app-monitor')
|
||||||
try:
|
try:
|
||||||
self._monitor_app()
|
self._monitor_app()
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.exception('Error in AppHealthMonitor thread.')
|
balog.exception('Error in AppHealthSubsystem thread.')
|
||||||
|
|
||||||
def _set_response(self) -> None:
|
def _set_response(self) -> None:
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
|
|
@ -463,13 +482,3 @@ class AppHealthMonitor(AppSubsystem):
|
||||||
time.sleep(1.042)
|
time.sleep(1.042)
|
||||||
|
|
||||||
self._first_check = False
|
self._first_check = False
|
||||||
|
|
||||||
@override
|
|
||||||
def on_app_suspend(self) -> None:
|
|
||||||
assert _babase.in_logic_thread()
|
|
||||||
self._running = False
|
|
||||||
|
|
||||||
@override
|
|
||||||
def on_app_unsuspend(self) -> None:
|
|
||||||
assert _babase.in_logic_thread()
|
|
||||||
self._running = True
|
|
||||||
|
|
|
||||||
4
dist/ba_data/python/babase/_cloud.py
vendored
4
dist/ba_data/python/babase/_cloud.py
vendored
|
|
@ -14,8 +14,8 @@ if TYPE_CHECKING:
|
||||||
class CloudSubscription:
|
class CloudSubscription:
|
||||||
"""User handle to a subscription to some cloud data.
|
"""User handle to a subscription to some cloud data.
|
||||||
|
|
||||||
Do not instantiate these directly; use the subscribe methods
|
Do not instantiate these directly; use the subscribe methods in
|
||||||
in *.app.plus.cloud to create them.
|
:class:`~baplus.CloudSubsystem` to create them.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, subscription_id: int) -> None:
|
def __init__(self, subscription_id: int) -> None:
|
||||||
|
|
|
||||||
42
dist/ba_data/python/babase/_devconsole.py
vendored
42
dist/ba_data/python/babase/_devconsole.py
vendored
|
|
@ -15,10 +15,13 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class DevConsoleTab:
|
class DevConsoleTab:
|
||||||
"""Defines behavior for a tab in the dev-console."""
|
"""Base class for a :class:`~babase.DevConsoleSubsystem` tab."""
|
||||||
|
|
||||||
def refresh(self) -> None:
|
def refresh(self) -> None:
|
||||||
"""Called when the tab should refresh itself."""
|
"""Called when the tab should refresh itself.
|
||||||
|
|
||||||
|
Overridden by subclasses to implement tab behavior.
|
||||||
|
"""
|
||||||
|
|
||||||
def request_refresh(self) -> None:
|
def request_refresh(self) -> None:
|
||||||
"""The tab can call this to request that it be refreshed."""
|
"""The tab can call this to request that it be refreshed."""
|
||||||
|
|
@ -91,22 +94,23 @@ class DevConsoleTab:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def width(self) -> float:
|
def width(self) -> float:
|
||||||
"""Return the current tab width. Only call during refreshes."""
|
"""The current tab width. Only valid during refreshes."""
|
||||||
assert _babase.app.devconsole.is_refreshing
|
assert _babase.app.devconsole.is_refreshing
|
||||||
return _babase.dev_console_tab_width()
|
return _babase.dev_console_tab_width()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def height(self) -> float:
|
def height(self) -> float:
|
||||||
"""Return the current tab height. Only call during refreshes."""
|
"""The current tab height. Only valid during refreshes."""
|
||||||
assert _babase.app.devconsole.is_refreshing
|
assert _babase.app.devconsole.is_refreshing
|
||||||
return _babase.dev_console_tab_height()
|
return _babase.dev_console_tab_height()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def base_scale(self) -> float:
|
def base_scale(self) -> float:
|
||||||
"""A scale value set depending on the app's UI scale.
|
"""A scale value based on the app's current :class:`~babase.UIScale`.
|
||||||
|
|
||||||
Dev-console tabs can incorporate this into their UI sizes and
|
Dev-console tabs can manually incorporate this into their UI
|
||||||
positions if they desire. This must be done manually however.
|
sizes and positions if they desire. By default, dev-console tabs
|
||||||
|
are uniform across all ui-scales.
|
||||||
"""
|
"""
|
||||||
assert _babase.app.devconsole.is_refreshing
|
assert _babase.app.devconsole.is_refreshing
|
||||||
return _babase.dev_console_base_scale()
|
return _babase.dev_console_base_scale()
|
||||||
|
|
@ -114,20 +118,21 @@ class DevConsoleTab:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DevConsoleTabEntry:
|
class DevConsoleTabEntry:
|
||||||
"""Represents a distinct tab in the dev-console."""
|
"""Represents a distinct tab in the :class:`~babase.DevConsoleSubsystem`."""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
factory: Callable[[], DevConsoleTab]
|
factory: Callable[[], DevConsoleTab]
|
||||||
|
|
||||||
|
|
||||||
class DevConsoleSubsystem:
|
class DevConsoleSubsystem:
|
||||||
"""Subsystem for wrangling the dev console.
|
"""Subsystem for wrangling the dev-console.
|
||||||
|
|
||||||
The single instance of this class can be found at
|
Access the single shared instance of this class via the
|
||||||
babase.app.devconsole. The dev-console is a simple always-available
|
:attr:`~babase.App.devconsole` attr on the :class:`~babase.App`
|
||||||
UI intended for use by developers; not end users. Traditionally it
|
class. The dev-console is a simple always-available UI intended for
|
||||||
is available by typing a backtick (`) key on a keyboard, but now can
|
use by developers; not end users. Traditionally it is available by
|
||||||
be accessed via an on-screen button (see settings/advanced to enable
|
typing a backtick (`) key on a keyboard, but can also be accessed
|
||||||
|
via an on-screen button (see settings/advanced/dev-tools to enable
|
||||||
said button).
|
said button).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -141,8 +146,8 @@ class DevConsoleSubsystem:
|
||||||
DevConsoleTabTest,
|
DevConsoleTabTest,
|
||||||
)
|
)
|
||||||
|
|
||||||
# All tabs in the dev-console. Add your own stuff here via
|
#: All tabs in the dev-console. Add your own stuff here via
|
||||||
# plugins or whatnot.
|
#: plugins or whatnot to customize the console.
|
||||||
self.tabs: list[DevConsoleTabEntry] = [
|
self.tabs: list[DevConsoleTabEntry] = [
|
||||||
DevConsoleTabEntry('Python', DevConsoleTabPython),
|
DevConsoleTabEntry('Python', DevConsoleTabPython),
|
||||||
DevConsoleTabEntry('AppModes', DevConsoleTabAppModes),
|
DevConsoleTabEntry('AppModes', DevConsoleTabAppModes),
|
||||||
|
|
@ -155,7 +160,10 @@ class DevConsoleSubsystem:
|
||||||
self._tab_instances: dict[str, DevConsoleTab] = {}
|
self._tab_instances: dict[str, DevConsoleTab] = {}
|
||||||
|
|
||||||
def do_refresh_tab(self, tabname: str) -> None:
|
def do_refresh_tab(self, tabname: str) -> None:
|
||||||
"""Called by the C++ layer when a tab should be filled out."""
|
"""Called by the C++ layer when a tab should be filled out.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
|
|
||||||
# Make noise if we have repeating tab names, as that breaks our
|
# Make noise if we have repeating tab names, as that breaks our
|
||||||
|
|
|
||||||
7
dist/ba_data/python/babase/_emptyappmode.py
vendored
7
dist/ba_data/python/babase/_emptyappmode.py
vendored
|
|
@ -17,7 +17,10 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
# ba_meta export babase.AppMode
|
# ba_meta export babase.AppMode
|
||||||
class EmptyAppMode(AppMode):
|
class EmptyAppMode(AppMode):
|
||||||
"""An AppMode that does not do much at all."""
|
"""An AppMode that does not do much at all.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -26,7 +29,7 @@ class EmptyAppMode(AppMode):
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@classmethod
|
@classmethod
|
||||||
def _can_handle_intent(cls, intent: AppIntent) -> bool:
|
def can_handle_intent_impl(cls, intent: AppIntent) -> bool:
|
||||||
# We support default and exec intents currently.
|
# We support default and exec intents currently.
|
||||||
return isinstance(intent, AppIntentExec | AppIntentDefault)
|
return isinstance(intent, AppIntentExec | AppIntentDefault)
|
||||||
|
|
||||||
|
|
|
||||||
151
dist/ba_data/python/babase/_error.py
vendored
151
dist/ba_data/python/babase/_error.py
vendored
|
|
@ -6,183 +6,66 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import _babase
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
class ContextError(Exception):
|
class ContextError(Exception):
|
||||||
"""Exception raised when a call is made in an invalid context.
|
"""Raised when a call is made in an invalid context.
|
||||||
|
|
||||||
Category: **Exception Classes**
|
Examples of this include calling UI functions within an activity
|
||||||
|
context or calling scene manipulation functions outside of a scene
|
||||||
Examples of this include calling UI functions within an Activity context
|
context.
|
||||||
or calling scene manipulation functions outside of a game context.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class NotFoundError(Exception):
|
class NotFoundError(Exception):
|
||||||
"""Exception raised when a referenced object does not exist.
|
"""Raised when a referenced object does not exist."""
|
||||||
|
|
||||||
Category: **Exception Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class PlayerNotFoundError(NotFoundError):
|
class PlayerNotFoundError(NotFoundError):
|
||||||
"""Exception raised when an expected player does not exist.
|
"""Raised when an expected player does not exist."""
|
||||||
|
|
||||||
Category: **Exception Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class SessionPlayerNotFoundError(NotFoundError):
|
class SessionPlayerNotFoundError(NotFoundError):
|
||||||
"""Exception raised when an expected session-player does not exist.
|
"""Exception raised when an expected session-player does not exist."""
|
||||||
|
|
||||||
Category: **Exception Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class TeamNotFoundError(NotFoundError):
|
class TeamNotFoundError(NotFoundError):
|
||||||
"""Exception raised when an expected bascenev1.Team does not exist.
|
"""Raised when an expected team does not exist."""
|
||||||
|
|
||||||
Category: **Exception Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class MapNotFoundError(NotFoundError):
|
class MapNotFoundError(NotFoundError):
|
||||||
"""Exception raised when an expected bascenev1.Map does not exist.
|
"""Raised when an expected map does not exist."""
|
||||||
|
|
||||||
Category: **Exception Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class DelegateNotFoundError(NotFoundError):
|
class DelegateNotFoundError(NotFoundError):
|
||||||
"""Exception raised when an expected delegate object does not exist.
|
"""Raised when an expected delegate object does not exist."""
|
||||||
|
|
||||||
Category: **Exception Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class SessionTeamNotFoundError(NotFoundError):
|
class SessionTeamNotFoundError(NotFoundError):
|
||||||
"""Exception raised when an expected session-team does not exist.
|
"""Raised when an expected session-team does not exist."""
|
||||||
|
|
||||||
Category: **Exception Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class NodeNotFoundError(NotFoundError):
|
class NodeNotFoundError(NotFoundError):
|
||||||
"""Exception raised when an expected Node does not exist.
|
"""Raised when an expected node does not exist."""
|
||||||
|
|
||||||
Category: **Exception Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class ActorNotFoundError(NotFoundError):
|
class ActorNotFoundError(NotFoundError):
|
||||||
"""Exception raised when an expected actor does not exist.
|
"""Raised when an expected actor does not exist."""
|
||||||
|
|
||||||
Category: **Exception Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class ActivityNotFoundError(NotFoundError):
|
class ActivityNotFoundError(NotFoundError):
|
||||||
"""Exception raised when an expected bascenev1.Activity does not exist.
|
"""Raised when an expected activity does not exist."""
|
||||||
|
|
||||||
Category: **Exception Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class SessionNotFoundError(NotFoundError):
|
class SessionNotFoundError(NotFoundError):
|
||||||
"""Exception raised when an expected session does not exist.
|
"""Raised when an expected session does not exist."""
|
||||||
|
|
||||||
Category: **Exception Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class InputDeviceNotFoundError(NotFoundError):
|
class InputDeviceNotFoundError(NotFoundError):
|
||||||
"""Exception raised when an expected input-device does not exist.
|
"""Raised when an expected input-device does not exist."""
|
||||||
|
|
||||||
Category: **Exception Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class WidgetNotFoundError(NotFoundError):
|
class WidgetNotFoundError(NotFoundError):
|
||||||
"""Exception raised when an expected widget does not exist.
|
"""Raised when an expected widget does not exist."""
|
||||||
|
|
||||||
Category: **Exception Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: Should integrate some sort of context printing into our
|
|
||||||
# log handling so we can just use logging.exception() and kill these
|
|
||||||
# two functions.
|
|
||||||
|
|
||||||
|
|
||||||
def print_exception(*args: Any, **keywds: Any) -> None:
|
|
||||||
"""Print info about an exception along with pertinent context state.
|
|
||||||
|
|
||||||
Category: **General Utility Functions**
|
|
||||||
|
|
||||||
Prints all arguments provided along with various info about the
|
|
||||||
current context and the outstanding exception.
|
|
||||||
Pass the keyword 'once' as True if you want the call to only happen
|
|
||||||
one time from an exact calling location.
|
|
||||||
"""
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
if keywds:
|
|
||||||
allowed_keywds = ['once']
|
|
||||||
if any(keywd not in allowed_keywds for keywd in keywds):
|
|
||||||
raise TypeError('invalid keyword(s)')
|
|
||||||
try:
|
|
||||||
# If we're only printing once and already have, bail.
|
|
||||||
if keywds.get('once', False):
|
|
||||||
if not _babase.do_once():
|
|
||||||
return
|
|
||||||
|
|
||||||
err_str = ' '.join([str(a) for a in args])
|
|
||||||
print('ERROR:', err_str)
|
|
||||||
_babase.print_context()
|
|
||||||
print('PRINTED-FROM:')
|
|
||||||
|
|
||||||
# Basically the output of traceback.print_stack()
|
|
||||||
stackstr = ''.join(traceback.format_stack())
|
|
||||||
print(stackstr, end='')
|
|
||||||
print('EXCEPTION:')
|
|
||||||
|
|
||||||
# Basically the output of traceback.print_exc()
|
|
||||||
excstr = traceback.format_exc()
|
|
||||||
print('\n'.join(' ' + l for l in excstr.splitlines()))
|
|
||||||
except Exception:
|
|
||||||
# I suppose using print_exception here would be a bad idea.
|
|
||||||
print('ERROR: exception in babase.print_exception():')
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
|
|
||||||
def print_error(err_str: str, once: bool = False) -> None:
|
|
||||||
"""Print info about an error along with pertinent context state.
|
|
||||||
|
|
||||||
Category: **General Utility Functions**
|
|
||||||
|
|
||||||
Prints all positional arguments provided along with various info about the
|
|
||||||
current context.
|
|
||||||
Pass the keyword 'once' as True if you want the call to only happen
|
|
||||||
one time from an exact calling location.
|
|
||||||
"""
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
try:
|
|
||||||
# If we're only printing once and already have, bail.
|
|
||||||
if once:
|
|
||||||
if not _babase.do_once():
|
|
||||||
return
|
|
||||||
|
|
||||||
print('ERROR:', err_str)
|
|
||||||
_babase.print_context()
|
|
||||||
|
|
||||||
# Basically the output of traceback.print_stack()
|
|
||||||
stackstr = ''.join(traceback.format_stack())
|
|
||||||
print(stackstr, end='')
|
|
||||||
except Exception:
|
|
||||||
print('ERROR: exception in babase.print_error():')
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
|
||||||
179
dist/ba_data/python/babase/_general.py
vendored
179
dist/ba_data/python/babase/_general.py
vendored
|
|
@ -34,10 +34,7 @@ DisplayTime = NewType('DisplayTime', float)
|
||||||
|
|
||||||
|
|
||||||
class Existable(Protocol):
|
class Existable(Protocol):
|
||||||
"""A Protocol for objects supporting an exists() method.
|
"""A Protocol for objects supporting an exists() method."""
|
||||||
|
|
||||||
Category: **Protocols**
|
|
||||||
"""
|
|
||||||
|
|
||||||
def exists(self) -> bool:
|
def exists(self) -> bool:
|
||||||
"""Whether this object exists."""
|
"""Whether this object exists."""
|
||||||
|
|
@ -50,15 +47,13 @@ T = TypeVar('T')
|
||||||
def existing(obj: ExistableT | None) -> ExistableT | None:
|
def existing(obj: ExistableT | None) -> ExistableT | None:
|
||||||
"""Convert invalid references to None for any babase.Existable object.
|
"""Convert invalid references to None for any babase.Existable object.
|
||||||
|
|
||||||
Category: **Gameplay Functions**
|
To best support type checking, it is important that invalid
|
||||||
|
references not be passed around and instead get converted to values
|
||||||
To best support type checking, it is important that invalid references
|
of None. That way the type checker can properly flag attempts to
|
||||||
not be passed around and instead get converted to values of None.
|
pass possibly-dead objects (``FooType | None``) into functions
|
||||||
That way the type checker can properly flag attempts to pass possibly-dead
|
expecting only live ones (``FooType``), etc. This call can be used
|
||||||
objects (FooType | None) into functions expecting only live ones
|
on any 'existable' object (one with an ``exists()`` method) and will
|
||||||
(FooType), etc. This call can be used on any 'existable' object
|
convert it to a ``None`` value if it does not exist.
|
||||||
(one with an exists() method) and will convert it to a None value
|
|
||||||
if it does not exist.
|
|
||||||
|
|
||||||
For more info, see notes on 'existables' here:
|
For more info, see notes on 'existables' here:
|
||||||
https://ballistica.net/wiki/Coding-Style-Guide
|
https://ballistica.net/wiki/Coding-Style-Guide
|
||||||
|
|
@ -70,12 +65,11 @@ def existing(obj: ExistableT | None) -> ExistableT | None:
|
||||||
def getclass(
|
def getclass(
|
||||||
name: str, subclassof: type[T], check_sdlib_modulename_clash: bool = False
|
name: str, subclassof: type[T], check_sdlib_modulename_clash: bool = False
|
||||||
) -> type[T]:
|
) -> type[T]:
|
||||||
"""Given a full class name such as foo.bar.MyClass, return the class.
|
"""Given a full class name such as ``foo.bar.MyClass``, return the class.
|
||||||
|
|
||||||
Category: **General Utility Functions**
|
The class will be checked to make sure it is a subclass of the
|
||||||
|
provided 'subclassof' class, and a :class:`TypeError` will be raised
|
||||||
The class will be checked to make sure it is a subclass of the provided
|
if not.
|
||||||
'subclassof' class, and a TypeError will be raised if not.
|
|
||||||
"""
|
"""
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
|
|
@ -92,68 +86,54 @@ def getclass(
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
|
|
||||||
def utf8_all(data: Any) -> Any:
|
|
||||||
"""Convert any unicode data in provided sequence(s) to utf8 bytes."""
|
|
||||||
if isinstance(data, dict):
|
|
||||||
return dict(
|
|
||||||
(utf8_all(key), utf8_all(value))
|
|
||||||
for key, value in list(data.items())
|
|
||||||
)
|
|
||||||
if isinstance(data, list):
|
|
||||||
return [utf8_all(element) for element in data]
|
|
||||||
if isinstance(data, tuple):
|
|
||||||
return tuple(utf8_all(element) for element in data)
|
|
||||||
if isinstance(data, str):
|
|
||||||
return data.encode('utf-8', errors='ignore')
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def get_type_name(cls: type) -> str:
|
def get_type_name(cls: type) -> str:
|
||||||
"""Return a full type name including module for a class."""
|
"""Return a fully qualified type name for a class."""
|
||||||
return f'{cls.__module__}.{cls.__name__}'
|
return f'{cls.__module__}.{cls.__qualname__}'
|
||||||
|
|
||||||
|
|
||||||
class _WeakCall:
|
class _WeakCall:
|
||||||
"""Wrap a callable and arguments into a single callable object.
|
"""Wrap a callable and arguments into a single callable object.
|
||||||
|
|
||||||
Category: **General Utility Classes**
|
When passed a bound method as the callable, the instance portion of
|
||||||
|
it is weak-referenced, meaning the underlying instance is free to
|
||||||
|
die if all other references to it go away. Should this occur,
|
||||||
|
calling the weak-call is simply a no-op.
|
||||||
|
|
||||||
When passed a bound method as the callable, the instance portion
|
Think of this as a handy way to tell an object to do something at
|
||||||
of it is weak-referenced, meaning the underlying instance is
|
some point in the future if it happens to still exist.
|
||||||
free to die if all other references to it go away. Should this
|
|
||||||
occur, calling the WeakCall is simply a no-op.
|
|
||||||
|
|
||||||
Think of this as a handy way to tell an object to do something
|
**EXAMPLE A:** This code will create a ``FooClass`` instance and
|
||||||
at some point in the future if it happens to still exist.
|
call its ``bar()`` method 5 seconds later; it will be kept alive
|
||||||
|
even though we overwrite its variable with None because the bound
|
||||||
|
method we pass as a timer callback (``foo.bar``) strong-references
|
||||||
|
it::
|
||||||
|
|
||||||
##### Examples
|
foo = FooClass()
|
||||||
**EXAMPLE A:** this code will create a FooClass instance and call its
|
babase.apptimer(5.0, foo.bar)
|
||||||
bar() method 5 seconds later; it will be kept alive even though
|
foo = None
|
||||||
we overwrite its variable with None because the bound method
|
|
||||||
we pass as a timer callback (foo.bar) strong-references it
|
|
||||||
>>> foo = FooClass()
|
|
||||||
... babase.apptimer(5.0, foo.bar)
|
|
||||||
... foo = None
|
|
||||||
|
|
||||||
**EXAMPLE B:** This code will *not* keep our object alive; it will die
|
**EXAMPLE B:** This code will *not* keep our object alive; it will
|
||||||
when we overwrite it with None and the timer will be a no-op when it
|
die when we overwrite it with ``None`` and the timer will be a no-op
|
||||||
fires
|
when it fires::
|
||||||
>>> foo = FooClass()
|
|
||||||
... babase.apptimer(5.0, ba.WeakCall(foo.bar))
|
|
||||||
... foo = None
|
|
||||||
|
|
||||||
**EXAMPLE C:** Wrap a method call with some positional and keyword args:
|
foo = FooClass()
|
||||||
>>> myweakcall = babase.WeakCall(self.dostuff, argval1,
|
babase.apptimer(5.0, ba.WeakCall(foo.bar))
|
||||||
... namedarg=argval2)
|
foo = None
|
||||||
... # Now we have a single callable to run that whole mess.
|
|
||||||
... # The same as calling myobj.dostuff(argval1, namedarg=argval2)
|
|
||||||
... # (provided my_obj still exists; this will do nothing
|
|
||||||
... # otherwise).
|
|
||||||
... myweakcall()
|
|
||||||
|
|
||||||
Note: additional args and keywords you provide to the WeakCall()
|
**EXAMPLE C:** Wrap a method call with some positional and keyword
|
||||||
constructor are stored as regular strong-references; you'll need
|
args::
|
||||||
to wrap them in weakrefs manually if desired.
|
|
||||||
|
myweakcall = babase.WeakCall(self.dostuff, argval1,
|
||||||
|
namedarg=argval2)
|
||||||
|
|
||||||
|
# Now we have a single callable to run that whole mess.
|
||||||
|
# The same as calling myobj.dostuff(argval1, namedarg=argval2)
|
||||||
|
# (provided my_obj still exists; this will do nothing otherwise).
|
||||||
|
myweakcall()
|
||||||
|
|
||||||
|
Note: additional args and keywords you provide to the weak-call
|
||||||
|
constructor are stored as regular strong-references; you'll need to
|
||||||
|
wrap them in weakrefs manually if desired.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Optimize performance a bit; we shouldn't need to be super dynamic.
|
# Optimize performance a bit; we shouldn't need to be super dynamic.
|
||||||
|
|
@ -162,11 +142,6 @@ class _WeakCall:
|
||||||
_did_invalid_call_warning = False
|
_did_invalid_call_warning = False
|
||||||
|
|
||||||
def __init__(self, *args: Any, **keywds: Any) -> None:
|
def __init__(self, *args: Any, **keywds: Any) -> None:
|
||||||
"""Instantiate a WeakCall.
|
|
||||||
|
|
||||||
Pass a callable as the first arg, followed by any number of
|
|
||||||
arguments or keywords.
|
|
||||||
"""
|
|
||||||
if hasattr(args[0], '__func__'):
|
if hasattr(args[0], '__func__'):
|
||||||
self._call = WeakMethod(args[0])
|
self._call = WeakMethod(args[0])
|
||||||
else:
|
else:
|
||||||
|
|
@ -203,37 +178,27 @@ class _WeakCall:
|
||||||
class _Call:
|
class _Call:
|
||||||
"""Wraps a callable and arguments into a single callable object.
|
"""Wraps a callable and arguments into a single callable object.
|
||||||
|
|
||||||
Category: **General Utility Classes**
|
|
||||||
|
|
||||||
The callable is strong-referenced so it won't die until this
|
The callable is strong-referenced so it won't die until this
|
||||||
object does.
|
object does.
|
||||||
|
|
||||||
WARNING: This is exactly the same as Python's built in functools.partial().
|
|
||||||
Use functools.partial instead of this for new code, as this will probably
|
|
||||||
be deprecated at some point.
|
|
||||||
|
|
||||||
Note that a bound method (ex: ``myobj.dosomething``) contains a reference
|
Note that a bound method (ex: ``myobj.dosomething``) contains a reference
|
||||||
to ``self`` (``myobj`` in that case), so you will be keeping that object
|
to ``self`` (``myobj`` in that case), so you will be keeping that object
|
||||||
alive too. Use babase.WeakCall if you want to pass a method to a callback
|
alive too. Use babase.WeakCall if you want to pass a method to a callback
|
||||||
without keeping its object alive.
|
without keeping its object alive.
|
||||||
|
|
||||||
|
Example: Wrap a method call with 1 positional and 1 keyword arg::
|
||||||
|
|
||||||
|
mycall = babase.Call(myobj.dostuff, argval, namedarg=argval2)
|
||||||
|
|
||||||
|
# Now we have a single callable to run that whole mess.
|
||||||
|
# ..the same as calling myobj.dostuff(argval, namedarg=argval2)
|
||||||
|
mycall()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Optimize performance a bit; we shouldn't need to be super dynamic.
|
# Optimize performance a bit; we shouldn't need to be super dynamic.
|
||||||
__slots__ = ['_call', '_args', '_keywds']
|
__slots__ = ['_call', '_args', '_keywds']
|
||||||
|
|
||||||
def __init__(self, *args: Any, **keywds: Any):
|
def __init__(self, *args: Any, **keywds: Any):
|
||||||
"""Instantiate a Call.
|
|
||||||
|
|
||||||
Pass a callable as the first arg, followed by any number of
|
|
||||||
arguments or keywords.
|
|
||||||
|
|
||||||
##### Example
|
|
||||||
Wrap a method call with 1 positional and 1 keyword arg:
|
|
||||||
>>> mycall = babase.Call(myobj.dostuff, argval, namedarg=argval2)
|
|
||||||
... # Now we have a single callable to run that whole mess.
|
|
||||||
... # ..the same as calling myobj.dostuff(argval, namedarg=argval2)
|
|
||||||
... mycall()
|
|
||||||
"""
|
|
||||||
self._call = args[0]
|
self._call = args[0]
|
||||||
self._args = args[1:]
|
self._args = args[1:]
|
||||||
self._keywds = keywds
|
self._keywds = keywds
|
||||||
|
|
@ -309,8 +274,6 @@ class WeakMethod:
|
||||||
def verify_object_death(obj: object) -> None:
|
def verify_object_death(obj: object) -> None:
|
||||||
"""Warn if an object does not get freed within a short period.
|
"""Warn if an object does not get freed within a short period.
|
||||||
|
|
||||||
Category: **General Utility Functions**
|
|
||||||
|
|
||||||
This can be handy to detect and prevent memory/resource leaks.
|
This can be handy to detect and prevent memory/resource leaks.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -351,27 +314,27 @@ def _verify_object_death(wref: weakref.ref) -> None:
|
||||||
def storagename(suffix: str | None = None) -> str:
|
def storagename(suffix: str | None = None) -> str:
|
||||||
"""Generate a unique name for storing class data in shared places.
|
"""Generate a unique name for storing class data in shared places.
|
||||||
|
|
||||||
Category: **General Utility Functions**
|
This consists of a leading underscore, the module path at the call
|
||||||
|
site with dots replaced by underscores, the containing class's
|
||||||
This consists of a leading underscore, the module path at the
|
|
||||||
call site with dots replaced by underscores, the containing class's
|
|
||||||
qualified name, and the provided suffix. When storing data in public
|
qualified name, and the provided suffix. When storing data in public
|
||||||
places such as 'customdata' dicts, this minimizes the chance of
|
places such as 'customdata' dicts, this minimizes the chance of
|
||||||
collisions with other similarly named classes.
|
collisions with other similarly named classes.
|
||||||
|
|
||||||
Note that this will function even if called in the class definition.
|
Note that this will function even if called in the class definition.
|
||||||
|
|
||||||
##### Examples
|
Example: Generate a unique name for storage purposes::
|
||||||
Generate a unique name for storage purposes:
|
|
||||||
>>> class MyThingie:
|
class MyThingie:
|
||||||
... # This will give something like
|
|
||||||
... # '_mymodule_submodule_mythingie_data'.
|
# This will give something like
|
||||||
... _STORENAME = babase.storagename('data')
|
# '_mymodule_submodule_mythingie_data'.
|
||||||
...
|
_STORENAME = babase.storagename('data')
|
||||||
... # Use that name to store some data in the Activity we were
|
|
||||||
... # passed.
|
# Use that name to store some data in the Activity we were
|
||||||
... def __init__(self, activity):
|
# passed.
|
||||||
... activity.customdata[self._STORENAME] = {}
|
def __init__(self, activity):
|
||||||
|
activity.customdata[self._STORENAME] = {}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
frame = inspect.currentframe()
|
frame = inspect.currentframe()
|
||||||
if frame is None:
|
if frame is None:
|
||||||
|
|
|
||||||
179
dist/ba_data/python/babase/_language.py
vendored
179
dist/ba_data/python/babase/_language.py
vendored
|
|
@ -5,12 +5,12 @@ from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import logging
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import TYPE_CHECKING, overload, override
|
from typing import TYPE_CHECKING, overload, override
|
||||||
|
|
||||||
import _babase
|
import _babase
|
||||||
from babase._appsubsystem import AppSubsystem
|
from babase._appsubsystem import AppSubsystem
|
||||||
|
from babase._logging import applog
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Any, Sequence
|
from typing import Any, Sequence
|
||||||
|
|
@ -21,8 +21,6 @@ if TYPE_CHECKING:
|
||||||
class LanguageSubsystem(AppSubsystem):
|
class LanguageSubsystem(AppSubsystem):
|
||||||
"""Language functionality for the app.
|
"""Language functionality for the app.
|
||||||
|
|
||||||
Category: **App Classes**
|
|
||||||
|
|
||||||
Access the single instance of this class at 'babase.app.lang'.
|
Access the single instance of this class at 'babase.app.lang'.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -37,16 +35,16 @@ class LanguageSubsystem(AppSubsystem):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def locale(self) -> str:
|
def locale(self) -> str:
|
||||||
"""Raw country/language code detected by the game (such as 'en_US').
|
"""Raw country/language code detected by the game (such as "en_US").
|
||||||
|
|
||||||
Generally for language-specific code you should look at
|
Generally for language-specific code you should look at
|
||||||
babase.App.language, which is the language the game is using
|
:attr:`language`, which is the language the game is using (which
|
||||||
(which may differ from locale if the user sets a language, etc.)
|
may differ from locale if the user sets a language, etc.)
|
||||||
"""
|
"""
|
||||||
env = _babase.env()
|
env = _babase.env()
|
||||||
locale = env.get('locale')
|
locale = env.get('locale')
|
||||||
if not isinstance(locale, str):
|
if not isinstance(locale, str):
|
||||||
logging.warning(
|
applog.warning(
|
||||||
'Seem to be running in a dummy env; returning en_US locale.'
|
'Seem to be running in a dummy env; returning en_US locale.'
|
||||||
)
|
)
|
||||||
locale = 'en_US'
|
locale = 'en_US'
|
||||||
|
|
@ -83,18 +81,17 @@ class LanguageSubsystem(AppSubsystem):
|
||||||
)
|
)
|
||||||
names = [n.replace('.json', '').capitalize() for n in names]
|
names = [n.replace('.json', '').capitalize() for n in names]
|
||||||
|
|
||||||
# FIXME: our simple capitalization fails on multi-word names;
|
# FIXME: our simple capitalization fails on multi-word
|
||||||
# should handle this in a better way...
|
# names; should handle this in a better way...
|
||||||
for i, name in enumerate(names):
|
for i, name in enumerate(names):
|
||||||
if name == 'Chinesetraditional':
|
if name == 'Chinesetraditional':
|
||||||
names[i] = 'ChineseTraditional'
|
names[i] = 'ChineseTraditional'
|
||||||
elif name == 'Piratespeak':
|
elif name == 'Piratespeak':
|
||||||
names[i] = 'PirateSpeak'
|
names[i] = 'PirateSpeak'
|
||||||
except Exception:
|
except Exception:
|
||||||
from babase import _error
|
applog.exception('Error building available language list.')
|
||||||
|
|
||||||
_error.print_exception()
|
|
||||||
names = []
|
names = []
|
||||||
|
|
||||||
for name in names:
|
for name in names:
|
||||||
if self._can_display_language(name):
|
if self._can_display_language(name):
|
||||||
langs.add(name)
|
langs.add(name)
|
||||||
|
|
@ -205,7 +202,7 @@ class LanguageSubsystem(AppSubsystem):
|
||||||
with open(lmodfile, encoding='utf-8') as infile:
|
with open(lmodfile, encoding='utf-8') as infile:
|
||||||
lmodvalues = json.loads(infile.read())
|
lmodvalues = json.loads(infile.read())
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.exception("Error importing language '%s'.", language)
|
applog.exception("Error importing language '%s'.", language)
|
||||||
_babase.screenmessage(
|
_babase.screenmessage(
|
||||||
f"Error setting language to '{language}';"
|
f"Error setting language to '{language}';"
|
||||||
f' see log for details.',
|
f' see log for details.',
|
||||||
|
|
@ -275,6 +272,7 @@ class LanguageSubsystem(AppSubsystem):
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def do_apply_app_config(self) -> None:
|
def do_apply_app_config(self) -> None:
|
||||||
|
""":meta private:"""
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
assert isinstance(_babase.app.config, dict)
|
assert isinstance(_babase.app.config, dict)
|
||||||
lang = _babase.app.config.get('Lang', self.default_language)
|
lang = _babase.app.config.get('Lang', self.default_language)
|
||||||
|
|
@ -289,7 +287,11 @@ class LanguageSubsystem(AppSubsystem):
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""Return a translation resource by name.
|
"""Return a translation resource by name.
|
||||||
|
|
||||||
DEPRECATED; use babase.Lstr functionality for these purposes.
|
.. warning::
|
||||||
|
|
||||||
|
Use :class:`~babase.Lstr` instead of this function whenever
|
||||||
|
possible, as it will gracefully handle displaying correctly
|
||||||
|
across multiple clients in multiple languages simultaneously.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# If we have no language set, try and set it to english.
|
# If we have no language set, try and set it to english.
|
||||||
|
|
@ -297,7 +299,7 @@ class LanguageSubsystem(AppSubsystem):
|
||||||
if self._language_merged is None:
|
if self._language_merged is None:
|
||||||
try:
|
try:
|
||||||
if _babase.do_once():
|
if _babase.do_once():
|
||||||
logging.warning(
|
applog.warning(
|
||||||
'get_resource() called before language'
|
'get_resource() called before language'
|
||||||
' set; falling back to english.'
|
' set; falling back to english.'
|
||||||
)
|
)
|
||||||
|
|
@ -305,9 +307,7 @@ class LanguageSubsystem(AppSubsystem):
|
||||||
'English', print_change=False, store_to_config=False
|
'English', print_change=False, store_to_config=False
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.exception(
|
applog.exception('Error setting fallback english language.')
|
||||||
'Error setting fallback english language.'
|
|
||||||
)
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# If they provided a fallback_resource value, try the
|
# If they provided a fallback_resource value, try the
|
||||||
|
|
@ -382,7 +382,11 @@ class LanguageSubsystem(AppSubsystem):
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Translate a value (or return the value if no translation available)
|
"""Translate a value (or return the value if no translation available)
|
||||||
|
|
||||||
DEPRECATED; use babase.Lstr functionality for these purposes.
|
.. warning::
|
||||||
|
|
||||||
|
Use :class:`~babase.Lstr` instead of this function whenever
|
||||||
|
possible, as it will gracefully handle displaying correctly
|
||||||
|
across multiple clients in multiple languages simultaneously.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
translated = self.get_resource('translations')[category][strval]
|
translated = self.get_resource('translations')[category][strval]
|
||||||
|
|
@ -495,38 +499,75 @@ class LanguageSubsystem(AppSubsystem):
|
||||||
class Lstr:
|
class Lstr:
|
||||||
"""Used to define strings in a language-independent way.
|
"""Used to define strings in a language-independent way.
|
||||||
|
|
||||||
Category: **General Utility Classes**
|
|
||||||
|
|
||||||
These should be used whenever possible in place of hard-coded
|
These should be used whenever possible in place of hard-coded
|
||||||
strings so that in-game or UI elements show up correctly on all
|
strings so that in-game or UI elements show up correctly on all
|
||||||
clients in their currently-active language.
|
clients in their currently active language.
|
||||||
|
|
||||||
To see available resource keys, look at any of the bs_language_*.py
|
To see available resource keys, look at any of the
|
||||||
files in the game or the translations pages at
|
``ba_data/data/languages/*.json`` files in the game or the
|
||||||
legacy.ballistica.net/translate.
|
translations pages at `legacy.ballistica.net/translate
|
||||||
|
<https://legacy.ballistica.net/translate>`_.
|
||||||
|
|
||||||
##### Examples
|
Args:
|
||||||
EXAMPLE 1: specify a string from a resource path
|
|
||||||
>>> mynode.text = babase.Lstr(resource='audioSettingsWindow.titleText')
|
|
||||||
|
|
||||||
EXAMPLE 2: specify a translated string via a category and english
|
resource:
|
||||||
value; if a translated value is available, it will be used; otherwise
|
Pass a string to look up a translation by resource key.
|
||||||
the english value will be. To see available translation categories,
|
|
||||||
look under the 'translations' resource section.
|
|
||||||
>>> mynode.text = babase.Lstr(translate=('gameDescriptions',
|
|
||||||
... 'Defeat all enemies'))
|
|
||||||
|
|
||||||
EXAMPLE 3: specify a raw value and some substitutions. Substitutions
|
translate:
|
||||||
can be used with resource and translate modes as well.
|
Pass a tuple consisting of a translation category and
|
||||||
>>> mynode.text = babase.Lstr(value='${A} / ${B}',
|
untranslated value. Any matching translation found in that
|
||||||
... subs=[('${A}', str(score)), ('${B}', str(total))])
|
category will be used. Otherwise the untranslated value will
|
||||||
|
be.
|
||||||
|
|
||||||
EXAMPLE 4: babase.Lstr's can be nested. This example would display the
|
value:
|
||||||
resource at res_a but replace ${NAME} with the value of the
|
Pass a regular string value to be used as-is.
|
||||||
resource at res_b
|
|
||||||
>>> mytextnode.text = babase.Lstr(
|
subs:
|
||||||
... resource='res_a',
|
A sequence of 2-member tuples consisting of values and
|
||||||
... subs=[('${NAME}', babase.Lstr(resource='res_b'))])
|
replacements. Replacements can be regular strings or other ``Lstr``
|
||||||
|
values.
|
||||||
|
|
||||||
|
fallback_resource:
|
||||||
|
A resource key that will be used if the main one is not present for
|
||||||
|
the current language instead of falling back to the english value
|
||||||
|
('resource' mode only).
|
||||||
|
|
||||||
|
fallback_value:
|
||||||
|
A regular string that will be used if neither the resource nor the
|
||||||
|
fallback resource is found ('resource' mode only).
|
||||||
|
|
||||||
|
|
||||||
|
**Example 1: Resource path** ::
|
||||||
|
|
||||||
|
mynode.text = babase.Lstr(resource='audioSettingsWindow.titleText')
|
||||||
|
|
||||||
|
**Example 2: Translation**
|
||||||
|
|
||||||
|
If a translated value is available, it will be used; otherwise the
|
||||||
|
English value will be. To see available translation categories, look
|
||||||
|
under the ``translations`` resource section. ::
|
||||||
|
|
||||||
|
mynode.text = babase.Lstr(translate=('gameDescriptions',
|
||||||
|
'Defeat all enemies'))
|
||||||
|
|
||||||
|
**Example 3: Substitutions**
|
||||||
|
|
||||||
|
Substitutions can be used with ``resource`` and ``translate`` modes
|
||||||
|
as well as the ``value`` shown here. ::
|
||||||
|
|
||||||
|
mynode.text = babase.Lstr(value='${A} / ${B}',
|
||||||
|
subs=[('${A}', str(score)),
|
||||||
|
('${B}', str(total))])
|
||||||
|
|
||||||
|
**Example 4: Nesting**
|
||||||
|
|
||||||
|
``Lstr`` instances can be nested. This example would display
|
||||||
|
the translated resource at ``'res_a'`` but replace any instances of
|
||||||
|
``'${NAME}'`` it contains with the translated resource at ``'res_b'``. ::
|
||||||
|
|
||||||
|
mytextnode.text = babase.Lstr(
|
||||||
|
resource='res_a',
|
||||||
|
subs=[('${NAME}', babase.Lstr(resource='res_b'))])
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# This class is used a lot in UI stuff and doesn't need to be
|
# This class is used a lot in UI stuff and doesn't need to be
|
||||||
|
|
@ -563,26 +604,13 @@ class Lstr:
|
||||||
"""Create an Lstr from a raw string value."""
|
"""Create an Lstr from a raw string value."""
|
||||||
|
|
||||||
def __init__(self, *args: Any, **keywds: Any) -> None:
|
def __init__(self, *args: Any, **keywds: Any) -> None:
|
||||||
"""Instantiate a Lstr.
|
|
||||||
|
|
||||||
Pass a value for either 'resource', 'translate',
|
|
||||||
or 'value'. (see Lstr help for examples).
|
|
||||||
'subs' can be a sequence of 2-member sequences consisting of values
|
|
||||||
and replacements.
|
|
||||||
'fallback_resource' can be a resource key that will be used if the
|
|
||||||
main one is not present for
|
|
||||||
the current language in place of falling back to the english value
|
|
||||||
('resource' mode only).
|
|
||||||
'fallback_value' can be a literal string that will be used if neither
|
|
||||||
the resource nor the fallback resource is found ('resource' mode only).
|
|
||||||
"""
|
|
||||||
# pylint: disable=too-many-branches
|
# pylint: disable=too-many-branches
|
||||||
if args:
|
if args:
|
||||||
raise TypeError('Lstr accepts only keyword arguments')
|
raise TypeError('Lstr accepts only keyword arguments')
|
||||||
|
|
||||||
# Basically just store the exact args they passed.
|
#: Basically just stores the exact args passed. However if Lstr
|
||||||
# However if they passed any Lstr values for subs,
|
#: values were passed for subs, they are replaced with that
|
||||||
# replace them with that Lstr's dict.
|
#: Lstr's dict.
|
||||||
self.args = keywds
|
self.args = keywds
|
||||||
our_type = type(self)
|
our_type = type(self)
|
||||||
|
|
||||||
|
|
@ -600,8 +628,8 @@ class Lstr:
|
||||||
subs_filtered.append((key, value))
|
subs_filtered.append((key, value))
|
||||||
self.args['subs'] = subs_filtered
|
self.args['subs'] = subs_filtered
|
||||||
|
|
||||||
# As of protocol 31 we support compact key names
|
# As of protocol 31 we support compact key names ('t' instead of
|
||||||
# ('t' instead of 'translate', etc). Convert as needed.
|
# 'translate', etc). Convert as needed.
|
||||||
if 'translate' in keywds:
|
if 'translate' in keywds:
|
||||||
keywds['t'] = keywds['translate']
|
keywds['t'] = keywds['translate']
|
||||||
del keywds['translate']
|
del keywds['translate']
|
||||||
|
|
@ -612,12 +640,10 @@ class Lstr:
|
||||||
keywds['v'] = keywds['value']
|
keywds['v'] = keywds['value']
|
||||||
del keywds['value']
|
del keywds['value']
|
||||||
if 'fallback' in keywds:
|
if 'fallback' in keywds:
|
||||||
from babase import _error
|
if _babase.do_once():
|
||||||
|
applog.error(
|
||||||
_error.print_error(
|
'Deprecated "fallback" arg passed to Lstr(); use '
|
||||||
'deprecated "fallback" arg passed to Lstr(); use '
|
'either "fallback_resource" or "fallback_value".'
|
||||||
'either "fallback_resource" or "fallback_value"',
|
|
||||||
once=True,
|
|
||||||
)
|
)
|
||||||
keywds['f'] = keywds['fallback']
|
keywds['f'] = keywds['fallback']
|
||||||
del keywds['fallback']
|
del keywds['fallback']
|
||||||
|
|
@ -632,15 +658,15 @@ class Lstr:
|
||||||
del keywds['fallback_value']
|
del keywds['fallback_value']
|
||||||
|
|
||||||
def evaluate(self) -> str:
|
def evaluate(self) -> str:
|
||||||
"""Evaluate the Lstr and returns a flat string in the current language.
|
"""Evaluate to a flat string in the current language.
|
||||||
|
|
||||||
You should avoid doing this as much as possible and instead pass
|
You should avoid doing this as much as possible and instead pass
|
||||||
and store Lstr values.
|
and store ``Lstr`` values.
|
||||||
"""
|
"""
|
||||||
return _babase.evaluate_lstr(self._get_json())
|
return _babase.evaluate_lstr(self._get_json())
|
||||||
|
|
||||||
def is_flat_value(self) -> bool:
|
def is_flat_value(self) -> bool:
|
||||||
"""Return whether the Lstr is a 'flat' value.
|
"""Return whether this instance represents a 'flat' value.
|
||||||
|
|
||||||
This is defined as a simple string value incorporating no
|
This is defined as a simple string value incorporating no
|
||||||
translations, resources, or substitutions. In this case it may
|
translations, resources, or substitutions. In this case it may
|
||||||
|
|
@ -655,20 +681,23 @@ class Lstr:
|
||||||
except Exception:
|
except Exception:
|
||||||
from babase import _error
|
from babase import _error
|
||||||
|
|
||||||
_error.print_exception('_get_json failed for', self.args)
|
applog.exception('_get_json failed for %s.', self.args)
|
||||||
return 'JSON_ERR'
|
return 'JSON_ERR'
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return '<ba.Lstr: ' + self._get_json() + '>'
|
return f'<ba.Lstr: {self._get_json()}>'
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return '<ba.Lstr: ' + self._get_json() + '>'
|
return f'<ba.Lstr: {self._get_json()}>'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_json(json_string: str) -> babase.Lstr:
|
def from_json(json_string: str) -> babase.Lstr:
|
||||||
"""Given a json string, returns a babase.Lstr. Does no validation."""
|
"""Given a json string, returns a ``Lstr``.
|
||||||
|
|
||||||
|
Does no validation.
|
||||||
|
"""
|
||||||
lstr = Lstr(value='')
|
lstr = Lstr(value='')
|
||||||
lstr.args = json.loads(json_string)
|
lstr.args = json.loads(json_string)
|
||||||
return lstr
|
return lstr
|
||||||
|
|
|
||||||
34
dist/ba_data/python/babase/_login.py
vendored
34
dist/ba_data/python/babase/_login.py
vendored
|
|
@ -22,7 +22,7 @@ logger = logging.getLogger('ba.loginadapter')
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LoginInfo:
|
class LoginInfo:
|
||||||
"""Basic info about a login available in the app.plus.accounts section."""
|
"""Info for a login used by :class:`~babase.AccountV2Handle`."""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
|
|
@ -70,7 +70,10 @@ class LoginAdapter:
|
||||||
self._last_sign_in_desc: str | None = None
|
self._last_sign_in_desc: str | None = None
|
||||||
|
|
||||||
def on_app_loading(self) -> None:
|
def on_app_loading(self) -> None:
|
||||||
"""Should be called for each adapter in on_app_loading."""
|
"""Should be called for each adapter in on_app_loading.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
|
|
||||||
assert not self._on_app_loading_called
|
assert not self._on_app_loading_called
|
||||||
self._on_app_loading_called = True
|
self._on_app_loading_called = True
|
||||||
|
|
@ -122,6 +125,8 @@ class LoginAdapter:
|
||||||
to the currently-in-use account.
|
to the currently-in-use account.
|
||||||
Note that the logins dict passed in should be immutable as
|
Note that the logins dict passed in should be immutable as
|
||||||
only a reference to it is stored, not a copy.
|
only a reference to it is stored, not a copy.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
"""
|
"""
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
|
@ -136,12 +141,12 @@ class LoginAdapter:
|
||||||
def on_back_end_active_change(self, active: bool) -> None:
|
def on_back_end_active_change(self, active: bool) -> None:
|
||||||
"""Called when active state for the back-end is (possibly) changing.
|
"""Called when active state for the back-end is (possibly) changing.
|
||||||
|
|
||||||
Meant to be overridden by subclasses.
|
Meant to be overridden by subclasses. Being active means that
|
||||||
Being active means that the implicit login provided by the back-end
|
the implicit login provided by the back-end is actually being
|
||||||
is actually being used by the app. It should therefore register
|
used by the app. It should therefore register unlocked
|
||||||
unlocked achievements, leaderboard scores, allow viewing native
|
achievements, leaderboard scores, allow viewing native UIs, etc.
|
||||||
UIs, etc. When not active it should ignore everything and behave
|
When not active it should ignore everything and behave as if
|
||||||
as if signed out, even if it technically is still signed in.
|
signed out, even if it technically is still signed in.
|
||||||
"""
|
"""
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
del active # Unused.
|
del active # Unused.
|
||||||
|
|
@ -156,7 +161,7 @@ class LoginAdapter:
|
||||||
|
|
||||||
This can be called even if the back-end is not implicitly signed in;
|
This can be called even if the back-end is not implicitly signed in;
|
||||||
the adapter will attempt to sign in if possible. An exception will
|
the adapter will attempt to sign in if possible. An exception will
|
||||||
be returned if the sign-in attempt fails.
|
be passed to the callback if the sign-in attempt fails.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
|
|
@ -275,11 +280,12 @@ class LoginAdapter:
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Get a sign-in token from the adapter back end.
|
"""Get a sign-in token from the adapter back end.
|
||||||
|
|
||||||
This token is then passed to the master-server to complete the
|
This token is then passed to the cloud to complete the sign-in
|
||||||
sign-in process. The adapter can use this opportunity to bring
|
process. The adapter can use this opportunity to bring up
|
||||||
up account creation UI, call its internal sign_in function, etc.
|
account creation UI, call its internal sign-in function, etc. as
|
||||||
as needed. The provided completion_cb should then be called with
|
needed. The provided ``completion_cb`` should then be called
|
||||||
either a token or None if sign in failed or was cancelled.
|
with either a token or with ``None`` if sign in failed or was
|
||||||
|
cancelled.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Default implementation simply fails immediately.
|
# Default implementation simply fails immediately.
|
||||||
|
|
|
||||||
22
dist/ba_data/python/babase/_math.py
vendored
22
dist/ba_data/python/babase/_math.py
vendored
|
|
@ -14,14 +14,13 @@ if TYPE_CHECKING:
|
||||||
def vec3validate(value: Sequence[float]) -> Sequence[float]:
|
def vec3validate(value: Sequence[float]) -> Sequence[float]:
|
||||||
"""Ensure a value is valid for use as a Vec3.
|
"""Ensure a value is valid for use as a Vec3.
|
||||||
|
|
||||||
category: General Utility Functions
|
Raises a TypeError exception if not. Valid values include any type
|
||||||
|
of sequence consisting of 3 numeric values. Returns the same value
|
||||||
|
as passed in (but with a definite type so this can be used to
|
||||||
|
disambiguate 'Any' types). Generally this should be used in 'if
|
||||||
|
__debug__' or assert clauses to keep runtime overhead minimal.
|
||||||
|
|
||||||
Raises a TypeError exception if not.
|
:meta private:
|
||||||
Valid values include any type of sequence consisting of 3 numeric values.
|
|
||||||
Returns the same value as passed in (but with a definite type
|
|
||||||
so this can be used to disambiguate 'Any' types).
|
|
||||||
Generally this should be used in 'if __debug__' or assert clauses
|
|
||||||
to keep runtime overhead minimal.
|
|
||||||
"""
|
"""
|
||||||
from numbers import Number
|
from numbers import Number
|
||||||
|
|
||||||
|
|
@ -37,9 +36,9 @@ def vec3validate(value: Sequence[float]) -> Sequence[float]:
|
||||||
def is_point_in_box(pnt: Sequence[float], box: Sequence[float]) -> bool:
|
def is_point_in_box(pnt: Sequence[float], box: Sequence[float]) -> bool:
|
||||||
"""Return whether a given point is within a given box.
|
"""Return whether a given point is within a given box.
|
||||||
|
|
||||||
category: General Utility Functions
|
|
||||||
|
|
||||||
For use with standard def boxes (position|rotate|scale).
|
For use with standard def boxes (position|rotate|scale).
|
||||||
|
|
||||||
|
:meta private:
|
||||||
"""
|
"""
|
||||||
return (
|
return (
|
||||||
(abs(pnt[0] - box[0]) <= box[6] * 0.5)
|
(abs(pnt[0] - box[0]) <= box[6] * 0.5)
|
||||||
|
|
@ -49,10 +48,7 @@ def is_point_in_box(pnt: Sequence[float], box: Sequence[float]) -> bool:
|
||||||
|
|
||||||
|
|
||||||
def normalized_color(color: Sequence[float]) -> tuple[float, ...]:
|
def normalized_color(color: Sequence[float]) -> tuple[float, ...]:
|
||||||
"""Scale a color so its largest value is 1; useful for coloring lights.
|
"""Scale a color so its largest value is 1.0; useful for coloring lights."""
|
||||||
|
|
||||||
category: General Utility Functions
|
|
||||||
"""
|
|
||||||
color_biased = tuple(max(c, 0.01) for c in color) # account for black
|
color_biased = tuple(max(c, 0.01) for c in color) # account for black
|
||||||
mult = 1.0 / max(color_biased)
|
mult = 1.0 / max(color_biased)
|
||||||
return tuple(c * mult for c in color_biased)
|
return tuple(c * mult for c in color_biased)
|
||||||
|
|
|
||||||
15
dist/ba_data/python/babase/_meta.py
vendored
15
dist/ba_data/python/babase/_meta.py
vendored
|
|
@ -48,9 +48,8 @@ class ScanResults:
|
||||||
class MetadataSubsystem:
|
class MetadataSubsystem:
|
||||||
"""Subsystem for working with script metadata in the app.
|
"""Subsystem for working with script metadata in the app.
|
||||||
|
|
||||||
Category: **App Classes**
|
Access the single shared instance of this class via the
|
||||||
|
:attr:`~babase.App.meta` attr on the :class:`~babase.App` class.
|
||||||
Access the single shared instance of this class at 'babase.app.meta'.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
|
@ -68,8 +67,10 @@ class MetadataSubsystem:
|
||||||
"""Begin the overall scan.
|
"""Begin the overall scan.
|
||||||
|
|
||||||
This will start scanning built in directories (which for vanilla
|
This will start scanning built in directories (which for vanilla
|
||||||
installs should be the vast majority of the work). This should only
|
installs should be the vast majority of the work). This should
|
||||||
be called once.
|
only be called once.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
"""
|
"""
|
||||||
assert self._scan_complete_cb is None
|
assert self._scan_complete_cb is None
|
||||||
assert self._scan is None
|
assert self._scan is None
|
||||||
|
|
@ -95,6 +96,8 @@ class MetadataSubsystem:
|
||||||
This is for parts of the scan that must be delayed until
|
This is for parts of the scan that must be delayed until
|
||||||
workspace sync completion or other such events. This must be
|
workspace sync completion or other such events. This must be
|
||||||
called exactly once.
|
called exactly once.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
"""
|
"""
|
||||||
assert self._scan is not None
|
assert self._scan is not None
|
||||||
self._scan.set_extras(self.extra_scan_dirs)
|
self._scan.set_extras(self.extra_scan_dirs)
|
||||||
|
|
@ -113,7 +116,7 @@ class MetadataSubsystem:
|
||||||
to messaged to the user in some way but the callback will be called
|
to messaged to the user in some way but the callback will be called
|
||||||
regardless.
|
regardless.
|
||||||
To run the completion callback directly in the bg thread where the
|
To run the completion callback directly in the bg thread where the
|
||||||
loading work happens, pass completion_cb_in_bg_thread=True.
|
loading work happens, pass ``completion_cb_in_bg_thread=True``.
|
||||||
"""
|
"""
|
||||||
Thread(
|
Thread(
|
||||||
target=partial(
|
target=partial(
|
||||||
|
|
|
||||||
27
dist/ba_data/python/babase/_mgen/enums.py
vendored
27
dist/ba_data/python/babase/_mgen/enums.py
vendored
|
|
@ -5,11 +5,7 @@ from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
class InputType(Enum):
|
class InputType(Enum):
|
||||||
"""Types of input a controller can send to the game.
|
"""Types of input a controller can send to the game."""
|
||||||
|
|
||||||
Category: Enums
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
UP_DOWN = 2
|
UP_DOWN = 2
|
||||||
LEFT_RIGHT = 3
|
LEFT_RIGHT = 3
|
||||||
|
|
@ -39,19 +35,18 @@ class InputType(Enum):
|
||||||
|
|
||||||
|
|
||||||
class QuitType(Enum):
|
class QuitType(Enum):
|
||||||
"""Types of input a controller can send to the game.
|
"""Types of quit behavior that can be requested from the app.
|
||||||
|
|
||||||
Category: Enums
|
|
||||||
|
|
||||||
'soft' may hide/reset the app but keep the process running, depending
|
'soft' may hide/reset the app but keep the process running, depending
|
||||||
on the platform.
|
on the platform (generally a thing on mobile).
|
||||||
|
|
||||||
'back' is a variant of 'soft' which may give 'back-button-pressed'
|
'back' is a variant of 'soft' which may give 'back-button-pressed'
|
||||||
behavior depending on the platform. (returning to some previous
|
behavior depending on the platform. (returning to some previous
|
||||||
activity instead of dumping to the home screen, etc.)
|
activity instead of dumping to the home screen, etc.)
|
||||||
|
|
||||||
'hard' leads to the process exiting. This generally should be avoided
|
'hard' leads to the process exiting. This generally should be avoided
|
||||||
on platforms such as mobile.
|
on platforms such as mobile where apps are expected to keep running
|
||||||
|
until killed by the OS.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
SOFT = 0
|
SOFT = 0
|
||||||
|
|
@ -65,8 +60,6 @@ class UIScale(Enum):
|
||||||
might render the game at similar pixel resolutions but the size they
|
might render the game at similar pixel resolutions but the size they
|
||||||
display content at will vary significantly.
|
display content at will vary significantly.
|
||||||
|
|
||||||
Category: Enums
|
|
||||||
|
|
||||||
'large' is used for devices such as desktop PCs where fine details can
|
'large' is used for devices such as desktop PCs where fine details can
|
||||||
be clearly seen. UI elements are generally smaller on the screen
|
be clearly seen. UI elements are generally smaller on the screen
|
||||||
and more content can be seen at once.
|
and more content can be seen at once.
|
||||||
|
|
@ -86,19 +79,13 @@ class UIScale(Enum):
|
||||||
|
|
||||||
|
|
||||||
class Permission(Enum):
|
class Permission(Enum):
|
||||||
"""Permissions that can be requested from the OS.
|
"""Permissions that can be requested from the OS."""
|
||||||
|
|
||||||
Category: Enums
|
|
||||||
"""
|
|
||||||
|
|
||||||
STORAGE = 0
|
STORAGE = 0
|
||||||
|
|
||||||
|
|
||||||
class SpecialChar(Enum):
|
class SpecialChar(Enum):
|
||||||
"""Special characters the game can print.
|
"""Special characters the game can print."""
|
||||||
|
|
||||||
Category: Enums
|
|
||||||
"""
|
|
||||||
|
|
||||||
DOWN_ARROW = 0
|
DOWN_ARROW = 0
|
||||||
UP_ARROW = 1
|
UP_ARROW = 1
|
||||||
|
|
|
||||||
5
dist/ba_data/python/babase/_net.py
vendored
5
dist/ba_data/python/babase/_net.py
vendored
|
|
@ -42,7 +42,10 @@ class NetworkSubsystem:
|
||||||
|
|
||||||
|
|
||||||
def get_ip_address_type(addr: str) -> socket.AddressFamily:
|
def get_ip_address_type(addr: str) -> socket.AddressFamily:
|
||||||
"""Return socket.AF_INET6 or socket.AF_INET4 for the provided address."""
|
"""Return an address-type given an address.
|
||||||
|
|
||||||
|
Can be :attr:`socket.AF_INET` or :attr:`socket.AF_INET6`.
|
||||||
|
"""
|
||||||
|
|
||||||
version = ipaddress.ip_address(addr).version
|
version = ipaddress.ip_address(addr).version
|
||||||
if version == 4:
|
if version == 4:
|
||||||
|
|
|
||||||
108
dist/ba_data/python/babase/_plugin.py
vendored
108
dist/ba_data/python/babase/_plugin.py
vendored
|
|
@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, override
|
||||||
|
|
||||||
import _babase
|
import _babase
|
||||||
from babase._appsubsystem import AppSubsystem
|
from babase._appsubsystem import AppSubsystem
|
||||||
|
from babase._logging import balog
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
@ -18,31 +19,36 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class PluginSubsystem(AppSubsystem):
|
class PluginSubsystem(AppSubsystem):
|
||||||
"""Subsystem for plugin handling in the app.
|
"""Subsystem for wrangling plugins.
|
||||||
|
|
||||||
Category: **App Classes**
|
Access the single shared instance of this class via the
|
||||||
|
:attr:`~babase.App.plugins` attr on the :class:`~babase.App` class.
|
||||||
Access the single shared instance of this class at `ba.app.plugins`.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
#: :meta private:
|
||||||
AUTO_ENABLE_NEW_PLUGINS_CONFIG_KEY = 'Auto Enable New Plugins'
|
AUTO_ENABLE_NEW_PLUGINS_CONFIG_KEY = 'Auto Enable New Plugins'
|
||||||
|
|
||||||
|
#: :meta private:
|
||||||
AUTO_ENABLE_NEW_PLUGINS_DEFAULT = True
|
AUTO_ENABLE_NEW_PLUGINS_DEFAULT = True
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
# Info about plugins that we are aware of. This may include
|
#: Info about plugins that we are aware of. This may include
|
||||||
# plugins discovered through meta-scanning as well as plugins
|
#: plugins discovered through meta-scanning as well as plugins
|
||||||
# registered in the app-config. This may include plugins that
|
#: registered in the app-config. This may include plugins that
|
||||||
# cannot be loaded for various reasons or that have been
|
#: cannot be loaded for various reasons or that have been
|
||||||
# intentionally disabled.
|
#: intentionally disabled.
|
||||||
self.plugin_specs: dict[str, babase.PluginSpec] = {}
|
self.plugin_specs: dict[str, babase.PluginSpec] = {}
|
||||||
|
|
||||||
# The set of live active plugin objects.
|
#: The set of live active plugin instances.
|
||||||
self.active_plugins: list[babase.Plugin] = []
|
self.active_plugins: list[babase.Plugin] = []
|
||||||
|
|
||||||
def on_meta_scan_complete(self) -> None:
|
def on_meta_scan_complete(self) -> None:
|
||||||
"""Called when meta-scanning is complete."""
|
"""Called when meta-scanning is complete.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
from babase._language import Lstr
|
from babase._language import Lstr
|
||||||
|
|
||||||
config_changed = False
|
config_changed = False
|
||||||
|
|
@ -160,61 +166,53 @@ class PluginSubsystem(AppSubsystem):
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def on_app_running(self) -> None:
|
def on_app_running(self) -> None:
|
||||||
|
""":meta private:"""
|
||||||
# Load up our plugins and go ahead and call their on_app_running
|
# Load up our plugins and go ahead and call their on_app_running
|
||||||
# calls.
|
# calls.
|
||||||
self.load_plugins()
|
self._load_plugins()
|
||||||
for plugin in self.active_plugins:
|
for plugin in self.active_plugins:
|
||||||
try:
|
try:
|
||||||
plugin.on_app_running()
|
plugin.on_app_running()
|
||||||
except Exception:
|
except Exception:
|
||||||
from babase import _error
|
balog.exception('Error in plugin on_app_running().')
|
||||||
|
|
||||||
_error.print_exception('Error in plugin on_app_running()')
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def on_app_suspend(self) -> None:
|
def on_app_suspend(self) -> None:
|
||||||
|
""":meta private:"""
|
||||||
for plugin in self.active_plugins:
|
for plugin in self.active_plugins:
|
||||||
try:
|
try:
|
||||||
plugin.on_app_suspend()
|
plugin.on_app_suspend()
|
||||||
except Exception:
|
except Exception:
|
||||||
from babase import _error
|
balog.exception('Error in plugin on_app_suspend().')
|
||||||
|
|
||||||
_error.print_exception('Error in plugin on_app_suspend()')
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def on_app_unsuspend(self) -> None:
|
def on_app_unsuspend(self) -> None:
|
||||||
|
""":meta private:"""
|
||||||
for plugin in self.active_plugins:
|
for plugin in self.active_plugins:
|
||||||
try:
|
try:
|
||||||
plugin.on_app_unsuspend()
|
plugin.on_app_unsuspend()
|
||||||
except Exception:
|
except Exception:
|
||||||
from babase import _error
|
balog.exception('Error in plugin on_app_unsuspend().')
|
||||||
|
|
||||||
_error.print_exception('Error in plugin on_app_unsuspend()')
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def on_app_shutdown(self) -> None:
|
def on_app_shutdown(self) -> None:
|
||||||
|
""":meta private:"""
|
||||||
for plugin in self.active_plugins:
|
for plugin in self.active_plugins:
|
||||||
try:
|
try:
|
||||||
plugin.on_app_shutdown()
|
plugin.on_app_shutdown()
|
||||||
except Exception:
|
except Exception:
|
||||||
from babase import _error
|
balog.exception('Error in plugin on_app_shutdown().')
|
||||||
|
|
||||||
_error.print_exception('Error in plugin on_app_shutdown()')
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def on_app_shutdown_complete(self) -> None:
|
def on_app_shutdown_complete(self) -> None:
|
||||||
|
""":meta private:"""
|
||||||
for plugin in self.active_plugins:
|
for plugin in self.active_plugins:
|
||||||
try:
|
try:
|
||||||
plugin.on_app_shutdown_complete()
|
plugin.on_app_shutdown_complete()
|
||||||
except Exception:
|
except Exception:
|
||||||
from babase import _error
|
balog.exception('Error in plugin on_app_shutdown_complete().')
|
||||||
|
|
||||||
_error.print_exception(
|
def _load_plugins(self) -> None:
|
||||||
'Error in plugin on_app_shutdown_complete()'
|
|
||||||
)
|
|
||||||
|
|
||||||
def load_plugins(self) -> None:
|
|
||||||
"""(internal)"""
|
|
||||||
|
|
||||||
# Load plugins from any specs that are enabled & able to.
|
# Load plugins from any specs that are enabled & able to.
|
||||||
for _class_path, plug_spec in sorted(self.plugin_specs.items()):
|
for _class_path, plug_spec in sorted(self.plugin_specs.items()):
|
||||||
|
|
@ -224,32 +222,36 @@ class PluginSubsystem(AppSubsystem):
|
||||||
|
|
||||||
|
|
||||||
class PluginSpec:
|
class PluginSpec:
|
||||||
"""Represents a plugin the engine knows about.
|
"""Represents a plugin the engine knows about."""
|
||||||
|
|
||||||
Category: **App Classes**
|
|
||||||
|
|
||||||
The 'enabled' attr represents whether this plugin is set to load.
|
|
||||||
Getting or setting that attr affects the corresponding app-config
|
|
||||||
key. Remember to commit the app-config after making any changes.
|
|
||||||
|
|
||||||
The 'attempted_load' attr will be True if the engine has attempted
|
|
||||||
to load the plugin. If 'attempted_load' is True for a PluginSpec
|
|
||||||
but the 'plugin' attr is None, it means there was an error loading
|
|
||||||
the plugin. If a plugin's api-version does not match the running
|
|
||||||
app, if a new plugin is detected with auto-enable-plugins disabled,
|
|
||||||
or if the user has explicitly disabled a plugin, the engine will not
|
|
||||||
even attempt to load it.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, class_path: str, loadable: bool):
|
def __init__(self, class_path: str, loadable: bool):
|
||||||
|
|
||||||
|
#: Fully qualified class path for the plugin.
|
||||||
self.class_path = class_path
|
self.class_path = class_path
|
||||||
|
|
||||||
|
#: Can we attempt to load the plugin?
|
||||||
self.loadable = loadable
|
self.loadable = loadable
|
||||||
|
|
||||||
|
#: Whether the engine has attempted to load the plugin. If this
|
||||||
|
#: is True but the value of :attr:`plugin` is None, it means
|
||||||
|
#: there was an error loading the plugin. If a plugin's
|
||||||
|
#: api-version does not match the running app, if a new plugin is
|
||||||
|
#: detected with auto-enable-plugins disabled, or if the user has
|
||||||
|
#: explicitly disabled a plugin, the engine will not even attempt
|
||||||
|
#: to load it.
|
||||||
self.attempted_load = False
|
self.attempted_load = False
|
||||||
|
|
||||||
|
#: The associated :class:`~babase.Plugin`, if any.
|
||||||
self.plugin: Plugin | None = None
|
self.plugin: Plugin | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enabled(self) -> bool:
|
def enabled(self) -> bool:
|
||||||
"""Whether the user wants this plugin to load."""
|
"""Whether this plugin is set to load.
|
||||||
|
|
||||||
|
Getting or setting this attr affects the corresponding
|
||||||
|
app-config key. Remember to commit the app-config after making any
|
||||||
|
changes.
|
||||||
|
"""
|
||||||
plugstates: dict[str, dict] = _babase.app.config.get('Plugins', {})
|
plugstates: dict[str, dict] = _babase.app.config.get('Plugins', {})
|
||||||
assert isinstance(plugstates, dict)
|
assert isinstance(plugstates, dict)
|
||||||
val = plugstates.get(self.class_path, {}).get('enabled', False) is True
|
val = plugstates.get(self.class_path, {}).get('enabled', False) is True
|
||||||
|
|
@ -321,12 +323,10 @@ class PluginSpec:
|
||||||
class Plugin:
|
class Plugin:
|
||||||
"""A plugin to alter app behavior in some way.
|
"""A plugin to alter app behavior in some way.
|
||||||
|
|
||||||
Category: **App Classes**
|
Plugins are discoverable by the :class:`~babase.MetadataSubsystem`
|
||||||
|
system and the user can select which ones they want to enable.
|
||||||
Plugins are discoverable by the meta-tag system
|
Enabled plugins are then called at specific times as the app is
|
||||||
and the user can select which ones they want to enable.
|
running in order to modify its behavior in some way.
|
||||||
Enabled plugins are then called at specific times as the
|
|
||||||
app is running in order to modify its behavior in some way.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def on_app_running(self) -> None:
|
def on_app_running(self) -> None:
|
||||||
|
|
|
||||||
23
dist/ba_data/python/babase/_stringedit.py
vendored
23
dist/ba_data/python/babase/_stringedit.py
vendored
|
|
@ -22,7 +22,12 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class StringEditSubsystem:
|
class StringEditSubsystem:
|
||||||
"""Full string-edit state for the app."""
|
"""Full string-edit state for the app.
|
||||||
|
|
||||||
|
Access the single shared instance of this class via the
|
||||||
|
:attr:`~babase.App.stringedit` attr on the :class:`~babase.App`
|
||||||
|
class.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.active_adapter = empty_weakref(StringEditAdapter)
|
self.active_adapter = empty_weakref(StringEditAdapter)
|
||||||
|
|
@ -35,11 +40,11 @@ class StringEditAdapter:
|
||||||
subclass this to make their contents editable on all platforms.
|
subclass this to make their contents editable on all platforms.
|
||||||
|
|
||||||
There can only be one string-edit at a time for the app. New
|
There can only be one string-edit at a time for the app. New
|
||||||
StringEdits will attempt to register themselves as the globally
|
string-edits will attempt to register themselves as the globally
|
||||||
active one in their constructor, but this may not succeed. When
|
active one in their constructor, but this may not succeed. If
|
||||||
creating a StringEditAdapter, always check its 'is_valid()' value after
|
:meth:`can_be_replaced()` returns ``True`` for an adapter
|
||||||
creating it. If this is False, it was not able to set itself as
|
immediately after creating it, that means it was not able to set
|
||||||
the global active one and should be discarded.
|
itself as the global one.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|
@ -72,8 +77,8 @@ class StringEditAdapter:
|
||||||
"""Return whether this adapter can be replaced by a new one.
|
"""Return whether this adapter can be replaced by a new one.
|
||||||
|
|
||||||
This is mainly a safeguard to allow adapters whose drivers have
|
This is mainly a safeguard to allow adapters whose drivers have
|
||||||
gone away without calling apply or cancel to time out and be
|
gone away without calling :meth:`apply` or :meth:`cancel` to
|
||||||
replaced with new ones.
|
time out and be replaced with new ones.
|
||||||
"""
|
"""
|
||||||
if not _babase.in_logic_thread():
|
if not _babase.in_logic_thread():
|
||||||
raise RuntimeError('This must be called from the logic thread.')
|
raise RuntimeError('This must be called from the logic thread.')
|
||||||
|
|
@ -104,7 +109,7 @@ class StringEditAdapter:
|
||||||
"""Should be called by the owner when editing is complete.
|
"""Should be called by the owner when editing is complete.
|
||||||
|
|
||||||
Note that in some cases this call may be a no-op (such as if
|
Note that in some cases this call may be a no-op (such as if
|
||||||
this StringEditAdapter is no longer the globally active one).
|
this adapter is no longer the globally active one).
|
||||||
"""
|
"""
|
||||||
if not _babase.in_logic_thread():
|
if not _babase.in_logic_thread():
|
||||||
raise RuntimeError('This must be called from the logic thread.')
|
raise RuntimeError('This must be called from the logic thread.')
|
||||||
|
|
|
||||||
16
dist/ba_data/python/babase/_text.py
vendored
16
dist/ba_data/python/babase/_text.py
vendored
|
|
@ -15,18 +15,18 @@ def timestring(
|
||||||
timeval: float | int,
|
timeval: float | int,
|
||||||
centi: bool = True,
|
centi: bool = True,
|
||||||
) -> babase.Lstr:
|
) -> babase.Lstr:
|
||||||
"""Generate a babase.Lstr for displaying a time value.
|
"""Generate a localized string for displaying a time value.
|
||||||
|
|
||||||
Category: **General Utility Functions**
|
Given a time value, returns a localized string with:
|
||||||
|
|
||||||
Given a time value, returns a babase.Lstr with:
|
|
||||||
(hours if > 0 ) : minutes : seconds : (centiseconds if centi=True).
|
(hours if > 0 ) : minutes : seconds : (centiseconds if centi=True).
|
||||||
|
|
||||||
WARNING: the underlying Lstr value is somewhat large so don't use this
|
.. warning::
|
||||||
to rapidly update Node text values for an onscreen timer or you may
|
|
||||||
consume significant network bandwidth. For that purpose you should
|
|
||||||
use a 'timedisplay' Node and attribute connections.
|
|
||||||
|
|
||||||
|
the underlying localized-string value is somewhat large, so don't
|
||||||
|
use this to rapidly update text values for an in-game timer or you
|
||||||
|
may consume significant network bandwidth. For that sort of thing
|
||||||
|
you should use things like 'timedisplay' nodes and attribute
|
||||||
|
connections.
|
||||||
"""
|
"""
|
||||||
from babase._language import Lstr
|
from babase._language import Lstr
|
||||||
|
|
||||||
|
|
|
||||||
5
dist/ba_data/python/babase/_workspace.py
vendored
5
dist/ba_data/python/babase/_workspace.py
vendored
|
|
@ -26,9 +26,8 @@ if TYPE_CHECKING:
|
||||||
class WorkspaceSubsystem:
|
class WorkspaceSubsystem:
|
||||||
"""Subsystem for workspace handling in the app.
|
"""Subsystem for workspace handling in the app.
|
||||||
|
|
||||||
Category: **App Classes**
|
Access the single shared instance of this class at
|
||||||
|
`ba.app.workspaces`.
|
||||||
Access the single shared instance of this class at `ba.app.workspaces`.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
|
|
||||||
8
dist/ba_data/python/babase/modutils.py
vendored
8
dist/ba_data/python/babase/modutils.py
vendored
|
|
@ -7,6 +7,7 @@ from typing import TYPE_CHECKING
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import _babase
|
import _babase
|
||||||
|
from babase._logging import applog
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
|
|
@ -95,14 +96,11 @@ def show_user_scripts() -> None:
|
||||||
with open(file_name, 'w', encoding='utf-8') as outfile:
|
with open(file_name, 'w', encoding='utf-8') as outfile:
|
||||||
outfile.write(
|
outfile.write(
|
||||||
'You can drop files in here to mod the game.'
|
'You can drop files in here to mod the game.'
|
||||||
' See settings/advanced'
|
' See settings/advanced in the game for more info.'
|
||||||
' in the game for more info.'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
from babase import _error
|
applog.exception('Error writing about_this_folder stuff.')
|
||||||
|
|
||||||
_error.print_exception('error writing about_this_folder stuff')
|
|
||||||
|
|
||||||
# On platforms that support it, open the dir in the UI.
|
# On platforms that support it, open the dir in the UI.
|
||||||
if _babase.supports_open_dir_externally():
|
if _babase.supports_open_dir_externally():
|
||||||
|
|
|
||||||
4
dist/ba_data/python/baclassic/__init__.py
vendored
4
dist/ba_data/python/baclassic/__init__.py
vendored
|
|
@ -5,8 +5,8 @@
|
||||||
This package/feature-set contains functionality related to the classic
|
This package/feature-set contains functionality related to the classic
|
||||||
BombSquad experience. Note that much legacy BombSquad code is still a
|
BombSquad experience. Note that much legacy BombSquad code is still a
|
||||||
bit tangled and thus this feature-set is largely inseperable from
|
bit tangled and thus this feature-set is largely inseperable from
|
||||||
scenev1 and uiv1. Future feature-sets will be designed in a more modular
|
:mod:`bascenev1` and :mod:`bauiv1`. Future feature-sets will be
|
||||||
way.
|
designed in a more modular way.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# ba_meta require api 9
|
# ba_meta require api 9
|
||||||
|
|
|
||||||
7
dist/ba_data/python/baclassic/_accountv1.py
vendored
7
dist/ba_data/python/baclassic/_accountv1.py
vendored
|
|
@ -17,9 +17,8 @@ if TYPE_CHECKING:
|
||||||
class AccountV1Subsystem:
|
class AccountV1Subsystem:
|
||||||
"""Subsystem for legacy account handling in the app.
|
"""Subsystem for legacy account handling in the app.
|
||||||
|
|
||||||
Category: **App Classes**
|
Access the single instance of this class at
|
||||||
|
'ba.app.classic.accounts'.
|
||||||
Access the single instance of this class at 'ba.app.classic.accounts'.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
|
@ -202,7 +201,7 @@ class AccountV1Subsystem:
|
||||||
|
|
||||||
# If the short version of our account name currently cant be
|
# If the short version of our account name currently cant be
|
||||||
# displayed by the game, cancel.
|
# displayed by the game, cancel.
|
||||||
if not babase.have_chars(
|
if not babase.can_display_chars(
|
||||||
plus.get_v1_account_display_string(full=False)
|
plus.get_v1_account_display_string(full=False)
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -73,8 +73,6 @@ ACH_LEVEL_NAMES = {
|
||||||
class AchievementSubsystem:
|
class AchievementSubsystem:
|
||||||
"""Subsystem for achievement handling.
|
"""Subsystem for achievement handling.
|
||||||
|
|
||||||
Category: **App Classes**
|
|
||||||
|
|
||||||
Access the single shared instance of this class at 'ba.app.ach'.
|
Access the single shared instance of this class at 'ba.app.ach'.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -530,10 +528,7 @@ def _display_next_achievement() -> None:
|
||||||
|
|
||||||
|
|
||||||
class Achievement:
|
class Achievement:
|
||||||
"""Represents attributes and state for an individual achievement.
|
"""Represents attributes and state for an individual achievement."""
|
||||||
|
|
||||||
Category: **App Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
|
||||||
2
dist/ba_data/python/baclassic/_ads.py
vendored
2
dist/ba_data/python/baclassic/_ads.py
vendored
|
|
@ -18,8 +18,6 @@ if TYPE_CHECKING:
|
||||||
class AdsSubsystem:
|
class AdsSubsystem:
|
||||||
"""Subsystem for ads functionality in the app.
|
"""Subsystem for ads functionality in the app.
|
||||||
|
|
||||||
Category: **App Classes**
|
|
||||||
|
|
||||||
Access the single shared instance of this class at 'ba.app.ads'.
|
Access the single shared instance of this class at 'ba.app.ads'.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
||||||
271
dist/ba_data/python/baclassic/_appmode.py
vendored
271
dist/ba_data/python/baclassic/_appmode.py
vendored
|
|
@ -9,6 +9,7 @@ from functools import partial
|
||||||
from typing import TYPE_CHECKING, override
|
from typing import TYPE_CHECKING, override
|
||||||
|
|
||||||
from bacommon.app import AppExperience
|
from bacommon.app import AppExperience
|
||||||
|
import bacommon.bs
|
||||||
import babase
|
import babase
|
||||||
import bauiv1
|
import bauiv1
|
||||||
from bauiv1lib.connectivity import wait_for_connectivity
|
from bauiv1lib.connectivity import wait_for_connectivity
|
||||||
|
|
@ -28,6 +29,8 @@ if TYPE_CHECKING:
|
||||||
class ClassicAppMode(babase.AppMode):
|
class ClassicAppMode(babase.AppMode):
|
||||||
"""AppMode for the classic BombSquad experience."""
|
"""AppMode for the classic BombSquad experience."""
|
||||||
|
|
||||||
|
_LEAGUE_VIS_VALS_CONFIG_KEY = 'ClassicLeagueVisVals'
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._on_primary_account_changed_callback: (
|
self._on_primary_account_changed_callback: (
|
||||||
CallbackRegistration | None
|
CallbackRegistration | None
|
||||||
|
|
@ -40,6 +43,11 @@ class ClassicAppMode(babase.AppMode):
|
||||||
|
|
||||||
self._have_account_values = False
|
self._have_account_values = False
|
||||||
self._have_connectivity = False
|
self._have_connectivity = False
|
||||||
|
self._current_account_id: str | None = None
|
||||||
|
self._should_restore_account_display_state = False
|
||||||
|
|
||||||
|
self._purchase_ui_pause: bauiv1.RootUIUpdatePause | None = None
|
||||||
|
self._last_tokens_value = 0
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -48,7 +56,7 @@ class ClassicAppMode(babase.AppMode):
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@classmethod
|
@classmethod
|
||||||
def _can_handle_intent(cls, intent: babase.AppIntent) -> bool:
|
def can_handle_intent_impl(cls, intent: babase.AppIntent) -> bool:
|
||||||
# We support default and exec intents currently.
|
# We support default and exec intents currently.
|
||||||
return isinstance(
|
return isinstance(
|
||||||
intent, babase.AppIntentExec | babase.AppIntentDefault
|
intent, babase.AppIntentExec | babase.AppIntentDefault
|
||||||
|
|
@ -148,9 +156,15 @@ class ClassicAppMode(babase.AppMode):
|
||||||
|
|
||||||
classic = babase.app.classic
|
classic = babase.app.classic
|
||||||
|
|
||||||
|
# Store latest league vis vals for any active account.
|
||||||
|
self._save_account_display_state()
|
||||||
|
|
||||||
# Stop being informed of account changes.
|
# Stop being informed of account changes.
|
||||||
self._on_primary_account_changed_callback = None
|
self._on_primary_account_changed_callback = None
|
||||||
|
|
||||||
|
# Cancel any ui-pause we may have had going.
|
||||||
|
self._purchase_ui_pause = None
|
||||||
|
|
||||||
# Remove anything following any current account.
|
# Remove anything following any current account.
|
||||||
self._update_for_primary_account(None)
|
self._update_for_primary_account(None)
|
||||||
|
|
||||||
|
|
@ -163,11 +177,151 @@ class ClassicAppMode(babase.AppMode):
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def on_app_active_changed(self) -> None:
|
def on_app_active_changed(self) -> None:
|
||||||
|
if not babase.app.active:
|
||||||
# If we've gone inactive, bring up the main menu, which has the
|
# If we've gone inactive, bring up the main menu, which has the
|
||||||
# side effect of pausing the action (when possible).
|
# side effect of pausing the action (when possible).
|
||||||
if not babase.app.active:
|
|
||||||
babase.invoke_main_menu()
|
babase.invoke_main_menu()
|
||||||
|
|
||||||
|
# Also store any league vis state for the active account.
|
||||||
|
# this may be our last chance to do this on mobile.
|
||||||
|
self._save_account_display_state()
|
||||||
|
|
||||||
|
@override
|
||||||
|
def on_purchase_process_begin(
|
||||||
|
self, item_id: str, user_initiated: bool
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
# Do the default thing (announces 'updating account...')
|
||||||
|
super().on_purchase_process_begin(
|
||||||
|
item_id=item_id, user_initiated=user_initiated
|
||||||
|
)
|
||||||
|
|
||||||
|
# Pause the root ui so stuff like token counts don't change
|
||||||
|
# automatically, allowing us to animate them. Note that we
|
||||||
|
# need to explicitly kill this pause if we are deactivated since
|
||||||
|
# we wouldn't get the on_purchase_process_end() call; the next
|
||||||
|
# app-mode would.
|
||||||
|
self._purchase_ui_pause = bauiv1.RootUIUpdatePause()
|
||||||
|
|
||||||
|
# Also grab our last known token count here to plug into animations.
|
||||||
|
# We need to do this here before the purchase gets submitted so that
|
||||||
|
# we know we're seeing the old value.
|
||||||
|
assert babase.app.classic is not None
|
||||||
|
self._last_tokens_value = babase.app.classic.tokens
|
||||||
|
|
||||||
|
@override
|
||||||
|
def on_purchase_process_end(
|
||||||
|
self, item_id: str, user_initiated: bool, applied: bool
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
# Let the UI auto-update again after any animations we may apply
|
||||||
|
# here.
|
||||||
|
self._purchase_ui_pause = None
|
||||||
|
|
||||||
|
# Ignore user_initiated; we want to announce newly applied stuff
|
||||||
|
# even if it was from a different launch or client or whatever.
|
||||||
|
del user_initiated
|
||||||
|
|
||||||
|
# If the purchase wasn't applied, do nothing. This likely means it
|
||||||
|
# was redundant or something else harmless.
|
||||||
|
if not applied:
|
||||||
|
return
|
||||||
|
|
||||||
|
if item_id.startswith('tokens'):
|
||||||
|
if item_id == 'tokens1':
|
||||||
|
tokens = bacommon.bs.TOKENS1_COUNT
|
||||||
|
tokens_str = str(tokens)
|
||||||
|
anim_time = 2.0
|
||||||
|
elif item_id == 'tokens2':
|
||||||
|
tokens = bacommon.bs.TOKENS2_COUNT
|
||||||
|
tokens_str = str(tokens)
|
||||||
|
anim_time = 2.5
|
||||||
|
elif item_id == 'tokens3':
|
||||||
|
tokens = bacommon.bs.TOKENS3_COUNT
|
||||||
|
tokens_str = str(tokens)
|
||||||
|
anim_time = 3.0
|
||||||
|
elif item_id == 'tokens4':
|
||||||
|
tokens = bacommon.bs.TOKENS4_COUNT
|
||||||
|
tokens_str = str(tokens)
|
||||||
|
anim_time = 3.5
|
||||||
|
else:
|
||||||
|
tokens = 0
|
||||||
|
tokens_str = '???'
|
||||||
|
anim_time = 2.5
|
||||||
|
logging.warning(
|
||||||
|
'Unhandled item_id in on_purchase_process_end: %s', item_id
|
||||||
|
)
|
||||||
|
|
||||||
|
assert babase.app.classic is not None
|
||||||
|
effects: list[bacommon.bs.ClientEffect] = [
|
||||||
|
bacommon.bs.ClientEffectTokensAnimation(
|
||||||
|
duration=anim_time,
|
||||||
|
startvalue=self._last_tokens_value,
|
||||||
|
endvalue=self._last_tokens_value + tokens,
|
||||||
|
),
|
||||||
|
bacommon.bs.ClientEffectDelay(anim_time),
|
||||||
|
bacommon.bs.ClientEffectScreenMessage(
|
||||||
|
message='You got ${COUNT} tokens!',
|
||||||
|
subs=['${COUNT}', tokens_str],
|
||||||
|
color=(0, 1, 0),
|
||||||
|
),
|
||||||
|
bacommon.bs.ClientEffectSound(
|
||||||
|
sound=bacommon.bs.ClientEffectSound.Sound.CASH_REGISTER
|
||||||
|
),
|
||||||
|
]
|
||||||
|
babase.app.classic.run_bs_client_effects(effects)
|
||||||
|
|
||||||
|
elif item_id.startswith('gold_pass'):
|
||||||
|
babase.screenmessage(
|
||||||
|
babase.Lstr(
|
||||||
|
translate=('serverResponses', 'You got a ${ITEM}!'),
|
||||||
|
subs=[
|
||||||
|
(
|
||||||
|
'${ITEM}',
|
||||||
|
babase.Lstr(resource='goldPass.goldPassText'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
color=(0, 1, 0),
|
||||||
|
)
|
||||||
|
if babase.asset_loads_allowed():
|
||||||
|
babase.getsimplesound('cashRegister').play()
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
# Fallback: simply announce item id.
|
||||||
|
logging.warning(
|
||||||
|
'on_purchase_process_end got unexpected item_id: %s.', item_id
|
||||||
|
)
|
||||||
|
babase.screenmessage(
|
||||||
|
babase.Lstr(
|
||||||
|
translate=('serverResponses', 'You got a ${ITEM}!'),
|
||||||
|
subs=[('${ITEM}', item_id)],
|
||||||
|
),
|
||||||
|
color=(0, 1, 0),
|
||||||
|
)
|
||||||
|
if babase.asset_loads_allowed():
|
||||||
|
babase.getsimplesound('cashRegister').play()
|
||||||
|
|
||||||
|
def on_engine_will_reset(self) -> None:
|
||||||
|
"""Called just before classic resets the engine.
|
||||||
|
|
||||||
|
This happens at various times such as session switches.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._save_account_display_state()
|
||||||
|
|
||||||
|
def on_engine_did_reset(self) -> None:
|
||||||
|
"""Called just after classic resets the engine.
|
||||||
|
|
||||||
|
This happens at various times such as session switches.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Restore any old league vis state we had; this allows the user
|
||||||
|
# to see animations for league improvements or other changes
|
||||||
|
# that have occurred since the last time we were visible.
|
||||||
|
self._restore_account_display_state()
|
||||||
|
|
||||||
def _update_for_primary_account(
|
def _update_for_primary_account(
|
||||||
self, account: babase.AccountV2Handle | None
|
self, account: babase.AccountV2Handle | None
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
@ -181,9 +335,18 @@ class ClassicAppMode(babase.AppMode):
|
||||||
assert classic is not None
|
assert classic is not None
|
||||||
|
|
||||||
if account is not None:
|
if account is not None:
|
||||||
|
self._current_account_id = account.accountid
|
||||||
babase.set_ui_account_state(True, account.tag)
|
babase.set_ui_account_state(True, account.tag)
|
||||||
|
self._should_restore_account_display_state = True
|
||||||
else:
|
else:
|
||||||
|
# If we had an account, save any existing league vis state
|
||||||
|
# so we'll properly animate to new values the next time we
|
||||||
|
# sign in.
|
||||||
|
self._save_account_display_state()
|
||||||
|
|
||||||
|
self._current_account_id = None
|
||||||
babase.set_ui_account_state(False)
|
babase.set_ui_account_state(False)
|
||||||
|
self._should_restore_account_display_state = False
|
||||||
|
|
||||||
# For testing subscription functionality.
|
# For testing subscription functionality.
|
||||||
if os.environ.get('BA_SUBSCRIPTION_TEST') == '1':
|
if os.environ.get('BA_SUBSCRIPTION_TEST') == '1':
|
||||||
|
|
@ -199,27 +362,39 @@ class ClassicAppMode(babase.AppMode):
|
||||||
|
|
||||||
if account is None:
|
if account is None:
|
||||||
classic.gold_pass = False
|
classic.gold_pass = False
|
||||||
|
classic.tokens = 0
|
||||||
classic.chest_dock_full = False
|
classic.chest_dock_full = False
|
||||||
classic.remove_ads = False
|
classic.remove_ads = False
|
||||||
self._account_data_sub = None
|
self._account_data_sub = None
|
||||||
_baclassic.set_root_ui_account_values(
|
_baclassic.set_root_ui_account_values(
|
||||||
tickets=-1,
|
tickets=-1,
|
||||||
tokens=-1,
|
tokens=-1,
|
||||||
league_rank=-1,
|
|
||||||
league_type='',
|
league_type='',
|
||||||
|
league_number=-1,
|
||||||
|
league_rank=-1,
|
||||||
achievements_percent_text='',
|
achievements_percent_text='',
|
||||||
level_text='',
|
level_text='',
|
||||||
xp_text='',
|
xp_text='',
|
||||||
inbox_count_text='',
|
inbox_count=-1,
|
||||||
|
inbox_count_is_max=False,
|
||||||
|
inbox_announce_text='',
|
||||||
gold_pass=False,
|
gold_pass=False,
|
||||||
chest_0_appearance='',
|
chest_0_appearance='',
|
||||||
chest_1_appearance='',
|
chest_1_appearance='',
|
||||||
chest_2_appearance='',
|
chest_2_appearance='',
|
||||||
chest_3_appearance='',
|
chest_3_appearance='',
|
||||||
|
chest_0_create_time=-1.0,
|
||||||
|
chest_1_create_time=-1.0,
|
||||||
|
chest_2_create_time=-1.0,
|
||||||
|
chest_3_create_time=-1.0,
|
||||||
chest_0_unlock_time=-1.0,
|
chest_0_unlock_time=-1.0,
|
||||||
chest_1_unlock_time=-1.0,
|
chest_1_unlock_time=-1.0,
|
||||||
chest_2_unlock_time=-1.0,
|
chest_2_unlock_time=-1.0,
|
||||||
chest_3_unlock_time=-1.0,
|
chest_3_unlock_time=-1.0,
|
||||||
|
chest_0_unlock_tokens=-1,
|
||||||
|
chest_1_unlock_tokens=-1,
|
||||||
|
chest_2_unlock_tokens=-1,
|
||||||
|
chest_3_unlock_tokens=-1,
|
||||||
chest_0_ad_allow_time=-1.0,
|
chest_0_ad_allow_time=-1.0,
|
||||||
chest_1_ad_allow_time=-1.0,
|
chest_1_ad_allow_time=-1.0,
|
||||||
chest_2_ad_allow_time=-1.0,
|
chest_2_ad_allow_time=-1.0,
|
||||||
|
|
@ -248,7 +423,7 @@ class ClassicAppMode(babase.AppMode):
|
||||||
# connectivity state here we get UI stuff un-fading a moment or
|
# connectivity state here we get UI stuff un-fading a moment or
|
||||||
# two before values appear (since the subscriptions have not
|
# two before values appear (since the subscriptions have not
|
||||||
# sent us any values yet) which looks odd.
|
# sent us any values yet) which looks odd.
|
||||||
_baclassic.set_root_ui_have_live_values(
|
_baclassic.set_have_live_account_values(
|
||||||
self._have_connectivity and self._have_account_values
|
self._have_connectivity and self._have_account_values
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -258,11 +433,10 @@ class ClassicAppMode(babase.AppMode):
|
||||||
def _on_classic_account_data_change(
|
def _on_classic_account_data_change(
|
||||||
self, val: bacommon.bs.ClassicAccountLiveData
|
self, val: bacommon.bs.ClassicAccountLiveData
|
||||||
) -> None:
|
) -> None:
|
||||||
# print('ACCOUNT CHANGED:', val)
|
|
||||||
achp = round(val.achievements / max(val.achievements_total, 1) * 100.0)
|
achp = round(val.achievements / max(val.achievements_total, 1) * 100.0)
|
||||||
ibc = str(val.inbox_count)
|
# ibc = str(val.inbox_count)
|
||||||
if val.inbox_count_is_max:
|
# if val.inbox_count_is_max:
|
||||||
ibc += '+'
|
# ibc += '+'
|
||||||
|
|
||||||
chest0 = val.chests.get('0')
|
chest0 = val.chests.get('0')
|
||||||
chest1 = val.chests.get('1')
|
chest1 = val.chests.get('1')
|
||||||
|
|
@ -275,6 +449,7 @@ class ClassicAppMode(babase.AppMode):
|
||||||
assert classic is not None
|
assert classic is not None
|
||||||
classic.remove_ads = val.remove_ads
|
classic.remove_ads = val.remove_ads
|
||||||
classic.gold_pass = val.gold_pass
|
classic.gold_pass = val.gold_pass
|
||||||
|
classic.tokens = val.tokens
|
||||||
classic.chest_dock_full = (
|
classic.chest_dock_full = (
|
||||||
chest0 is not None
|
chest0 is not None
|
||||||
and chest1 is not None
|
and chest1 is not None
|
||||||
|
|
@ -285,14 +460,21 @@ class ClassicAppMode(babase.AppMode):
|
||||||
_baclassic.set_root_ui_account_values(
|
_baclassic.set_root_ui_account_values(
|
||||||
tickets=val.tickets,
|
tickets=val.tickets,
|
||||||
tokens=val.tokens,
|
tokens=val.tokens,
|
||||||
league_rank=(-1 if val.league_rank is None else val.league_rank),
|
|
||||||
league_type=(
|
league_type=(
|
||||||
'' if val.league_type is None else val.league_type.value
|
'' if val.league_type is None else val.league_type.value
|
||||||
),
|
),
|
||||||
|
league_number=(-1 if val.league_num is None else val.league_num),
|
||||||
|
league_rank=(-1 if val.league_rank is None else val.league_rank),
|
||||||
achievements_percent_text=f'{achp}%',
|
achievements_percent_text=f'{achp}%',
|
||||||
level_text=str(val.level),
|
level_text=str(val.level),
|
||||||
xp_text=f'{val.xp}/{val.xpmax}',
|
xp_text=f'{val.xp}/{val.xpmax}',
|
||||||
inbox_count_text=ibc,
|
inbox_count=val.inbox_count,
|
||||||
|
inbox_count_is_max=val.inbox_count_is_max,
|
||||||
|
inbox_announce_text=(
|
||||||
|
babase.Lstr(resource='unclaimedPrizesText').evaluate()
|
||||||
|
if val.inbox_contains_prize
|
||||||
|
else ''
|
||||||
|
),
|
||||||
gold_pass=val.gold_pass,
|
gold_pass=val.gold_pass,
|
||||||
chest_0_appearance=(
|
chest_0_appearance=(
|
||||||
'' if chest0 is None else chest0.appearance.value
|
'' if chest0 is None else chest0.appearance.value
|
||||||
|
|
@ -306,6 +488,18 @@ class ClassicAppMode(babase.AppMode):
|
||||||
chest_3_appearance=(
|
chest_3_appearance=(
|
||||||
'' if chest3 is None else chest3.appearance.value
|
'' if chest3 is None else chest3.appearance.value
|
||||||
),
|
),
|
||||||
|
chest_0_create_time=(
|
||||||
|
-1.0 if chest0 is None else chest0.create_time.timestamp()
|
||||||
|
),
|
||||||
|
chest_1_create_time=(
|
||||||
|
-1.0 if chest1 is None else chest1.create_time.timestamp()
|
||||||
|
),
|
||||||
|
chest_2_create_time=(
|
||||||
|
-1.0 if chest2 is None else chest2.create_time.timestamp()
|
||||||
|
),
|
||||||
|
chest_3_create_time=(
|
||||||
|
-1.0 if chest3 is None else chest3.create_time.timestamp()
|
||||||
|
),
|
||||||
chest_0_unlock_time=(
|
chest_0_unlock_time=(
|
||||||
-1.0 if chest0 is None else chest0.unlock_time.timestamp()
|
-1.0 if chest0 is None else chest0.unlock_time.timestamp()
|
||||||
),
|
),
|
||||||
|
|
@ -318,6 +512,18 @@ class ClassicAppMode(babase.AppMode):
|
||||||
chest_3_unlock_time=(
|
chest_3_unlock_time=(
|
||||||
-1.0 if chest3 is None else chest3.unlock_time.timestamp()
|
-1.0 if chest3 is None else chest3.unlock_time.timestamp()
|
||||||
),
|
),
|
||||||
|
chest_0_unlock_tokens=(
|
||||||
|
-1 if chest0 is None else chest0.unlock_tokens
|
||||||
|
),
|
||||||
|
chest_1_unlock_tokens=(
|
||||||
|
-1 if chest1 is None else chest1.unlock_tokens
|
||||||
|
),
|
||||||
|
chest_2_unlock_tokens=(
|
||||||
|
-1 if chest2 is None else chest2.unlock_tokens
|
||||||
|
),
|
||||||
|
chest_3_unlock_tokens=(
|
||||||
|
-1 if chest3 is None else chest3.unlock_tokens
|
||||||
|
),
|
||||||
chest_0_ad_allow_time=(
|
chest_0_ad_allow_time=(
|
||||||
-1.0
|
-1.0
|
||||||
if chest0 is None or chest0.ad_allow_time is None
|
if chest0 is None or chest0.ad_allow_time is None
|
||||||
|
|
@ -339,6 +545,14 @@ class ClassicAppMode(babase.AppMode):
|
||||||
else chest3.ad_allow_time.timestamp()
|
else chest3.ad_allow_time.timestamp()
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
if self._should_restore_account_display_state:
|
||||||
|
# If we have a previous display-state for this account,
|
||||||
|
# restore it. This will cause us to animate or otherwise
|
||||||
|
# display league changes that have occurred since we were
|
||||||
|
# last visible. Note we need to do this *after* setting real
|
||||||
|
# vals so there is a current state to animate to.
|
||||||
|
self._restore_account_display_state()
|
||||||
|
self._should_restore_account_display_state = False
|
||||||
|
|
||||||
# Note that we have values and updated faded state accordingly.
|
# Note that we have values and updated faded state accordingly.
|
||||||
self._have_account_values = True
|
self._have_account_values = True
|
||||||
|
|
@ -656,3 +870,38 @@ class ClassicAppMode(babase.AppMode):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _save_account_display_state(self) -> None:
|
||||||
|
|
||||||
|
# If we currently have an account, save the state of what we're
|
||||||
|
# currently displaying for it in the root ui/etc. We'll then
|
||||||
|
# restore that state as a starting point the next time we are
|
||||||
|
# active. This allows things like league rank changes to be
|
||||||
|
# properly animated even if they occurred while we were offline
|
||||||
|
# or while the UI was hidden.
|
||||||
|
|
||||||
|
if self._current_account_id is not None:
|
||||||
|
vals = _baclassic.get_account_display_state()
|
||||||
|
if vals is not None:
|
||||||
|
# Stuff our account id in there and save it to our
|
||||||
|
# config.
|
||||||
|
assert 'a' not in vals
|
||||||
|
vals['a'] = self._current_account_id
|
||||||
|
cfg = babase.app.config
|
||||||
|
cfg[self._LEAGUE_VIS_VALS_CONFIG_KEY] = vals
|
||||||
|
cfg.commit()
|
||||||
|
|
||||||
|
def _restore_account_display_state(self) -> None:
|
||||||
|
|
||||||
|
# If we currently have an account and it matches the
|
||||||
|
# display-state we have stored in the config, restore the state.
|
||||||
|
if self._current_account_id is not None:
|
||||||
|
cfg = babase.app.config
|
||||||
|
vals = cfg.get(self._LEAGUE_VIS_VALS_CONFIG_KEY)
|
||||||
|
if isinstance(vals, dict):
|
||||||
|
valsaccount = vals.get('a')
|
||||||
|
if (
|
||||||
|
isinstance(valsaccount, str)
|
||||||
|
and valsaccount == self._current_account_id
|
||||||
|
):
|
||||||
|
_baclassic.set_account_display_state(vals)
|
||||||
|
|
|
||||||
17
dist/ba_data/python/baclassic/_appsubsystem.py
vendored
17
dist/ba_data/python/baclassic/_appsubsystem.py
vendored
|
|
@ -77,6 +77,7 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
||||||
# Classic-specific account state.
|
# Classic-specific account state.
|
||||||
self.remove_ads = False
|
self.remove_ads = False
|
||||||
self.gold_pass = False
|
self.gold_pass = False
|
||||||
|
self.tokens = 0
|
||||||
self.chest_dock_full = False
|
self.chest_dock_full = False
|
||||||
|
|
||||||
# Main Menu.
|
# Main Menu.
|
||||||
|
|
@ -384,8 +385,6 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
||||||
def getmaps(self, playtype: str) -> list[str]:
|
def getmaps(self, playtype: str) -> list[str]:
|
||||||
"""Return a list of bascenev1.Map types supporting a playtype str.
|
"""Return a list of bascenev1.Map types supporting a playtype str.
|
||||||
|
|
||||||
Category: **Asset Functions**
|
|
||||||
|
|
||||||
Maps supporting a given playtype must provide a particular set of
|
Maps supporting a given playtype must provide a particular set of
|
||||||
features and lend themselves to a certain style of play.
|
features and lend themselves to a certain style of play.
|
||||||
|
|
||||||
|
|
@ -751,10 +750,7 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
||||||
)
|
)
|
||||||
|
|
||||||
def preload_map_preview_media(self) -> None:
|
def preload_map_preview_media(self) -> None:
|
||||||
"""Preload media needed for map preview UIs.
|
"""Preload media needed for map preview UIs."""
|
||||||
|
|
||||||
Category: **Asset Functions**
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
bauiv1.getmesh('level_select_button_opaque')
|
bauiv1.getmesh('level_select_button_opaque')
|
||||||
bauiv1.getmesh('level_select_button_transparent')
|
bauiv1.getmesh('level_select_button_transparent')
|
||||||
|
|
@ -802,6 +798,9 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
||||||
if babase.app.env.gui:
|
if babase.app.env.gui:
|
||||||
bauiv1.getsound('swish').play()
|
bauiv1.getsound('swish').play()
|
||||||
|
|
||||||
|
# Pause gameplay.
|
||||||
|
self.pause()
|
||||||
|
|
||||||
babase.app.ui_v1.set_main_window(
|
babase.app.ui_v1.set_main_window(
|
||||||
InGameMenuWindow(), is_top_level=True, suppress_warning=True
|
InGameMenuWindow(), is_top_level=True, suppress_warning=True
|
||||||
)
|
)
|
||||||
|
|
@ -854,11 +853,13 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def run_bs_client_effects(effects: list[bacommon.bs.ClientEffect]) -> None:
|
def run_bs_client_effects(
|
||||||
|
effects: list[bacommon.bs.ClientEffect], delay: float = 0.0
|
||||||
|
) -> None:
|
||||||
"""Run client effects sent from the master server."""
|
"""Run client effects sent from the master server."""
|
||||||
from baclassic._clienteffect import run_bs_client_effects
|
from baclassic._clienteffect import run_bs_client_effects
|
||||||
|
|
||||||
run_bs_client_effects(effects)
|
run_bs_client_effects(effects, delay=delay)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def basic_client_ui_button_label_str(
|
def basic_client_ui_button_label_str(
|
||||||
|
|
|
||||||
70
dist/ba_data/python/baclassic/_clienteffect.py
vendored
70
dist/ba_data/python/baclassic/_clienteffect.py
vendored
|
|
@ -12,17 +12,23 @@ from efro.util import strict_partial
|
||||||
import bacommon.bs
|
import bacommon.bs
|
||||||
import bauiv1
|
import bauiv1
|
||||||
|
|
||||||
|
import _baclassic
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def run_bs_client_effects(effects: list[bacommon.bs.ClientEffect]) -> None:
|
def run_bs_client_effects(
|
||||||
|
effects: list[bacommon.bs.ClientEffect], delay: float = 0.0
|
||||||
|
) -> None:
|
||||||
"""Run effects."""
|
"""Run effects."""
|
||||||
# pylint: disable=too-many-branches
|
# pylint: disable=too-many-branches
|
||||||
|
from bacommon.bs import ClientEffectTypeID
|
||||||
|
|
||||||
delay = 0.0
|
|
||||||
for effect in effects:
|
for effect in effects:
|
||||||
if isinstance(effect, bacommon.bs.ClientEffectScreenMessage):
|
effecttype = effect.get_type_id()
|
||||||
|
if effecttype is ClientEffectTypeID.SCREEN_MESSAGE:
|
||||||
|
assert isinstance(effect, bacommon.bs.ClientEffectScreenMessage)
|
||||||
textfin = bauiv1.Lstr(
|
textfin = bauiv1.Lstr(
|
||||||
translate=('serverResponses', effect.message)
|
translate=('serverResponses', effect.message)
|
||||||
).evaluate()
|
).evaluate()
|
||||||
|
|
@ -41,7 +47,8 @@ def run_bs_client_effects(effects: list[bacommon.bs.ClientEffect]) -> None:
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
elif isinstance(effect, bacommon.bs.ClientEffectSound):
|
elif effecttype is ClientEffectTypeID.SOUND:
|
||||||
|
assert isinstance(effect, bacommon.bs.ClientEffectSound)
|
||||||
smcls = bacommon.bs.ClientEffectSound.Sound
|
smcls = bacommon.bs.ClientEffectSound.Sound
|
||||||
soundfile: str | None = None
|
soundfile: str | None = None
|
||||||
if effect.sound is smcls.UNKNOWN:
|
if effect.sound is smcls.UNKNOWN:
|
||||||
|
|
@ -66,12 +73,63 @@ def run_bs_client_effects(effects: list[bacommon.bs.ClientEffect]) -> None:
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
elif isinstance(effect, bacommon.bs.ClientEffectDelay):
|
elif effecttype is ClientEffectTypeID.DELAY:
|
||||||
|
assert isinstance(effect, bacommon.bs.ClientEffectDelay)
|
||||||
delay += effect.seconds
|
delay += effect.seconds
|
||||||
else:
|
|
||||||
|
elif effecttype is ClientEffectTypeID.CHEST_WAIT_TIME_ANIMATION:
|
||||||
|
assert isinstance(
|
||||||
|
effect, bacommon.bs.ClientEffectChestWaitTimeAnimation
|
||||||
|
)
|
||||||
|
bauiv1.apptimer(
|
||||||
|
delay,
|
||||||
|
strict_partial(
|
||||||
|
_baclassic.animate_root_ui_chest_unlock_time,
|
||||||
|
chestid=effect.chestid,
|
||||||
|
duration=effect.duration,
|
||||||
|
startvalue=effect.startvalue.timestamp(),
|
||||||
|
endvalue=effect.endvalue.timestamp(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
elif effecttype is ClientEffectTypeID.TICKETS_ANIMATION:
|
||||||
|
assert isinstance(effect, bacommon.bs.ClientEffectTicketsAnimation)
|
||||||
|
bauiv1.apptimer(
|
||||||
|
delay,
|
||||||
|
strict_partial(
|
||||||
|
_baclassic.animate_root_ui_tickets,
|
||||||
|
duration=effect.duration,
|
||||||
|
startvalue=effect.startvalue,
|
||||||
|
endvalue=effect.endvalue,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
elif effecttype is ClientEffectTypeID.TOKENS_ANIMATION:
|
||||||
|
assert isinstance(effect, bacommon.bs.ClientEffectTokensAnimation)
|
||||||
|
bauiv1.apptimer(
|
||||||
|
delay,
|
||||||
|
strict_partial(
|
||||||
|
_baclassic.animate_root_ui_tokens,
|
||||||
|
duration=effect.duration,
|
||||||
|
startvalue=effect.startvalue,
|
||||||
|
endvalue=effect.endvalue,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
elif effecttype is ClientEffectTypeID.UNKNOWN:
|
||||||
# Server should not send us stuff we can't digest. Make
|
# Server should not send us stuff we can't digest. Make
|
||||||
# some noise if it happens.
|
# some noise if it happens.
|
||||||
logging.error(
|
logging.error(
|
||||||
'Got unrecognized bacommon.bs.ClientEffect;'
|
'Got unrecognized bacommon.bs.ClientEffect;'
|
||||||
' should not happen.'
|
' should not happen.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# For type-checking purposes to remind us to implement new
|
||||||
|
# types; should this this in real life.
|
||||||
|
assert_never(effecttype)
|
||||||
|
|
||||||
|
# Lastly, put a pause on root ui auto-updates so that everything we
|
||||||
|
# just scheduled is free to muck with it freely.
|
||||||
|
bauiv1.root_ui_pause_updates()
|
||||||
|
bauiv1.apptimer(delay + 0.25, bauiv1.root_ui_resume_updates)
|
||||||
|
|
|
||||||
38
dist/ba_data/python/baclassic/_hooks.py
vendored
Normal file
38
dist/ba_data/python/baclassic/_hooks.py
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Released under the MIT License. See LICENSE for details.
|
||||||
|
#
|
||||||
|
"""Hooks for C++ layer to use for ClassicAppMode."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import babase
|
||||||
|
|
||||||
|
|
||||||
|
def on_engine_will_reset() -> None:
|
||||||
|
"""Called just before classic resets the engine."""
|
||||||
|
from baclassic._appmode import ClassicAppMode
|
||||||
|
|
||||||
|
appmode = babase.app.mode
|
||||||
|
|
||||||
|
# Just pass this along to our mode instance for handling.
|
||||||
|
if isinstance(appmode, ClassicAppMode):
|
||||||
|
appmode.on_engine_will_reset()
|
||||||
|
else:
|
||||||
|
logging.error(
|
||||||
|
'on_engine_will_reset called without ClassicAppMode active.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def on_engine_did_reset() -> None:
|
||||||
|
"""Called just after classic resets the engine."""
|
||||||
|
from baclassic._appmode import ClassicAppMode
|
||||||
|
|
||||||
|
appmode = babase.app.mode
|
||||||
|
|
||||||
|
# Just pass this along to our mode instance for handling.
|
||||||
|
if isinstance(appmode, ClassicAppMode):
|
||||||
|
appmode.on_engine_did_reset()
|
||||||
|
else:
|
||||||
|
logging.error(
|
||||||
|
'on_engine_did_reset called without ClassicAppMode active.'
|
||||||
|
)
|
||||||
14
dist/ba_data/python/baclassic/_music.py
vendored
14
dist/ba_data/python/baclassic/_music.py
vendored
|
|
@ -20,10 +20,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class MusicPlayMode(Enum):
|
class MusicPlayMode(Enum):
|
||||||
"""Influences behavior when playing music.
|
"""Influences behavior when playing music."""
|
||||||
|
|
||||||
Category: **Enums**
|
|
||||||
"""
|
|
||||||
|
|
||||||
REGULAR = 'regular'
|
REGULAR = 'regular'
|
||||||
TEST = 'test'
|
TEST = 'test'
|
||||||
|
|
@ -31,10 +28,7 @@ class MusicPlayMode(Enum):
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AssetSoundtrackEntry:
|
class AssetSoundtrackEntry:
|
||||||
"""A music entry using an internal asset.
|
"""A music entry using an internal asset."""
|
||||||
|
|
||||||
Category: **App Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
assetname: str
|
assetname: str
|
||||||
volume: float = 1.0
|
volume: float = 1.0
|
||||||
|
|
@ -81,8 +75,6 @@ ASSET_SOUNDTRACK_ENTRIES: dict[MusicType, AssetSoundtrackEntry] = {
|
||||||
class MusicSubsystem:
|
class MusicSubsystem:
|
||||||
"""Subsystem for music playback in the app.
|
"""Subsystem for music playback in the app.
|
||||||
|
|
||||||
Category: **App Classes**
|
|
||||||
|
|
||||||
Access the single shared instance of this class at 'ba.app.music'.
|
Access the single shared instance of this class at 'ba.app.music'.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -374,8 +366,6 @@ class MusicSubsystem:
|
||||||
class MusicPlayer:
|
class MusicPlayer:
|
||||||
"""Wrangles soundtrack music playback.
|
"""Wrangles soundtrack music playback.
|
||||||
|
|
||||||
Category: **App Classes**
|
|
||||||
|
|
||||||
Music can be played either through the game itself
|
Music can be played either through the game itself
|
||||||
or via a platform-specific external player.
|
or via a platform-specific external player.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
18
dist/ba_data/python/baclassic/_net.py
vendored
18
dist/ba_data/python/baclassic/_net.py
vendored
|
|
@ -96,7 +96,7 @@ class MasterServerV1CallThread(threading.Thread):
|
||||||
try:
|
try:
|
||||||
classic = babase.app.classic
|
classic = babase.app.classic
|
||||||
assert classic is not None
|
assert classic is not None
|
||||||
self._data = babase.utf8_all(self._data)
|
self._data = _utf8_all(self._data)
|
||||||
babase.set_thread_name('BA_ServerCallThread')
|
babase.set_thread_name('BA_ServerCallThread')
|
||||||
if self._request_type == 'get':
|
if self._request_type == 'get':
|
||||||
msaddr = plus.get_master_server_address()
|
msaddr = plus.get_master_server_address()
|
||||||
|
|
@ -164,3 +164,19 @@ class MasterServerV1CallThread(threading.Thread):
|
||||||
babase.Call(self._run_callback, response_data),
|
babase.Call(self._run_callback, response_data),
|
||||||
from_other_thread=True,
|
from_other_thread=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _utf8_all(data: Any) -> Any:
|
||||||
|
"""Convert any unicode data in provided sequence(s) to utf8 bytes."""
|
||||||
|
if isinstance(data, dict):
|
||||||
|
return dict(
|
||||||
|
(_utf8_all(key), _utf8_all(value))
|
||||||
|
for key, value in list(data.items())
|
||||||
|
)
|
||||||
|
if isinstance(data, list):
|
||||||
|
return [_utf8_all(element) for element in data]
|
||||||
|
if isinstance(data, tuple):
|
||||||
|
return tuple(_utf8_all(element) for element in data)
|
||||||
|
if isinstance(data, str):
|
||||||
|
return data.encode('utf-8', errors='ignore')
|
||||||
|
return data
|
||||||
|
|
|
||||||
7
dist/ba_data/python/baclassic/_servermode.py
vendored
7
dist/ba_data/python/baclassic/_servermode.py
vendored
|
|
@ -87,10 +87,7 @@ def _cmd(command_data: bytes) -> None:
|
||||||
|
|
||||||
|
|
||||||
class ServerController:
|
class ServerController:
|
||||||
"""Overall controller for the app in server mode.
|
"""Overall controller for the app in server mode."""
|
||||||
|
|
||||||
Category: **App Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, config: ServerConfig) -> None:
|
def __init__(self, config: ServerConfig) -> None:
|
||||||
self._config = config
|
self._config = config
|
||||||
|
|
@ -390,7 +387,7 @@ class ServerController:
|
||||||
f' ({app.env.engine_build_number})'
|
f' ({app.env.engine_build_number})'
|
||||||
f' entering server-mode {curtimestr}{Clr.RST}'
|
f' entering server-mode {curtimestr}{Clr.RST}'
|
||||||
)
|
)
|
||||||
print(startupmsg)
|
logging.info(startupmsg)
|
||||||
|
|
||||||
if sessiontype is bascenev1.FreeForAllSession:
|
if sessiontype is bascenev1.FreeForAllSession:
|
||||||
appcfg['Free-for-All Playlist Selection'] = self._playlist_name
|
appcfg['Free-for-All Playlist Selection'] = self._playlist_name
|
||||||
|
|
|
||||||
5
dist/ba_data/python/baclassic/_store.py
vendored
5
dist/ba_data/python/baclassic/_store.py
vendored
|
|
@ -565,10 +565,7 @@ class StoreSubsystem:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_unowned_maps(self) -> list[str]:
|
def get_unowned_maps(self) -> list[str]:
|
||||||
"""Return the list of local maps not owned by the current account.
|
"""Return the list of local maps not owned by the current account."""
|
||||||
|
|
||||||
Category: **Asset Functions**
|
|
||||||
"""
|
|
||||||
plus = babase.app.plus
|
plus = babase.app.plus
|
||||||
unowned_maps: set[str] = set()
|
unowned_maps: set[str] = set()
|
||||||
if babase.app.env.gui:
|
if babase.app.env.gui:
|
||||||
|
|
|
||||||
5
dist/ba_data/python/bacommon/__init__.py
vendored
5
dist/ba_data/python/bacommon/__init__.py
vendored
|
|
@ -1,3 +1,6 @@
|
||||||
# Released under the MIT License. See LICENSE for details.
|
# Released under the MIT License. See LICENSE for details.
|
||||||
#
|
#
|
||||||
"""Functionality and data common to ballistica client and server components."""
|
"""Functionality and data shared by all Ballistica components.
|
||||||
|
|
||||||
|
This includes clients, various servers, tools, etc.
|
||||||
|
"""
|
||||||
|
|
|
||||||
137
dist/ba_data/python/bacommon/app.py
vendored
137
dist/ba_data/python/bacommon/app.py
vendored
|
|
@ -1,6 +1,6 @@
|
||||||
# Released under the MIT License. See LICENSE for details.
|
# Released under the MIT License. See LICENSE for details.
|
||||||
#
|
#
|
||||||
"""Common high level values/functionality related to apps."""
|
"""Common high level values/functionality related to Ballistica apps."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
@ -10,6 +10,8 @@ from typing import TYPE_CHECKING, Annotated
|
||||||
|
|
||||||
from efro.dataclassio import ioprepped, IOAttrs
|
from efro.dataclassio import ioprepped, IOAttrs
|
||||||
|
|
||||||
|
from bacommon.locale import Locale
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -17,19 +19,41 @@ if TYPE_CHECKING:
|
||||||
class AppInterfaceIdiom(Enum):
|
class AppInterfaceIdiom(Enum):
|
||||||
"""A general form-factor or method of experiencing a Ballistica app.
|
"""A general form-factor or method of experiencing a Ballistica app.
|
||||||
|
|
||||||
Note that it is possible for a running app to switch idioms (for
|
Note that it may be possible for a running app to switch idioms (for
|
||||||
instance if a mobile device or computer is connected to a TV).
|
instance if a mobile device or computer is connected to a TV).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PHONE = 'phone'
|
#: Small screen; assumed to have touch as primary input.
|
||||||
TABLET = 'tablet'
|
PHONE = 'phn'
|
||||||
DESKTOP = 'desktop'
|
|
||||||
|
#: Medium size screen; assumed to have touch as primary input.
|
||||||
|
TABLET = 'tab'
|
||||||
|
|
||||||
|
#: Medium size screen; assumed to have game controller as primary
|
||||||
|
#: input.
|
||||||
|
HANDHELD = 'hnd'
|
||||||
|
|
||||||
|
#: Large screen with high amount of detail visible; assumed to have
|
||||||
|
#: keyboard/mouse as primary input.
|
||||||
|
DESKTOP = 'dsk'
|
||||||
|
|
||||||
|
#: Large screen with medium amount of detail visible; assumed to have
|
||||||
|
#: game controller as primary input.
|
||||||
TV = 'tv'
|
TV = 'tv'
|
||||||
XR = 'xr'
|
|
||||||
|
#: Displayed over or in place of of the real world on a headset;
|
||||||
|
#: assumed to have hand tracking or spacial controllers as primary
|
||||||
|
#: input.
|
||||||
|
XR_HEADSET = 'xrh'
|
||||||
|
|
||||||
|
#: Displayed over or instead of the real world on a screen; assumed
|
||||||
|
#: to have device movement augmented by physical or touchscreen
|
||||||
|
#: controls as primary input.
|
||||||
|
XR_SCREEN = 'xrs'
|
||||||
|
|
||||||
|
|
||||||
class AppExperience(Enum):
|
class AppExperience(Enum):
|
||||||
"""A particular experience that can be provided by a Ballistica app.
|
"""A particular experience provided by a Ballistica app.
|
||||||
|
|
||||||
This is one metric used to isolate different playerbases from each
|
This is one metric used to isolate different playerbases from each
|
||||||
other where there might be no technical barriers doing so. For
|
other where there might be no technical barriers doing so. For
|
||||||
|
|
@ -44,36 +68,36 @@ class AppExperience(Enum):
|
||||||
support multiple experiences, or there may be multiple apps
|
support multiple experiences, or there may be multiple apps
|
||||||
targeting one experience. Cloud components such as leagues are
|
targeting one experience. Cloud components such as leagues are
|
||||||
generally associated with an AppExperience so that they are only
|
generally associated with an AppExperience so that they are only
|
||||||
visible to client apps designed for that play style.
|
visible to client apps designed for that play style, and the same is
|
||||||
|
true for games joinable over the local network, bluetooth, etc.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# An experience that is supported everywhere. Used for the default
|
#: An experience that is supported everywhere. Used for the default
|
||||||
# empty AppMode when starting the app, etc.
|
#: empty AppMode when starting the app, etc.
|
||||||
EMPTY = 'empty'
|
EMPTY = 'empt'
|
||||||
|
|
||||||
# The traditional BombSquad experience: multiple players using
|
#: The traditional BombSquad experience - multiple players using
|
||||||
# traditional game controllers (or touch screen equivalents) in a
|
#: game controllers (or touch screen equivalents) in a single arena
|
||||||
# single arena small enough for all action to be viewed on a single
|
#: small enough for all action to be viewed on a single screen.
|
||||||
# screen.
|
MELEE = 'mlee'
|
||||||
MELEE = 'melee'
|
|
||||||
|
|
||||||
# The traditional BombSquad Remote experience; buttons on a
|
#: The traditional BombSquad Remote experience; buttons on a
|
||||||
# touch-screen allowing a mobile device to be used as a game
|
#: touch-screen allowing a mobile device to be used as a game
|
||||||
# controller.
|
#: controller.
|
||||||
REMOTE = 'remote'
|
REMOTE = 'rmt'
|
||||||
|
|
||||||
|
|
||||||
class AppArchitecture(Enum):
|
class AppArchitecture(Enum):
|
||||||
"""Processor architecture the App is running on."""
|
"""Processor architecture an app can be running on."""
|
||||||
|
|
||||||
ARM = 'arm'
|
ARM = 'arm'
|
||||||
ARM64 = 'arm64'
|
ARM64 = 'arm64'
|
||||||
X86 = 'x86'
|
X86 = 'x86'
|
||||||
X86_64 = 'x86_64'
|
X86_64 = 'x64'
|
||||||
|
|
||||||
|
|
||||||
class AppPlatform(Enum):
|
class AppPlatform(Enum):
|
||||||
"""Overall platform a Ballistica build is targeting.
|
"""Overall platform a build can target.
|
||||||
|
|
||||||
Each distinct flavor of an app has a unique combination of
|
Each distinct flavor of an app has a unique combination of
|
||||||
AppPlatform and AppVariant. Generally platform describes a set of
|
AppPlatform and AppVariant. Generally platform describes a set of
|
||||||
|
|
@ -82,9 +106,9 @@ class AppPlatform(Enum):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
MAC = 'mac'
|
MAC = 'mac'
|
||||||
WINDOWS = 'windows'
|
WINDOWS = 'win'
|
||||||
LINUX = 'linux'
|
LINUX = 'lin'
|
||||||
ANDROID = 'android'
|
ANDROID = 'andr'
|
||||||
IOS = 'ios'
|
IOS = 'ios'
|
||||||
TVOS = 'tvos'
|
TVOS = 'tvos'
|
||||||
|
|
||||||
|
|
@ -98,43 +122,58 @@ class AppVariant(Enum):
|
||||||
build.
|
build.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Default builds.
|
#: Default builds.
|
||||||
GENERIC = 'generic'
|
GENERIC = 'gen'
|
||||||
|
|
||||||
# Builds intended for public testing (may have some extra checks
|
#: Builds intended for public testing (may have some extra checks
|
||||||
# or logging enabled).
|
#: or logging enabled).
|
||||||
TEST = 'test'
|
TEST = 'tst'
|
||||||
|
|
||||||
# Various stores.
|
# Various stores.
|
||||||
AMAZON_APPSTORE = 'amazon_appstore'
|
AMAZON_APPSTORE = 'amzn'
|
||||||
GOOGLE_PLAY = 'google_play'
|
GOOGLE_PLAY = 'gpl'
|
||||||
APP_STORE = 'app_store'
|
APPLE_APP_STORE = 'appl'
|
||||||
WINDOWS_STORE = 'windows_store'
|
WINDOWS_STORE = 'wins'
|
||||||
STEAM = 'steam'
|
STEAM = 'stm'
|
||||||
META = 'meta'
|
META = 'meta'
|
||||||
EPIC_GAMES_STORE = 'epic_games_store'
|
EPIC_GAMES_STORE = 'epic'
|
||||||
|
|
||||||
# Other.
|
# Other.
|
||||||
ARCADE = 'arcade'
|
ARCADE = 'arcd'
|
||||||
DEMO = 'demo'
|
DEMO = 'demo'
|
||||||
|
|
||||||
|
|
||||||
|
class AppName(Enum):
|
||||||
|
"""A predefined Ballistica app name.
|
||||||
|
|
||||||
|
This encompasses official or well-known apps. Other app projects
|
||||||
|
should set this to CUSTOM and provide a 'name_custom' value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
BOMBSQUAD = 'bs'
|
||||||
|
CUSTOM = 'c'
|
||||||
|
|
||||||
|
|
||||||
@ioprepped
|
@ioprepped
|
||||||
@dataclass
|
@dataclass
|
||||||
class AppInstanceInfo:
|
class AppInstanceInfo:
|
||||||
"""General info about an individual running app."""
|
"""General info about an individual running ballistica app."""
|
||||||
|
|
||||||
name = Annotated[str, IOAttrs('n')]
|
name: Annotated[str, IOAttrs('name')]
|
||||||
|
name_custom: Annotated[
|
||||||
|
str | None, IOAttrs('namc', soft_default=None, store_default=False)
|
||||||
|
]
|
||||||
|
|
||||||
engine_version = Annotated[str, IOAttrs('ev')]
|
engine_version: Annotated[str, IOAttrs('evrs')]
|
||||||
engine_build = Annotated[int, IOAttrs('eb')]
|
engine_build: Annotated[int, IOAttrs('ebld')]
|
||||||
|
|
||||||
platform = Annotated[AppPlatform, IOAttrs('p')]
|
platform: Annotated[AppPlatform, IOAttrs('plat')]
|
||||||
variant = Annotated[AppVariant, IOAttrs('va')]
|
variant: Annotated[AppVariant, IOAttrs('vrnt')]
|
||||||
architecture = Annotated[AppArchitecture, IOAttrs('a')]
|
architecture: Annotated[AppArchitecture, IOAttrs('arch')]
|
||||||
os_version = Annotated[str | None, IOAttrs('o')]
|
os_version: Annotated[str | None, IOAttrs('osvr')]
|
||||||
|
|
||||||
interface_idiom: Annotated[AppInterfaceIdiom, IOAttrs('i')]
|
interface_idiom: Annotated[AppInterfaceIdiom, IOAttrs('intf')]
|
||||||
locale: Annotated[str, IOAttrs('l')]
|
locale: Annotated[Locale, IOAttrs('loc')]
|
||||||
|
|
||||||
device: Annotated[str | None, IOAttrs('d')]
|
#: OS-specific string describing the device running the app.
|
||||||
|
device: Annotated[str | None, IOAttrs('devc')]
|
||||||
|
|
|
||||||
109
dist/ba_data/python/bacommon/bacloud.py
vendored
109
dist/ba_data/python/bacommon/bacloud.py
vendored
|
|
@ -51,44 +51,7 @@ class RequestData:
|
||||||
@ioprepped
|
@ioprepped
|
||||||
@dataclass
|
@dataclass
|
||||||
class ResponseData:
|
class ResponseData:
|
||||||
"""Response sent from the bacloud server to the client.
|
"""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 gzipped
|
|
||||||
and uploaded to an 'uploads_inline' bytes 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: If present, describes files the client should individually
|
|
||||||
request from the server if not already present on the client.
|
|
||||||
downloads_inline: If present, pathnames mapped to 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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@ioprepped
|
@ioprepped
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
@ -101,62 +64,114 @@ class ResponseData:
|
||||||
"""Individual download."""
|
"""Individual download."""
|
||||||
|
|
||||||
path: Annotated[str, IOAttrs('p')]
|
path: Annotated[str, IOAttrs('p')]
|
||||||
# Args include with this particular request (combined with
|
|
||||||
# baseargs).
|
#: Args include with this particular request (combined with
|
||||||
|
#: baseargs).
|
||||||
args: Annotated[dict[str, str], IOAttrs('a')]
|
args: Annotated[dict[str, str], IOAttrs('a')]
|
||||||
|
|
||||||
# TODO: could add a hash here if we want the client to
|
# TODO: could add a hash here if we want the client to
|
||||||
# verify hashes.
|
# verify hashes.
|
||||||
|
|
||||||
# If present, will be prepended to all entry paths via os.path.join.
|
#: If present, will be prepended to all entry paths via os.path.join.
|
||||||
basepath: Annotated[str | None, IOAttrs('p')]
|
basepath: Annotated[str | None, IOAttrs('p')]
|
||||||
|
|
||||||
# Server command that should be called for each download. The
|
#: Server command that should be called for each download. The
|
||||||
# server command is expected to respond with a downloads_inline
|
#: server command is expected to respond with a downloads_inline
|
||||||
# containing a single 'default' entry. In the future this may
|
#: containing a single 'default' entry. In the future this may
|
||||||
# be expanded to a more streaming-friendly process.
|
#: be expanded to a more streaming-friendly process.
|
||||||
cmd: Annotated[str, IOAttrs('c')]
|
cmd: Annotated[str, IOAttrs('c')]
|
||||||
|
|
||||||
# Args that should be included with all download requests.
|
#: Args that should be included with all download requests.
|
||||||
baseargs: Annotated[dict[str, str], IOAttrs('a')]
|
baseargs: Annotated[dict[str, str], IOAttrs('a')]
|
||||||
|
|
||||||
# Everything that should be downloaded.
|
#: Everything that should be downloaded.
|
||||||
entries: Annotated[list[Entry], IOAttrs('e')]
|
entries: Annotated[list[Entry], IOAttrs('e')]
|
||||||
|
|
||||||
|
#: If present, client should print this message before any other
|
||||||
|
#: response processing (including error handling) occurs.
|
||||||
message: Annotated[str | None, IOAttrs('m', store_default=False)] = None
|
message: Annotated[str | None, IOAttrs('m', store_default=False)] = None
|
||||||
|
|
||||||
|
#: End arg for message print() call.
|
||||||
message_end: Annotated[str, IOAttrs('m_end', store_default=False)] = '\n'
|
message_end: Annotated[str, IOAttrs('m_end', store_default=False)] = '\n'
|
||||||
|
|
||||||
|
#: If present, client should abort with this error message.
|
||||||
error: Annotated[str | None, IOAttrs('e', store_default=False)] = None
|
error: Annotated[str | None, IOAttrs('e', store_default=False)] = None
|
||||||
|
|
||||||
|
#: How long to wait before proceeding with remaining response (can
|
||||||
|
#: be useful when waiting for server progress in a loop).
|
||||||
delay_seconds: Annotated[float, IOAttrs('d', store_default=False)] = 0.0
|
delay_seconds: Annotated[float, IOAttrs('d', store_default=False)] = 0.0
|
||||||
|
|
||||||
|
#: If present, a token that should be stored client-side and passed
|
||||||
|
#: with subsequent commands.
|
||||||
login: Annotated[str | None, IOAttrs('l', store_default=False)] = None
|
login: Annotated[str | None, IOAttrs('l', store_default=False)] = None
|
||||||
|
|
||||||
|
#: If True, any existing client-side token should be discarded.
|
||||||
logout: Annotated[bool, IOAttrs('lo', store_default=False)] = False
|
logout: Annotated[bool, IOAttrs('lo', store_default=False)] = False
|
||||||
|
|
||||||
|
#: If present, client should generate a manifest of this dir.
|
||||||
|
#: It should be added to end_command args as 'manifest'.
|
||||||
dir_manifest: Annotated[str | None, IOAttrs('man', store_default=False)] = (
|
dir_manifest: Annotated[str | None, IOAttrs('man', store_default=False)] = (
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
#: If present, client should upload the requested files (arg1)
|
||||||
|
#: individually to a server command (arg2) with provided args (arg3).
|
||||||
uploads: Annotated[
|
uploads: Annotated[
|
||||||
tuple[list[str], str, dict] | None, IOAttrs('u', store_default=False)
|
tuple[list[str], str, dict] | None, IOAttrs('u', store_default=False)
|
||||||
] = None
|
] = None
|
||||||
|
|
||||||
|
#: If present, a list of pathnames that should be gzipped
|
||||||
|
#: and uploaded to an 'uploads_inline' bytes dict in end_command args.
|
||||||
|
#: This should be limited to relatively small files.
|
||||||
uploads_inline: Annotated[
|
uploads_inline: Annotated[
|
||||||
list[str] | None, IOAttrs('uinl', store_default=False)
|
list[str] | None, IOAttrs('uinl', store_default=False)
|
||||||
] = None
|
] = None
|
||||||
|
|
||||||
|
#: If present, file paths that should be deleted on the client.
|
||||||
deletes: Annotated[
|
deletes: Annotated[
|
||||||
list[str] | None, IOAttrs('dlt', store_default=False)
|
list[str] | None, IOAttrs('dlt', store_default=False)
|
||||||
] = None
|
] = None
|
||||||
|
|
||||||
|
#: If present, describes files the client should individually
|
||||||
|
#: request from the server if not already present on the client.
|
||||||
downloads: Annotated[
|
downloads: Annotated[
|
||||||
Downloads | None, IOAttrs('dl', store_default=False)
|
Downloads | None, IOAttrs('dl', store_default=False)
|
||||||
] = None
|
] = None
|
||||||
|
|
||||||
|
#: If present, pathnames mapped to 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.
|
||||||
downloads_inline: Annotated[
|
downloads_inline: Annotated[
|
||||||
dict[str, bytes] | None, IOAttrs('dinl', store_default=False)
|
dict[str, bytes] | None, IOAttrs('dinl', store_default=False)
|
||||||
] = None
|
] = None
|
||||||
|
|
||||||
|
#: If present, all empty dirs under this one should be removed.
|
||||||
dir_prune_empty: Annotated[
|
dir_prune_empty: Annotated[
|
||||||
str | None, IOAttrs('dpe', store_default=False)
|
str | None, IOAttrs('dpe', store_default=False)
|
||||||
] = None
|
] = None
|
||||||
|
|
||||||
|
#: If present, url to display to the user.
|
||||||
open_url: Annotated[str | None, IOAttrs('url', store_default=False)] = None
|
open_url: Annotated[str | None, IOAttrs('url', store_default=False)] = None
|
||||||
|
|
||||||
|
#: 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).
|
||||||
input_prompt: Annotated[
|
input_prompt: Annotated[
|
||||||
tuple[str, bool] | None, IOAttrs('inp', store_default=False)
|
tuple[str, bool] | None, IOAttrs('inp', store_default=False)
|
||||||
] = None
|
] = None
|
||||||
|
|
||||||
|
#: If present, a message that should be printed after all other
|
||||||
|
#: response processing is done.
|
||||||
end_message: Annotated[str | None, IOAttrs('em', store_default=False)] = (
|
end_message: Annotated[str | None, IOAttrs('em', store_default=False)] = (
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
#: End arg for end_message print() call.
|
||||||
end_message_end: Annotated[str, IOAttrs('eme', store_default=False)] = '\n'
|
end_message_end: Annotated[str, IOAttrs('eme', store_default=False)] = '\n'
|
||||||
|
|
||||||
|
#: If present, this command is run with these args at the end
|
||||||
|
#: of response processing.
|
||||||
end_command: Annotated[
|
end_command: Annotated[
|
||||||
tuple[str, dict] | None, IOAttrs('ec', store_default=False)
|
tuple[str, dict] | None, IOAttrs('ec', store_default=False)
|
||||||
] = None
|
] = None
|
||||||
|
|
|
||||||
188
dist/ba_data/python/bacommon/bs.py
vendored
188
dist/ba_data/python/bacommon/bs.py
vendored
|
|
@ -13,6 +13,12 @@ from efro.util import pairs_to_flat
|
||||||
from efro.dataclassio import ioprepped, IOAttrs, IOMultiType
|
from efro.dataclassio import ioprepped, IOAttrs, IOMultiType
|
||||||
from efro.message import Message, Response
|
from efro.message import Message, Response
|
||||||
|
|
||||||
|
# Token counts for our various packs.
|
||||||
|
TOKENS1_COUNT = 50
|
||||||
|
TOKENS2_COUNT = 500
|
||||||
|
TOKENS3_COUNT = 1200
|
||||||
|
TOKENS4_COUNT = 2600
|
||||||
|
|
||||||
|
|
||||||
@ioprepped
|
@ioprepped
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
@ -89,7 +95,9 @@ class ClassicAccountLiveData:
|
||||||
ClassicChestAppearance,
|
ClassicChestAppearance,
|
||||||
IOAttrs('a', enum_fallback=ClassicChestAppearance.UNKNOWN),
|
IOAttrs('a', enum_fallback=ClassicChestAppearance.UNKNOWN),
|
||||||
]
|
]
|
||||||
|
create_time: Annotated[datetime.datetime, IOAttrs('c')]
|
||||||
unlock_time: Annotated[datetime.datetime, IOAttrs('t')]
|
unlock_time: Annotated[datetime.datetime, IOAttrs('t')]
|
||||||
|
unlock_tokens: Annotated[int, IOAttrs('k')]
|
||||||
ad_allow_time: Annotated[datetime.datetime | None, IOAttrs('at')]
|
ad_allow_time: Annotated[datetime.datetime | None, IOAttrs('at')]
|
||||||
|
|
||||||
class LeagueType(Enum):
|
class LeagueType(Enum):
|
||||||
|
|
@ -119,6 +127,7 @@ class ClassicAccountLiveData:
|
||||||
|
|
||||||
inbox_count: Annotated[int, IOAttrs('ibc')]
|
inbox_count: Annotated[int, IOAttrs('ibc')]
|
||||||
inbox_count_is_max: Annotated[bool, IOAttrs('ibcm')]
|
inbox_count_is_max: Annotated[bool, IOAttrs('ibcm')]
|
||||||
|
inbox_contains_prize: Annotated[bool, IOAttrs('icp')]
|
||||||
|
|
||||||
chests: Annotated[dict[str, Chest], IOAttrs('c')]
|
chests: Annotated[dict[str, Chest], IOAttrs('c')]
|
||||||
|
|
||||||
|
|
@ -341,64 +350,6 @@ class ChestInfoResponse(Response):
|
||||||
user_tokens: Annotated[int | None, IOAttrs('t')]
|
user_tokens: Annotated[int | None, IOAttrs('t')]
|
||||||
|
|
||||||
|
|
||||||
@ioprepped
|
|
||||||
@dataclass
|
|
||||||
class ChestActionMessage(Message):
|
|
||||||
"""Request action about a chest."""
|
|
||||||
|
|
||||||
class Action(Enum):
|
|
||||||
"""Types of actions we can request."""
|
|
||||||
|
|
||||||
# Unlocking (for free or with tokens).
|
|
||||||
UNLOCK = 'u'
|
|
||||||
|
|
||||||
# Watched an ad to reduce wait.
|
|
||||||
AD = 'ad'
|
|
||||||
|
|
||||||
action: Annotated[Action, IOAttrs('a')]
|
|
||||||
|
|
||||||
# Tokens we are paying (only applies to unlock).
|
|
||||||
token_payment: Annotated[int, IOAttrs('t')]
|
|
||||||
|
|
||||||
chest_id: Annotated[str, IOAttrs('i')]
|
|
||||||
|
|
||||||
@override
|
|
||||||
@classmethod
|
|
||||||
def get_response_types(cls) -> list[type[Response] | None]:
|
|
||||||
return [ChestActionResponse]
|
|
||||||
|
|
||||||
|
|
||||||
@ioprepped
|
|
||||||
@dataclass
|
|
||||||
class ChestActionResponse(Response):
|
|
||||||
"""Here's the results of that action you asked for, boss."""
|
|
||||||
|
|
||||||
# Tokens that were actually charged.
|
|
||||||
tokens_charged: Annotated[int, IOAttrs('t')] = 0
|
|
||||||
|
|
||||||
# If present, signifies the chest has been opened and we should show
|
|
||||||
# the user this stuff that was in it.
|
|
||||||
contents: Annotated[list[DisplayItemWrapper] | None, IOAttrs('c')] = None
|
|
||||||
|
|
||||||
# If contents are present, which of the chest's prize-sets they
|
|
||||||
# represent.
|
|
||||||
prizeindex: Annotated[int, IOAttrs('i')] = 0
|
|
||||||
|
|
||||||
# Printable error if something goes wrong.
|
|
||||||
error: Annotated[str | None, IOAttrs('e')] = None
|
|
||||||
|
|
||||||
# Printable warning. Shown in orange with an error sound. Does not
|
|
||||||
# mean the action failed; only that there's something to tell the
|
|
||||||
# users such as 'It looks like you are faking ad views; stop it or
|
|
||||||
# you won't have ad options anymore.'
|
|
||||||
warning: Annotated[str | None, IOAttrs('w')] = None
|
|
||||||
|
|
||||||
# Printable success message. Shown in green with a cash-register
|
|
||||||
# sound. Can be used for things like successful wait reductions via
|
|
||||||
# ad views.
|
|
||||||
success_msg: Annotated[str | None, IOAttrs('s')] = None
|
|
||||||
|
|
||||||
|
|
||||||
class ClientUITypeID(Enum):
|
class ClientUITypeID(Enum):
|
||||||
"""Type ID for each of our subclasses."""
|
"""Type ID for each of our subclasses."""
|
||||||
|
|
||||||
|
|
@ -717,6 +668,9 @@ class ClientEffectTypeID(Enum):
|
||||||
SCREEN_MESSAGE = 'm'
|
SCREEN_MESSAGE = 'm'
|
||||||
SOUND = 's'
|
SOUND = 's'
|
||||||
DELAY = 'd'
|
DELAY = 'd'
|
||||||
|
CHEST_WAIT_TIME_ANIMATION = 't'
|
||||||
|
TICKETS_ANIMATION = 'ta'
|
||||||
|
TOKENS_ANIMATION = 'toa'
|
||||||
|
|
||||||
|
|
||||||
class ClientEffect(IOMultiType[ClientEffectTypeID]):
|
class ClientEffect(IOMultiType[ClientEffectTypeID]):
|
||||||
|
|
@ -738,6 +692,7 @@ class ClientEffect(IOMultiType[ClientEffectTypeID]):
|
||||||
def get_type(cls, type_id: ClientEffectTypeID) -> type[ClientEffect]:
|
def get_type(cls, type_id: ClientEffectTypeID) -> type[ClientEffect]:
|
||||||
"""Return the subclass for each of our type-ids."""
|
"""Return the subclass for each of our type-ids."""
|
||||||
# pylint: disable=cyclic-import
|
# pylint: disable=cyclic-import
|
||||||
|
# pylint: disable=too-many-return-statements
|
||||||
|
|
||||||
t = ClientEffectTypeID
|
t = ClientEffectTypeID
|
||||||
if type_id is t.UNKNOWN:
|
if type_id is t.UNKNOWN:
|
||||||
|
|
@ -748,6 +703,12 @@ class ClientEffect(IOMultiType[ClientEffectTypeID]):
|
||||||
return ClientEffectSound
|
return ClientEffectSound
|
||||||
if type_id is t.DELAY:
|
if type_id is t.DELAY:
|
||||||
return ClientEffectDelay
|
return ClientEffectDelay
|
||||||
|
if type_id is t.CHEST_WAIT_TIME_ANIMATION:
|
||||||
|
return ClientEffectChestWaitTimeAnimation
|
||||||
|
if type_id is t.TICKETS_ANIMATION:
|
||||||
|
return ClientEffectTicketsAnimation
|
||||||
|
if type_id is t.TOKENS_ANIMATION:
|
||||||
|
return ClientEffectTokensAnimation
|
||||||
|
|
||||||
# Important to make sure we provide all types.
|
# Important to make sure we provide all types.
|
||||||
assert_never(type_id)
|
assert_never(type_id)
|
||||||
|
|
@ -809,6 +770,52 @@ class ClientEffectSound(ClientEffect):
|
||||||
return ClientEffectTypeID.SOUND
|
return ClientEffectTypeID.SOUND
|
||||||
|
|
||||||
|
|
||||||
|
@ioprepped
|
||||||
|
@dataclass
|
||||||
|
class ClientEffectChestWaitTimeAnimation(ClientEffect):
|
||||||
|
"""Animate chest wait time changing."""
|
||||||
|
|
||||||
|
chestid: Annotated[str, IOAttrs('c')]
|
||||||
|
duration: Annotated[float, IOAttrs('u')]
|
||||||
|
startvalue: Annotated[datetime.datetime, IOAttrs('o')]
|
||||||
|
endvalue: Annotated[datetime.datetime, IOAttrs('n')]
|
||||||
|
|
||||||
|
@override
|
||||||
|
@classmethod
|
||||||
|
def get_type_id(cls) -> ClientEffectTypeID:
|
||||||
|
return ClientEffectTypeID.CHEST_WAIT_TIME_ANIMATION
|
||||||
|
|
||||||
|
|
||||||
|
@ioprepped
|
||||||
|
@dataclass
|
||||||
|
class ClientEffectTicketsAnimation(ClientEffect):
|
||||||
|
"""Animate tickets count."""
|
||||||
|
|
||||||
|
duration: Annotated[float, IOAttrs('u')]
|
||||||
|
startvalue: Annotated[int, IOAttrs('s')]
|
||||||
|
endvalue: Annotated[int, IOAttrs('e')]
|
||||||
|
|
||||||
|
@override
|
||||||
|
@classmethod
|
||||||
|
def get_type_id(cls) -> ClientEffectTypeID:
|
||||||
|
return ClientEffectTypeID.TICKETS_ANIMATION
|
||||||
|
|
||||||
|
|
||||||
|
@ioprepped
|
||||||
|
@dataclass
|
||||||
|
class ClientEffectTokensAnimation(ClientEffect):
|
||||||
|
"""Animate tokens count."""
|
||||||
|
|
||||||
|
duration: Annotated[float, IOAttrs('u')]
|
||||||
|
startvalue: Annotated[int, IOAttrs('s')]
|
||||||
|
endvalue: Annotated[int, IOAttrs('e')]
|
||||||
|
|
||||||
|
@override
|
||||||
|
@classmethod
|
||||||
|
def get_type_id(cls) -> ClientEffectTypeID:
|
||||||
|
return ClientEffectTypeID.TOKENS_ANIMATION
|
||||||
|
|
||||||
|
|
||||||
@ioprepped
|
@ioprepped
|
||||||
@dataclass
|
@dataclass
|
||||||
class ClientEffectDelay(ClientEffect):
|
class ClientEffectDelay(ClientEffect):
|
||||||
|
|
@ -885,3 +892,68 @@ class ScoreSubmitResponse(Response):
|
||||||
|
|
||||||
# Things we should show on our end.
|
# Things we should show on our end.
|
||||||
effects: Annotated[list[ClientEffect], IOAttrs('fx')]
|
effects: Annotated[list[ClientEffect], IOAttrs('fx')]
|
||||||
|
|
||||||
|
|
||||||
|
@ioprepped
|
||||||
|
@dataclass
|
||||||
|
class ChestActionMessage(Message):
|
||||||
|
"""Request action about a chest."""
|
||||||
|
|
||||||
|
class Action(Enum):
|
||||||
|
"""Types of actions we can request."""
|
||||||
|
|
||||||
|
# Unlocking (for free or with tokens).
|
||||||
|
UNLOCK = 'u'
|
||||||
|
|
||||||
|
# Watched an ad to reduce wait.
|
||||||
|
AD = 'ad'
|
||||||
|
|
||||||
|
action: Annotated[Action, IOAttrs('a')]
|
||||||
|
|
||||||
|
# Tokens we are paying (only applies to unlock).
|
||||||
|
token_payment: Annotated[int, IOAttrs('t')]
|
||||||
|
|
||||||
|
chest_id: Annotated[str, IOAttrs('i')]
|
||||||
|
|
||||||
|
@override
|
||||||
|
@classmethod
|
||||||
|
def get_response_types(cls) -> list[type[Response] | None]:
|
||||||
|
return [ChestActionResponse]
|
||||||
|
|
||||||
|
|
||||||
|
@ioprepped
|
||||||
|
@dataclass
|
||||||
|
class ChestActionResponse(Response):
|
||||||
|
"""Here's the results of that action you asked for, boss."""
|
||||||
|
|
||||||
|
# Tokens that were actually charged.
|
||||||
|
tokens_charged: Annotated[int, IOAttrs('t')] = 0
|
||||||
|
|
||||||
|
# If present, signifies the chest has been opened and we should show
|
||||||
|
# the user this stuff that was in it.
|
||||||
|
contents: Annotated[list[DisplayItemWrapper] | None, IOAttrs('c')] = None
|
||||||
|
|
||||||
|
# If contents are present, which of the chest's prize-sets they
|
||||||
|
# represent.
|
||||||
|
prizeindex: Annotated[int, IOAttrs('i')] = 0
|
||||||
|
|
||||||
|
# Printable error if something goes wrong.
|
||||||
|
error: Annotated[str | None, IOAttrs('e')] = None
|
||||||
|
|
||||||
|
# Printable warning. Shown in orange with an error sound. Does not
|
||||||
|
# mean the action failed; only that there's something to tell the
|
||||||
|
# users such as 'It looks like you are faking ad views; stop it or
|
||||||
|
# you won't have ad options anymore.'
|
||||||
|
warning: Annotated[str | None, IOAttrs('w', store_default=False)] = None
|
||||||
|
|
||||||
|
# Printable success message. Shown in green with a cash-register
|
||||||
|
# sound. Can be used for things like successful wait reductions via
|
||||||
|
# ad views. Used in builds earlier than 22311; can remove once
|
||||||
|
# 22311+ is ubiquitous.
|
||||||
|
success_msg: Annotated[str | None, IOAttrs('s', store_default=False)] = None
|
||||||
|
|
||||||
|
# Effects to show on the client. Replaces warning and success_msg in
|
||||||
|
# build 22311 or newer.
|
||||||
|
effects: Annotated[
|
||||||
|
list[ClientEffect], IOAttrs('fx', store_default=False)
|
||||||
|
] = field(default_factory=list)
|
||||||
|
|
|
||||||
43
dist/ba_data/python/bacommon/cloud.py
vendored
43
dist/ba_data/python/bacommon/cloud.py
vendored
|
|
@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, Annotated, override
|
||||||
|
|
||||||
from efro.message import Message, Response
|
from efro.message import Message, Response
|
||||||
from efro.dataclassio import ioprepped, IOAttrs
|
from efro.dataclassio import ioprepped, IOAttrs
|
||||||
|
from bacommon.securedata import SecureDataChecker
|
||||||
from bacommon.transfer import DirectoryManifest
|
from bacommon.transfer import DirectoryManifest
|
||||||
from bacommon.login import LoginType
|
from bacommon.login import LoginType
|
||||||
|
|
||||||
|
|
@ -300,3 +301,45 @@ class StoreQueryResponse(Response):
|
||||||
|
|
||||||
available_purchases: Annotated[list[Purchase], IOAttrs('p')]
|
available_purchases: Annotated[list[Purchase], IOAttrs('p')]
|
||||||
token_info_url: Annotated[str, IOAttrs('tiu')]
|
token_info_url: Annotated[str, IOAttrs('tiu')]
|
||||||
|
|
||||||
|
|
||||||
|
@ioprepped
|
||||||
|
@dataclass
|
||||||
|
class SecureDataCheckMessage(Message):
|
||||||
|
"""Was this data signed by the master-server?."""
|
||||||
|
|
||||||
|
data: Annotated[bytes, IOAttrs('d')]
|
||||||
|
signature: Annotated[bytes, IOAttrs('s')]
|
||||||
|
|
||||||
|
@override
|
||||||
|
@classmethod
|
||||||
|
def get_response_types(cls) -> list[type[Response] | None]:
|
||||||
|
return [SecureDataCheckResponse]
|
||||||
|
|
||||||
|
|
||||||
|
@ioprepped
|
||||||
|
@dataclass
|
||||||
|
class SecureDataCheckResponse(Response):
|
||||||
|
"""Here's the result of that data check, boss."""
|
||||||
|
|
||||||
|
# Whether the data signature was valid.
|
||||||
|
result: Annotated[bool, IOAttrs('v')]
|
||||||
|
|
||||||
|
|
||||||
|
@ioprepped
|
||||||
|
@dataclass
|
||||||
|
class SecureDataCheckerRequest(Message):
|
||||||
|
"""Can I get a checker over here?."""
|
||||||
|
|
||||||
|
@override
|
||||||
|
@classmethod
|
||||||
|
def get_response_types(cls) -> list[type[Response] | None]:
|
||||||
|
return [SecureDataCheckerResponse]
|
||||||
|
|
||||||
|
|
||||||
|
@ioprepped
|
||||||
|
@dataclass
|
||||||
|
class SecureDataCheckerResponse(Response):
|
||||||
|
"""Here's that checker ya asked for, boss."""
|
||||||
|
|
||||||
|
checker: Annotated[SecureDataChecker, IOAttrs('c')]
|
||||||
|
|
|
||||||
23
dist/ba_data/python/bacommon/locale.py
vendored
Normal file
23
dist/ba_data/python/bacommon/locale.py
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Released under the MIT License. See LICENSE for details.
|
||||||
|
#
|
||||||
|
"""Functionality for wrangling locale info."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Locale(Enum):
|
||||||
|
"""A distinct combination of language and possibly country/etc.
|
||||||
|
|
||||||
|
Note that some locales here may be superseded by other more specific
|
||||||
|
ones (for instance PORTUGUESE -> PORTUGUESE_BRAZIL), but the
|
||||||
|
originals must continue to exist here since they may remain in use
|
||||||
|
in the wild.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ENGLISH = 'en'
|
||||||
10
dist/ba_data/python/bacommon/login.py
vendored
10
dist/ba_data/python/bacommon/login.py
vendored
|
|
@ -20,18 +20,18 @@ if TYPE_CHECKING:
|
||||||
class LoginType(Enum):
|
class LoginType(Enum):
|
||||||
"""Types of logins available."""
|
"""Types of logins available."""
|
||||||
|
|
||||||
# Email/password
|
#: Email/password
|
||||||
EMAIL = 'email'
|
EMAIL = 'email'
|
||||||
|
|
||||||
# Google Play Game Services
|
#: Google Play Game Services
|
||||||
GPGS = 'gpgs'
|
GPGS = 'gpgs'
|
||||||
|
|
||||||
# Apple's Game Center
|
#: Apple's Game Center
|
||||||
GAME_CENTER = 'game_center'
|
GAME_CENTER = 'game_center'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def displayname(self) -> str:
|
def displayname(self) -> str:
|
||||||
"""Human readable name for this value."""
|
"""A human readable name for this value."""
|
||||||
cls = type(self)
|
cls = type(self)
|
||||||
match self:
|
match self:
|
||||||
case cls.EMAIL:
|
case cls.EMAIL:
|
||||||
|
|
@ -43,7 +43,7 @@ class LoginType(Enum):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def displaynameshort(self) -> str:
|
def displaynameshort(self) -> str:
|
||||||
"""Human readable name for this value."""
|
"""A short human readable name for this value."""
|
||||||
cls = type(self)
|
cls = type(self)
|
||||||
match self:
|
match self:
|
||||||
case cls.EMAIL:
|
case cls.EMAIL:
|
||||||
|
|
|
||||||
56
dist/ba_data/python/bacommon/securedata.py
vendored
Normal file
56
dist/ba_data/python/bacommon/securedata.py
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
# Released under the MIT License. See LICENSE for details.
|
||||||
|
#
|
||||||
|
"""Functionality related to verifying ballistica server generated data."""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING, Annotated
|
||||||
|
|
||||||
|
from efro.util import utc_now
|
||||||
|
from efro.dataclassio import ioprepped, IOAttrs
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ioprepped
|
||||||
|
@dataclass
|
||||||
|
class SecureDataChecker:
|
||||||
|
"""Verifies data as being signed by our master server."""
|
||||||
|
|
||||||
|
# Time period this checker is valid for.
|
||||||
|
starttime: Annotated[datetime.datetime, IOAttrs('s')]
|
||||||
|
endtime: Annotated[datetime.datetime, IOAttrs('e')]
|
||||||
|
|
||||||
|
# Current set of public keys.
|
||||||
|
publickeys: Annotated[list[bytes], IOAttrs('k')]
|
||||||
|
|
||||||
|
def check(self, data: bytes, signature: bytes) -> bool:
|
||||||
|
"""Verify data, returning True if successful.
|
||||||
|
|
||||||
|
Note that this call imports and uses the cryptography module and
|
||||||
|
can be slow; it generally should be done in a background thread
|
||||||
|
or on a server.
|
||||||
|
"""
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import ed25519
|
||||||
|
from cryptography.exceptions import InvalidSignature
|
||||||
|
|
||||||
|
now = utc_now()
|
||||||
|
|
||||||
|
# Make sure we seem valid based on local time.
|
||||||
|
if now < self.starttime:
|
||||||
|
raise RuntimeError('SecureDataChecker starttime is in the future.')
|
||||||
|
if now > self.endtime:
|
||||||
|
raise RuntimeError('SecureDataChecker endtime is in the past.')
|
||||||
|
|
||||||
|
# Try our keys from newest to oldest. Most stuff will be using
|
||||||
|
# the newest key so this should be most efficient.
|
||||||
|
for key in reversed(self.publickeys):
|
||||||
|
try:
|
||||||
|
publickey = ed25519.Ed25519PublicKey.from_public_bytes(key)
|
||||||
|
publickey.verify(signature, data)
|
||||||
|
return True
|
||||||
|
except InvalidSignature:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
# Released under the MIT License. See LICENSE for details.
|
# Released under the MIT License. See LICENSE for details.
|
||||||
#
|
#
|
||||||
"""Workspace functionality."""
|
"""Functionality related to ballistica.net workspaces."""
|
||||||
|
|
|
||||||
45
dist/ba_data/python/baenv.py
vendored
45
dist/ba_data/python/baenv.py
vendored
|
|
@ -1,11 +1,11 @@
|
||||||
# Released under the MIT License. See LICENSE for details.
|
# Released under the MIT License. See LICENSE for details.
|
||||||
#
|
#
|
||||||
"""Manage ballistica execution environment.
|
"""Manage Ballistica execution environment.
|
||||||
|
|
||||||
This module is used to set up and/or check the global Python environment
|
This module is used to set up and/or check the global Python environment
|
||||||
before running a ballistica app. This includes things such as paths,
|
before running a Ballistica app. This includes things such as paths,
|
||||||
logging, and app-dirs. Because these things are global in nature, this
|
logging, and app-dirs. Because these things are global in nature, this
|
||||||
should be done before any ballistica modules are imported.
|
should be done before any Ballistica modules are imported.
|
||||||
|
|
||||||
This module can also be exec'ed directly to set up a default environment
|
This module can also be exec'ed directly to set up a default environment
|
||||||
and then run the app.
|
and then run the app.
|
||||||
|
|
@ -53,46 +53,46 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
# Build number and version of the ballistica binary we expect to be
|
# Build number and version of the ballistica binary we expect to be
|
||||||
# using.
|
# using.
|
||||||
TARGET_BALLISTICA_BUILD = 22278
|
TARGET_BALLISTICA_BUILD = 22350
|
||||||
TARGET_BALLISTICA_VERSION = '1.7.37'
|
TARGET_BALLISTICA_VERSION = '1.7.39'
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class EnvConfig:
|
class EnvConfig:
|
||||||
"""Final config values we provide to the engine."""
|
"""Final config values we provide to the engine."""
|
||||||
|
|
||||||
# Where app config/state data lives.
|
#: Where app config/state data lives.
|
||||||
config_dir: str
|
config_dir: str
|
||||||
|
|
||||||
# Directory containing ba_data and any other platform-specific data.
|
#: Directory containing ba_data and any other platform-specific data.
|
||||||
data_dir: str
|
data_dir: str
|
||||||
|
|
||||||
# Where the app's built-in Python stuff lives.
|
#: Where the app's built-in Python stuff lives.
|
||||||
app_python_dir: str | None
|
app_python_dir: str | None
|
||||||
|
|
||||||
# Where the app's built-in Python stuff lives in the default case.
|
#: Where the app's built-in Python stuff lives in the default case.
|
||||||
standard_app_python_dir: str
|
standard_app_python_dir: str
|
||||||
|
|
||||||
# Where the app's bundled third party Python stuff lives.
|
#: Where the app's bundled third party Python stuff lives.
|
||||||
site_python_dir: str | None
|
site_python_dir: str | None
|
||||||
|
|
||||||
# Custom Python provided by the user (mods).
|
#: Custom Python provided by the user (mods).
|
||||||
user_python_dir: str | None
|
user_python_dir: str | None
|
||||||
|
|
||||||
# We have a mechanism allowing app scripts to be overridden by
|
#: We have a mechanism allowing app scripts to be overridden by
|
||||||
# placing a specially named directory in a user-scripts dir.
|
#: placing a specially named directory in a user-scripts dir. This is
|
||||||
# This is true if that is enabled.
|
#: true if that is enabled.
|
||||||
is_user_app_python_dir: bool
|
is_user_app_python_dir: bool
|
||||||
|
|
||||||
# Our fancy app log handler. This handles feeding logs, stdout, and
|
#: Our fancy app log handler. This handles feeding logs, stdout, and
|
||||||
# stderr into the engine so they show up on in-app consoles, etc.
|
#: stderr into the engine so they show up on in-app consoles, etc.
|
||||||
log_handler: LogHandler | None
|
log_handler: LogHandler | None
|
||||||
|
|
||||||
# Initial data from the config.json file in the config dir. The
|
#: Initial data from the config.json file in the config dir. The
|
||||||
# config file is parsed by
|
#: config file is parsed by
|
||||||
initial_app_config: Any
|
initial_app_config: Any
|
||||||
|
|
||||||
# Timestamp when we first started doing stuff.
|
#: Timestamp when we first started doing stuff.
|
||||||
launch_time: float
|
launch_time: float
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -100,10 +100,9 @@ class EnvConfig:
|
||||||
class _EnvGlobals:
|
class _EnvGlobals:
|
||||||
"""Globals related to baenv's operation.
|
"""Globals related to baenv's operation.
|
||||||
|
|
||||||
We store this in __main__ instead of in our own module because it
|
We store this in __main__ instead of in our own module because it is
|
||||||
is likely that multiple versions of our module will be spun up
|
likely that multiple versions of our module will be spun up and we
|
||||||
and we want a single set of globals (see notes at top of our module
|
want a single set of globals (see notes at top of our module code).
|
||||||
code).
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
config: EnvConfig | None = None
|
config: EnvConfig | None = None
|
||||||
|
|
|
||||||
6
dist/ba_data/python/baplus/__init__.py
vendored
6
dist/ba_data/python/baplus/__init__.py
vendored
|
|
@ -3,10 +3,10 @@
|
||||||
"""Closed-source bits of ballistica.
|
"""Closed-source bits of ballistica.
|
||||||
|
|
||||||
This code concerns sensitive things like accounts and master-server
|
This code concerns sensitive things like accounts and master-server
|
||||||
communication so the native C++ parts of it remain closed. Native
|
communication, so the native C++ parts of it remain closed. Native
|
||||||
precompiled static libraries of this portion are provided for those who
|
precompiled static libraries of this portion are provided for those who
|
||||||
want to compile the rest of the engine, or a fully open-source app
|
want to compile the rest of the engine, or a fully open-source app can
|
||||||
can also be built by removing this feature-set.
|
also be built by removing this feature-set.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
|
||||||
135
dist/ba_data/python/baplus/_appsubsystem.py
vendored
135
dist/ba_data/python/baplus/_appsubsystem.py
vendored
|
|
@ -21,10 +21,10 @@ if TYPE_CHECKING:
|
||||||
class PlusAppSubsystem(AppSubsystem):
|
class PlusAppSubsystem(AppSubsystem):
|
||||||
"""Subsystem for plus functionality in the app.
|
"""Subsystem for plus functionality in the app.
|
||||||
|
|
||||||
The single shared instance of this app can be accessed at
|
Access the single shared instance of this class via the
|
||||||
babase.app.plus. Note that it is possible for this to be None if the
|
:attr:`~babase.App.plus` attr on the :class:`~babase.App` class.
|
||||||
plus package is not present, and code should handle that case
|
Note that it is possible for this to be ``None`` if the plus package
|
||||||
gracefully.
|
is not present, so code should handle that case gracefully.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# pylint: disable=too-many-public-methods
|
# pylint: disable=too-many-public-methods
|
||||||
|
|
@ -38,6 +38,7 @@ class PlusAppSubsystem(AppSubsystem):
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def on_app_loading(self) -> None:
|
def on_app_loading(self) -> None:
|
||||||
|
""":meta private:"""
|
||||||
_baplus.on_app_loading()
|
_baplus.on_app_loading()
|
||||||
self.accounts.on_app_loading()
|
self.accounts.on_app_loading()
|
||||||
|
|
||||||
|
|
@ -45,173 +46,164 @@ class PlusAppSubsystem(AppSubsystem):
|
||||||
def add_v1_account_transaction(
|
def add_v1_account_transaction(
|
||||||
transaction: dict, callback: Callable | None = None
|
transaction: dict, callback: Callable | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.add_v1_account_transaction(transaction, callback)
|
return _baplus.add_v1_account_transaction(transaction, callback)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def game_service_has_leaderboard(game: str, config: str) -> bool:
|
def game_service_has_leaderboard(game: str, config: str) -> bool:
|
||||||
"""(internal)
|
"""Given a game and config string, returns whether there is a
|
||||||
|
leaderboard for it on the game service.
|
||||||
|
|
||||||
Given a game and config string, returns whether there is a leaderboard
|
:meta private:
|
||||||
for it on the game service.
|
|
||||||
"""
|
"""
|
||||||
return _baplus.game_service_has_leaderboard(game, config)
|
return _baplus.game_service_has_leaderboard(game, config)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_master_server_address(source: int = -1, version: int = 1) -> str:
|
def get_master_server_address(source: int = -1, version: int = 1) -> str:
|
||||||
"""(internal)
|
"""Return the address of the master server.
|
||||||
|
|
||||||
Return the address of the master server.
|
:meta private:
|
||||||
"""
|
"""
|
||||||
return _baplus.get_master_server_address(source, version)
|
return _baplus.get_master_server_address(source, version)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_classic_news_show() -> str:
|
def get_classic_news_show() -> str:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.get_classic_news_show()
|
return _baplus.get_classic_news_show()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_price(item: str) -> str | None:
|
def get_price(item: str) -> str | None:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.get_price(item)
|
return _baplus.get_price(item)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_v1_account_product_purchased(item: str) -> bool:
|
def get_v1_account_product_purchased(item: str) -> bool:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.get_v1_account_product_purchased(item)
|
return _baplus.get_v1_account_product_purchased(item)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_v1_account_product_purchases_state() -> int:
|
def get_v1_account_product_purchases_state() -> int:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.get_v1_account_product_purchases_state()
|
return _baplus.get_v1_account_product_purchases_state()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_v1_account_display_string(full: bool = True) -> str:
|
def get_v1_account_display_string(full: bool = True) -> str:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.get_v1_account_display_string(full)
|
return _baplus.get_v1_account_display_string(full)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_v1_account_misc_read_val(name: str, default_value: Any) -> Any:
|
def get_v1_account_misc_read_val(name: str, default_value: Any) -> Any:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.get_v1_account_misc_read_val(name, default_value)
|
return _baplus.get_v1_account_misc_read_val(name, default_value)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_v1_account_misc_read_val_2(name: str, default_value: Any) -> Any:
|
def get_v1_account_misc_read_val_2(name: str, default_value: Any) -> Any:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.get_v1_account_misc_read_val_2(name, default_value)
|
return _baplus.get_v1_account_misc_read_val_2(name, default_value)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_v1_account_misc_val(name: str, default_value: Any) -> Any:
|
def get_v1_account_misc_val(name: str, default_value: Any) -> Any:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.get_v1_account_misc_val(name, default_value)
|
return _baplus.get_v1_account_misc_val(name, default_value)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_v1_account_name() -> str:
|
def get_v1_account_name() -> str:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.get_v1_account_name()
|
return _baplus.get_v1_account_name()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_v1_account_public_login_id() -> str | None:
|
def get_v1_account_public_login_id() -> str | None:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.get_v1_account_public_login_id()
|
return _baplus.get_v1_account_public_login_id()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_v1_account_state() -> str:
|
def get_v1_account_state() -> str:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.get_v1_account_state()
|
return _baplus.get_v1_account_state()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_v1_account_state_num() -> int:
|
def get_v1_account_state_num() -> int:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.get_v1_account_state_num()
|
return _baplus.get_v1_account_state_num()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_v1_account_ticket_count() -> int:
|
def get_v1_account_ticket_count() -> int:
|
||||||
"""(internal)
|
"""Return the number of tickets for the current account.
|
||||||
|
|
||||||
Return the number of tickets for the current account.
|
:meta private:
|
||||||
"""
|
"""
|
||||||
return _baplus.get_v1_account_ticket_count()
|
return _baplus.get_v1_account_ticket_count()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_v1_account_type() -> str:
|
def get_v1_account_type() -> str:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.get_v1_account_type()
|
return _baplus.get_v1_account_type()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_v2_fleet() -> str:
|
def get_v2_fleet() -> str:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.get_v2_fleet()
|
return _baplus.get_v2_fleet()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def have_outstanding_v1_account_transactions() -> bool:
|
def have_outstanding_v1_account_transactions() -> bool:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.have_outstanding_v1_account_transactions()
|
return _baplus.have_outstanding_v1_account_transactions()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def in_game_purchase(item: str, price: int) -> None:
|
def in_game_purchase(item: str, price: int) -> None:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.in_game_purchase(item, price)
|
return _baplus.in_game_purchase(item, price)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_blessed() -> bool:
|
def is_blessed() -> bool:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.is_blessed()
|
return _baplus.is_blessed()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mark_config_dirty() -> None:
|
def mark_config_dirty() -> None:
|
||||||
"""(internal)
|
""":meta private:"""
|
||||||
|
|
||||||
Category: General Utility Functions
|
|
||||||
"""
|
|
||||||
return _baplus.mark_config_dirty()
|
return _baplus.mark_config_dirty()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def power_ranking_query(callback: Callable, season: Any = None) -> None:
|
def power_ranking_query(callback: Callable, season: Any = None) -> None:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.power_ranking_query(callback, season)
|
return _baplus.power_ranking_query(callback, season)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def purchase(item: str) -> None:
|
def purchase(item: str) -> None:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.purchase(item)
|
return _baplus.purchase(item)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def report_achievement(
|
def report_achievement(
|
||||||
achievement: str, pass_to_account: bool = True
|
achievement: str, pass_to_account: bool = True
|
||||||
) -> None:
|
) -> None:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.report_achievement(achievement, pass_to_account)
|
return _baplus.report_achievement(achievement, pass_to_account)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def reset_achievements() -> None:
|
def reset_achievements() -> None:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.reset_achievements()
|
return _baplus.reset_achievements()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def restore_purchases() -> None:
|
def restore_purchases() -> None:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.restore_purchases()
|
return _baplus.restore_purchases()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def run_v1_account_transactions() -> None:
|
def run_v1_account_transactions() -> None:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.run_v1_account_transactions()
|
return _baplus.run_v1_account_transactions()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def sign_in_v1(account_type: str) -> None:
|
def sign_in_v1(account_type: str) -> None:
|
||||||
"""(internal)
|
""":meta private:"""
|
||||||
|
|
||||||
Category: General Utility Functions
|
|
||||||
"""
|
|
||||||
return _baplus.sign_in_v1(account_type)
|
return _baplus.sign_in_v1(account_type)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def sign_out_v1(v2_embedded: bool = False) -> None:
|
def sign_out_v1(v2_embedded: bool = False) -> None:
|
||||||
"""(internal)
|
""":meta private:"""
|
||||||
|
|
||||||
Category: General Utility Functions
|
|
||||||
"""
|
|
||||||
return _baplus.sign_out_v1(v2_embedded)
|
return _baplus.sign_out_v1(v2_embedded)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -228,12 +220,14 @@ class PlusAppSubsystem(AppSubsystem):
|
||||||
campaign: str | None = None,
|
campaign: str | None = None,
|
||||||
level: str | None = None,
|
level: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""(internal)
|
"""Submit a score to the server.
|
||||||
|
|
||||||
Submit a score to the server; callback will be called with the results.
|
Callback will be called with the results. As a courtesy, please
|
||||||
As a courtesy, please don't send fake scores to the server. I'd prefer
|
don't send fake scores to the server. I'd prefer to devote my
|
||||||
to devote my time to improving the game instead of trying to make the
|
time to improving the game instead of trying to make the score
|
||||||
score server more mischief-proof.
|
server more mischief-proof.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
"""
|
"""
|
||||||
return _baplus.submit_score(
|
return _baplus.submit_score(
|
||||||
game,
|
game,
|
||||||
|
|
@ -252,41 +246,59 @@ class PlusAppSubsystem(AppSubsystem):
|
||||||
def tournament_query(
|
def tournament_query(
|
||||||
callback: Callable[[dict | None], None], args: dict
|
callback: Callable[[dict | None], None], args: dict
|
||||||
) -> None:
|
) -> None:
|
||||||
"""(internal)"""
|
""":meta private:"""
|
||||||
return _baplus.tournament_query(callback, args)
|
return _baplus.tournament_query(callback, args)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def supports_purchases() -> bool:
|
def supports_purchases() -> bool:
|
||||||
"""Does this platform support in-app-purchases?"""
|
"""Does this platform support in-app-purchases?
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
return _baplus.supports_purchases()
|
return _baplus.supports_purchases()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def have_incentivized_ad() -> bool:
|
def have_incentivized_ad() -> bool:
|
||||||
"""Is an incentivized ad available?"""
|
"""Is an incentivized ad available?
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
return _baplus.have_incentivized_ad()
|
return _baplus.have_incentivized_ad()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def has_video_ads() -> bool:
|
def has_video_ads() -> bool:
|
||||||
"""Are video ads available?"""
|
"""Are video ads available?
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
return _baplus.has_video_ads()
|
return _baplus.has_video_ads()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def can_show_ad() -> bool:
|
def can_show_ad() -> bool:
|
||||||
"""Can we show an ad?"""
|
"""Can we show an ad?
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
return _baplus.can_show_ad()
|
return _baplus.can_show_ad()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def show_ad(
|
def show_ad(
|
||||||
purpose: str, on_completion_call: Callable[[], None] | None = None
|
purpose: str, on_completion_call: Callable[[], None] | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Show an ad."""
|
"""Show an ad.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
_baplus.show_ad(purpose, on_completion_call)
|
_baplus.show_ad(purpose, on_completion_call)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def show_ad_2(
|
def show_ad_2(
|
||||||
purpose: str, on_completion_call: Callable[[bool], None] | None = None
|
purpose: str, on_completion_call: Callable[[bool], None] | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Show an ad."""
|
"""Show an ad.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
_baplus.show_ad_2(purpose, on_completion_call)
|
_baplus.show_ad_2(purpose, on_completion_call)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -295,5 +307,8 @@ class PlusAppSubsystem(AppSubsystem):
|
||||||
game: str | None = None,
|
game: str | None = None,
|
||||||
game_version: str | None = None,
|
game_version: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Show game-service provided UI."""
|
"""Show game-service provided UI.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
_baplus.show_game_service_ui(show, game, game_version)
|
_baplus.show_game_service_ui(show, game, game_version)
|
||||||
|
|
|
||||||
56
dist/ba_data/python/baplus/_cloud.py
vendored
56
dist/ba_data/python/baplus/_cloud.py
vendored
|
|
@ -24,7 +24,12 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class CloudSubsystem(babase.AppSubsystem):
|
class CloudSubsystem(babase.AppSubsystem):
|
||||||
"""Manages communication with cloud components."""
|
"""Manages communication with cloud components.
|
||||||
|
|
||||||
|
Access the shared single instance of this class via the
|
||||||
|
:attr:`~baplus.PlusAppSubsystem.cloud` attr on the
|
||||||
|
:class:`~baplus.PlusAppSubsystem` class.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
@ -34,19 +39,25 @@ class CloudSubsystem(babase.AppSubsystem):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def connected(self) -> bool:
|
def connected(self) -> bool:
|
||||||
"""Property equivalent of CloudSubsystem.is_connected()."""
|
"""Whether a connection to the cloud is present.
|
||||||
return self.is_connected()
|
|
||||||
|
|
||||||
def is_connected(self) -> bool:
|
|
||||||
"""Return whether a connection to the cloud is present.
|
|
||||||
|
|
||||||
This is a good indicator (though not for certain) that sending
|
This is a good indicator (though not for certain) that sending
|
||||||
messages will succeed.
|
messages will succeed.
|
||||||
"""
|
"""
|
||||||
return False # Needs to be overridden
|
return self.is_connected()
|
||||||
|
|
||||||
|
def is_connected(self) -> bool:
|
||||||
|
"""Implementation for connected attr.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
def on_connectivity_changed(self, connected: bool) -> None:
|
def on_connectivity_changed(self, connected: bool) -> None:
|
||||||
"""Called when cloud connectivity state changes."""
|
"""Called when cloud connectivity state changes.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
babase.balog.debug('Connectivity is now %s.', connected)
|
babase.balog.debug('Connectivity is now %s.', connected)
|
||||||
|
|
||||||
plus = babase.app.plus
|
plus = babase.app.plus
|
||||||
|
|
@ -172,6 +183,24 @@ class CloudSubsystem(babase.AppSubsystem):
|
||||||
],
|
],
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def send_message_cb(
|
||||||
|
self,
|
||||||
|
msg: bacommon.cloud.SecureDataCheckMessage,
|
||||||
|
on_response: Callable[
|
||||||
|
[bacommon.cloud.SecureDataCheckResponse | Exception], None
|
||||||
|
],
|
||||||
|
) -> None: ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def send_message_cb(
|
||||||
|
self,
|
||||||
|
msg: bacommon.cloud.SecureDataCheckerRequest,
|
||||||
|
on_response: Callable[
|
||||||
|
[bacommon.cloud.SecureDataCheckerResponse | Exception], None
|
||||||
|
],
|
||||||
|
) -> None: ...
|
||||||
|
|
||||||
def send_message_cb(
|
def send_message_cb(
|
||||||
self,
|
self,
|
||||||
msg: Message,
|
msg: Message,
|
||||||
|
|
@ -179,7 +208,7 @@ class CloudSubsystem(babase.AppSubsystem):
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Asynchronously send a message to the cloud from the logic thread.
|
"""Asynchronously send a message to the cloud from the logic thread.
|
||||||
|
|
||||||
The provided on_response call will be run in the logic thread
|
The provided ``on_response`` call will be run in the logic thread
|
||||||
and passed either the response or the error that occurred.
|
and passed either the response or the error that occurred.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
|
|
@ -221,7 +250,7 @@ class CloudSubsystem(babase.AppSubsystem):
|
||||||
) -> bacommon.cloud.TestResponse: ...
|
) -> bacommon.cloud.TestResponse: ...
|
||||||
|
|
||||||
async def send_message_async(self, msg: Message) -> Response | None:
|
async def send_message_async(self, msg: Message) -> Response | None:
|
||||||
"""Synchronously send a message to the cloud.
|
"""Asynchronously send a message to the cloud.
|
||||||
|
|
||||||
Must be called from the logic thread.
|
Must be called from the logic thread.
|
||||||
"""
|
"""
|
||||||
|
|
@ -232,7 +261,10 @@ class CloudSubsystem(babase.AppSubsystem):
|
||||||
def subscribe_test(
|
def subscribe_test(
|
||||||
self, updatecall: Callable[[int | None], None]
|
self, updatecall: Callable[[int | None], None]
|
||||||
) -> babase.CloudSubscription:
|
) -> babase.CloudSubscription:
|
||||||
"""Subscribe to some test data."""
|
"""Subscribe to some test data.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
'Cloud functionality is not present in this build.'
|
'Cloud functionality is not present in this build.'
|
||||||
)
|
)
|
||||||
|
|
@ -250,6 +282,8 @@ class CloudSubsystem(babase.AppSubsystem):
|
||||||
"""Unsubscribe from some subscription.
|
"""Unsubscribe from some subscription.
|
||||||
|
|
||||||
Do not call this manually; it is called by CloudSubscription.
|
Do not call this manually; it is called by CloudSubscription.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
'Cloud functionality is not present in this build.'
|
'Cloud functionality is not present in this build.'
|
||||||
|
|
|
||||||
11
dist/ba_data/python/bascenev1/__init__.py
vendored
11
dist/ba_data/python/bascenev1/__init__.py
vendored
|
|
@ -18,12 +18,15 @@ import logging
|
||||||
|
|
||||||
from efro.util import set_canonical_module_names
|
from efro.util import set_canonical_module_names
|
||||||
from babase import (
|
from babase import (
|
||||||
|
ActivityNotFoundError,
|
||||||
add_clean_frame_callback,
|
add_clean_frame_callback,
|
||||||
app,
|
app,
|
||||||
|
App,
|
||||||
AppIntent,
|
AppIntent,
|
||||||
AppIntentDefault,
|
AppIntentDefault,
|
||||||
AppIntentExec,
|
AppIntentExec,
|
||||||
AppMode,
|
AppMode,
|
||||||
|
AppState,
|
||||||
apptime,
|
apptime,
|
||||||
AppTime,
|
AppTime,
|
||||||
apptimer,
|
apptimer,
|
||||||
|
|
@ -52,6 +55,7 @@ from babase import (
|
||||||
safecolor,
|
safecolor,
|
||||||
screenmessage,
|
screenmessage,
|
||||||
set_analytics_screen,
|
set_analytics_screen,
|
||||||
|
SessionNotFoundError,
|
||||||
storagename,
|
storagename,
|
||||||
timestring,
|
timestring,
|
||||||
UIScale,
|
UIScale,
|
||||||
|
|
@ -164,7 +168,7 @@ from bascenev1._dependency import (
|
||||||
from bascenev1._dualteamsession import DualTeamSession
|
from bascenev1._dualteamsession import DualTeamSession
|
||||||
from bascenev1._freeforallsession import FreeForAllSession
|
from bascenev1._freeforallsession import FreeForAllSession
|
||||||
from bascenev1._gameactivity import GameActivity
|
from bascenev1._gameactivity import GameActivity
|
||||||
from bascenev1._gameresults import GameResults
|
from bascenev1._gameresults import GameResults, WinnerGroup
|
||||||
from bascenev1._gameutils import (
|
from bascenev1._gameutils import (
|
||||||
animate,
|
animate,
|
||||||
animate_array,
|
animate_array,
|
||||||
|
|
@ -246,15 +250,18 @@ from bascenev1._teamgame import TeamGameActivity
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'Activity',
|
'Activity',
|
||||||
'ActivityData',
|
'ActivityData',
|
||||||
|
'ActivityNotFoundError',
|
||||||
'Actor',
|
'Actor',
|
||||||
'animate',
|
'animate',
|
||||||
'animate_array',
|
'animate_array',
|
||||||
'add_clean_frame_callback',
|
'add_clean_frame_callback',
|
||||||
'app',
|
'app',
|
||||||
|
'App',
|
||||||
'AppIntent',
|
'AppIntent',
|
||||||
'AppIntentDefault',
|
'AppIntentDefault',
|
||||||
'AppIntentExec',
|
'AppIntentExec',
|
||||||
'AppMode',
|
'AppMode',
|
||||||
|
'AppState',
|
||||||
'AppTime',
|
'AppTime',
|
||||||
'apptime',
|
'apptime',
|
||||||
'apptimer',
|
'apptimer',
|
||||||
|
|
@ -414,6 +421,7 @@ __all__ = [
|
||||||
'ScoreConfig',
|
'ScoreConfig',
|
||||||
'ScoreScreenActivity',
|
'ScoreScreenActivity',
|
||||||
'ScoreType',
|
'ScoreType',
|
||||||
|
'SessionNotFoundError',
|
||||||
'broadcastmessage',
|
'broadcastmessage',
|
||||||
'Session',
|
'Session',
|
||||||
'SessionData',
|
'SessionData',
|
||||||
|
|
@ -462,6 +470,7 @@ __all__ = [
|
||||||
'unlock_all_input',
|
'unlock_all_input',
|
||||||
'Vec3',
|
'Vec3',
|
||||||
'WeakCall',
|
'WeakCall',
|
||||||
|
'WinnerGroup',
|
||||||
]
|
]
|
||||||
|
|
||||||
# We want stuff here to show up as bascenev1.Foo instead of
|
# We want stuff here to show up as bascenev1.Foo instead of
|
||||||
|
|
|
||||||
244
dist/ba_data/python/bascenev1/_activity.py
vendored
244
dist/ba_data/python/bascenev1/_activity.py
vendored
|
|
@ -23,99 +23,98 @@ TeamT = TypeVar('TeamT', bound=Team)
|
||||||
|
|
||||||
|
|
||||||
class Activity(DependencyComponent, Generic[PlayerT, TeamT]):
|
class Activity(DependencyComponent, Generic[PlayerT, TeamT]):
|
||||||
"""Units of execution wrangled by a bascenev1.Session.
|
"""Units of execution wrangled by a :class:`bascenev1.Session`.
|
||||||
|
|
||||||
Category: Gameplay Classes
|
Examples of activities include games, score-screens, cutscenes, etc.
|
||||||
|
A :class:`bascenev1.Session` has one 'current' activity at any time,
|
||||||
Examples of Activities include games, score-screens, cutscenes, etc.
|
though their existence can overlap during transitions.
|
||||||
A bascenev1.Session has one 'current' Activity at any time, though
|
|
||||||
their existence can overlap during transitions.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# pylint: disable=too-many-public-methods
|
# pylint: disable=too-many-public-methods
|
||||||
|
|
||||||
|
#: The settings dict passed in when the activity was made. This
|
||||||
|
#: attribute is deprecated and should be avoided when possible;
|
||||||
|
#: activities should pull all values they need from the ``settings``
|
||||||
|
#: arg passed to the activity's __init__ call.
|
||||||
settings_raw: dict[str, Any]
|
settings_raw: dict[str, Any]
|
||||||
"""The settings dict passed in when the activity was made.
|
|
||||||
This attribute is deprecated and should be avoided when possible;
|
|
||||||
activities should pull all values they need from the 'settings' arg
|
|
||||||
passed to the Activity __init__ call."""
|
|
||||||
|
|
||||||
|
#: The list of teams in the activity. This gets populated just before
|
||||||
|
#: on_begin() is called and is updated automatically as players join
|
||||||
|
#: or leave the game. (at least in free-for-all mode where every
|
||||||
|
#: player gets their own team; in teams mode there are always 2 teams
|
||||||
|
#: regardless of the player count).
|
||||||
teams: list[TeamT]
|
teams: list[TeamT]
|
||||||
"""The list of bascenev1.Team-s in the Activity. This gets populated just
|
|
||||||
before on_begin() is called and is updated automatically as players
|
|
||||||
join or leave the game. (at least in free-for-all mode where every
|
|
||||||
player gets their own team; in teams mode there are always 2 teams
|
|
||||||
regardless of the player count)."""
|
|
||||||
|
|
||||||
|
#: The list of players in the activity. This gets populated just
|
||||||
|
#: before :meth:`~bascenev1.Activity.on_begin()` is called and is
|
||||||
|
#: updated automatically as players join or leave the game.
|
||||||
players: list[PlayerT]
|
players: list[PlayerT]
|
||||||
"""The list of bascenev1.Player-s in the Activity. This gets populated
|
|
||||||
just before on_begin() is called and is updated automatically as
|
|
||||||
players join or leave the game."""
|
|
||||||
|
|
||||||
|
#: Whether to print every time a player dies. This can be pertinent
|
||||||
|
#: in games such as Death-Match but can be annoying in games where it
|
||||||
|
#: doesn't matter.
|
||||||
announce_player_deaths = False
|
announce_player_deaths = False
|
||||||
"""Whether to print every time a player dies. This can be pertinent
|
|
||||||
in games such as Death-Match but can be annoying in games where it
|
|
||||||
doesn't matter."""
|
|
||||||
|
|
||||||
|
#: Joining activities are for waiting for initial player joins. They
|
||||||
|
#: are treated slightly differently than regular activities, mainly
|
||||||
|
#: in that all players are passed to the activity at once instead of
|
||||||
|
#: as each joins.
|
||||||
is_joining_activity = False
|
is_joining_activity = False
|
||||||
"""Joining activities are for waiting for initial player joins.
|
|
||||||
They are treated slightly differently than regular activities,
|
|
||||||
mainly in that all players are passed to the activity at once
|
|
||||||
instead of as each joins."""
|
|
||||||
|
|
||||||
|
#: Whether scene-time should still progress when in menus/etc.
|
||||||
allow_pausing = False
|
allow_pausing = False
|
||||||
"""Whether game-time should still progress when in menus/etc."""
|
|
||||||
|
|
||||||
|
#: Whether idle players can potentially be kicked (should not happen
|
||||||
|
#: in menus/etc).
|
||||||
allow_kick_idle_players = True
|
allow_kick_idle_players = True
|
||||||
"""Whether idle players can potentially be kicked (should not happen in
|
|
||||||
menus/etc)."""
|
|
||||||
|
|
||||||
|
#: In vr mode, this determines whether overlay nodes (text, images,
|
||||||
|
#: etc) are created at a fixed position in space or one that moves
|
||||||
|
#: based on the current map. Generally this should be on for games
|
||||||
|
#: and off for transitions/score-screens/etc. that persist between
|
||||||
|
#: maps.
|
||||||
use_fixed_vr_overlay = False
|
use_fixed_vr_overlay = False
|
||||||
"""In vr mode, this determines whether overlay nodes (text, images, etc)
|
|
||||||
are created at a fixed position in space or one that moves based on
|
|
||||||
the current map. Generally this should be on for games and off for
|
|
||||||
transitions/score-screens/etc. that persist between maps."""
|
|
||||||
|
|
||||||
|
#: If True, runs in slow motion and turns down sound pitch.
|
||||||
slow_motion = False
|
slow_motion = False
|
||||||
"""If True, runs in slow motion and turns down sound pitch."""
|
|
||||||
|
|
||||||
|
#: Set this to True to inherit slow motion setting from previous
|
||||||
|
#: activity (useful for transitions to avoid hitches).
|
||||||
inherits_slow_motion = False
|
inherits_slow_motion = False
|
||||||
"""Set this to True to inherit slow motion setting from previous
|
|
||||||
activity (useful for transitions to avoid hitches)."""
|
|
||||||
|
|
||||||
|
#: Set this to True to keep playing the music from the previous
|
||||||
|
#: activity (without even restarting it).
|
||||||
inherits_music = False
|
inherits_music = False
|
||||||
"""Set this to True to keep playing the music from the previous activity
|
|
||||||
(without even restarting it)."""
|
|
||||||
|
|
||||||
|
#: Set this to true to inherit VR camera offsets from the previous
|
||||||
|
#: activity (useful for preventing sporadic camera movement during
|
||||||
|
#: transitions).
|
||||||
inherits_vr_camera_offset = False
|
inherits_vr_camera_offset = False
|
||||||
"""Set this to true to inherit VR camera offsets from the previous
|
|
||||||
activity (useful for preventing sporadic camera movement
|
|
||||||
during transitions)."""
|
|
||||||
|
|
||||||
|
#: Set this to true to inherit (non-fixed) VR overlay positioning
|
||||||
|
#: from the previous activity (useful for prevent sporadic overlay
|
||||||
|
#: jostling during transitions).
|
||||||
inherits_vr_overlay_center = False
|
inherits_vr_overlay_center = False
|
||||||
"""Set this to true to inherit (non-fixed) VR overlay positioning from
|
|
||||||
the previous activity (useful for prevent sporadic overlay jostling
|
|
||||||
during transitions)."""
|
|
||||||
|
|
||||||
|
#: Set this to true to inherit screen tint/vignette colors from the
|
||||||
|
#: previous activity (useful to prevent sudden color changes during
|
||||||
|
#: transitions).
|
||||||
inherits_tint = False
|
inherits_tint = False
|
||||||
"""Set this to true to inherit screen tint/vignette colors from the
|
|
||||||
previous activity (useful to prevent sudden color changes during
|
|
||||||
transitions)."""
|
|
||||||
|
|
||||||
|
#: Whether players should be allowed to join in the middle of this
|
||||||
|
#: activity. Note that a :class:`bascenev1.Session` may not allow
|
||||||
|
#: mid-activity-joins even if the activity says it is ok.
|
||||||
allow_mid_activity_joins: bool = True
|
allow_mid_activity_joins: bool = True
|
||||||
"""Whether players should be allowed to join in the middle of this
|
|
||||||
activity. Note that Sessions may not allow mid-activity-joins even
|
|
||||||
if the activity says its ok."""
|
|
||||||
|
|
||||||
|
#: If the activity fades or transitions in, it should set the length
|
||||||
|
#: of time here so that previous activities will be kept alive for
|
||||||
|
#: that long (avoiding 'holes' in the screen) This value is given in
|
||||||
|
#: real-time seconds.
|
||||||
transition_time = 0.0
|
transition_time = 0.0
|
||||||
"""If the activity fades or transitions in, it should set the length of
|
|
||||||
time here so that previous activities will be kept alive for that
|
|
||||||
long (avoiding 'holes' in the screen)
|
|
||||||
This value is given in real-time seconds."""
|
|
||||||
|
|
||||||
|
#: Is it ok to show an ad after this activity ends before showing the
|
||||||
|
#: next activity?
|
||||||
can_show_ad_on_death = False
|
can_show_ad_on_death = False
|
||||||
"""Is it ok to show an ad after this activity ends before showing
|
|
||||||
the next activity?"""
|
|
||||||
|
|
||||||
def __init__(self, settings: dict):
|
def __init__(self, settings: dict):
|
||||||
"""Creates an Activity in the current bascenev1.Session.
|
"""Creates an Activity in the current bascenev1.Session.
|
||||||
|
|
@ -149,7 +148,7 @@ class Activity(DependencyComponent, Generic[PlayerT, TeamT]):
|
||||||
|
|
||||||
self._session = weakref.ref(_bascenev1.getsession())
|
self._session = weakref.ref(_bascenev1.getsession())
|
||||||
|
|
||||||
# Preloaded data for actors, maps, etc; indexed by type.
|
#: Preloaded data for actors, maps, etc; indexed by type.
|
||||||
self.preloads: dict[type, Any] = {}
|
self.preloads: dict[type, Any] = {}
|
||||||
|
|
||||||
# Hopefully can eventually kill this; activities should
|
# Hopefully can eventually kill this; activities should
|
||||||
|
|
@ -208,8 +207,9 @@ class Activity(DependencyComponent, Generic[PlayerT, TeamT]):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def globalsnode(self) -> bascenev1.Node:
|
def globalsnode(self) -> bascenev1.Node:
|
||||||
"""The 'globals' bascenev1.Node for the activity. This contains various
|
"""The 'globals' :class:`~bascenev1.Node` for the activity.
|
||||||
global controls and values.
|
|
||||||
|
This contains various global controls and values.
|
||||||
"""
|
"""
|
||||||
node = self._globalsnode
|
node = self._globalsnode
|
||||||
if not node:
|
if not node:
|
||||||
|
|
@ -221,7 +221,7 @@ class Activity(DependencyComponent, Generic[PlayerT, TeamT]):
|
||||||
"""The stats instance accessible while the activity is running.
|
"""The stats instance accessible while the activity is running.
|
||||||
|
|
||||||
If access is attempted before or after, raises a
|
If access is attempted before or after, raises a
|
||||||
bascenev1.NotFoundError.
|
:class:`~bascenev1.NotFoundError`.
|
||||||
"""
|
"""
|
||||||
if self._stats is None:
|
if self._stats is None:
|
||||||
raise babase.NotFoundError()
|
raise babase.NotFoundError()
|
||||||
|
|
@ -260,22 +260,24 @@ class Activity(DependencyComponent, Generic[PlayerT, TeamT]):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def playertype(self) -> type[PlayerT]:
|
def playertype(self) -> type[PlayerT]:
|
||||||
"""The type of bascenev1.Player this Activity is using."""
|
"""The :class:`~bascenev1.Player` subclass this activity uses."""
|
||||||
return self._playertype
|
return self._playertype
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def teamtype(self) -> type[TeamT]:
|
def teamtype(self) -> type[TeamT]:
|
||||||
"""The type of bascenev1.Team this Activity is using."""
|
"""The :class:`~bascenev1.Team` subclass this activity uses."""
|
||||||
return self._teamtype
|
return self._teamtype
|
||||||
|
|
||||||
def set_has_ended(self, val: bool) -> None:
|
def set_has_ended(self, val: bool) -> None:
|
||||||
"""(internal)"""
|
"""Internal - used by session.
|
||||||
|
|
||||||
|
:meta private:"""
|
||||||
self._has_ended = val
|
self._has_ended = val
|
||||||
|
|
||||||
def expire(self) -> None:
|
def expire(self) -> None:
|
||||||
"""Begin the process of tearing down the activity.
|
"""Internal; Begin the process of tearing down the activity.
|
||||||
|
|
||||||
(internal)
|
:meta private:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Create an app-timer that watches a weak-ref of this activity
|
# Create an app-timer that watches a weak-ref of this activity
|
||||||
|
|
@ -304,11 +306,12 @@ class Activity(DependencyComponent, Generic[PlayerT, TeamT]):
|
||||||
)
|
)
|
||||||
|
|
||||||
def retain_actor(self, actor: bascenev1.Actor) -> None:
|
def retain_actor(self, actor: bascenev1.Actor) -> None:
|
||||||
"""Add a strong-reference to a bascenev1.Actor to this Activity.
|
"""Add a strong-ref to a :class:`bascenev1.Actor` to this activity.
|
||||||
|
|
||||||
The reference will be lazily released once bascenev1.Actor.exists()
|
The reference will be lazily released once
|
||||||
returns False for the Actor. The bascenev1.Actor.autoretain() method
|
:meth:`bascenev1.Actor.exists()` returns False for the actor.
|
||||||
is a convenient way to access this same functionality.
|
The :meth:`bascenev1.Actor.autoretain()` method is a convenient
|
||||||
|
way to access this same functionality.
|
||||||
"""
|
"""
|
||||||
if __debug__:
|
if __debug__:
|
||||||
from bascenev1._actor import Actor
|
from bascenev1._actor import Actor
|
||||||
|
|
@ -317,9 +320,9 @@ class Activity(DependencyComponent, Generic[PlayerT, TeamT]):
|
||||||
self._actor_refs.append(actor)
|
self._actor_refs.append(actor)
|
||||||
|
|
||||||
def add_actor_weak_ref(self, actor: bascenev1.Actor) -> None:
|
def add_actor_weak_ref(self, actor: bascenev1.Actor) -> None:
|
||||||
"""Add a weak-reference to a bascenev1.Actor to the bascenev1.Activity.
|
"""Add a weak-ref to a :class:`bascenev1.Actor` to the activity.
|
||||||
|
|
||||||
(called by the bascenev1.Actor base class)
|
(called by the :class:`bascenev1.Actor` base class)
|
||||||
"""
|
"""
|
||||||
if __debug__:
|
if __debug__:
|
||||||
from bascenev1._actor import Actor
|
from bascenev1._actor import Actor
|
||||||
|
|
@ -329,9 +332,10 @@ class Activity(DependencyComponent, Generic[PlayerT, TeamT]):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def session(self) -> bascenev1.Session:
|
def session(self) -> bascenev1.Session:
|
||||||
"""The bascenev1.Session this bascenev1.Activity belongs to.
|
"""The session this activity belongs to.
|
||||||
|
|
||||||
Raises a babase.SessionNotFoundError if the Session no longer exists.
|
Raises a :class:`~bascenev1.SessionNotFoundError` if the session
|
||||||
|
no longer exists.
|
||||||
"""
|
"""
|
||||||
session = self._session()
|
session = self._session()
|
||||||
if session is None:
|
if session is None:
|
||||||
|
|
@ -339,44 +343,44 @@ class Activity(DependencyComponent, Generic[PlayerT, TeamT]):
|
||||||
return session
|
return session
|
||||||
|
|
||||||
def on_player_join(self, player: PlayerT) -> None:
|
def on_player_join(self, player: PlayerT) -> None:
|
||||||
"""Called when a new bascenev1.Player has joined the Activity.
|
"""Called when a player joins the activity.
|
||||||
|
|
||||||
(including the initial set of Players)
|
(including the initial set of players)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def on_player_leave(self, player: PlayerT) -> None:
|
def on_player_leave(self, player: PlayerT) -> None:
|
||||||
"""Called when a bascenev1.Player is leaving the Activity."""
|
"""Called when a player is leaving the Activity."""
|
||||||
|
|
||||||
def on_team_join(self, team: TeamT) -> None:
|
def on_team_join(self, team: TeamT) -> None:
|
||||||
"""Called when a new bascenev1.Team joins the Activity.
|
"""Called when a new team joins the activity.
|
||||||
|
|
||||||
(including the initial set of Teams)
|
(including the initial set of teams)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def on_team_leave(self, team: TeamT) -> None:
|
def on_team_leave(self, team: TeamT) -> None:
|
||||||
"""Called when a bascenev1.Team leaves the Activity."""
|
"""Called when a team leaves the activity."""
|
||||||
|
|
||||||
def on_transition_in(self) -> None:
|
def on_transition_in(self) -> None:
|
||||||
"""Called when the Activity is first becoming visible.
|
"""Called when the activity is first becoming visible.
|
||||||
|
|
||||||
Upon this call, the Activity should fade in backgrounds,
|
Upon this call, the activity should fade in backgrounds, start
|
||||||
start playing music, etc. It does not yet have access to players
|
playing music, etc. It does not yet have access to players or
|
||||||
or teams, however. They remain owned by the previous Activity
|
teams, however. They remain owned by the previous activity up
|
||||||
up until bascenev1.Activity.on_begin() is called.
|
until :meth:`~bascenev1.Activity.on_begin()` is called.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def on_transition_out(self) -> None:
|
def on_transition_out(self) -> None:
|
||||||
"""Called when your activity begins transitioning out.
|
"""Called when your activity begins transitioning out.
|
||||||
|
|
||||||
Note that this may happen at any time even if bascenev1.Activity.end()
|
Note that this may happen at any time even if
|
||||||
has not been called.
|
:meth:`bascenev1.Activity.end()` has not been called.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def on_begin(self) -> None:
|
def on_begin(self) -> None:
|
||||||
"""Called once the previous Activity has finished transitioning out.
|
"""Called once the previous activity has finished transitioning out.
|
||||||
|
|
||||||
At this point the activity's initial players and teams are filled in
|
At this point the activity's initial players and teams are
|
||||||
and it should begin its actual game logic.
|
filled in and it should begin its actual game logic.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def handlemessage(self, msg: Any) -> Any:
|
def handlemessage(self, msg: Any) -> Any:
|
||||||
|
|
@ -385,11 +389,11 @@ class Activity(DependencyComponent, Generic[PlayerT, TeamT]):
|
||||||
return UNHANDLED
|
return UNHANDLED
|
||||||
|
|
||||||
def has_transitioned_in(self) -> bool:
|
def has_transitioned_in(self) -> bool:
|
||||||
"""Return whether bascenev1.Activity.on_transition_in() has run."""
|
"""Whether :meth:`~bascenev1.Activity.on_transition_in()` has run."""
|
||||||
return self._has_transitioned_in
|
return self._has_transitioned_in
|
||||||
|
|
||||||
def has_begun(self) -> bool:
|
def has_begun(self) -> bool:
|
||||||
"""Return whether bascenev1.Activity.on_begin() has run."""
|
"""Whether :meth:`~bascenev1.Activity.on_begin()` has run."""
|
||||||
return self._has_begun
|
return self._has_begun
|
||||||
|
|
||||||
def has_ended(self) -> bool:
|
def has_ended(self) -> bool:
|
||||||
|
|
@ -397,13 +401,13 @@ class Activity(DependencyComponent, Generic[PlayerT, TeamT]):
|
||||||
return self._has_ended
|
return self._has_ended
|
||||||
|
|
||||||
def is_transitioning_out(self) -> bool:
|
def is_transitioning_out(self) -> bool:
|
||||||
"""Return whether bascenev1.Activity.on_transition_out() has run."""
|
"""Whether :meth:`~bascenev1.Activity.on_transition_out()` has run."""
|
||||||
return self._transitioning_out
|
return self._transitioning_out
|
||||||
|
|
||||||
def transition_in(self, prev_globals: bascenev1.Node | None) -> None:
|
def transition_in(self, prev_globals: bascenev1.Node | None) -> None:
|
||||||
"""Called by Session to kick off transition-in.
|
"""Internal; called by session to kick off transition-in.
|
||||||
|
|
||||||
(internal)
|
:meta private:
|
||||||
"""
|
"""
|
||||||
assert not self._has_transitioned_in
|
assert not self._has_transitioned_in
|
||||||
self._has_transitioned_in = True
|
self._has_transitioned_in = True
|
||||||
|
|
@ -459,7 +463,10 @@ class Activity(DependencyComponent, Generic[PlayerT, TeamT]):
|
||||||
self._activity_data.make_foreground()
|
self._activity_data.make_foreground()
|
||||||
|
|
||||||
def transition_out(self) -> None:
|
def transition_out(self) -> None:
|
||||||
"""Called by the Session to start us transitioning out."""
|
"""Internal; called by session to start us transitioning out.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
assert not self._transitioning_out
|
assert not self._transitioning_out
|
||||||
self._transitioning_out = True
|
self._transitioning_out = True
|
||||||
with self.context:
|
with self.context:
|
||||||
|
|
@ -469,9 +476,9 @@ class Activity(DependencyComponent, Generic[PlayerT, TeamT]):
|
||||||
logging.exception('Error in on_transition_out for %s.', self)
|
logging.exception('Error in on_transition_out for %s.', self)
|
||||||
|
|
||||||
def begin(self, session: bascenev1.Session) -> None:
|
def begin(self, session: bascenev1.Session) -> None:
|
||||||
"""Begin the activity.
|
"""Internal; Begin the activity.
|
||||||
|
|
||||||
(internal)
|
:meta private:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
assert not self._has_begun
|
assert not self._has_begun
|
||||||
|
|
@ -499,45 +506,46 @@ class Activity(DependencyComponent, Generic[PlayerT, TeamT]):
|
||||||
def end(
|
def end(
|
||||||
self, results: Any = None, delay: float = 0.0, force: bool = False
|
self, results: Any = None, delay: float = 0.0, force: bool = False
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Commences Activity shutdown and delivers results to the Session.
|
"""Commence activity shutdown and delivers results to the session.
|
||||||
|
|
||||||
'delay' is the time delay before the Activity actually ends
|
'delay' is the time delay before the Activity actually ends (in
|
||||||
(in seconds). Further calls to end() will be ignored up until
|
seconds). Further end calls will be ignored up until this time,
|
||||||
this time, unless 'force' is True, in which case the new results
|
unless 'force' is True, in which case the new results will
|
||||||
will replace the old.
|
replace the old.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Ask the session to end us.
|
# Ask the session to end us.
|
||||||
self.session.end_activity(self, results, delay, force)
|
self.session.end_activity(self, results, delay, force)
|
||||||
|
|
||||||
def create_player(self, sessionplayer: bascenev1.SessionPlayer) -> PlayerT:
|
def create_player(self, sessionplayer: bascenev1.SessionPlayer) -> PlayerT:
|
||||||
"""Create the Player instance for this Activity.
|
"""Create a :class:`bascenev1.Player` instance for this activity.
|
||||||
|
|
||||||
Subclasses can override this if the activity's player class
|
Note that the player object should not be used at this point as
|
||||||
requires a custom constructor; otherwise it will be called with
|
it is not yet fully wired up; wait for
|
||||||
no args. Note that the player object should not be used at this
|
:meth:`bascenev1.Activity.on_player_join()` for that.
|
||||||
point as it is not yet fully wired up; wait for
|
|
||||||
bascenev1.Activity.on_player_join() for that.
|
|
||||||
"""
|
"""
|
||||||
del sessionplayer # Unused.
|
del sessionplayer # Unused.
|
||||||
player = self._playertype()
|
player = self._playertype()
|
||||||
return player
|
return player
|
||||||
|
|
||||||
def create_team(self, sessionteam: bascenev1.SessionTeam) -> TeamT:
|
def create_team(self, sessionteam: bascenev1.SessionTeam) -> TeamT:
|
||||||
"""Create the Team instance for this Activity.
|
"""Create a :class:`bascenev1.Team` instance for this activity.
|
||||||
|
|
||||||
Subclasses can override this if the activity's team class
|
Subclasses can override this if the activity's team class
|
||||||
requires a custom constructor; otherwise it will be called with
|
requires a custom constructor; otherwise it will be called with
|
||||||
no args. Note that the team object should not be used at this
|
no args. Note that the team object should not be used at this
|
||||||
point as it is not yet fully wired up; wait for on_team_join()
|
point as it is not yet fully wired up; wait for
|
||||||
for that.
|
:meth:`bascenev1.Activity.on_team_join()` for that.
|
||||||
"""
|
"""
|
||||||
del sessionteam # Unused.
|
del sessionteam # Unused.
|
||||||
team = self._teamtype()
|
team = self._teamtype()
|
||||||
return team
|
return team
|
||||||
|
|
||||||
def add_player(self, sessionplayer: bascenev1.SessionPlayer) -> None:
|
def add_player(self, sessionplayer: bascenev1.SessionPlayer) -> None:
|
||||||
"""(internal)"""
|
"""Internal
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
assert sessionplayer.sessionteam is not None
|
assert sessionplayer.sessionteam is not None
|
||||||
sessionplayer.resetinput()
|
sessionplayer.resetinput()
|
||||||
sessionteam = sessionplayer.sessionteam
|
sessionteam = sessionplayer.sessionteam
|
||||||
|
|
@ -565,7 +573,7 @@ class Activity(DependencyComponent, Generic[PlayerT, TeamT]):
|
||||||
logging.exception('Error in on_player_join for %s.', self)
|
logging.exception('Error in on_player_join for %s.', self)
|
||||||
|
|
||||||
def remove_player(self, sessionplayer: bascenev1.SessionPlayer) -> None:
|
def remove_player(self, sessionplayer: bascenev1.SessionPlayer) -> None:
|
||||||
"""Remove a player from the Activity while it is running.
|
"""Remove a player from the activity while it is running.
|
||||||
|
|
||||||
(internal)
|
(internal)
|
||||||
"""
|
"""
|
||||||
|
|
@ -584,10 +592,6 @@ class Activity(DependencyComponent, Generic[PlayerT, TeamT]):
|
||||||
self.players.remove(player)
|
self.players.remove(player)
|
||||||
assert player not in self.players
|
assert player not in self.players
|
||||||
|
|
||||||
# This should allow our bascenev1.Player instance to die.
|
|
||||||
# Complain if that doesn't happen.
|
|
||||||
# verify_object_death(player)
|
|
||||||
|
|
||||||
with self.context:
|
with self.context:
|
||||||
try:
|
try:
|
||||||
self.on_player_leave(player)
|
self.on_player_leave(player)
|
||||||
|
|
@ -608,9 +612,9 @@ class Activity(DependencyComponent, Generic[PlayerT, TeamT]):
|
||||||
self._players_that_left.append(weakref.ref(player))
|
self._players_that_left.append(weakref.ref(player))
|
||||||
|
|
||||||
def add_team(self, sessionteam: bascenev1.SessionTeam) -> None:
|
def add_team(self, sessionteam: bascenev1.SessionTeam) -> None:
|
||||||
"""Add a team to the Activity
|
"""Internal; Add a team to the activity
|
||||||
|
|
||||||
(internal)
|
:meta private:
|
||||||
"""
|
"""
|
||||||
assert not self.expired
|
assert not self.expired
|
||||||
|
|
||||||
|
|
@ -624,9 +628,9 @@ class Activity(DependencyComponent, Generic[PlayerT, TeamT]):
|
||||||
logging.exception('Error in on_team_join for %s.', self)
|
logging.exception('Error in on_team_join for %s.', self)
|
||||||
|
|
||||||
def remove_team(self, sessionteam: bascenev1.SessionTeam) -> None:
|
def remove_team(self, sessionteam: bascenev1.SessionTeam) -> None:
|
||||||
"""Remove a team from a Running Activity
|
"""Internal; remove a team from a running activity
|
||||||
|
|
||||||
(internal)
|
:meta private:
|
||||||
"""
|
"""
|
||||||
assert not self.expired
|
assert not self.expired
|
||||||
assert sessionteam.activityteam is not None
|
assert sessionteam.activityteam is not None
|
||||||
|
|
|
||||||
152
dist/ba_data/python/bascenev1/_actor.py
vendored
152
dist/ba_data/python/bascenev1/_actor.py
vendored
|
|
@ -27,53 +27,57 @@ ActorT = TypeVar('ActorT', bound='Actor')
|
||||||
|
|
||||||
|
|
||||||
class Actor:
|
class Actor:
|
||||||
"""High level logical entities in a bascenev1.Activity.
|
"""High level logical entities in an :class:`~bascenev1.Activity`.
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
Actors act as controllers, combining some number of
|
||||||
|
:class:`~bascenev1.Node`, :class:`~bascenev1.Texture`,
|
||||||
|
:class:`~bascenev1.Sound`, and other type objects into a high-level
|
||||||
|
cohesive unit.
|
||||||
|
|
||||||
Actors act as controllers, combining some number of Nodes, Textures,
|
Some example actors include the :class:`~bascenev1lib.actor.bomb.Bomb`,
|
||||||
Sounds, etc. into a high-level cohesive unit.
|
:class:`~bascenev1lib.actor.flag.Flag`, and
|
||||||
|
:class:`~bascenev1lib.actor.spaz.Spaz`, classes that live in the
|
||||||
|
:mod:`bascenev1lib.actor` package.
|
||||||
|
|
||||||
Some example actors include the Bomb, Flag, and Spaz classes that
|
One key feature of actors is that they generally 'die' (killing off
|
||||||
live in the bascenev1lib.actor.* modules.
|
or transitioning out their nodes) when the last Python reference to
|
||||||
|
them disappears, so you can use logic such as::
|
||||||
|
|
||||||
One key feature of Actors is that they generally 'die'
|
# Create a flag actor in our game activity (self):
|
||||||
(killing off or transitioning out their nodes) when the last Python
|
from bascenev1lib.actor.flag import Flag
|
||||||
reference to them disappears, so you can use logic such as:
|
|
||||||
|
|
||||||
##### Example
|
self.flag = Flag(position=(0, 10, 0))
|
||||||
>>> # Create a flag Actor in our game activity:
|
|
||||||
... from bascenev1lib.actor.flag import Flag
|
# Later, destroy the flag (provided nothing else is holding a
|
||||||
... self.flag = Flag(position=(0, 10, 0))
|
# reference to it). We could also just assign a new flag to this
|
||||||
...
|
# value. Either way, the old flag should disappear.
|
||||||
... # Later, destroy the flag.
|
self.flag = None
|
||||||
... # (provided nothing else is holding a reference to it)
|
|
||||||
... # We could also just assign a new flag to this value.
|
|
||||||
... # Either way, the old flag disappears.
|
|
||||||
... self.flag = None
|
|
||||||
|
|
||||||
This is in contrast to the behavior of the more low level
|
This is in contrast to the behavior of the more low level
|
||||||
bascenev1.Node, which is always explicitly created and destroyed
|
:class:`~bascenev1.Node` class, which is always explicitly created
|
||||||
and doesn't care how many Python references to it exist.
|
and destroyed and doesn't care how many Python references to it
|
||||||
|
exist.
|
||||||
|
|
||||||
Note, however, that you can use the bascenev1.Actor.autoretain() method
|
Note, however, that you can use the :meth:`~bascenev1.Actor.autoretain()`
|
||||||
if you want an Actor to stick around until explicitly killed
|
method if you want an actor to stick around until explicitly killed
|
||||||
regardless of references.
|
regardless of references.
|
||||||
|
|
||||||
Another key feature of bascenev1.Actor is its
|
Another key feature of actors is their
|
||||||
bascenev1.Actor.handlemessage() method, which takes a single arbitrary
|
:meth:`~bascenev1.Actor.handlemessage()` method, which takes a single
|
||||||
object as an argument. This provides a safe way to communicate between
|
arbitrary object as an argument. This provides a safe way to communicate
|
||||||
bascenev1.Actor, bascenev1.Activity, bascenev1.Session, and any other
|
between :class:`~bascenev1.Actor`, :class:`~bascenev1.Activity`,
|
||||||
class providing a handlemessage() method. The most universally handled
|
:class:`~bascenev1.Session`, and any other class providing a
|
||||||
message type for Actors is the bascenev1.DieMessage.
|
``handlemessage()`` method. The most universally handled
|
||||||
|
message type for actors is the :class:`~bascenev1.DieMessage`.
|
||||||
|
|
||||||
Another way to kill the flag from the example above:
|
Another way to kill the flag from the example above:
|
||||||
We can safely call this on any type with a 'handlemessage' method
|
We can safely call this on any type with a ``handlemessage`` method
|
||||||
(though its not guaranteed to always have a meaningful effect).
|
(though its not guaranteed to always have a meaningful effect).
|
||||||
In this case the Actor instance will still be around, but its
|
In this case the actor instance will still be around, but its
|
||||||
bascenev1.Actor.exists() and bascenev1.Actor.is_alive() methods will
|
:meth:`~bascenev1.Actor.exists()` and :meth:`~bascenev1.Actor.is_alive()`
|
||||||
both return False.
|
methods will both return False::
|
||||||
>>> self.flag.handlemessage(bascenev1.DieMessage())
|
|
||||||
|
self.flag.handlemessage(bascenev1.DieMessage())
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
|
@ -108,17 +112,17 @@ class Actor:
|
||||||
return UNHANDLED
|
return UNHANDLED
|
||||||
|
|
||||||
def autoretain(self: ActorT) -> ActorT:
|
def autoretain(self: ActorT) -> ActorT:
|
||||||
"""Keep this Actor alive without needing to hold a reference to it.
|
"""Keep this actor alive without needing to hold a reference to it.
|
||||||
|
|
||||||
This keeps the bascenev1.Actor in existence by storing a reference
|
This keeps the actor in existence by storing a reference to it
|
||||||
to it with the bascenev1.Activity it was created in. The reference
|
with the :class:`~bascenev1.Activity` it was created in. The
|
||||||
is lazily released once bascenev1.Actor.exists() returns False for
|
reference is lazily released once
|
||||||
it or when the Activity is set as expired. This can be a convenient
|
:meth:`~bascenev1.Actor.exists()` returns False for the actor or
|
||||||
alternative to storing references explicitly just to keep a
|
when the :class:`~bascenev1.Activity` is set as expired. This
|
||||||
bascenev1.Actor from dying.
|
can be a convenient alternative to storing references explicitly
|
||||||
For convenience, this method returns the bascenev1.Actor it is called
|
just to keep an actor from dying. For convenience, this method
|
||||||
with, enabling chained statements such as:
|
returns the actor it is called with, enabling chained statements
|
||||||
myflag = bascenev1.Flag().autoretain()
|
such as: ``myflag = bascenev1.Flag().autoretain()``
|
||||||
"""
|
"""
|
||||||
activity = self._activity()
|
activity = self._activity()
|
||||||
if activity is None:
|
if activity is None:
|
||||||
|
|
@ -127,43 +131,44 @@ class Actor:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def on_expire(self) -> None:
|
def on_expire(self) -> None:
|
||||||
"""Called for remaining `bascenev1.Actor`s when their activity dies.
|
"""Called for remaining actors when their activity dies.
|
||||||
|
|
||||||
Actors can use this opportunity to clear callbacks or other
|
Actors can use this opportunity to clear callbacks or other
|
||||||
references which have the potential of keeping the bascenev1.Activity
|
references which have the potential of keeping the
|
||||||
alive inadvertently (Activities can not exit cleanly while
|
:class:`~bascenev1.Activity` alive inadvertently (activities can
|
||||||
any Python references to them remain.)
|
not exit cleanly while any Python references to them remain.)
|
||||||
|
|
||||||
Once an actor is expired (see bascenev1.Actor.is_expired()) it should
|
Once an actor is expired (see :attr:`~bascenev1.Actor.expired`)
|
||||||
no longer perform any game-affecting operations (creating, modifying,
|
it should no longer perform any game-affecting operations
|
||||||
or deleting nodes, media, timers, etc.) Attempts to do so will
|
(creating, modifying, or deleting nodes, media, timers, etc.)
|
||||||
likely result in errors.
|
Attempts to do so will likely result in errors.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def expired(self) -> bool:
|
def expired(self) -> bool:
|
||||||
"""Whether the Actor is expired.
|
"""Whether the actor is expired.
|
||||||
|
|
||||||
(see bascenev1.Actor.on_expire())
|
(see :meth:`~bascenev1.Actor.on_expire()`)
|
||||||
"""
|
"""
|
||||||
activity = self.getactivity(doraise=False)
|
activity = self.getactivity(doraise=False)
|
||||||
return True if activity is None else activity.expired
|
return True if activity is None else activity.expired
|
||||||
|
|
||||||
def exists(self) -> bool:
|
def exists(self) -> bool:
|
||||||
"""Returns whether the Actor is still present in a meaningful way.
|
"""Returns whether the actor is still present in a meaningful way.
|
||||||
|
|
||||||
Note that a dying character should still return True here as long as
|
Note that a dying character should still return True here as long as
|
||||||
their corpse is visible; this is about presence, not being 'alive'
|
their corpse is visible; this is about presence, not being 'alive'
|
||||||
(see bascenev1.Actor.is_alive() for that).
|
(see :meth:`~bascenev1.Actor.is_alive()` for that).
|
||||||
|
|
||||||
If this returns False, it is assumed the Actor can be completely
|
If this returns False, it is assumed the actor can be completely
|
||||||
deleted without affecting the game; this call is often used
|
deleted without affecting the game; this call is often used when
|
||||||
when pruning lists of Actors, such as with bascenev1.Actor.autoretain()
|
pruning lists of actors, such as with
|
||||||
|
:meth:`bascenev1.Actor.autoretain()`
|
||||||
|
|
||||||
The default implementation of this method always return True.
|
The default implementation of this method always return True.
|
||||||
|
|
||||||
Note that the boolean operator for the Actor class calls this method,
|
Note that the boolean operator for the actor class calls this method,
|
||||||
so a simple "if myactor" test will conveniently do the right thing
|
so a simple ``if myactor`` test will conveniently do the right thing
|
||||||
even if myactor is set to None.
|
even if myactor is set to None.
|
||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
@ -173,22 +178,21 @@ class Actor:
|
||||||
return self.exists()
|
return self.exists()
|
||||||
|
|
||||||
def is_alive(self) -> bool:
|
def is_alive(self) -> bool:
|
||||||
"""Returns whether the Actor is 'alive'.
|
"""Returns whether the actor is 'alive'.
|
||||||
|
|
||||||
What this means is up to the Actor.
|
What this means is up to the actor. It is not a requirement for
|
||||||
It is not a requirement for Actors to be able to die;
|
actors to be able to die; just that they report whether they
|
||||||
just that they report whether they consider themselves
|
consider themselves to be alive or not. In cases where
|
||||||
to be alive or not. In cases where dead/alive is
|
dead/alive is irrelevant, True should be returned.
|
||||||
irrelevant, True should be returned.
|
|
||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def activity(self) -> bascenev1.Activity:
|
def activity(self) -> bascenev1.Activity:
|
||||||
"""The Activity this Actor was created in.
|
"""The activity this actor was created in.
|
||||||
|
|
||||||
Raises a bascenev1.ActivityNotFoundError if the Activity no longer
|
Raises a :class:`~bascenev1.ActivityNotFoundError` if the
|
||||||
exists.
|
activity no longer exists.
|
||||||
"""
|
"""
|
||||||
activity = self._activity()
|
activity = self._activity()
|
||||||
if activity is None:
|
if activity is None:
|
||||||
|
|
@ -208,11 +212,11 @@ class Actor:
|
||||||
) -> bascenev1.Activity | None: ...
|
) -> bascenev1.Activity | None: ...
|
||||||
|
|
||||||
def getactivity(self, doraise: bool = True) -> bascenev1.Activity | None:
|
def getactivity(self, doraise: bool = True) -> bascenev1.Activity | None:
|
||||||
"""Return the bascenev1.Activity this Actor is associated with.
|
"""Return the activity this actor is associated with.
|
||||||
|
|
||||||
If the Activity no longer exists, raises a
|
If the activity no longer exists, raises a
|
||||||
bascenev1.ActivityNotFoundError or returns None depending on whether
|
:class:`~bascenev1.ActivityNotFoundError` or returns None
|
||||||
'doraise' is True.
|
depending on whether ``doraise`` is True.
|
||||||
"""
|
"""
|
||||||
activity = self._activity()
|
activity = self._activity()
|
||||||
if activity is None and doraise:
|
if activity is None and doraise:
|
||||||
|
|
|
||||||
21
dist/ba_data/python/bascenev1/_campaign.py
vendored
21
dist/ba_data/python/bascenev1/_campaign.py
vendored
|
|
@ -19,10 +19,7 @@ def register_campaign(campaign: bascenev1.Campaign) -> None:
|
||||||
|
|
||||||
|
|
||||||
class Campaign:
|
class Campaign:
|
||||||
"""Represents a unique set or series of baclassic.Level-s.
|
"""Represents a unique set of :class:`~bascenev1.Level` instances."""
|
||||||
|
|
||||||
Category: **App Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
@ -39,18 +36,18 @@ class Campaign:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""The name of the Campaign."""
|
"""The name of the campaign."""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sequential(self) -> bool:
|
def sequential(self) -> bool:
|
||||||
"""Whether this Campaign's levels must be played in sequence."""
|
"""Whether this campaign's levels must be played in sequence."""
|
||||||
return self._sequential
|
return self._sequential
|
||||||
|
|
||||||
def addlevel(
|
def addlevel(
|
||||||
self, level: bascenev1.Level, index: int | None = None
|
self, level: bascenev1.Level, index: int | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Adds a baclassic.Level to the Campaign."""
|
"""Add a level to the campaign."""
|
||||||
if level.campaign is not None:
|
if level.campaign is not None:
|
||||||
raise RuntimeError('Level already belongs to a campaign.')
|
raise RuntimeError('Level already belongs to a campaign.')
|
||||||
level.set_campaign(self, len(self._levels))
|
level.set_campaign(self, len(self._levels))
|
||||||
|
|
@ -61,11 +58,11 @@ class Campaign:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def levels(self) -> list[bascenev1.Level]:
|
def levels(self) -> list[bascenev1.Level]:
|
||||||
"""The list of baclassic.Level-s in the Campaign."""
|
"""The list of levels in the campaign."""
|
||||||
return self._levels
|
return self._levels
|
||||||
|
|
||||||
def getlevel(self, name: str) -> bascenev1.Level:
|
def getlevel(self, name: str) -> bascenev1.Level:
|
||||||
"""Return a contained baclassic.Level by name."""
|
"""Return a contained level by name."""
|
||||||
|
|
||||||
for level in self._levels:
|
for level in self._levels:
|
||||||
if level.name == name:
|
if level.name == name:
|
||||||
|
|
@ -75,11 +72,11 @@ class Campaign:
|
||||||
)
|
)
|
||||||
|
|
||||||
def reset(self) -> None:
|
def reset(self) -> None:
|
||||||
"""Reset state for the Campaign."""
|
"""Reset state for the campaign."""
|
||||||
babase.app.config.setdefault('Campaigns', {})[self._name] = {}
|
babase.app.config.setdefault('Campaigns', {})[self._name] = {}
|
||||||
|
|
||||||
# FIXME should these give/take baclassic.Level instances instead
|
# FIXME: should these give/take baclassic.Level instances instead of
|
||||||
# of level names?..
|
# level names?..
|
||||||
def set_selected_level(self, levelname: str) -> None:
|
def set_selected_level(self, levelname: str) -> None:
|
||||||
"""Set the Level currently selected in the UI (by name)."""
|
"""Set the Level currently selected in the UI (by name)."""
|
||||||
self.configdict['Selection'] = levelname
|
self.configdict['Selection'] = levelname
|
||||||
|
|
|
||||||
23
dist/ba_data/python/bascenev1/_collision.py
vendored
23
dist/ba_data/python/bascenev1/_collision.py
vendored
|
|
@ -14,10 +14,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class Collision:
|
class Collision:
|
||||||
"""A class providing info about occurring collisions.
|
"""A class providing info about occurring collisions."""
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def position(self) -> bascenev1.Vec3:
|
def position(self) -> bascenev1.Vec3:
|
||||||
|
|
@ -28,9 +25,9 @@ class Collision:
|
||||||
def sourcenode(self) -> bascenev1.Node:
|
def sourcenode(self) -> bascenev1.Node:
|
||||||
"""The node containing the material triggering the current callback.
|
"""The node containing the material triggering the current callback.
|
||||||
|
|
||||||
Throws a bascenev1.NodeNotFoundError if the node does not exist,
|
Throws a :class:`~babase.NodeNotFoundError` if the node does
|
||||||
though the node should always exist (at least at the start of the
|
not exist, though the node should always exist (at least at the
|
||||||
collision callback).
|
start of the collision callback).
|
||||||
"""
|
"""
|
||||||
node = _bascenev1.get_collision_info('sourcenode')
|
node = _bascenev1.get_collision_info('sourcenode')
|
||||||
assert isinstance(node, (_bascenev1.Node, type(None)))
|
assert isinstance(node, (_bascenev1.Node, type(None)))
|
||||||
|
|
@ -42,9 +39,10 @@ class Collision:
|
||||||
def opposingnode(self) -> bascenev1.Node:
|
def opposingnode(self) -> bascenev1.Node:
|
||||||
"""The node the current callback material node is hitting.
|
"""The node the current callback material node is hitting.
|
||||||
|
|
||||||
Throws a bascenev1.NodeNotFoundError if the node does not exist.
|
Throws a :class:`~babase.NodeNotFoundError` if the node does
|
||||||
This can be expected in some cases such as in 'disconnect'
|
not exist. This can be expected in some cases such as in
|
||||||
callbacks triggered by deleting a currently-colliding node.
|
'disconnect' callbacks triggered by deleting a
|
||||||
|
currently-colliding node.
|
||||||
"""
|
"""
|
||||||
node = _bascenev1.get_collision_info('opposingnode')
|
node = _bascenev1.get_collision_info('opposingnode')
|
||||||
assert isinstance(node, (_bascenev1.Node, type(None)))
|
assert isinstance(node, (_bascenev1.Node, type(None)))
|
||||||
|
|
@ -65,8 +63,5 @@ _collision = Collision()
|
||||||
|
|
||||||
|
|
||||||
def getcollision() -> Collision:
|
def getcollision() -> Collision:
|
||||||
"""Return the in-progress collision.
|
"""Return the in-progress collision."""
|
||||||
|
|
||||||
Category: **Gameplay Functions**
|
|
||||||
"""
|
|
||||||
return _collision
|
return _collision
|
||||||
|
|
|
||||||
5
dist/ba_data/python/bascenev1/_coopgame.py
vendored
5
dist/ba_data/python/bascenev1/_coopgame.py
vendored
|
|
@ -23,10 +23,7 @@ TeamT = TypeVar('TeamT', bound='bascenev1.Team')
|
||||||
|
|
||||||
|
|
||||||
class CoopGameActivity(GameActivity[PlayerT, TeamT]):
|
class CoopGameActivity(GameActivity[PlayerT, TeamT]):
|
||||||
"""Base class for cooperative-mode games.
|
"""Base class for cooperative-mode games."""
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
# We can assume our session is a CoopSession.
|
# We can assume our session is a CoopSession.
|
||||||
session: bascenev1.CoopSession
|
session: bascenev1.CoopSession
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,10 @@ TEAM_NAMES = ['Good Guys']
|
||||||
|
|
||||||
|
|
||||||
class CoopSession(Session):
|
class CoopSession(Session):
|
||||||
"""A bascenev1.Session which runs cooperative-mode games.
|
"""A session which runs cooperative-mode games.
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
These generally consist of 1-4 players against the computer and
|
||||||
|
include functionality such as high score lists.
|
||||||
These generally consist of 1-4 players against
|
|
||||||
the computer and include functionality such as
|
|
||||||
high score lists.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use_teams = True
|
use_teams = True
|
||||||
|
|
|
||||||
18
dist/ba_data/python/bascenev1/_dependency.py
vendored
18
dist/ba_data/python/bascenev1/_dependency.py
vendored
|
|
@ -22,8 +22,6 @@ T = TypeVar('T', bound='DependencyComponent')
|
||||||
class Dependency(Generic[T]):
|
class Dependency(Generic[T]):
|
||||||
"""A dependency on a DependencyComponent (with an optional config).
|
"""A dependency on a DependencyComponent (with an optional config).
|
||||||
|
|
||||||
Category: **Dependency Classes**
|
|
||||||
|
|
||||||
This class is used to request and access functionality provided
|
This class is used to request and access functionality provided
|
||||||
by other DependencyComponent classes from a DependencyComponent class.
|
by other DependencyComponent classes from a DependencyComponent class.
|
||||||
The class functions as a descriptor, allowing dependencies to
|
The class functions as a descriptor, allowing dependencies to
|
||||||
|
|
@ -92,10 +90,7 @@ class Dependency(Generic[T]):
|
||||||
|
|
||||||
|
|
||||||
class DependencyComponent:
|
class DependencyComponent:
|
||||||
"""Base class for all classes that can act as or use dependencies.
|
"""Base class for all classes that can act as or use dependencies."""
|
||||||
|
|
||||||
Category: **Dependency Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
_dep_entry: weakref.ref[DependencyEntry]
|
_dep_entry: weakref.ref[DependencyEntry]
|
||||||
|
|
||||||
|
|
@ -173,8 +168,6 @@ class DependencyEntry:
|
||||||
class DependencySet(Generic[T]):
|
class DependencySet(Generic[T]):
|
||||||
"""Set of resolved dependencies and their associated data.
|
"""Set of resolved dependencies and their associated data.
|
||||||
|
|
||||||
Category: **Dependency Classes**
|
|
||||||
|
|
||||||
To use DependencyComponents, a set must be created, resolved, and then
|
To use DependencyComponents, a set must be created, resolved, and then
|
||||||
loaded. The DependencyComponents are only valid while the set remains
|
loaded. The DependencyComponents are only valid while the set remains
|
||||||
in existence.
|
in existence.
|
||||||
|
|
@ -296,10 +289,7 @@ class DependencySet(Generic[T]):
|
||||||
|
|
||||||
|
|
||||||
class AssetPackage(DependencyComponent):
|
class AssetPackage(DependencyComponent):
|
||||||
"""bascenev1.DependencyComponent representing a bundled package of assets.
|
"""bascenev1.DependencyComponent representing a package of assets."""
|
||||||
|
|
||||||
Category: **Asset Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
@ -430,9 +420,7 @@ def test_depset() -> None:
|
||||||
|
|
||||||
|
|
||||||
class DependencyError(Exception):
|
class DependencyError(Exception):
|
||||||
"""Exception raised when one or more bascenev1.Dependency items are missing.
|
""":class:`Exception` raised when bascenev1.Dependency items are missing.
|
||||||
|
|
||||||
Category: **Exception Classes**
|
|
||||||
|
|
||||||
(this will generally be missing assets).
|
(this will generally be missing assets).
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class DualTeamSession(MultiTeamSession):
|
class DualTeamSession(MultiTeamSession):
|
||||||
"""bascenev1.Session type for teams mode games.
|
"""bascenev1.Session type for teams mode games."""
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Base class overrides:
|
# Base class overrides:
|
||||||
use_teams = True
|
use_teams = True
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class FreeForAllSession(MultiTeamSession):
|
class FreeForAllSession(MultiTeamSession):
|
||||||
"""bascenev1.Session type for free-for-all mode games.
|
"""bascenev1.Session type for free-for-all mode games."""
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
use_teams = False
|
use_teams = False
|
||||||
use_team_colors = False
|
use_team_colors = False
|
||||||
|
|
|
||||||
18
dist/ba_data/python/bascenev1/_gameactivity.py
vendored
18
dist/ba_data/python/bascenev1/_gameactivity.py
vendored
|
|
@ -31,10 +31,7 @@ TeamT = TypeVar('TeamT', bound='bascenev1.Team')
|
||||||
|
|
||||||
|
|
||||||
class GameActivity(Activity[PlayerT, TeamT]):
|
class GameActivity(Activity[PlayerT, TeamT]):
|
||||||
"""Common base class for all game bascenev1.Activities.
|
"""Common base class for all game bascenev1.Activities."""
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
# pylint: disable=too-many-public-methods
|
# pylint: disable=too-many-public-methods
|
||||||
|
|
||||||
|
|
@ -198,7 +195,7 @@ class GameActivity(Activity[PlayerT, TeamT]):
|
||||||
def supports_session_type(
|
def supports_session_type(
|
||||||
cls, sessiontype: type[bascenev1.Session]
|
cls, sessiontype: type[bascenev1.Session]
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Return whether this game supports the provided Session type."""
|
"""Return whether this game supports the provided session type."""
|
||||||
from bascenev1._multiteamsession import MultiTeamSession
|
from bascenev1._multiteamsession import MultiTeamSession
|
||||||
|
|
||||||
# By default, games support any versus mode
|
# By default, games support any versus mode
|
||||||
|
|
@ -208,8 +205,8 @@ class GameActivity(Activity[PlayerT, TeamT]):
|
||||||
"""Instantiate the Activity."""
|
"""Instantiate the Activity."""
|
||||||
super().__init__(settings)
|
super().__init__(settings)
|
||||||
|
|
||||||
# Holds some flattened info about the player set at the point
|
#: Holds some flattened info about the player set at the point
|
||||||
# when on_begin() is called.
|
#: when :meth:`on_begin()` is called.
|
||||||
self.initialplayerinfos: list[bascenev1.PlayerInfo] | None = None
|
self.initialplayerinfos: list[bascenev1.PlayerInfo] | None = None
|
||||||
|
|
||||||
# Go ahead and get our map loading.
|
# Go ahead and get our map loading.
|
||||||
|
|
@ -794,9 +791,10 @@ class GameActivity(Activity[PlayerT, TeamT]):
|
||||||
self.spawn_player(player)
|
self.spawn_player(player)
|
||||||
|
|
||||||
def spawn_player(self, player: PlayerT) -> bascenev1.Actor:
|
def spawn_player(self, player: PlayerT) -> bascenev1.Actor:
|
||||||
"""Spawn *something* for the provided bascenev1.Player.
|
"""Spawn *something* for the provided player.
|
||||||
|
|
||||||
The default implementation simply calls spawn_player_spaz().
|
The default implementation simply calls
|
||||||
|
:meth:`spawn_player_spaz()`.
|
||||||
"""
|
"""
|
||||||
assert player # Dead references should never be passed as args.
|
assert player # Dead references should never be passed as args.
|
||||||
|
|
||||||
|
|
@ -808,7 +806,7 @@ class GameActivity(Activity[PlayerT, TeamT]):
|
||||||
position: Sequence[float] = (0, 0, 0),
|
position: Sequence[float] = (0, 0, 0),
|
||||||
angle: float | None = None,
|
angle: float | None = None,
|
||||||
) -> PlayerSpaz:
|
) -> PlayerSpaz:
|
||||||
"""Create and wire up a bascenev1.PlayerSpaz for the provided Player."""
|
"""Create and wire up a player-spaz for the provided player."""
|
||||||
# pylint: disable=too-many-locals
|
# pylint: disable=too-many-locals
|
||||||
# pylint: disable=cyclic-import
|
# pylint: disable=cyclic-import
|
||||||
from bascenev1._gameutils import animate
|
from bascenev1._gameutils import animate
|
||||||
|
|
|
||||||
29
dist/ba_data/python/bascenev1/_gameresults.py
vendored
29
dist/ba_data/python/bascenev1/_gameresults.py
vendored
|
|
@ -21,20 +21,17 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class WinnerGroup:
|
class WinnerGroup:
|
||||||
"""Entry for a winning team or teams calculated by game-results."""
|
"""Winning team or teams as calculated by a :class:`GameResults`."""
|
||||||
|
|
||||||
score: int | None
|
score: int | None
|
||||||
teams: Sequence[bascenev1.SessionTeam]
|
teams: list[bascenev1.SessionTeam]
|
||||||
|
|
||||||
|
|
||||||
class GameResults:
|
class GameResults:
|
||||||
"""
|
"""Results for a completed game.
|
||||||
Results for a completed game.
|
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
Upon completion, a game should fill one of these out and pass it to
|
||||||
|
its :meth:`~bascenev1.Activity.end()` call.
|
||||||
Upon completion, a game should fill one of these out and pass it to its
|
|
||||||
bascenev1.Activity.end call.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
|
@ -69,8 +66,8 @@ class GameResults:
|
||||||
def set_team_score(self, team: bascenev1.Team, score: int | None) -> None:
|
def set_team_score(self, team: bascenev1.Team, score: int | None) -> None:
|
||||||
"""Set the score for a given team.
|
"""Set the score for a given team.
|
||||||
|
|
||||||
This can be a number or None.
|
This can be a number or None (see the ``none_is_winner`` arg in
|
||||||
(see the none_is_winner arg in the constructor)
|
the constructor).
|
||||||
"""
|
"""
|
||||||
assert isinstance(team, Team)
|
assert isinstance(team, Team)
|
||||||
sessionteam = team.sessionteam
|
sessionteam = team.sessionteam
|
||||||
|
|
@ -79,7 +76,7 @@ class GameResults:
|
||||||
def get_sessionteam_score(
|
def get_sessionteam_score(
|
||||||
self, sessionteam: bascenev1.SessionTeam
|
self, sessionteam: bascenev1.SessionTeam
|
||||||
) -> int | None:
|
) -> int | None:
|
||||||
"""Return the score for a given bascenev1.SessionTeam."""
|
"""Return the score for a given team."""
|
||||||
assert isinstance(sessionteam, SessionTeam)
|
assert isinstance(sessionteam, SessionTeam)
|
||||||
for score in list(self._scores.values()):
|
for score in list(self._scores.values()):
|
||||||
if score[0]() is sessionteam:
|
if score[0]() is sessionteam:
|
||||||
|
|
@ -90,7 +87,7 @@ class GameResults:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sessionteams(self) -> list[bascenev1.SessionTeam]:
|
def sessionteams(self) -> list[bascenev1.SessionTeam]:
|
||||||
"""Return all bascenev1.SessionTeams in the results."""
|
"""Return all teams in the results."""
|
||||||
if not self._game_set:
|
if not self._game_set:
|
||||||
raise RuntimeError("Can't get teams until game is set.")
|
raise RuntimeError("Can't get teams until game is set.")
|
||||||
teams = []
|
teams = []
|
||||||
|
|
@ -104,13 +101,13 @@ class GameResults:
|
||||||
def has_score_for_sessionteam(
|
def has_score_for_sessionteam(
|
||||||
self, sessionteam: bascenev1.SessionTeam
|
self, sessionteam: bascenev1.SessionTeam
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Return whether there is a score for a given session-team."""
|
"""Return whether there is a score for a given team."""
|
||||||
return any(s[0]() is sessionteam for s in self._scores.values())
|
return any(s[0]() is sessionteam for s in self._scores.values())
|
||||||
|
|
||||||
def get_sessionteam_score_str(
|
def get_sessionteam_score_str(
|
||||||
self, sessionteam: bascenev1.SessionTeam
|
self, sessionteam: bascenev1.SessionTeam
|
||||||
) -> babase.Lstr:
|
) -> babase.Lstr:
|
||||||
"""Return the score for the given session-team as an Lstr.
|
"""Return the score for the given team as an :class:`~bascenev1.Lstr`.
|
||||||
|
|
||||||
(properly formatted for the score type.)
|
(properly formatted for the score type.)
|
||||||
"""
|
"""
|
||||||
|
|
@ -163,7 +160,7 @@ class GameResults:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def winning_sessionteam(self) -> bascenev1.SessionTeam | None:
|
def winning_sessionteam(self) -> bascenev1.SessionTeam | None:
|
||||||
"""The winning SessionTeam if there is exactly one, or else None."""
|
"""The winning team if there is exactly one, or else None."""
|
||||||
if not self._game_set:
|
if not self._game_set:
|
||||||
raise RuntimeError("Can't get winners until game is set.")
|
raise RuntimeError("Can't get winners until game is set.")
|
||||||
winners = self.winnergroups
|
winners = self.winnergroups
|
||||||
|
|
@ -173,7 +170,7 @@ class GameResults:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def winnergroups(self) -> list[WinnerGroup]:
|
def winnergroups(self) -> list[WinnerGroup]:
|
||||||
"""Get an ordered list of winner groups."""
|
"""The ordered list of winner-groups."""
|
||||||
if not self._game_set:
|
if not self._game_set:
|
||||||
raise RuntimeError("Can't get winners until game is set.")
|
raise RuntimeError("Can't get winners until game is set.")
|
||||||
|
|
||||||
|
|
|
||||||
16
dist/ba_data/python/bascenev1/_gameutils.py
vendored
16
dist/ba_data/python/bascenev1/_gameutils.py
vendored
|
|
@ -31,10 +31,7 @@ TROPHY_CHARS = {
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GameTip:
|
class GameTip:
|
||||||
"""Defines a tip presentable to the user at the start of a game.
|
"""Defines a tip presentable to the user at the start of a game."""
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
text: str
|
text: str
|
||||||
icon: bascenev1.Texture | None = None
|
icon: bascenev1.Texture | None = None
|
||||||
|
|
@ -57,8 +54,6 @@ def animate(
|
||||||
) -> bascenev1.Node:
|
) -> bascenev1.Node:
|
||||||
"""Animate values on a target bascenev1.Node.
|
"""Animate values on a target bascenev1.Node.
|
||||||
|
|
||||||
Category: **Gameplay Functions**
|
|
||||||
|
|
||||||
Creates an 'animcurve' node with the provided values and time as an input,
|
Creates an 'animcurve' node with the provided values and time as an input,
|
||||||
connect it to the provided attribute, and set it to die with the target.
|
connect it to the provided attribute, and set it to die with the target.
|
||||||
Key values are provided as time:value dictionary pairs. Time values are
|
Key values are provided as time:value dictionary pairs. Time values are
|
||||||
|
|
@ -118,8 +113,6 @@ def animate_array(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Animate an array of values on a target bascenev1.Node.
|
"""Animate an array of values on a target bascenev1.Node.
|
||||||
|
|
||||||
Category: **Gameplay Functions**
|
|
||||||
|
|
||||||
Like bs.animate, but operates on array attributes.
|
Like bs.animate, but operates on array attributes.
|
||||||
"""
|
"""
|
||||||
combine = _bascenev1.newnode('combine', owner=node, attrs={'size': size})
|
combine = _bascenev1.newnode('combine', owner=node, attrs={'size': size})
|
||||||
|
|
@ -176,10 +169,7 @@ def animate_array(
|
||||||
def show_damage_count(
|
def show_damage_count(
|
||||||
damage: str, position: Sequence[float], direction: Sequence[float]
|
damage: str, position: Sequence[float], direction: Sequence[float]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Pop up a damage count at a position in space.
|
"""Pop up a damage count at a position in space."""
|
||||||
|
|
||||||
Category: **Gameplay Functions**
|
|
||||||
"""
|
|
||||||
lifespan = 1.0
|
lifespan = 1.0
|
||||||
app = babase.app
|
app = babase.app
|
||||||
|
|
||||||
|
|
@ -239,8 +229,6 @@ def show_damage_count(
|
||||||
def cameraflash(duration: float = 999.0) -> None:
|
def cameraflash(duration: float = 999.0) -> None:
|
||||||
"""Create a strobing camera flash effect.
|
"""Create a strobing camera flash effect.
|
||||||
|
|
||||||
Category: **Gameplay Functions**
|
|
||||||
|
|
||||||
(as seen when a team wins a game)
|
(as seen when a team wins a game)
|
||||||
Duration is in seconds.
|
Duration is in seconds.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
50
dist/ba_data/python/bascenev1/_level.py
vendored
50
dist/ba_data/python/bascenev1/_level.py
vendored
|
|
@ -16,10 +16,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class Level:
|
class Level:
|
||||||
"""An entry in a bascenev1.Campaign.
|
"""An entry in a :class:`~bascenev1.Campaign`."""
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
@ -46,7 +43,7 @@ class Level:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""The unique name for this Level."""
|
"""The unique name for this level."""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
def get_settings(self) -> dict[str, Any]:
|
def get_settings(self) -> dict[str, Any]:
|
||||||
|
|
@ -60,16 +57,12 @@ class Level:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def preview_texture_name(self) -> str:
|
def preview_texture_name(self) -> str:
|
||||||
"""The preview texture name for this Level."""
|
"""The preview texture name for this level."""
|
||||||
return self._preview_texture_name
|
return self._preview_texture_name
|
||||||
|
|
||||||
# def get_preview_texture(self) -> bauiv1.Texture:
|
|
||||||
# """Load/return the preview Texture for this Level."""
|
|
||||||
# return _bauiv1.gettexture(self._preview_texture_name)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def displayname(self) -> bascenev1.Lstr:
|
def displayname(self) -> bascenev1.Lstr:
|
||||||
"""The localized name for this Level."""
|
"""The localized name for this level."""
|
||||||
return babase.Lstr(
|
return babase.Lstr(
|
||||||
translate=(
|
translate=(
|
||||||
'coopLevelNames',
|
'coopLevelNames',
|
||||||
|
|
@ -86,20 +79,20 @@ class Level:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def gametype(self) -> type[bascenev1.GameActivity]:
|
def gametype(self) -> type[bascenev1.GameActivity]:
|
||||||
"""The type of game used for this Level."""
|
"""The type of game used for this level."""
|
||||||
return self._gametype
|
return self._gametype
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def campaign(self) -> bascenev1.Campaign | None:
|
def campaign(self) -> bascenev1.Campaign | None:
|
||||||
"""The baclassic.Campaign this Level is associated with, or None."""
|
"""The campaign this level is associated with, or None."""
|
||||||
return None if self._campaign is None else self._campaign()
|
return None if self._campaign is None else self._campaign()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def index(self) -> int:
|
def index(self) -> int:
|
||||||
"""The zero-based index of this Level in its baclassic.Campaign.
|
"""The zero-based index of this level in its campaign.
|
||||||
|
|
||||||
Access results in a RuntimeError if the Level is not assigned to a
|
Access results in a RuntimeError if the level is not assigned to
|
||||||
Campaign.
|
a campaign.
|
||||||
"""
|
"""
|
||||||
if self._index is None:
|
if self._index is None:
|
||||||
raise RuntimeError('Level is not part of a Campaign')
|
raise RuntimeError('Level is not part of a Campaign')
|
||||||
|
|
@ -107,7 +100,7 @@ class Level:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def complete(self) -> bool:
|
def complete(self) -> bool:
|
||||||
"""Whether this Level has been completed."""
|
"""Whether this level has been completed."""
|
||||||
config = self._get_config_dict()
|
config = self._get_config_dict()
|
||||||
val = config.get('Complete', False)
|
val = config.get('Complete', False)
|
||||||
assert isinstance(val, bool)
|
assert isinstance(val, bool)
|
||||||
|
|
@ -123,7 +116,7 @@ class Level:
|
||||||
config['Complete'] = val
|
config['Complete'] = val
|
||||||
|
|
||||||
def get_high_scores(self) -> dict:
|
def get_high_scores(self) -> dict:
|
||||||
"""Return the current high scores for this Level."""
|
"""Return the current high scores for this level."""
|
||||||
config = self._get_config_dict()
|
config = self._get_config_dict()
|
||||||
high_scores_key = 'High Scores' + self.get_score_version_string()
|
high_scores_key = 'High Scores' + self.get_score_version_string()
|
||||||
if high_scores_key not in config:
|
if high_scores_key not in config:
|
||||||
|
|
@ -137,10 +130,11 @@ class Level:
|
||||||
config[high_scores_key] = high_scores
|
config[high_scores_key] = high_scores
|
||||||
|
|
||||||
def get_score_version_string(self) -> str:
|
def get_score_version_string(self) -> str:
|
||||||
"""Return the score version string for this Level.
|
"""Return the score version string for this level.
|
||||||
|
|
||||||
If a Level's gameplay changes significantly, its version string
|
If a level's gameplay changes significantly, its version string
|
||||||
can be changed to separate its new high score lists/etc. from the old.
|
can be changed to separate its new high score lists/etc. from
|
||||||
|
the old.
|
||||||
"""
|
"""
|
||||||
if self._score_version_string is None:
|
if self._score_version_string is None:
|
||||||
scorever = self._gametype.getscoreconfig().version
|
scorever = self._gametype.getscoreconfig().version
|
||||||
|
|
@ -152,13 +146,13 @@ class Level:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rating(self) -> float:
|
def rating(self) -> float:
|
||||||
"""The current rating for this Level."""
|
"""The current rating for this level."""
|
||||||
val = self._get_config_dict().get('Rating', 0.0)
|
val = self._get_config_dict().get('Rating', 0.0)
|
||||||
assert isinstance(val, float)
|
assert isinstance(val, float)
|
||||||
return val
|
return val
|
||||||
|
|
||||||
def set_rating(self, rating: float) -> None:
|
def set_rating(self, rating: float) -> None:
|
||||||
"""Set a rating for this Level, replacing the old ONLY IF higher."""
|
"""Set a rating for this level, replacing the old ONLY IF higher."""
|
||||||
old_rating = self.rating
|
old_rating = self.rating
|
||||||
config = self._get_config_dict()
|
config = self._get_config_dict()
|
||||||
config['Rating'] = max(old_rating, rating)
|
config['Rating'] = max(old_rating, rating)
|
||||||
|
|
@ -166,8 +160,9 @@ class Level:
|
||||||
def _get_config_dict(self) -> dict[str, Any]:
|
def _get_config_dict(self) -> dict[str, Any]:
|
||||||
"""Return/create the persistent state dict for this level.
|
"""Return/create the persistent state dict for this level.
|
||||||
|
|
||||||
The referenced dict exists under the game's config dict and
|
The referenced dict exists under the game's config dict and can
|
||||||
can be modified in place."""
|
be modified in place.
|
||||||
|
"""
|
||||||
campaign = self.campaign
|
campaign = self.campaign
|
||||||
if campaign is None:
|
if campaign is None:
|
||||||
raise RuntimeError('Level is not in a campaign.')
|
raise RuntimeError('Level is not in a campaign.')
|
||||||
|
|
@ -179,8 +174,9 @@ class Level:
|
||||||
return val
|
return val
|
||||||
|
|
||||||
def set_campaign(self, campaign: bascenev1.Campaign, index: int) -> None:
|
def set_campaign(self, campaign: bascenev1.Campaign, index: int) -> None:
|
||||||
"""For use by baclassic.Campaign when adding levels to itself.
|
"""Internal: Used by campaign when adding levels to itself.
|
||||||
|
|
||||||
(internal)"""
|
:meta private:
|
||||||
|
"""
|
||||||
self._campaign = weakref.ref(campaign)
|
self._campaign = weakref.ref(campaign)
|
||||||
self._index = index
|
self._index = index
|
||||||
|
|
|
||||||
34
dist/ba_data/python/bascenev1/_lobby.py
vendored
34
dist/ba_data/python/bascenev1/_lobby.py
vendored
|
|
@ -178,10 +178,7 @@ class ChangeMessage:
|
||||||
|
|
||||||
|
|
||||||
class Chooser:
|
class Chooser:
|
||||||
"""A character/team selector for a bascenev1.Player.
|
"""A character/team selector for a player."""
|
||||||
|
|
||||||
Category: Gameplay Classes
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __del__(self) -> None:
|
def __del__(self) -> None:
|
||||||
# Just kill off our base node; the rest should go down with it.
|
# Just kill off our base node; the rest should go down with it.
|
||||||
|
|
@ -364,7 +361,7 @@ class Chooser:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sessionplayer(self) -> bascenev1.SessionPlayer:
|
def sessionplayer(self) -> bascenev1.SessionPlayer:
|
||||||
"""The bascenev1.SessionPlayer associated with this chooser."""
|
"""The session-player associated with this chooser."""
|
||||||
return self._sessionplayer
|
return self._sessionplayer
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -373,11 +370,17 @@ class Chooser:
|
||||||
return self._ready
|
return self._ready
|
||||||
|
|
||||||
def set_vpos(self, vpos: float) -> None:
|
def set_vpos(self, vpos: float) -> None:
|
||||||
"""(internal)"""
|
"""(internal)
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
self._vpos = vpos
|
self._vpos = vpos
|
||||||
|
|
||||||
def set_dead(self, val: bool) -> None:
|
def set_dead(self, val: bool) -> None:
|
||||||
"""(internal)"""
|
"""(internal)
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
self._dead = val
|
self._dead = val
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -387,7 +390,7 @@ class Chooser:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def lobby(self) -> bascenev1.Lobby:
|
def lobby(self) -> bascenev1.Lobby:
|
||||||
"""The chooser's baclassic.Lobby."""
|
"""The chooser's lobby."""
|
||||||
lobby = self._lobby()
|
lobby = self._lobby()
|
||||||
if lobby is None:
|
if lobby is None:
|
||||||
raise babase.NotFoundError('Lobby does not exist.')
|
raise babase.NotFoundError('Lobby does not exist.')
|
||||||
|
|
@ -940,10 +943,7 @@ class Chooser:
|
||||||
|
|
||||||
|
|
||||||
class Lobby:
|
class Lobby:
|
||||||
"""Container for baclassic.Choosers.
|
"""Environment where players can selecting characters, etc."""
|
||||||
|
|
||||||
Category: Gameplay Classes
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __del__(self) -> None:
|
def __del__(self) -> None:
|
||||||
# Reset any players that still have a chooser in us.
|
# Reset any players that still have a chooser in us.
|
||||||
|
|
@ -987,7 +987,7 @@ class Lobby:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def use_team_colors(self) -> bool:
|
def use_team_colors(self) -> bool:
|
||||||
"""A bool for whether this lobby is using team colors.
|
"""Whether this lobby is using team colors.
|
||||||
|
|
||||||
If False, inidividual player colors are used instead.
|
If False, inidividual player colors are used instead.
|
||||||
"""
|
"""
|
||||||
|
|
@ -995,7 +995,7 @@ class Lobby:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sessionteams(self) -> list[bascenev1.SessionTeam]:
|
def sessionteams(self) -> list[bascenev1.SessionTeam]:
|
||||||
"""bascenev1.SessionTeams available in this lobby."""
|
"""The teams available in this lobby."""
|
||||||
allteams = []
|
allteams = []
|
||||||
for tref in self._sessionteams:
|
for tref in self._sessionteams:
|
||||||
team = tref()
|
team = tref()
|
||||||
|
|
@ -1004,7 +1004,7 @@ class Lobby:
|
||||||
return allteams
|
return allteams
|
||||||
|
|
||||||
def get_choosers(self) -> list[Chooser]:
|
def get_choosers(self) -> list[Chooser]:
|
||||||
"""Return the lobby's current choosers."""
|
"""The current choosers present."""
|
||||||
return self.choosers
|
return self.choosers
|
||||||
|
|
||||||
def create_join_info(self) -> JoinInfo:
|
def create_join_info(self) -> JoinInfo:
|
||||||
|
|
@ -1070,8 +1070,8 @@ class Lobby:
|
||||||
found = True
|
found = True
|
||||||
|
|
||||||
# Mark it as dead since there could be more
|
# Mark it as dead since there could be more
|
||||||
# change-commands/etc coming in still for it;
|
# change-commands/etc coming in still for it; want to
|
||||||
# want to avoid duplicate player-adds/etc.
|
# avoid duplicate player-adds/etc.
|
||||||
chooser.set_dead(True)
|
chooser.set_dead(True)
|
||||||
self.choosers.remove(chooser)
|
self.choosers.remove(chooser)
|
||||||
break
|
break
|
||||||
|
|
|
||||||
14
dist/ba_data/python/bascenev1/_map.py
vendored
14
dist/ba_data/python/bascenev1/_map.py
vendored
|
|
@ -20,8 +20,6 @@ if TYPE_CHECKING:
|
||||||
def get_filtered_map_name(name: str) -> str:
|
def get_filtered_map_name(name: str) -> str:
|
||||||
"""Filter a map name to account for name changes, etc.
|
"""Filter a map name to account for name changes, etc.
|
||||||
|
|
||||||
Category: **Asset Functions**
|
|
||||||
|
|
||||||
This can be used to support old playlists, etc.
|
This can be used to support old playlists, etc.
|
||||||
"""
|
"""
|
||||||
# Some legacy name fallbacks... can remove these eventually.
|
# Some legacy name fallbacks... can remove these eventually.
|
||||||
|
|
@ -33,18 +31,12 @@ def get_filtered_map_name(name: str) -> str:
|
||||||
|
|
||||||
|
|
||||||
def get_map_display_string(name: str) -> babase.Lstr:
|
def get_map_display_string(name: str) -> babase.Lstr:
|
||||||
"""Return a babase.Lstr for displaying a given map\'s name.
|
"""Return a babase.Lstr for displaying a given map's name."""
|
||||||
|
|
||||||
Category: **Asset Functions**
|
|
||||||
"""
|
|
||||||
return babase.Lstr(translate=('mapsNames', name))
|
return babase.Lstr(translate=('mapsNames', name))
|
||||||
|
|
||||||
|
|
||||||
def get_map_class(name: str) -> type[Map]:
|
def get_map_class(name: str) -> type[Map]:
|
||||||
"""Return a map type given a name.
|
"""Return a map type given a name."""
|
||||||
|
|
||||||
Category: **Asset Functions**
|
|
||||||
"""
|
|
||||||
assert babase.app.classic is not None
|
assert babase.app.classic is not None
|
||||||
name = get_filtered_map_name(name)
|
name = get_filtered_map_name(name)
|
||||||
try:
|
try:
|
||||||
|
|
@ -57,8 +49,6 @@ def get_map_class(name: str) -> type[Map]:
|
||||||
class Map(Actor):
|
class Map(Actor):
|
||||||
"""A game map.
|
"""A game map.
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
|
||||||
|
|
||||||
Consists of a collection of terrain nodes, metadata, and other
|
Consists of a collection of terrain nodes, metadata, and other
|
||||||
functionality comprising a game map.
|
functionality comprising a game map.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
80
dist/ba_data/python/bascenev1/_messages.py
vendored
80
dist/ba_data/python/bascenev1/_messages.py
vendored
|
|
@ -28,17 +28,11 @@ UNHANDLED = _UnhandledType()
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class OutOfBoundsMessage:
|
class OutOfBoundsMessage:
|
||||||
"""A message telling an object that it is out of bounds.
|
"""A message telling an object that it is out of bounds."""
|
||||||
|
|
||||||
Category: Message Classes
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class DeathType(Enum):
|
class DeathType(Enum):
|
||||||
"""A reason for a death.
|
"""A reason for a death."""
|
||||||
|
|
||||||
Category: Enums
|
|
||||||
"""
|
|
||||||
|
|
||||||
GENERIC = 'generic'
|
GENERIC = 'generic'
|
||||||
OUT_OF_BOUNDS = 'out_of_bounds'
|
OUT_OF_BOUNDS = 'out_of_bounds'
|
||||||
|
|
@ -52,29 +46,24 @@ class DeathType(Enum):
|
||||||
class DieMessage:
|
class DieMessage:
|
||||||
"""A message telling an object to die.
|
"""A message telling an object to die.
|
||||||
|
|
||||||
Category: **Message Classes**
|
|
||||||
|
|
||||||
Most bascenev1.Actor-s respond to this.
|
Most bascenev1.Actor-s respond to this.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
#: If this is set to True, the actor should disappear immediately.
|
||||||
|
#: This is for 'removing' stuff from the game more so than 'killing'
|
||||||
|
#: it. If False, the actor should die a 'normal' death and can take
|
||||||
|
#: its time with lingering corpses, sound effects, etc.
|
||||||
immediate: bool = False
|
immediate: bool = False
|
||||||
"""If this is set to True, the actor should disappear immediately.
|
|
||||||
This is for 'removing' stuff from the game more so than 'killing'
|
|
||||||
it. If False, the actor should die a 'normal' death and can take
|
|
||||||
its time with lingering corpses, sound effects, etc."""
|
|
||||||
|
|
||||||
|
#: The particular reason for death.
|
||||||
how: DeathType = DeathType.GENERIC
|
how: DeathType = DeathType.GENERIC
|
||||||
"""The particular reason for death."""
|
|
||||||
|
|
||||||
|
|
||||||
PlayerT = TypeVar('PlayerT', bound='bascenev1.Player')
|
PlayerT = TypeVar('PlayerT', bound='bascenev1.Player')
|
||||||
|
|
||||||
|
|
||||||
class PlayerDiedMessage:
|
class PlayerDiedMessage:
|
||||||
"""A message saying a bascenev1.Player has died.
|
"""A message saying a bascenev1.Player has died."""
|
||||||
|
|
||||||
Category: **Message Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
killed: bool
|
killed: bool
|
||||||
"""If True, the player was killed;
|
"""If True, the player was killed;
|
||||||
|
|
@ -129,8 +118,6 @@ class PlayerDiedMessage:
|
||||||
class StandMessage:
|
class StandMessage:
|
||||||
"""A message telling an object to move to a position in space.
|
"""A message telling an object to move to a position in space.
|
||||||
|
|
||||||
Category: **Message Classes**
|
|
||||||
|
|
||||||
Used when teleporting players to home base, etc.
|
Used when teleporting players to home base, etc.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -143,10 +130,7 @@ class StandMessage:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PickUpMessage:
|
class PickUpMessage:
|
||||||
"""Tells an object that it has picked something up.
|
"""Tells an object that it has picked something up."""
|
||||||
|
|
||||||
Category: **Message Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
node: bascenev1.Node
|
node: bascenev1.Node
|
||||||
"""The bascenev1.Node that is getting picked up."""
|
"""The bascenev1.Node that is getting picked up."""
|
||||||
|
|
@ -154,18 +138,12 @@ class PickUpMessage:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DropMessage:
|
class DropMessage:
|
||||||
"""Tells an object that it has dropped what it was holding.
|
"""Tells an object that it has dropped what it was holding."""
|
||||||
|
|
||||||
Category: **Message Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PickedUpMessage:
|
class PickedUpMessage:
|
||||||
"""Tells an object that it has been picked up by something.
|
"""Tells an object that it has been picked up by something."""
|
||||||
|
|
||||||
Category: **Message Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
node: bascenev1.Node
|
node: bascenev1.Node
|
||||||
"""The bascenev1.Node doing the picking up."""
|
"""The bascenev1.Node doing the picking up."""
|
||||||
|
|
@ -173,10 +151,7 @@ class PickedUpMessage:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DroppedMessage:
|
class DroppedMessage:
|
||||||
"""Tells an object that it has been dropped.
|
"""Tells an object that it has been dropped."""
|
||||||
|
|
||||||
Category: **Message Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
node: bascenev1.Node
|
node: bascenev1.Node
|
||||||
"""The bascenev1.Node doing the dropping."""
|
"""The bascenev1.Node doing the dropping."""
|
||||||
|
|
@ -184,18 +159,12 @@ class DroppedMessage:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ShouldShatterMessage:
|
class ShouldShatterMessage:
|
||||||
"""Tells an object that it should shatter.
|
"""Tells an object that it should shatter."""
|
||||||
|
|
||||||
Category: **Message Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ImpactDamageMessage:
|
class ImpactDamageMessage:
|
||||||
"""Tells an object that it has been jarred violently.
|
"""Tells an object that it has been jarred violently."""
|
||||||
|
|
||||||
Category: **Message Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
intensity: float
|
intensity: float
|
||||||
"""The intensity of the impact."""
|
"""The intensity of the impact."""
|
||||||
|
|
@ -205,26 +174,21 @@ class ImpactDamageMessage:
|
||||||
class FreezeMessage:
|
class FreezeMessage:
|
||||||
"""Tells an object to become frozen.
|
"""Tells an object to become frozen.
|
||||||
|
|
||||||
Category: **Message Classes**
|
|
||||||
|
|
||||||
As seen in the effects of an ice bascenev1.Bomb.
|
As seen in the effects of an ice bascenev1.Bomb.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
time: float = 5.0
|
||||||
|
"""The amount of time the object will be frozen."""
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ThawMessage:
|
class ThawMessage:
|
||||||
"""Tells an object to stop being frozen.
|
"""Tells an object to stop being frozen."""
|
||||||
|
|
||||||
Category: **Message Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CelebrateMessage:
|
class CelebrateMessage:
|
||||||
"""Tells an object to celebrate.
|
"""Tells an object to celebrate."""
|
||||||
|
|
||||||
Category: **Message Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
duration: float = 10.0
|
duration: float = 10.0
|
||||||
"""Amount of time to celebrate in seconds."""
|
"""Amount of time to celebrate in seconds."""
|
||||||
|
|
@ -233,10 +197,8 @@ class CelebrateMessage:
|
||||||
class HitMessage:
|
class HitMessage:
|
||||||
"""Tells an object it has been hit in some way.
|
"""Tells an object it has been hit in some way.
|
||||||
|
|
||||||
Category: **Message Classes**
|
This is used by punches, explosions, etc to convey their effect to a
|
||||||
|
target.
|
||||||
This is used by punches, explosions, etc to convey
|
|
||||||
their effect to a target.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,6 @@ DEFAULT_TEAM_NAMES = ('Blue', 'Red')
|
||||||
class MultiTeamSession(Session):
|
class MultiTeamSession(Session):
|
||||||
"""Common base for DualTeamSession and FreeForAllSession.
|
"""Common base for DualTeamSession and FreeForAllSession.
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
|
||||||
|
|
||||||
Free-for-all-mode is essentially just teams-mode with each
|
Free-for-all-mode is essentially just teams-mode with each
|
||||||
bascenev1.Player having their own bascenev1.Team, so there is much
|
bascenev1.Player having their own bascenev1.Team, so there is much
|
||||||
overlap in functionality.
|
overlap in functionality.
|
||||||
|
|
|
||||||
15
dist/ba_data/python/bascenev1/_music.py
vendored
15
dist/ba_data/python/bascenev1/_music.py
vendored
|
|
@ -16,8 +16,6 @@ if TYPE_CHECKING:
|
||||||
class MusicType(Enum):
|
class MusicType(Enum):
|
||||||
"""Types of music available to play in-game.
|
"""Types of music available to play in-game.
|
||||||
|
|
||||||
Category: **Enums**
|
|
||||||
|
|
||||||
These do not correspond to specific pieces of music, but rather to
|
These do not correspond to specific pieces of music, but rather to
|
||||||
'situations'. The actual music played for each type can be overridden
|
'situations'. The actual music played for each type can be overridden
|
||||||
by the game or by the user.
|
by the game or by the user.
|
||||||
|
|
@ -50,16 +48,15 @@ class MusicType(Enum):
|
||||||
def setmusic(musictype: MusicType | None, continuous: bool = False) -> None:
|
def setmusic(musictype: MusicType | None, continuous: bool = False) -> None:
|
||||||
"""Set the app to play (or stop playing) a certain type of music.
|
"""Set the app to play (or stop playing) a certain type of music.
|
||||||
|
|
||||||
category: **Gameplay Functions**
|
This function will handle loading and playing sound assets as
|
||||||
|
necessary, and also supports custom user soundtracks on specific
|
||||||
This function will handle loading and playing sound assets as necessary,
|
platforms so the user can override particular game music with their
|
||||||
and also supports custom user soundtracks on specific platforms so the
|
own.
|
||||||
user can override particular game music with their own.
|
|
||||||
|
|
||||||
Pass None to stop music.
|
Pass None to stop music.
|
||||||
|
|
||||||
if 'continuous' is True and musictype is the same as what is already
|
if ``continuous`` is True and musictype is the same as what is
|
||||||
playing, the playing track will not be restarted.
|
already playing, the playing track will not be restarted.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# All we do here now is set a few music attrs on the current globals
|
# All we do here now is set a few music attrs on the current globals
|
||||||
|
|
|
||||||
2
dist/ba_data/python/bascenev1/_nodeactor.py
vendored
2
dist/ba_data/python/bascenev1/_nodeactor.py
vendored
|
|
@ -18,8 +18,6 @@ if TYPE_CHECKING:
|
||||||
class NodeActor(Actor):
|
class NodeActor(Actor):
|
||||||
"""A simple bascenev1.Actor type that wraps a single bascenev1.Node.
|
"""A simple bascenev1.Actor type that wraps a single bascenev1.Node.
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
|
||||||
|
|
||||||
This Actor will delete its Node when told to die, and it's
|
This Actor will delete its Node when told to die, and it's
|
||||||
exists() call will return whether the Node still exists or not.
|
exists() call will return whether the Node still exists or not.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
21
dist/ba_data/python/bascenev1/_player.py
vendored
21
dist/ba_data/python/bascenev1/_player.py
vendored
|
|
@ -24,10 +24,7 @@ TeamT = TypeVar('TeamT', bound='bascenev1.Team')
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PlayerInfo:
|
class PlayerInfo:
|
||||||
"""Holds basic info about a player.
|
"""Holds basic info about a player."""
|
||||||
|
|
||||||
Category: Gameplay Classes
|
|
||||||
"""
|
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
character: str
|
character: str
|
||||||
|
|
@ -35,10 +32,7 @@ class PlayerInfo:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class StandLocation:
|
class StandLocation:
|
||||||
"""Describes a point in space and an angle to face.
|
"""Describes a point in space and an angle to face."""
|
||||||
|
|
||||||
Category: Gameplay Classes
|
|
||||||
"""
|
|
||||||
|
|
||||||
position: babase.Vec3
|
position: babase.Vec3
|
||||||
angle: float | None = None
|
angle: float | None = None
|
||||||
|
|
@ -47,8 +41,6 @@ class StandLocation:
|
||||||
class Player(Generic[TeamT]):
|
class Player(Generic[TeamT]):
|
||||||
"""A player in a specific bascenev1.Activity.
|
"""A player in a specific bascenev1.Activity.
|
||||||
|
|
||||||
Category: Gameplay Classes
|
|
||||||
|
|
||||||
These correspond to bascenev1.SessionPlayer objects, but are associated
|
These correspond to bascenev1.SessionPlayer objects, but are associated
|
||||||
with a single bascenev1.Activity instance. This allows activities to
|
with a single bascenev1.Activity instance. This allows activities to
|
||||||
specify their own custom bascenev1.Player types.
|
specify their own custom bascenev1.Player types.
|
||||||
|
|
@ -282,8 +274,6 @@ class Player(Generic[TeamT]):
|
||||||
class EmptyPlayer(Player['bascenev1.EmptyTeam']):
|
class EmptyPlayer(Player['bascenev1.EmptyTeam']):
|
||||||
"""An empty player for use by Activities that don't need to define one.
|
"""An empty player for use by Activities that don't need to define one.
|
||||||
|
|
||||||
Category: Gameplay Classes
|
|
||||||
|
|
||||||
bascenev1.Player and bascenev1.Team are 'Generic' types, and so passing
|
bascenev1.Player and bascenev1.Team are 'Generic' types, and so passing
|
||||||
those top level classes as type arguments when defining a
|
those top level classes as type arguments when defining a
|
||||||
bascenev1.Activity reduces type safety. For example,
|
bascenev1.Activity reduces type safety. For example,
|
||||||
|
|
@ -306,8 +296,6 @@ class EmptyPlayer(Player['bascenev1.EmptyTeam']):
|
||||||
def playercast(totype: type[PlayerT], player: bascenev1.Player) -> PlayerT:
|
def playercast(totype: type[PlayerT], player: bascenev1.Player) -> PlayerT:
|
||||||
"""Cast a bascenev1.Player to a specific bascenev1.Player subclass.
|
"""Cast a bascenev1.Player to a specific bascenev1.Player subclass.
|
||||||
|
|
||||||
Category: Gameplay Functions
|
|
||||||
|
|
||||||
When writing type-checked code, sometimes code will deal with raw
|
When writing type-checked code, sometimes code will deal with raw
|
||||||
bascenev1.Player objects which need to be cast back to a game's actual
|
bascenev1.Player objects which need to be cast back to a game's actual
|
||||||
player type so that access can be properly type-checked. This function
|
player type so that access can be properly type-checked. This function
|
||||||
|
|
@ -324,9 +312,6 @@ def playercast(totype: type[PlayerT], player: bascenev1.Player) -> PlayerT:
|
||||||
def playercast_o(
|
def playercast_o(
|
||||||
totype: type[PlayerT], player: bascenev1.Player | None
|
totype: type[PlayerT], player: bascenev1.Player | None
|
||||||
) -> PlayerT | None:
|
) -> PlayerT | None:
|
||||||
"""A variant of bascenev1.playercast() for use with optional Player values.
|
"""A variant of bascenev1.playercast() for optional Player values."""
|
||||||
|
|
||||||
Category: Gameplay Functions
|
|
||||||
"""
|
|
||||||
assert isinstance(player, (totype, type(None)))
|
assert isinstance(player, (totype, type(None)))
|
||||||
return player
|
return player
|
||||||
|
|
|
||||||
11
dist/ba_data/python/bascenev1/_powerup.py
vendored
11
dist/ba_data/python/bascenev1/_powerup.py
vendored
|
|
@ -17,9 +17,8 @@ if TYPE_CHECKING:
|
||||||
class PowerupMessage:
|
class PowerupMessage:
|
||||||
"""A message telling an object to accept a powerup.
|
"""A message telling an object to accept a powerup.
|
||||||
|
|
||||||
Category: **Message Classes**
|
This message is normally received by touching a
|
||||||
|
bascenev1.PowerupBox.
|
||||||
This message is normally received by touching a bascenev1.PowerupBox.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
poweruptype: str
|
poweruptype: str
|
||||||
|
|
@ -38,10 +37,8 @@ class PowerupMessage:
|
||||||
class PowerupAcceptMessage:
|
class PowerupAcceptMessage:
|
||||||
"""A message informing a bascenev1.Powerup that it was accepted.
|
"""A message informing a bascenev1.Powerup that it was accepted.
|
||||||
|
|
||||||
Category: **Message Classes**
|
This is generally sent in response to a bascenev1.PowerupMessage to
|
||||||
|
inform the box (or whoever granted it) that it can go away.
|
||||||
This is generally sent in response to a bascenev1.PowerupMessage
|
|
||||||
to inform the box (or whoever granted it) that it can go away.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
10
dist/ba_data/python/bascenev1/_score.py
vendored
10
dist/ba_data/python/bascenev1/_score.py
vendored
|
|
@ -14,10 +14,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
class ScoreType(Enum):
|
class ScoreType(Enum):
|
||||||
"""Type of scores.
|
"""Type of scores."""
|
||||||
|
|
||||||
Category: **Enums**
|
|
||||||
"""
|
|
||||||
|
|
||||||
SECONDS = 's'
|
SECONDS = 's'
|
||||||
MILLISECONDS = 'ms'
|
MILLISECONDS = 'ms'
|
||||||
|
|
@ -26,10 +23,7 @@ class ScoreType(Enum):
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ScoreConfig:
|
class ScoreConfig:
|
||||||
"""Settings for how a game handles scores.
|
"""Settings for how a game handles scores."""
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
label: str = 'Score'
|
label: str = 'Score'
|
||||||
"""A label show to the user for scores; 'Score', 'Time Survived', etc."""
|
"""A label show to the user for scores; 'Score', 'Time Survived', etc."""
|
||||||
|
|
|
||||||
118
dist/ba_data/python/bascenev1/_session.py
vendored
118
dist/ba_data/python/bascenev1/_session.py
vendored
|
|
@ -40,58 +40,59 @@ def set_max_players_override(max_players: int | None) -> None:
|
||||||
|
|
||||||
|
|
||||||
class Session:
|
class Session:
|
||||||
"""Defines a high level series of bascenev1.Activity-es.
|
"""Wrangles a series of :class:`~bascenev1.Activity` instances.
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
Examples of sessions are :class:`bascenev1.FreeForAllSession`,
|
||||||
|
:class:`bascenev1.DualTeamSession`, and
|
||||||
|
:class:`bascenev1.CoopSession`.
|
||||||
|
|
||||||
Examples of sessions are bascenev1.FreeForAllSession,
|
A session is responsible for wrangling and transitioning between
|
||||||
bascenev1.DualTeamSession, and bascenev1.CoopSession.
|
various activity instances such as mini-games and score-screens, and
|
||||||
|
for maintaining state between them (players, teams, score tallies,
|
||||||
A Session is responsible for wrangling and transitioning between various
|
etc).
|
||||||
bascenev1.Activity instances such as mini-games and score-screens, and for
|
|
||||||
maintaining state between them (players, teams, score tallies, etc).
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
#: Whether this session groups players into an explicit set of teams.
|
||||||
|
#: If this is off, a unique team is generated for each player that
|
||||||
|
#: joins.
|
||||||
use_teams: bool = False
|
use_teams: bool = False
|
||||||
"""Whether this session groups players into an explicit set of
|
|
||||||
teams. If this is off, a unique team is generated for each
|
|
||||||
player that joins."""
|
|
||||||
|
|
||||||
|
#: Whether players on a team should all adopt the colors of that team
|
||||||
|
#: instead of their own profile colors. This only applies if
|
||||||
|
#: :attr:`use_teams` is enabled.
|
||||||
use_team_colors: bool = True
|
use_team_colors: bool = True
|
||||||
"""Whether players on a team should all adopt the colors of that
|
|
||||||
team instead of their own profile colors. This only applies if
|
|
||||||
use_teams is enabled."""
|
|
||||||
|
|
||||||
# Note: even though these are instance vars, we annotate and document them
|
# Note: even though these are instance vars, we annotate and
|
||||||
# at the class level so that looks better and nobody get lost while
|
# document them at the class level so that looks better and nobody
|
||||||
# reading large __init__
|
# get lost while reading large __init__
|
||||||
|
|
||||||
|
#: The lobby instance where new players go to select a
|
||||||
|
#: profile/team/etc. before being added to games. Be aware this value
|
||||||
|
#: may be None if a session does not allow any such selection.
|
||||||
lobby: bascenev1.Lobby
|
lobby: bascenev1.Lobby
|
||||||
"""The baclassic.Lobby instance where new bascenev1.Player-s go to select
|
|
||||||
a Profile/Team/etc. before being added to games.
|
|
||||||
Be aware this value may be None if a Session does not allow
|
|
||||||
any such selection."""
|
|
||||||
|
|
||||||
|
#: The maximum number of players allowed in the Session.
|
||||||
max_players: int
|
max_players: int
|
||||||
"""The maximum number of players allowed in the Session."""
|
|
||||||
|
|
||||||
|
#: The minimum number of players who must be present for the Session
|
||||||
|
#: to proceed past the initial joining screen
|
||||||
min_players: int
|
min_players: int
|
||||||
"""The minimum number of players who must be present for the Session
|
|
||||||
to proceed past the initial joining screen"""
|
|
||||||
|
|
||||||
|
#: All players in the session. Note that most things should use the
|
||||||
|
#: list of :class:`~bascenev1.Player` instances found in the
|
||||||
|
#: :class:`~bascenev1.Activity`; not this. Some players, such as
|
||||||
|
#: those who have not yet selected a character, will only be found on
|
||||||
|
#: this list.
|
||||||
sessionplayers: list[bascenev1.SessionPlayer]
|
sessionplayers: list[bascenev1.SessionPlayer]
|
||||||
"""All bascenev1.SessionPlayers in the Session. Most things should use
|
|
||||||
the list of bascenev1.Player-s in bascenev1.Activity; not this. Some
|
|
||||||
players, such as those who have not yet selected a character, will
|
|
||||||
only be found on this list."""
|
|
||||||
|
|
||||||
|
#: A shared dictionary for objects to use as storage on this session.
|
||||||
|
#: Ensure that keys here are unique to avoid collisions.
|
||||||
customdata: dict
|
customdata: dict
|
||||||
"""A shared dictionary for objects to use as storage on this session.
|
|
||||||
Ensure that keys here are unique to avoid collisions."""
|
|
||||||
|
|
||||||
|
#: All the teams in the session. Most things will operate on the list
|
||||||
|
#: of :class:`~bascenev1.Team` instances found in an
|
||||||
|
#: :class:`~bascenev1.Activity`; not this.
|
||||||
sessionteams: list[bascenev1.SessionTeam]
|
sessionteams: list[bascenev1.SessionTeam]
|
||||||
"""All the bascenev1.SessionTeams in the Session. Most things should
|
|
||||||
use the list of bascenev1.Team-s in bascenev1.Activity; not this."""
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
@ -243,7 +244,7 @@ class Session:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sessionglobalsnode(self) -> bascenev1.Node:
|
def sessionglobalsnode(self) -> bascenev1.Node:
|
||||||
"""The sessionglobals bascenev1.Node for the session."""
|
"""The sessionglobals node for the session."""
|
||||||
node = self._sessionglobalsnode
|
node = self._sessionglobalsnode
|
||||||
if not node:
|
if not node:
|
||||||
raise babase.NodeNotFoundError()
|
raise babase.NodeNotFoundError()
|
||||||
|
|
@ -254,15 +255,15 @@ class Session:
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Ask ourself if we should allow joins during an Activity.
|
"""Ask ourself if we should allow joins during an Activity.
|
||||||
|
|
||||||
Note that for a join to be allowed, both the Session and Activity
|
Note that for a join to be allowed, both the session and
|
||||||
have to be ok with it (via this function and the
|
activity have to be ok with it (via this function and the
|
||||||
Activity.allow_mid_activity_joins property.
|
:attr:`bascenev1.Activity.allow_mid_activity_joins` property.
|
||||||
"""
|
"""
|
||||||
del activity # Unused.
|
del activity # Unused.
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def on_player_request(self, player: bascenev1.SessionPlayer) -> bool:
|
def on_player_request(self, player: bascenev1.SessionPlayer) -> bool:
|
||||||
"""Called when a new bascenev1.Player wants to join the Session.
|
"""Called when a new player wants to join the session.
|
||||||
|
|
||||||
This should return True or False to accept/reject.
|
This should return True or False to accept/reject.
|
||||||
"""
|
"""
|
||||||
|
|
@ -426,10 +427,10 @@ class Session:
|
||||||
)
|
)
|
||||||
|
|
||||||
def end(self) -> None:
|
def end(self) -> None:
|
||||||
"""Initiates an end to the session and a return to the main menu.
|
"""Initiate an end to the session and a return to the main menu.
|
||||||
|
|
||||||
Note that this happens asynchronously, allowing the
|
Note that this happens asynchronously, allowing the session and
|
||||||
session and its activities to shut down gracefully.
|
its activities to shut down gracefully.
|
||||||
"""
|
"""
|
||||||
self._wants_to_end = True
|
self._wants_to_end = True
|
||||||
if self._next_activity is None:
|
if self._next_activity is None:
|
||||||
|
|
@ -457,10 +458,10 @@ class Session:
|
||||||
self._ending = True # Prevent further actions.
|
self._ending = True # Prevent further actions.
|
||||||
|
|
||||||
def on_team_join(self, team: bascenev1.SessionTeam) -> None:
|
def on_team_join(self, team: bascenev1.SessionTeam) -> None:
|
||||||
"""Called when a new bascenev1.Team joins the session."""
|
"""Called when a new team joins the session."""
|
||||||
|
|
||||||
def on_team_leave(self, team: bascenev1.SessionTeam) -> None:
|
def on_team_leave(self, team: bascenev1.SessionTeam) -> None:
|
||||||
"""Called when a bascenev1.Team is leaving the session."""
|
"""Called when a team is leaving the session."""
|
||||||
|
|
||||||
def end_activity(
|
def end_activity(
|
||||||
self,
|
self,
|
||||||
|
|
@ -469,12 +470,12 @@ class Session:
|
||||||
delay: float,
|
delay: float,
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Commence shutdown of a bascenev1.Activity (if not already occurring).
|
"""Commence shutdown of an activity (if not already occurring).
|
||||||
|
|
||||||
'delay' is the time delay before the Activity actually ends
|
'delay' is the time delay before the activity actually ends (in
|
||||||
(in seconds). Further calls to end() will be ignored up until
|
seconds). Further calls to end the activity will be ignored up
|
||||||
this time, unless 'force' is True, in which case the new results
|
until this time, unless 'force' is True, in which case the new
|
||||||
will replace the old.
|
results will replace the old.
|
||||||
"""
|
"""
|
||||||
# Only pay attention if this is coming from our current activity.
|
# Only pay attention if this is coming from our current activity.
|
||||||
if activity is not self._activity_retained:
|
if activity is not self._activity_retained:
|
||||||
|
|
@ -530,13 +531,13 @@ class Session:
|
||||||
self._session._in_set_activity = False
|
self._session._in_set_activity = False
|
||||||
|
|
||||||
def setactivity(self, activity: bascenev1.Activity) -> None:
|
def setactivity(self, activity: bascenev1.Activity) -> None:
|
||||||
"""Assign a new current bascenev1.Activity for the session.
|
"""Assign a new current activity for the session.
|
||||||
|
|
||||||
Note that this will not change the current context to the new
|
Note that this will not change the current context to the new
|
||||||
Activity's. Code must be run in the new activity's methods
|
activity's. Code must be run in the new activity's methods
|
||||||
(on_transition_in, etc) to get it. (so you can't do
|
(:meth:`~bascenev1.Activity.on_transition_in()`, etc) to get it.
|
||||||
session.setactivity(foo) and then bascenev1.newnode() to add a node
|
(so you can't do ``session.setactivity(foo)`` and then
|
||||||
to foo)
|
``bascenev1.newnode()`` to add a node to foo).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Make sure we don't get called recursively.
|
# Make sure we don't get called recursively.
|
||||||
|
|
@ -656,16 +657,16 @@ class Session:
|
||||||
def on_activity_end(
|
def on_activity_end(
|
||||||
self, activity: bascenev1.Activity, results: Any
|
self, activity: bascenev1.Activity, results: Any
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Called when the current bascenev1.Activity has ended.
|
"""Called when the current activity has ended.
|
||||||
|
|
||||||
The bascenev1.Session should look at the results and start
|
The session should look at the results and start another
|
||||||
another bascenev1.Activity.
|
activity.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def begin_next_activity(self) -> None:
|
def begin_next_activity(self) -> None:
|
||||||
"""Called once the previous activity has been totally torn down.
|
"""Called once the previous activity has been totally torn down.
|
||||||
|
|
||||||
This means we're ready to begin the next one
|
This means we're ready to begin the next one.
|
||||||
"""
|
"""
|
||||||
if self._next_activity is None:
|
if self._next_activity is None:
|
||||||
# Should this ever happen?
|
# Should this ever happen?
|
||||||
|
|
@ -742,7 +743,10 @@ class Session:
|
||||||
def transitioning_out_activity_was_freed(
|
def transitioning_out_activity_was_freed(
|
||||||
self, can_show_ad_on_death: bool
|
self, can_show_ad_on_death: bool
|
||||||
) -> None:
|
) -> None:
|
||||||
"""(internal)"""
|
"""(internal)
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
# pylint: disable=cyclic-import
|
# pylint: disable=cyclic-import
|
||||||
|
|
||||||
# Since things should be generally still right now, it's a good time
|
# Since things should be generally still right now, it's a good time
|
||||||
|
|
|
||||||
35
dist/ba_data/python/bascenev1/_settings.py
vendored
35
dist/ba_data/python/bascenev1/_settings.py
vendored
|
|
@ -13,10 +13,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Setting:
|
class Setting:
|
||||||
"""Defines a user-controllable setting for a game or other entity.
|
"""Defines a user-controllable setting for a game or other entity."""
|
||||||
|
|
||||||
Category: Gameplay Classes
|
|
||||||
"""
|
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
default: Any
|
default: Any
|
||||||
|
|
@ -24,20 +21,14 @@ class Setting:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BoolSetting(Setting):
|
class BoolSetting(Setting):
|
||||||
"""A boolean game setting.
|
"""A boolean game setting."""
|
||||||
|
|
||||||
Category: Settings Classes
|
|
||||||
"""
|
|
||||||
|
|
||||||
default: bool
|
default: bool
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class IntSetting(Setting):
|
class IntSetting(Setting):
|
||||||
"""An integer game setting.
|
"""An integer game setting."""
|
||||||
|
|
||||||
Category: Settings Classes
|
|
||||||
"""
|
|
||||||
|
|
||||||
default: int
|
default: int
|
||||||
min_value: int = 0
|
min_value: int = 0
|
||||||
|
|
@ -47,10 +38,7 @@ class IntSetting(Setting):
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FloatSetting(Setting):
|
class FloatSetting(Setting):
|
||||||
"""A floating point game setting.
|
"""A floating point game setting."""
|
||||||
|
|
||||||
Category: Settings Classes
|
|
||||||
"""
|
|
||||||
|
|
||||||
default: float
|
default: float
|
||||||
min_value: float = 0.0
|
min_value: float = 0.0
|
||||||
|
|
@ -60,20 +48,14 @@ class FloatSetting(Setting):
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ChoiceSetting(Setting):
|
class ChoiceSetting(Setting):
|
||||||
"""A setting with multiple choices.
|
"""A setting with multiple choices."""
|
||||||
|
|
||||||
Category: Settings Classes
|
|
||||||
"""
|
|
||||||
|
|
||||||
choices: list[tuple[str, Any]]
|
choices: list[tuple[str, Any]]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class IntChoiceSetting(ChoiceSetting):
|
class IntChoiceSetting(ChoiceSetting):
|
||||||
"""An int setting with multiple choices.
|
"""An int setting with multiple choices."""
|
||||||
|
|
||||||
Category: Settings Classes
|
|
||||||
"""
|
|
||||||
|
|
||||||
default: int
|
default: int
|
||||||
choices: list[tuple[str, int]]
|
choices: list[tuple[str, int]]
|
||||||
|
|
@ -81,10 +63,7 @@ class IntChoiceSetting(ChoiceSetting):
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FloatChoiceSetting(ChoiceSetting):
|
class FloatChoiceSetting(ChoiceSetting):
|
||||||
"""A float setting with multiple choices.
|
"""A float setting with multiple choices."""
|
||||||
|
|
||||||
Category: Settings Classes
|
|
||||||
"""
|
|
||||||
|
|
||||||
default: float
|
default: float
|
||||||
choices: list[tuple[str, float]]
|
choices: list[tuple[str, float]]
|
||||||
|
|
|
||||||
12
dist/ba_data/python/bascenev1/_stats.py
vendored
12
dist/ba_data/python/bascenev1/_stats.py
vendored
|
|
@ -22,10 +22,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PlayerScoredMessage:
|
class PlayerScoredMessage:
|
||||||
"""Informs something that a bascenev1.Player scored.
|
"""Informs something that a bascenev1.Player scored."""
|
||||||
|
|
||||||
Category: **Message Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
score: int
|
score: int
|
||||||
"""The score value."""
|
"""The score value."""
|
||||||
|
|
@ -34,8 +31,6 @@ class PlayerScoredMessage:
|
||||||
class PlayerRecord:
|
class PlayerRecord:
|
||||||
"""Stats for an individual player in a bascenev1.Stats object.
|
"""Stats for an individual player in a bascenev1.Stats object.
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
|
||||||
|
|
||||||
This does not necessarily correspond to a bascenev1.Player that is
|
This does not necessarily correspond to a bascenev1.Player that is
|
||||||
still present (stats may be retained for players that leave
|
still present (stats may be retained for players that leave
|
||||||
mid-game)
|
mid-game)
|
||||||
|
|
@ -253,10 +248,7 @@ class PlayerRecord:
|
||||||
|
|
||||||
|
|
||||||
class Stats:
|
class Stats:
|
||||||
"""Manages scores and statistics for a bascenev1.Session.
|
"""Manages scores and statistics for a bascenev1.Session."""
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._activity: weakref.ref[bascenev1.Activity] | None = None
|
self._activity: weakref.ref[bascenev1.Activity] | None = None
|
||||||
|
|
|
||||||
94
dist/ba_data/python/bascenev1/_team.py
vendored
94
dist/ba_data/python/bascenev1/_team.py
vendored
|
|
@ -17,34 +17,32 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class SessionTeam:
|
class SessionTeam:
|
||||||
"""A team of one or more bascenev1.SessionPlayers.
|
"""A team of one or more :class:`~bascenev1.SessionPlayer`.
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
Note that a player will *always* have a team. in some cases, such as
|
||||||
|
free-for-all :class:`~bascenev1.Sessions`, each team consists of
|
||||||
Note that a SessionPlayer *always* has a SessionTeam;
|
just one player.
|
||||||
in some cases, such as free-for-all bascenev1.Sessions,
|
|
||||||
each SessionTeam consists of just one SessionPlayer.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Annotate our attr types at the class level so they're introspectable.
|
# We annotate our attr types at the class level so they're more
|
||||||
|
# introspectable by docs tools/etc.
|
||||||
|
|
||||||
|
#: The team's name.
|
||||||
name: babase.Lstr | str
|
name: babase.Lstr | str
|
||||||
"""The team's name."""
|
|
||||||
|
|
||||||
|
#: The team's color.
|
||||||
color: tuple[float, ...] # FIXME: can't we make this fixed len?
|
color: tuple[float, ...] # FIXME: can't we make this fixed len?
|
||||||
"""The team's color."""
|
|
||||||
|
|
||||||
|
#: The list of players on the team.
|
||||||
players: list[bascenev1.SessionPlayer]
|
players: list[bascenev1.SessionPlayer]
|
||||||
"""The list of bascenev1.SessionPlayer-s on the team."""
|
|
||||||
|
|
||||||
|
#: A dict for use by the current :class:`~bascenev1.Session` for
|
||||||
|
#: storing data associated with this team. Unlike customdata, this
|
||||||
|
#: persists for the duration of the session.
|
||||||
customdata: dict
|
customdata: dict
|
||||||
"""A dict for use by the current bascenev1.Session for
|
|
||||||
storing data associated with this team.
|
|
||||||
Unlike customdata, this persists for the duration
|
|
||||||
of the session."""
|
|
||||||
|
|
||||||
|
#: The unique numeric id of the team.
|
||||||
id: int
|
id: int
|
||||||
"""The unique numeric id of the team."""
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
@ -52,12 +50,6 @@ class SessionTeam:
|
||||||
name: babase.Lstr | str = '',
|
name: babase.Lstr | str = '',
|
||||||
color: Sequence[float] = (1.0, 1.0, 1.0),
|
color: Sequence[float] = (1.0, 1.0, 1.0),
|
||||||
):
|
):
|
||||||
"""Instantiate a bascenev1.SessionTeam.
|
|
||||||
|
|
||||||
In most cases, all teams are provided to you by the bascenev1.Session,
|
|
||||||
bascenev1.Session, so calling this shouldn't be necessary.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.id = team_id
|
self.id = team_id
|
||||||
self.name = name
|
self.name = name
|
||||||
self.color = tuple(color)
|
self.color = tuple(color)
|
||||||
|
|
@ -66,7 +58,10 @@ class SessionTeam:
|
||||||
self.activityteam: Team | None = None
|
self.activityteam: Team | None = None
|
||||||
|
|
||||||
def leave(self) -> None:
|
def leave(self) -> None:
|
||||||
"""(internal)"""
|
"""(internal)
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
self.customdata = {}
|
self.customdata = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -74,12 +69,11 @@ PlayerT = TypeVar('PlayerT', bound='bascenev1.Player')
|
||||||
|
|
||||||
|
|
||||||
class Team(Generic[PlayerT]):
|
class Team(Generic[PlayerT]):
|
||||||
"""A team in a specific bascenev1.Activity.
|
"""A team in a specific :class:`~bascenev1.Activity`.
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
These correspond to :class:`~bascenev1.SessionTeam` objects, but are
|
||||||
|
created per activity so that the activity can use its own custom
|
||||||
These correspond to bascenev1.SessionTeam objects, but are created
|
team subclass.
|
||||||
per activity so that the activity can use its own custom team subclass.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Defining these types at the class level instead of in __init__ so
|
# Defining these types at the class level instead of in __init__ so
|
||||||
|
|
@ -97,9 +91,9 @@ class Team(Generic[PlayerT]):
|
||||||
# get called by default if a dataclass inherits from us.
|
# get called by default if a dataclass inherits from us.
|
||||||
|
|
||||||
def postinit(self, sessionteam: SessionTeam) -> None:
|
def postinit(self, sessionteam: SessionTeam) -> None:
|
||||||
"""Wire up a newly created SessionTeam.
|
"""Internal: Wire up a newly created SessionTeam.
|
||||||
|
|
||||||
(internal)
|
:meta private:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Sanity check; if a dataclass is created that inherits from us,
|
# Sanity check; if a dataclass is created that inherits from us,
|
||||||
|
|
@ -136,22 +130,23 @@ class Team(Generic[PlayerT]):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def customdata(self) -> dict:
|
def customdata(self) -> dict:
|
||||||
"""Arbitrary values associated with the team.
|
"""Arbitrary values associated with the team. Though it is
|
||||||
Though it is encouraged that most player values be properly defined
|
encouraged that most player values be properly defined on the
|
||||||
on the bascenev1.Team subclass, it may be useful for player-agnostic
|
:class:`~bascenev1.Team` subclass, it may be useful for
|
||||||
objects to store values here. This dict is cleared when the team
|
player-agnostic objects to store values here. This dict is
|
||||||
leaves or expires so objects stored here will be disposed of at
|
cleared when the team leaves or expires so objects stored here
|
||||||
the expected time, unlike the Team instance itself which may
|
will be disposed of at the expected time, unlike the
|
||||||
continue to be referenced after it is no longer part of the game.
|
:class:`~bascenev1.Team` instance itself which may continue to
|
||||||
|
be referenced after it is no longer part of the game.
|
||||||
"""
|
"""
|
||||||
assert self._postinited
|
assert self._postinited
|
||||||
assert not self._expired
|
assert not self._expired
|
||||||
return self._customdata
|
return self._customdata
|
||||||
|
|
||||||
def leave(self) -> None:
|
def leave(self) -> None:
|
||||||
"""Called when the Team leaves a running game.
|
"""Internal: Called when the team leaves a running game.
|
||||||
|
|
||||||
(internal)
|
:meta private:
|
||||||
"""
|
"""
|
||||||
assert self._postinited
|
assert self._postinited
|
||||||
assert not self._expired
|
assert not self._expired
|
||||||
|
|
@ -159,9 +154,9 @@ class Team(Generic[PlayerT]):
|
||||||
del self.players
|
del self.players
|
||||||
|
|
||||||
def expire(self) -> None:
|
def expire(self) -> None:
|
||||||
"""Called when the Team is expiring (due to the Activity expiring).
|
"""Internal: Called when team is expiring (due to its activity).
|
||||||
|
|
||||||
(internal)
|
:meta private:
|
||||||
"""
|
"""
|
||||||
assert self._postinited
|
assert self._postinited
|
||||||
assert not self._expired
|
assert not self._expired
|
||||||
|
|
@ -180,9 +175,10 @@ class Team(Generic[PlayerT]):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sessionteam(self) -> SessionTeam:
|
def sessionteam(self) -> SessionTeam:
|
||||||
"""Return the bascenev1.SessionTeam corresponding to this Team.
|
"""The :class:`~bascenev1.SessionTeam` corresponding to this team.
|
||||||
|
|
||||||
Throws a babase.SessionTeamNotFoundError if there is none.
|
Throws a :class:`~babase.SessionTeamNotFoundError` if there is
|
||||||
|
none.
|
||||||
"""
|
"""
|
||||||
assert self._postinited
|
assert self._postinited
|
||||||
if self._sessionteam is not None:
|
if self._sessionteam is not None:
|
||||||
|
|
@ -194,18 +190,16 @@ class Team(Generic[PlayerT]):
|
||||||
|
|
||||||
|
|
||||||
class EmptyTeam(Team['bascenev1.EmptyPlayer']):
|
class EmptyTeam(Team['bascenev1.EmptyPlayer']):
|
||||||
"""An empty player for use by Activities that don't need to define one.
|
"""An empty player for use by Activities that don't define one.
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
bascenev1.Player and bascenev1.Team are 'Generic' types, and so
|
||||||
|
passing those top level classes as type arguments when defining a
|
||||||
bascenev1.Player and bascenev1.Team are 'Generic' types, and so passing
|
|
||||||
those top level classes as type arguments when defining a
|
|
||||||
bascenev1.Activity reduces type safety. For example,
|
bascenev1.Activity reduces type safety. For example,
|
||||||
activity.teams[0].player will have type 'Any' in that case. For that
|
activity.teams[0].player will have type 'Any' in that case. For that
|
||||||
reason, it is better to pass EmptyPlayer and EmptyTeam when defining
|
reason, it is better to pass EmptyPlayer and EmptyTeam when defining
|
||||||
a bascenev1.Activity that does not need custom types of its own.
|
a bascenev1.Activity that does not need custom types of its own.
|
||||||
|
|
||||||
Note that EmptyPlayer defines its team type as EmptyTeam and vice versa,
|
Note that EmptyPlayer defines its team type as EmptyTeam and vice
|
||||||
so if you want to define your own class for one of them you should do so
|
versa, so if you want to define your own class for one of them you
|
||||||
for both.
|
should do so for both.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
33
dist/ba_data/python/bascenev1/_teamgame.py
vendored
33
dist/ba_data/python/bascenev1/_teamgame.py
vendored
|
|
@ -29,10 +29,8 @@ TeamT = TypeVar('TeamT', bound='bascenev1.Team')
|
||||||
class TeamGameActivity(GameActivity[PlayerT, TeamT]):
|
class TeamGameActivity(GameActivity[PlayerT, TeamT]):
|
||||||
"""Base class for teams and free-for-all mode games.
|
"""Base class for teams and free-for-all mode games.
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
(Free-for-all is essentially just a special case where every player
|
||||||
|
has their own team)
|
||||||
(Free-for-all is essentially just a special case where every
|
|
||||||
bascenev1.Player has their own bascenev1.Team)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -40,11 +38,7 @@ class TeamGameActivity(GameActivity[PlayerT, TeamT]):
|
||||||
def supports_session_type(
|
def supports_session_type(
|
||||||
cls, sessiontype: type[bascenev1.Session]
|
cls, sessiontype: type[bascenev1.Session]
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
# By default, team games support dual-teams and ffa.
|
||||||
Class method override;
|
|
||||||
returns True for ba.DualTeamSessions and ba.FreeForAllSessions;
|
|
||||||
False otherwise.
|
|
||||||
"""
|
|
||||||
return issubclass(sessiontype, DualTeamSession) or issubclass(
|
return issubclass(sessiontype, DualTeamSession) or issubclass(
|
||||||
sessiontype, FreeForAllSession
|
sessiontype, FreeForAllSession
|
||||||
)
|
)
|
||||||
|
|
@ -52,9 +46,9 @@ class TeamGameActivity(GameActivity[PlayerT, TeamT]):
|
||||||
def __init__(self, settings: dict):
|
def __init__(self, settings: dict):
|
||||||
super().__init__(settings)
|
super().__init__(settings)
|
||||||
|
|
||||||
# By default we don't show kill-points in free-for-all sessions.
|
# By default we don't show kill-points in free-for-all sessions
|
||||||
# (there's usually some activity-specific score and we don't
|
# (there's usually some activity-specific score and we don't
|
||||||
# wanna confuse things)
|
# wanna confuse things).
|
||||||
if isinstance(self.session, FreeForAllSession):
|
if isinstance(self.session, FreeForAllSession):
|
||||||
self.show_kill_points = False
|
self.show_kill_points = False
|
||||||
|
|
||||||
|
|
@ -66,9 +60,9 @@ class TeamGameActivity(GameActivity[PlayerT, TeamT]):
|
||||||
|
|
||||||
super().on_transition_in()
|
super().on_transition_in()
|
||||||
|
|
||||||
# On the first game, show the controls UI momentarily.
|
# On the first game, show the controls UI momentarily (unless
|
||||||
# (unless we're being run in co-op mode, in which case we leave
|
# we're being run in co-op mode, in which case we leave it up to
|
||||||
# it up to them)
|
# them).
|
||||||
if not isinstance(self.session, CoopSession) and getattr(
|
if not isinstance(self.session, CoopSession) and getattr(
|
||||||
self, 'show_controls_guide', True
|
self, 'show_controls_guide', True
|
||||||
):
|
):
|
||||||
|
|
@ -114,12 +108,13 @@ class TeamGameActivity(GameActivity[PlayerT, TeamT]):
|
||||||
position: Sequence[float] | None = None,
|
position: Sequence[float] | None = None,
|
||||||
angle: float | None = None,
|
angle: float | None = None,
|
||||||
) -> PlayerSpaz:
|
) -> PlayerSpaz:
|
||||||
"""
|
"""Override to spawn and wire up a standard
|
||||||
Method override; spawns and wires up a standard bascenev1.PlayerSpaz
|
:class:`~bascenev1lib.actor.playerspaz.PlayerSpaz` for a
|
||||||
for a bascenev1.Player.
|
:class:`~bascenev1.Player`.
|
||||||
|
|
||||||
If position or angle is not supplied, a default will be chosen based
|
If position or angle is not supplied, a default will be chosen
|
||||||
on the bascenev1.Player and their bascenev1.Team.
|
based on the :class:`~bascenev1.Player` and their
|
||||||
|
:class:`~bascenev1.Team`.
|
||||||
"""
|
"""
|
||||||
if position is None:
|
if position is None:
|
||||||
# In teams-mode get our team-start-location.
|
# In teams-mode get our team-start-location.
|
||||||
|
|
|
||||||
104
dist/ba_data/python/bascenev1lib/actor/bomb.py
vendored
104
dist/ba_data/python/bascenev1lib/actor/bomb.py
vendored
|
|
@ -17,115 +17,115 @@ from bascenev1lib.gameutils import SharedObjects
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Any, Sequence, Callable
|
from typing import Any, Sequence, Callable
|
||||||
|
|
||||||
|
import bascenev1
|
||||||
|
|
||||||
PlayerT = TypeVar('PlayerT', bound='bs.Player')
|
PlayerT = TypeVar('PlayerT', bound='bs.Player')
|
||||||
|
|
||||||
|
|
||||||
class BombFactory:
|
class BombFactory:
|
||||||
"""Wraps up media and other resources used by bs.Bombs.
|
"""Wraps up media and other resources used by bs.Bombs.
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
|
||||||
|
|
||||||
A single instance of this is shared between all bombs
|
A single instance of this is shared between all bombs
|
||||||
and can be retrieved via bascenev1lib.actor.bomb.get_factory().
|
and can be retrieved via bascenev1lib.actor.bomb.get_factory().
|
||||||
"""
|
"""
|
||||||
|
|
||||||
bomb_mesh: bs.Mesh
|
bomb_mesh: bascenev1.Mesh
|
||||||
"""The bs.Mesh of a standard or ice bomb."""
|
"""The mesh used for standard or ice bombs."""
|
||||||
|
|
||||||
sticky_bomb_mesh: bs.Mesh
|
sticky_bomb_mesh: bascenev1.Mesh
|
||||||
"""The bs.Mesh of a sticky-bomb."""
|
"""The mesh used for sticky-bombs."""
|
||||||
|
|
||||||
impact_bomb_mesh: bs.Mesh
|
impact_bomb_mesh: bascenev1.Mesh
|
||||||
"""The bs.Mesh of an impact-bomb."""
|
"""The mesh used for impact-bombs."""
|
||||||
|
|
||||||
land_mine_mesh: bs.Mesh
|
land_mine_mesh: bascenev1.Mesh
|
||||||
"""The bs.Mesh of a land-mine."""
|
"""The mesh used for land-mines."""
|
||||||
|
|
||||||
tnt_mesh: bs.Mesh
|
tnt_mesh: bascenev1.Mesh
|
||||||
"""The bs.Mesh of a tnt box."""
|
"""The mesh used of a tnt box."""
|
||||||
|
|
||||||
regular_tex: bs.Texture
|
regular_tex: bascenev1.Texture
|
||||||
"""The bs.Texture for regular bombs."""
|
"""The texture used for regular bombs."""
|
||||||
|
|
||||||
ice_tex: bs.Texture
|
ice_tex: bascenev1.Texture
|
||||||
"""The bs.Texture for ice bombs."""
|
"""The bs.Texture for ice bombs."""
|
||||||
|
|
||||||
sticky_tex: bs.Texture
|
sticky_tex: bascenev1.Texture
|
||||||
"""The bs.Texture for sticky bombs."""
|
"""The bs.Texture for sticky bombs."""
|
||||||
|
|
||||||
impact_tex: bs.Texture
|
impact_tex: bascenev1.Texture
|
||||||
"""The bs.Texture for impact bombs."""
|
"""The bs.Texture for impact bombs."""
|
||||||
|
|
||||||
impact_lit_tex: bs.Texture
|
impact_lit_tex: bascenev1.Texture
|
||||||
"""The bs.Texture for impact bombs with lights lit."""
|
"""The bs.Texture for impact bombs with lights lit."""
|
||||||
|
|
||||||
land_mine_tex: bs.Texture
|
land_mine_tex: bascenev1.Texture
|
||||||
"""The bs.Texture for land-mines."""
|
"""The bs.Texture for land-mines."""
|
||||||
|
|
||||||
land_mine_lit_tex: bs.Texture
|
land_mine_lit_tex: bascenev1.Texture
|
||||||
"""The bs.Texture for land-mines with the light lit."""
|
"""The bs.Texture for land-mines with the light lit."""
|
||||||
|
|
||||||
tnt_tex: bs.Texture
|
tnt_tex: bascenev1.Texture
|
||||||
"""The bs.Texture for tnt boxes."""
|
"""The bs.Texture for tnt boxes."""
|
||||||
|
|
||||||
hiss_sound: bs.Sound
|
hiss_sound: bascenev1.Sound
|
||||||
"""The bs.Sound for the hiss sound an ice bomb makes."""
|
"""The sound for the hiss sound an ice bomb makes."""
|
||||||
|
|
||||||
debris_fall_sound: bs.Sound
|
debris_fall_sound: bascenev1.Sound
|
||||||
"""The bs.Sound for random falling debris after an explosion."""
|
"""The sound for random falling debris after an explosion."""
|
||||||
|
|
||||||
wood_debris_fall_sound: bs.Sound
|
wood_debris_fall_sound: bascenev1.Sound
|
||||||
"""A bs.Sound for random wood debris falling after an explosion."""
|
"""A sound for random wood debris falling after an explosion."""
|
||||||
|
|
||||||
explode_sounds: Sequence[bs.Sound]
|
explode_sounds: Sequence[bascenev1.Sound]
|
||||||
"""A tuple of bs.Sound-s for explosions."""
|
"""A tuple of sounds for explosions."""
|
||||||
|
|
||||||
freeze_sound: bs.Sound
|
freeze_sound: bascenev1.Sound
|
||||||
"""A bs.Sound of an ice bomb freezing something."""
|
"""A sound of an ice bomb freezing something."""
|
||||||
|
|
||||||
fuse_sound: bs.Sound
|
fuse_sound: bascenev1.Sound
|
||||||
"""A bs.Sound of a burning fuse."""
|
"""A sound of a burning fuse."""
|
||||||
|
|
||||||
activate_sound: bs.Sound
|
activate_sound: bascenev1.Sound
|
||||||
"""A bs.Sound for an activating impact bomb."""
|
"""A sound for an activating impact bomb."""
|
||||||
|
|
||||||
warn_sound: bs.Sound
|
warn_sound: bascenev1.Sound
|
||||||
"""A bs.Sound for an impact bomb about to explode due to time-out."""
|
"""A sound for an impact bomb about to explode due to time-out."""
|
||||||
|
|
||||||
bomb_material: bs.Material
|
bomb_material: bascenev1.Material
|
||||||
"""A bs.Material applied to all bombs."""
|
"""A bs.Material applied to all bombs."""
|
||||||
|
|
||||||
normal_sound_material: bs.Material
|
normal_sound_material: bascenev1.Material
|
||||||
"""A bs.Material that generates standard bomb noises on impacts, etc."""
|
"""A bs.Material that generates standard bomb noises on impacts, etc."""
|
||||||
|
|
||||||
sticky_material: bs.Material
|
sticky_material: bascenev1.Material
|
||||||
"""A bs.Material that makes 'splat' sounds and makes collisions softer."""
|
"""A bs.Material that makes 'splat' sounds and makes collisions softer."""
|
||||||
|
|
||||||
land_mine_no_explode_material: bs.Material
|
land_mine_no_explode_material: bascenev1.Material
|
||||||
"""A bs.Material that keeps land-mines from blowing up.
|
"""A bs.Material that keeps land-mines from blowing up.
|
||||||
Applied to land-mines when they are created to allow land-mines to
|
Applied to land-mines when they are created to allow land-mines to
|
||||||
touch without exploding."""
|
touch without exploding."""
|
||||||
|
|
||||||
land_mine_blast_material: bs.Material
|
land_mine_blast_material: bascenev1.Material
|
||||||
"""A bs.Material applied to activated land-mines that causes them to
|
"""A bs.Material applied to activated land-mines that causes them to
|
||||||
explode on impact."""
|
explode on impact."""
|
||||||
|
|
||||||
impact_blast_material: bs.Material
|
impact_blast_material: bascenev1.Material
|
||||||
"""A bs.Material applied to activated impact-bombs that causes them to
|
"""A bs.Material applied to activated impact-bombs that causes them to
|
||||||
explode on impact."""
|
explode on impact."""
|
||||||
|
|
||||||
blast_material: bs.Material
|
blast_material: bascenev1.Material
|
||||||
"""A bs.Material applied to bomb blast geometry which triggers impact
|
"""A bs.Material applied to bomb blast geometry which triggers impact
|
||||||
events with what it touches."""
|
events with what it touches."""
|
||||||
|
|
||||||
dink_sounds: Sequence[bs.Sound]
|
dink_sounds: Sequence[bascenev1.Sound]
|
||||||
"""A tuple of bs.Sound-s for when bombs hit the ground."""
|
"""A tuple of sounds for when bombs hit the ground."""
|
||||||
|
|
||||||
sticky_impact_sound: bs.Sound
|
sticky_impact_sound: bascenev1.Sound
|
||||||
"""The bs.Sound for a squish made by a sticky bomb hitting something."""
|
"""The sound for a squish made by a sticky bomb hitting something."""
|
||||||
|
|
||||||
roll_sound: bs.Sound
|
roll_sound: bascenev1.Sound
|
||||||
"""bs.Sound for a rolling bomb."""
|
"""The sound for a rolling bomb."""
|
||||||
|
|
||||||
_STORENAME = bs.storagename()
|
_STORENAME = bs.storagename()
|
||||||
|
|
||||||
|
|
@ -140,8 +140,8 @@ class BombFactory:
|
||||||
assert isinstance(factory, BombFactory)
|
assert isinstance(factory, BombFactory)
|
||||||
return factory
|
return factory
|
||||||
|
|
||||||
def random_explode_sound(self) -> bs.Sound:
|
def random_explode_sound(self) -> bascenev1.Sound:
|
||||||
"""Return a random explosion bs.Sound from the factory."""
|
"""Return a random explosion sound from the factory."""
|
||||||
return self.explode_sounds[random.randrange(len(self.explode_sounds))]
|
return self.explode_sounds[random.randrange(len(self.explode_sounds))]
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
|
|
||||||
58
dist/ba_data/python/bascenev1lib/actor/flag.py
vendored
58
dist/ba_data/python/bascenev1lib/actor/flag.py
vendored
|
|
@ -16,38 +16,31 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class FlagFactory:
|
class FlagFactory:
|
||||||
"""Wraps up media and other resources used by `Flag`s.
|
"""Wraps up media and resources used by :class:`Flag`.
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
A single instance of this is shared between all flags and can be
|
||||||
|
retrieved via :meth:`FlagFactory.get()`.
|
||||||
A single instance of this is shared between all flags
|
|
||||||
and can be retrieved via FlagFactory.get().
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
#: The material applied to all flags.
|
||||||
flagmaterial: bs.Material
|
flagmaterial: bs.Material
|
||||||
"""The bs.Material applied to all `Flag`s."""
|
|
||||||
|
|
||||||
|
#: The sound used when a flag hits the ground.
|
||||||
impact_sound: bs.Sound
|
impact_sound: bs.Sound
|
||||||
"""The bs.Sound used when a `Flag` hits the ground."""
|
|
||||||
|
|
||||||
|
#: The sound used when a flag skids along the ground.
|
||||||
skid_sound: bs.Sound
|
skid_sound: bs.Sound
|
||||||
"""The bs.Sound used when a `Flag` skids along the ground."""
|
|
||||||
|
|
||||||
|
#: A material that prevents contact with most objects.
|
||||||
|
#: This gets applied to 'non-touchable' flags.
|
||||||
no_hit_material: bs.Material
|
no_hit_material: bs.Material
|
||||||
"""A bs.Material that prevents contact with most objects;
|
|
||||||
applied to 'non-touchable' flags."""
|
|
||||||
|
|
||||||
flag_texture: bs.Texture
|
flag_texture: bs.Texture
|
||||||
"""The bs.Texture for flags."""
|
"""The texture for flags."""
|
||||||
|
|
||||||
_STORENAME = bs.storagename()
|
_STORENAME = bs.storagename()
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""Instantiate a `FlagFactory`.
|
|
||||||
|
|
||||||
You shouldn't need to do this; call FlagFactory.get() to
|
|
||||||
get a shared instance.
|
|
||||||
"""
|
|
||||||
shared = SharedObjects.get()
|
shared = SharedObjects.get()
|
||||||
self.flagmaterial = bs.Material()
|
self.flagmaterial = bs.Material()
|
||||||
self.flagmaterial.add_actions(
|
self.flagmaterial.add_actions(
|
||||||
|
|
@ -110,7 +103,7 @@ class FlagFactory:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls) -> FlagFactory:
|
def get(cls) -> FlagFactory:
|
||||||
"""Get/create a shared `FlagFactory` instance."""
|
"""Get/create a shared flag-factory instance."""
|
||||||
activity = bs.getactivity()
|
activity = bs.getactivity()
|
||||||
factory = activity.customdata.get(cls._STORENAME)
|
factory = activity.customdata.get(cls._STORENAME)
|
||||||
if factory is None:
|
if factory is None:
|
||||||
|
|
@ -122,51 +115,40 @@ class FlagFactory:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FlagPickedUpMessage:
|
class FlagPickedUpMessage:
|
||||||
"""A message saying a `Flag` has been picked up.
|
"""A message saying a flag has been picked up."""
|
||||||
|
|
||||||
Category: **Message Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
#: The flag that has been picked up.
|
||||||
flag: Flag
|
flag: Flag
|
||||||
"""The `Flag` that has been picked up."""
|
|
||||||
|
|
||||||
|
#: The bs.Node doing the picking up.
|
||||||
node: bs.Node
|
node: bs.Node
|
||||||
"""The bs.Node doing the picking up."""
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FlagDiedMessage:
|
class FlagDiedMessage:
|
||||||
"""A message saying a `Flag` has died.
|
"""A message saying a `Flag` has died."""
|
||||||
|
|
||||||
Category: **Message Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
#: The flag that died.
|
||||||
flag: Flag
|
flag: Flag
|
||||||
"""The `Flag` that died."""
|
|
||||||
|
|
||||||
|
#: Whether the flag killed itself.
|
||||||
self_kill: bool = False
|
self_kill: bool = False
|
||||||
"""If the `Flag` killed itself or not."""
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FlagDroppedMessage:
|
class FlagDroppedMessage:
|
||||||
"""A message saying a `Flag` has been dropped.
|
"""A message saying a `Flag` has been dropped."""
|
||||||
|
|
||||||
Category: **Message Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
#: The flag that was dropped.
|
||||||
flag: Flag
|
flag: Flag
|
||||||
"""The `Flag` that was dropped."""
|
|
||||||
|
|
||||||
|
#: The node that was holding the flag.
|
||||||
node: bs.Node
|
node: bs.Node
|
||||||
"""The bs.Node that was holding it."""
|
|
||||||
|
|
||||||
|
|
||||||
class Flag(bs.Actor):
|
class Flag(bs.Actor):
|
||||||
"""A flag; used in games such as capture-the-flag or king-of-the-hill.
|
"""A flag; used in games such as capture-the-flag or king-of-the-hill.
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
|
||||||
|
|
||||||
Can be stationary or carry-able by players.
|
Can be stationary or carry-able by players.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -382,7 +364,7 @@ class Flag(bs.Actor):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def project_stand(pos: Sequence[float]) -> None:
|
def project_stand(pos: Sequence[float]) -> None:
|
||||||
"""Project a flag-stand onto the ground at the given position.
|
"""Project a flag-stand onto the ground from a position.
|
||||||
|
|
||||||
Useful for games such as capture-the-flag to show where a
|
Useful for games such as capture-the-flag to show where a
|
||||||
movable flag originated from.
|
movable flag originated from.
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@ if TYPE_CHECKING:
|
||||||
class OnScreenCountdown(bs.Actor):
|
class OnScreenCountdown(bs.Actor):
|
||||||
"""A Handy On-Screen Timer.
|
"""A Handy On-Screen Timer.
|
||||||
|
|
||||||
category: Gameplay Classes
|
|
||||||
|
|
||||||
Useful for time-based games that count down to zero.
|
Useful for time-based games that count down to zero.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@ if TYPE_CHECKING:
|
||||||
class OnScreenTimer(bs.Actor):
|
class OnScreenTimer(bs.Actor):
|
||||||
"""A handy on-screen timer.
|
"""A handy on-screen timer.
|
||||||
|
|
||||||
category: Gameplay Classes
|
|
||||||
|
|
||||||
Useful for time-based games where time increases.
|
Useful for time-based games where time increases.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,7 @@ PlayerT = TypeVar('PlayerT', bound=bs.Player)
|
||||||
|
|
||||||
|
|
||||||
class PlayerSpazHurtMessage:
|
class PlayerSpazHurtMessage:
|
||||||
"""A message saying a PlayerSpaz was hurt.
|
"""A message saying a PlayerSpaz was hurt."""
|
||||||
|
|
||||||
Category: **Message Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
spaz: PlayerSpaz
|
spaz: PlayerSpaz
|
||||||
"""The PlayerSpaz that was hurt"""
|
"""The PlayerSpaz that was hurt"""
|
||||||
|
|
@ -33,8 +30,6 @@ class PlayerSpazHurtMessage:
|
||||||
class PlayerSpaz(Spaz):
|
class PlayerSpaz(Spaz):
|
||||||
"""A Spaz subclass meant to be controlled by a bascenev1.Player.
|
"""A Spaz subclass meant to be controlled by a bascenev1.Player.
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
|
||||||
|
|
||||||
When a PlayerSpaz dies, it delivers a bascenev1.PlayerDiedMessage
|
When a PlayerSpaz dies, it delivers a bascenev1.PlayerDiedMessage
|
||||||
to the current bascenev1.Activity. (unless the death was the result
|
to the current bascenev1.Activity. (unless the death was the result
|
||||||
of the player leaving the game, in which case no message is sent)
|
of the player leaving the game, in which case no message is sent)
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,6 @@ class _TouchedMessage:
|
||||||
class PowerupBoxFactory:
|
class PowerupBoxFactory:
|
||||||
"""A collection of media and other resources used by bs.Powerups.
|
"""A collection of media and other resources used by bs.Powerups.
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
|
||||||
|
|
||||||
A single instance of this is shared between all powerups
|
A single instance of this is shared between all powerups
|
||||||
and can be retrieved via bs.Powerup.get_factory().
|
and can be retrieved via bs.Powerup.get_factory().
|
||||||
"""
|
"""
|
||||||
|
|
@ -190,19 +188,18 @@ class PowerupBoxFactory:
|
||||||
class PowerupBox(bs.Actor):
|
class PowerupBox(bs.Actor):
|
||||||
"""A box that grants a powerup.
|
"""A box that grants a powerup.
|
||||||
|
|
||||||
category: Gameplay Classes
|
This will deliver a :class:`~bascenev1.PowerupMessage` to anything
|
||||||
|
that touches it which has the
|
||||||
This will deliver a bs.PowerupMessage to anything that touches it
|
:class:`~PowerupBoxFactory.powerup_accept_material` applied.
|
||||||
which has the bs.PowerupBoxFactory.powerup_accept_material applied.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
#: The string powerup type. This can be 'triple_bombs', 'punch',
|
||||||
|
#: 'ice_bombs', 'impact_bombs', 'land_mines', 'sticky_bombs',
|
||||||
|
#: 'shield', 'health', or 'curse'.
|
||||||
poweruptype: str
|
poweruptype: str
|
||||||
"""The string powerup type. This can be 'triple_bombs', 'punch',
|
|
||||||
'ice_bombs', 'impact_bombs', 'land_mines', 'sticky_bombs', 'shield',
|
|
||||||
'health', or 'curse'."""
|
|
||||||
|
|
||||||
node: bs.Node
|
node: bs.Node
|
||||||
"""The 'prop' bs.Node representing this box."""
|
"""The 'prop' node representing this box."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,7 @@ import bascenev1 as bs
|
||||||
class RespawnIcon:
|
class RespawnIcon:
|
||||||
"""An icon with a countdown that appears alongside the screen.
|
"""An icon with a countdown that appears alongside the screen.
|
||||||
|
|
||||||
category: Gameplay Classes
|
This is used to indicate that a player is waiting to respawn.
|
||||||
|
|
||||||
This is used to indicate that a bascenev1.Player is waiting to respawn.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_MASKTEXSTORENAME = bs.storagename('masktex')
|
_MASKTEXSTORENAME = bs.storagename('masktex')
|
||||||
|
|
|
||||||
|
|
@ -16,17 +16,12 @@ if TYPE_CHECKING:
|
||||||
class Spawner:
|
class Spawner:
|
||||||
"""Utility for delayed spawning of objects.
|
"""Utility for delayed spawning of objects.
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
Creates a light flash and sends a Spawner.SpawnMessage to the
|
||||||
|
current activity after a delay.
|
||||||
Creates a light flash and sends a Spawner.SpawnMessage
|
|
||||||
to the current activity after a delay.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class SpawnMessage:
|
class SpawnMessage:
|
||||||
"""Spawn message sent by a Spawner after its delay has passed.
|
"""Spawn message sent by a Spawner after its delay has passed."""
|
||||||
|
|
||||||
Category: **Message Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
spawner: Spawner
|
spawner: Spawner
|
||||||
"""The bascenev1.Spawner we came from."""
|
"""The bascenev1.Spawner we came from."""
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,6 @@ class Spaz(bs.Actor):
|
||||||
"""
|
"""
|
||||||
Base class for various Spazzes.
|
Base class for various Spazzes.
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
|
||||||
|
|
||||||
A Spaz is the standard little humanoid character in the game.
|
A Spaz is the standard little humanoid character in the game.
|
||||||
It can be controlled by a player or by AI, and can have
|
It can be controlled by a player or by AI, and can have
|
||||||
various different appearances. The name 'Spaz' is not to be
|
various different appearances. The name 'Spaz' is not to be
|
||||||
|
|
@ -886,7 +884,9 @@ class Spaz(bs.Actor):
|
||||||
if not self.frozen:
|
if not self.frozen:
|
||||||
self.frozen = True
|
self.frozen = True
|
||||||
self.node.frozen = True
|
self.node.frozen = True
|
||||||
bs.timer(5.0, bs.WeakCall(self.handlemessage, bs.ThawMessage()))
|
bs.timer(
|
||||||
|
msg.time, bs.WeakCall(self.handlemessage, bs.ThawMessage())
|
||||||
|
)
|
||||||
# Instantly shatter if we're already dead.
|
# Instantly shatter if we're already dead.
|
||||||
# (otherwise its hard to tell we're dead).
|
# (otherwise its hard to tell we're dead).
|
||||||
if self.hitpoints <= 0:
|
if self.hitpoints <= 0:
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,7 @@ PRO_BOT_HIGHLIGHT = (0.6, 0.1, 0.05)
|
||||||
|
|
||||||
|
|
||||||
class SpazBotPunchedMessage:
|
class SpazBotPunchedMessage:
|
||||||
"""A message saying a bs.SpazBot got punched.
|
"""A message saying a bs.SpazBot got punched."""
|
||||||
|
|
||||||
Category: **Message Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
spazbot: SpazBot
|
spazbot: SpazBot
|
||||||
"""The bs.SpazBot that got punched."""
|
"""The bs.SpazBot that got punched."""
|
||||||
|
|
@ -44,10 +41,7 @@ class SpazBotPunchedMessage:
|
||||||
|
|
||||||
|
|
||||||
class SpazBotDiedMessage:
|
class SpazBotDiedMessage:
|
||||||
"""A message saying a bs.SpazBot has died.
|
"""A message saying a bs.SpazBot has died."""
|
||||||
|
|
||||||
Category: **Message Classes**
|
|
||||||
"""
|
|
||||||
|
|
||||||
spazbot: SpazBot
|
spazbot: SpazBot
|
||||||
"""The SpazBot that was killed."""
|
"""The SpazBot that was killed."""
|
||||||
|
|
@ -73,8 +67,6 @@ class SpazBotDiedMessage:
|
||||||
class SpazBot(Spaz):
|
class SpazBot(Spaz):
|
||||||
"""A really dumb AI version of bs.Spaz.
|
"""A really dumb AI version of bs.Spaz.
|
||||||
|
|
||||||
Category: **Bot Classes**
|
|
||||||
|
|
||||||
Add these to a bs.BotSet to use them.
|
Add these to a bs.BotSet to use them.
|
||||||
|
|
||||||
Note: currently the AI has no real ability to
|
Note: currently the AI has no real ability to
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,6 @@ if TYPE_CHECKING:
|
||||||
class SpazFactory:
|
class SpazFactory:
|
||||||
"""Wraps up media and other resources used by bs.Spaz instances.
|
"""Wraps up media and other resources used by bs.Spaz instances.
|
||||||
|
|
||||||
Category: **Gameplay Classes**
|
|
||||||
|
|
||||||
Generally one of these is created per bascenev1.Activity and shared
|
Generally one of these is created per bascenev1.Activity and shared
|
||||||
between all spaz instances. Use bs.Spaz.get_factory() to return
|
between all spaz instances. Use bs.Spaz.get_factory() to return
|
||||||
the shared factory for the current activity.
|
the shared factory for the current activity.
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,6 @@ if TYPE_CHECKING:
|
||||||
class ZoomText(bs.Actor):
|
class ZoomText(bs.Actor):
|
||||||
"""Big Zooming Text.
|
"""Big Zooming Text.
|
||||||
|
|
||||||
Category: Gameplay Classes
|
|
||||||
|
|
||||||
Used for things such as the 'BOB WINS' victory messages.
|
Used for things such as the 'BOB WINS' victory messages.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,14 @@ class Team(bs.Team[Player]):
|
||||||
"""Our team type for this game."""
|
"""Our team type for this game."""
|
||||||
|
|
||||||
def __init__(self, base_pos: Sequence[float], flag: Flag) -> None:
|
def __init__(self, base_pos: Sequence[float], flag: Flag) -> None:
|
||||||
|
|
||||||
|
#: Where our base is.
|
||||||
self.base_pos = base_pos
|
self.base_pos = base_pos
|
||||||
|
|
||||||
|
#: Flag for this team.
|
||||||
self.flag = flag
|
self.flag = flag
|
||||||
|
|
||||||
|
#: Current score.
|
||||||
self.score = 0
|
self.score = 0
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@ if TYPE_CHECKING:
|
||||||
class SharedObjects:
|
class SharedObjects:
|
||||||
"""Various common components for use in games.
|
"""Various common components for use in games.
|
||||||
|
|
||||||
Category: Gameplay Classes
|
|
||||||
|
|
||||||
Objects contained here are created on-demand as accessed and shared
|
Objects contained here are created on-demand as accessed and shared
|
||||||
by everything in the current activity. This includes things such as
|
by everything in the current activity. This includes things such as
|
||||||
standard materials.
|
standard materials.
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ if TYPE_CHECKING:
|
||||||
class TemplateFsAppSubsystem:
|
class TemplateFsAppSubsystem:
|
||||||
"""Subsystem for TemplateFs functionality in the app.
|
"""Subsystem for TemplateFs functionality in the app.
|
||||||
|
|
||||||
The single shared instance of this class can be accessed at
|
If :attr:`~batools.featureset.FeatureSet.has_python_app_subsystem`
|
||||||
ba*.app.templatefs. Note that it is possible for ba*.app.templatefs
|
is enabled for our feature-set, the single shared instance of this
|
||||||
to be None if the TemplateFs feature-set is not enabled, and code
|
class can be accessed as `template_fs` on the :class:`~babase.App`
|
||||||
should handle that case gracefully.
|
instance.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
6
dist/ba_data/python/bauiv1/__init__.py
vendored
6
dist/ba_data/python/bauiv1/__init__.py
vendored
|
|
@ -21,6 +21,7 @@ from babase import (
|
||||||
add_clean_frame_callback,
|
add_clean_frame_callback,
|
||||||
allows_ticket_sales,
|
allows_ticket_sales,
|
||||||
app,
|
app,
|
||||||
|
App,
|
||||||
AppIntent,
|
AppIntent,
|
||||||
AppIntentDefault,
|
AppIntentDefault,
|
||||||
AppIntentExec,
|
AppIntentExec,
|
||||||
|
|
@ -28,6 +29,7 @@ from babase import (
|
||||||
appname,
|
appname,
|
||||||
appnameupper,
|
appnameupper,
|
||||||
apptime,
|
apptime,
|
||||||
|
AppState,
|
||||||
AppTime,
|
AppTime,
|
||||||
apptimer,
|
apptimer,
|
||||||
AppTimer,
|
AppTimer,
|
||||||
|
|
@ -137,6 +139,7 @@ from bauiv1._uitypes import (
|
||||||
BasicMainWindowState,
|
BasicMainWindowState,
|
||||||
uicleanupcheck,
|
uicleanupcheck,
|
||||||
MainWindow,
|
MainWindow,
|
||||||
|
RootUIUpdatePause,
|
||||||
)
|
)
|
||||||
from bauiv1._appsubsystem import UIV1AppSubsystem
|
from bauiv1._appsubsystem import UIV1AppSubsystem
|
||||||
|
|
||||||
|
|
@ -144,6 +147,7 @@ __all__ = [
|
||||||
'add_clean_frame_callback',
|
'add_clean_frame_callback',
|
||||||
'allows_ticket_sales',
|
'allows_ticket_sales',
|
||||||
'app',
|
'app',
|
||||||
|
'App',
|
||||||
'AppIntent',
|
'AppIntent',
|
||||||
'AppIntentDefault',
|
'AppIntentDefault',
|
||||||
'AppIntentExec',
|
'AppIntentExec',
|
||||||
|
|
@ -151,6 +155,7 @@ __all__ = [
|
||||||
'appname',
|
'appname',
|
||||||
'appnameupper',
|
'appnameupper',
|
||||||
'appnameupper',
|
'appnameupper',
|
||||||
|
'AppState',
|
||||||
'apptime',
|
'apptime',
|
||||||
'AppTime',
|
'AppTime',
|
||||||
'apptimer',
|
'apptimer',
|
||||||
|
|
@ -229,6 +234,7 @@ __all__ = [
|
||||||
'request_permission',
|
'request_permission',
|
||||||
'root_ui_pause_updates',
|
'root_ui_pause_updates',
|
||||||
'root_ui_resume_updates',
|
'root_ui_resume_updates',
|
||||||
|
'RootUIUpdatePause',
|
||||||
'rowwidget',
|
'rowwidget',
|
||||||
'safecolor',
|
'safecolor',
|
||||||
'screenmessage',
|
'screenmessage',
|
||||||
|
|
|
||||||
123
dist/ba_data/python/bauiv1/_appsubsystem.py
vendored
123
dist/ba_data/python/bauiv1/_appsubsystem.py
vendored
|
|
@ -32,8 +32,6 @@ if TYPE_CHECKING:
|
||||||
class UIV1AppSubsystem(babase.AppSubsystem):
|
class UIV1AppSubsystem(babase.AppSubsystem):
|
||||||
"""Consolidated UI functionality for the app.
|
"""Consolidated UI functionality for the app.
|
||||||
|
|
||||||
Category: **App Classes**
|
|
||||||
|
|
||||||
To use this class, access the single instance of it at 'ba.app.ui'.
|
To use this class, access the single instance of it at 'ba.app.ui'.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -86,9 +84,12 @@ class UIV1AppSubsystem(babase.AppSubsystem):
|
||||||
self.heading_color = (0.72, 0.7, 0.75)
|
self.heading_color = (0.72, 0.7, 0.75)
|
||||||
self.infotextcolor = (0.7, 0.9, 0.7)
|
self.infotextcolor = (0.7, 0.9, 0.7)
|
||||||
|
|
||||||
self._last_win_recreate_size: tuple[float, float] | None = None
|
self.window_auto_recreate_suppress_count = 0
|
||||||
self._last_screen_size_win_recreate_time: float | None = None
|
|
||||||
self._screen_size_win_recreate_timer: babase.AppTimer | None = None
|
self._last_win_recreate_screen_size: tuple[float, float] | None = None
|
||||||
|
self._last_win_recreate_uiscale: bauiv1.UIScale | None = None
|
||||||
|
self._last_win_recreate_time: float | None = None
|
||||||
|
self._win_recreate_timer: babase.AppTimer | None = None
|
||||||
|
|
||||||
# Elements in our root UI will call anything here when
|
# Elements in our root UI will call anything here when
|
||||||
# activated.
|
# activated.
|
||||||
|
|
@ -196,6 +197,15 @@ class UIV1AppSubsystem(babase.AppSubsystem):
|
||||||
# pylint: disable=too-many-statements
|
# pylint: disable=too-many-statements
|
||||||
from bauiv1._uitypes import MainWindow
|
from bauiv1._uitypes import MainWindow
|
||||||
|
|
||||||
|
# If we haven't grabbed initial uiscale or screen size for recreate
|
||||||
|
# comparision purposes, this is a good time to do so.
|
||||||
|
if self._last_win_recreate_screen_size is None:
|
||||||
|
self._last_win_recreate_screen_size = (
|
||||||
|
babase.get_virtual_screen_size()
|
||||||
|
)
|
||||||
|
if self._last_win_recreate_uiscale is None:
|
||||||
|
self._last_win_recreate_uiscale = babase.app.ui_v1.uiscale
|
||||||
|
|
||||||
# Encourage migration to the new higher level nav calls.
|
# Encourage migration to the new higher level nav calls.
|
||||||
if not suppress_warning:
|
if not suppress_warning:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
|
|
@ -398,6 +408,23 @@ class UIV1AppSubsystem(babase.AppSubsystem):
|
||||||
suppress_warning=True,
|
suppress_warning=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def should_suppress_window_recreates(self) -> bool:
|
||||||
|
"""Should we avoid auto-recreating windows at the current time?"""
|
||||||
|
|
||||||
|
# This is slightly hack-ish and ideally we can get to the point
|
||||||
|
# where we never need this and can remove it.
|
||||||
|
|
||||||
|
# Currently string-edits grab a weak-ref to the exact text
|
||||||
|
# widget they're targeting. So we need to suppress recreates
|
||||||
|
# while edits are in progress. Ideally we should change that to
|
||||||
|
# use ids or something that would survive a recreate.
|
||||||
|
if babase.app.stringedit.active_adapter() is not None:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Suppress if anything else is requesting suppression (such as
|
||||||
|
# generic Windows that don't handle being recreated).
|
||||||
|
return babase.app.ui_v1.window_auto_recreate_suppress_count > 0
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def on_ui_scale_change(self) -> None:
|
def on_ui_scale_change(self) -> None:
|
||||||
# Update our stored UIScale.
|
# Update our stored UIScale.
|
||||||
|
|
@ -406,64 +433,82 @@ class UIV1AppSubsystem(babase.AppSubsystem):
|
||||||
# Update native bits (allow root widget to rebuild itself/etc.)
|
# Update native bits (allow root widget to rebuild itself/etc.)
|
||||||
_bauiv1.on_ui_scale_change()
|
_bauiv1.on_ui_scale_change()
|
||||||
|
|
||||||
# Lastly, if we have a main window, recreate it to pick up the
|
self._schedule_main_win_recreate()
|
||||||
# new UIScale/etc.
|
|
||||||
mainwindow = self.get_main_window()
|
|
||||||
if mainwindow is not None:
|
|
||||||
winstate = self.save_main_window_state(mainwindow)
|
|
||||||
self.clear_main_window(transition='instant')
|
|
||||||
self.restore_main_window_state(winstate)
|
|
||||||
|
|
||||||
# Store the size we created this for to avoid redundant
|
|
||||||
# future recreates.
|
|
||||||
self._last_win_recreate_size = babase.get_virtual_screen_size()
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def on_screen_size_change(self) -> None:
|
def on_screen_size_change(self) -> None:
|
||||||
|
|
||||||
# Recreating a MainWindow is a kinda heavy thing and it doesn't
|
self._schedule_main_win_recreate()
|
||||||
# seem like we should be doing it at 120hz during a live window
|
|
||||||
# resize, so let's limit the max rate we do it.
|
|
||||||
now = time.monotonic()
|
|
||||||
|
|
||||||
# 4 refreshes per second seems reasonable.
|
def _schedule_main_win_recreate(self) -> None:
|
||||||
interval = 0.25
|
|
||||||
|
|
||||||
# If there is a timer set already, do nothing.
|
# If there is a timer set already, do nothing.
|
||||||
if self._screen_size_win_recreate_timer is not None:
|
if self._win_recreate_timer is not None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Recreating a MainWindow is a kinda heavy thing and it doesn't
|
||||||
|
# seem like we should be doing it at 120hz during a live window
|
||||||
|
# resize, so let's limit the max rate we do it. We also use the
|
||||||
|
# same mechanism to defer window recreates while anything is
|
||||||
|
# suppressing them.
|
||||||
|
now = time.monotonic()
|
||||||
|
|
||||||
|
# Up to 4 refreshes per second seems reasonable.
|
||||||
|
interval = 0.25
|
||||||
|
|
||||||
# Ok; there's no timer. Schedule one.
|
# Ok; there's no timer. Schedule one.
|
||||||
till_update = (
|
till_update = (
|
||||||
|
interval
|
||||||
|
if self.should_suppress_window_recreates()
|
||||||
|
else (
|
||||||
0.0
|
0.0
|
||||||
if self._last_screen_size_win_recreate_time is None
|
if self._last_win_recreate_time is None
|
||||||
else max(
|
else max(0.0, self._last_win_recreate_time + interval - now)
|
||||||
0.0, self._last_screen_size_win_recreate_time + interval - now
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self._screen_size_win_recreate_timer = babase.AppTimer(
|
self._win_recreate_timer = babase.AppTimer(
|
||||||
till_update, self._do_screen_size_win_recreate
|
till_update, self._do_main_win_recreate
|
||||||
)
|
)
|
||||||
|
|
||||||
def _do_screen_size_win_recreate(self) -> None:
|
def _do_main_win_recreate(self) -> None:
|
||||||
self._last_screen_size_win_recreate_time = time.monotonic()
|
self._last_win_recreate_time = time.monotonic()
|
||||||
self._screen_size_win_recreate_timer = None
|
self._win_recreate_timer = None
|
||||||
|
|
||||||
# Avoid recreating if we're already at this size. This prevents
|
# If win-recreates are currently suppressed, just kick off
|
||||||
# a redundant recreate when ui scale changes.
|
# another timer. We'll do our actual thing once suppression
|
||||||
virtual_screen_size = babase.get_virtual_screen_size()
|
# finally ends.
|
||||||
if virtual_screen_size == self._last_win_recreate_size:
|
if self.should_suppress_window_recreates():
|
||||||
|
self._schedule_main_win_recreate()
|
||||||
return
|
return
|
||||||
|
|
||||||
mainwindow = self.get_main_window()
|
mainwindow = self.get_main_window()
|
||||||
if (
|
|
||||||
mainwindow is not None
|
# Can't recreate what doesn't exist.
|
||||||
and mainwindow.refreshes_on_screen_size_changes
|
if mainwindow is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
virtual_screen_size = babase.get_virtual_screen_size()
|
||||||
|
uiscale = babase.app.ui_v1.uiscale
|
||||||
|
|
||||||
|
# These should always get actual values when a main-window is
|
||||||
|
# assigned so should never still be None here.
|
||||||
|
assert self._last_win_recreate_uiscale is not None
|
||||||
|
assert self._last_win_recreate_screen_size is not None
|
||||||
|
|
||||||
|
# If uiscale hasn't changed and our screen-size hasn't either
|
||||||
|
# (or it has but we don't care) then we're done.
|
||||||
|
if uiscale is self._last_win_recreate_uiscale and (
|
||||||
|
virtual_screen_size == self._last_win_recreate_screen_size
|
||||||
|
or not mainwindow.refreshes_on_screen_size_changes
|
||||||
):
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Do the recreate.
|
||||||
winstate = self.save_main_window_state(mainwindow)
|
winstate = self.save_main_window_state(mainwindow)
|
||||||
self.clear_main_window(transition='instant')
|
self.clear_main_window(transition='instant')
|
||||||
self.restore_main_window_state(winstate)
|
self.restore_main_window_state(winstate)
|
||||||
|
|
||||||
# Store the size we created this for to avoid redundant
|
# Store the size we created this for to avoid redundant
|
||||||
# future recreates.
|
# future recreates.
|
||||||
self._last_win_recreate_size = virtual_screen_size
|
self._last_win_recreate_uiscale = uiscale
|
||||||
|
self._last_win_recreate_screen_size = virtual_screen_size
|
||||||
|
|
|
||||||
8
dist/ba_data/python/bauiv1/_keyboard.py
vendored
8
dist/ba_data/python/bauiv1/_keyboard.py
vendored
|
|
@ -13,11 +13,9 @@ if TYPE_CHECKING:
|
||||||
class Keyboard:
|
class Keyboard:
|
||||||
"""Chars definitions for on-screen keyboard.
|
"""Chars definitions for on-screen keyboard.
|
||||||
|
|
||||||
Category: **App Classes**
|
Keyboards are discoverable by the meta-tag system and the user can
|
||||||
|
select which one they want to use. On-screen keyboard uses chars
|
||||||
Keyboards are discoverable by the meta-tag system
|
from active babase.Keyboard.
|
||||||
and the user can select which one they want to use.
|
|
||||||
On-screen keyboard uses chars from active babase.Keyboard.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
|
|
|
||||||
50
dist/ba_data/python/bauiv1/_uitypes.py
vendored
50
dist/ba_data/python/bauiv1/_uitypes.py
vendored
|
|
@ -27,19 +27,37 @@ DEBUG_UI_CLEANUP_CHECKS = os.environ.get('BA_DEBUG_UI_CLEANUP_CHECKS') == '1'
|
||||||
class Window:
|
class Window:
|
||||||
"""A basic window.
|
"""A basic window.
|
||||||
|
|
||||||
Category: User Interface Classes
|
|
||||||
|
|
||||||
Essentially wraps a ContainerWidget with some higher level
|
Essentially wraps a ContainerWidget with some higher level
|
||||||
functionality.
|
functionality.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, root_widget: bauiv1.Widget, cleanupcheck: bool = True):
|
def __init__(
|
||||||
|
self,
|
||||||
|
root_widget: bauiv1.Widget,
|
||||||
|
cleanupcheck: bool = True,
|
||||||
|
prevent_main_window_auto_recreate: bool = True,
|
||||||
|
):
|
||||||
self._root_widget = root_widget
|
self._root_widget = root_widget
|
||||||
|
|
||||||
# Complain if we outlive our root widget.
|
# By default, the presence of any generic windows prevents the
|
||||||
|
# app from running its fancy main-window-auto-recreate mechanism
|
||||||
|
# on screen-resizes and whatnot. This avoids things like
|
||||||
|
# temporary popup windows getting stuck under auto-re-created
|
||||||
|
# main-windows.
|
||||||
|
self._prevent_main_window_auto_recreate = (
|
||||||
|
prevent_main_window_auto_recreate
|
||||||
|
)
|
||||||
|
if prevent_main_window_auto_recreate:
|
||||||
|
babase.app.ui_v1.window_auto_recreate_suppress_count += 1
|
||||||
|
|
||||||
|
# Generally we complain if we outlive our root widget.
|
||||||
if cleanupcheck:
|
if cleanupcheck:
|
||||||
uicleanupcheck(self, root_widget)
|
uicleanupcheck(self, root_widget)
|
||||||
|
|
||||||
|
def __del__(self) -> None:
|
||||||
|
if self._prevent_main_window_auto_recreate:
|
||||||
|
babase.app.ui_v1.window_auto_recreate_suppress_count -= 1
|
||||||
|
|
||||||
def get_root_widget(self) -> bauiv1.Widget:
|
def get_root_widget(self) -> bauiv1.Widget:
|
||||||
"""Return the root widget."""
|
"""Return the root widget."""
|
||||||
return self._root_widget
|
return self._root_widget
|
||||||
|
|
@ -66,8 +84,8 @@ class MainWindow(Window):
|
||||||
):
|
):
|
||||||
"""Create a MainWindow given a root widget and transition info.
|
"""Create a MainWindow given a root widget and transition info.
|
||||||
|
|
||||||
Automatically handles in and out transitions on the provided widget,
|
Automatically handles in and out transitions on the provided
|
||||||
so there is no need to set transitions when creating it.
|
widget, so there is no need to set transitions when creating it.
|
||||||
"""
|
"""
|
||||||
# A back-state supplied by the ui system.
|
# A back-state supplied by the ui system.
|
||||||
self.main_window_back_state: MainWindowState | None = None
|
self.main_window_back_state: MainWindowState | None = None
|
||||||
|
|
@ -89,7 +107,11 @@ class MainWindow(Window):
|
||||||
|
|
||||||
self._main_window_transition = transition
|
self._main_window_transition = transition
|
||||||
self._main_window_origin_widget = origin_widget
|
self._main_window_origin_widget = origin_widget
|
||||||
super().__init__(root_widget, cleanupcheck)
|
super().__init__(
|
||||||
|
root_widget,
|
||||||
|
cleanupcheck=cleanupcheck,
|
||||||
|
prevent_main_window_auto_recreate=False,
|
||||||
|
)
|
||||||
|
|
||||||
scale_origin: tuple[float, float] | None
|
scale_origin: tuple[float, float] | None
|
||||||
if origin_widget is not None:
|
if origin_widget is not None:
|
||||||
|
|
@ -120,7 +142,7 @@ class MainWindow(Window):
|
||||||
|
|
||||||
# Note: normally transition of None means instant, but we use
|
# Note: normally transition of None means instant, but we use
|
||||||
# that to mean 'do the default' so we support a special
|
# that to mean 'do the default' so we support a special
|
||||||
# 'instant' string..
|
# 'instant' string.
|
||||||
if transition == 'instant':
|
if transition == 'instant':
|
||||||
self._root_widget.delete()
|
self._root_widget.delete()
|
||||||
else:
|
else:
|
||||||
|
|
@ -304,8 +326,6 @@ class UICleanupCheck:
|
||||||
def uicleanupcheck(obj: Any, widget: bauiv1.Widget) -> None:
|
def uicleanupcheck(obj: Any, widget: bauiv1.Widget) -> None:
|
||||||
"""Checks to ensure a widget-owning object gets cleaned up properly.
|
"""Checks to ensure a widget-owning object gets cleaned up properly.
|
||||||
|
|
||||||
Category: User Interface Functions
|
|
||||||
|
|
||||||
This adds a check which will print an error message if the provided
|
This adds a check which will print an error message if the provided
|
||||||
object still exists ~5 seconds after the provided bauiv1.Widget dies.
|
object still exists ~5 seconds after the provided bauiv1.Widget dies.
|
||||||
|
|
||||||
|
|
@ -407,3 +427,13 @@ class TextWidgetStringEditAdapter(babase.StringEditAdapter):
|
||||||
def _do_cancel(self) -> None:
|
def _do_cancel(self) -> None:
|
||||||
if self.widget:
|
if self.widget:
|
||||||
_bauiv1.textwidget(edit=self.widget, adapter_finished=True)
|
_bauiv1.textwidget(edit=self.widget, adapter_finished=True)
|
||||||
|
|
||||||
|
|
||||||
|
class RootUIUpdatePause:
|
||||||
|
"""Pauses updates to the root-ui while in existence."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
_bauiv1.root_ui_pause_updates()
|
||||||
|
|
||||||
|
def __del__(self) -> None:
|
||||||
|
_bauiv1.root_ui_resume_updates()
|
||||||
|
|
|
||||||
|
|
@ -338,8 +338,8 @@ class AccountSettingsWindow(bui.MainWindow):
|
||||||
deprecated_space = 60
|
deprecated_space = 60
|
||||||
|
|
||||||
# Game Center currently has a single UI for everything.
|
# Game Center currently has a single UI for everything.
|
||||||
show_game_service_button = game_center_active
|
show_game_center_button = game_center_active
|
||||||
game_service_button_space = 60.0
|
game_center_button_space = 60.0
|
||||||
|
|
||||||
# Phasing this out (for V2 accounts at least).
|
# Phasing this out (for V2 accounts at least).
|
||||||
show_linked_accounts_text = (
|
show_linked_accounts_text = (
|
||||||
|
|
@ -431,8 +431,8 @@ class AccountSettingsWindow(bui.MainWindow):
|
||||||
self._sub_height += sign_in_button_space
|
self._sub_height += sign_in_button_space
|
||||||
if show_device_sign_in_button:
|
if show_device_sign_in_button:
|
||||||
self._sub_height += sign_in_button_space + deprecated_space
|
self._sub_height += sign_in_button_space + deprecated_space
|
||||||
if show_game_service_button:
|
if show_game_center_button:
|
||||||
self._sub_height += game_service_button_space
|
self._sub_height += game_center_button_space
|
||||||
if show_linked_accounts_text:
|
if show_linked_accounts_text:
|
||||||
self._sub_height += linked_accounts_text_space
|
self._sub_height += linked_accounts_text_space
|
||||||
if show_achievements_text:
|
if show_achievements_text:
|
||||||
|
|
@ -880,14 +880,14 @@ class AccountSettingsWindow(bui.MainWindow):
|
||||||
bui.widget(edit=btn, left_widget=bbtn)
|
bui.widget(edit=btn, left_widget=bbtn)
|
||||||
|
|
||||||
# the button to go to OS-Specific leaderboards/high-score-lists/etc.
|
# the button to go to OS-Specific leaderboards/high-score-lists/etc.
|
||||||
if show_game_service_button:
|
if show_game_center_button:
|
||||||
button_width = 300
|
button_width = 300
|
||||||
v -= game_service_button_space * 0.6
|
v -= game_center_button_space * 1.0
|
||||||
if game_center_active:
|
if game_center_active:
|
||||||
# Note: Apparently Game Center is just called 'Game Center'
|
# Note: Apparently Game Center is just called 'Game Center'
|
||||||
# in all languages. Can revisit if not true.
|
# in all languages. Can revisit if not true.
|
||||||
# https://developer.apple.com/forums/thread/725779
|
# https://developer.apple.com/forums/thread/725779
|
||||||
game_service_button_label = bui.Lstr(
|
game_center_button_label = bui.Lstr(
|
||||||
value=bui.charstr(bui.SpecialChar.GAME_CENTER_LOGO)
|
value=bui.charstr(bui.SpecialChar.GAME_CENTER_LOGO)
|
||||||
+ 'Game Center'
|
+ 'Game Center'
|
||||||
)
|
)
|
||||||
|
|
@ -895,15 +895,15 @@ class AccountSettingsWindow(bui.MainWindow):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"unknown account type: '" + str(v1_account_type) + "'"
|
"unknown account type: '" + str(v1_account_type) + "'"
|
||||||
)
|
)
|
||||||
self._game_service_button = btn = bui.buttonwidget(
|
self._game_center_button = btn = bui.buttonwidget(
|
||||||
parent=self._subcontainer,
|
parent=self._subcontainer,
|
||||||
position=((self._sub_width - button_width) * 0.5, v),
|
position=((self._sub_width - button_width) * 0.5, v),
|
||||||
color=(0.55, 0.5, 0.6),
|
color=(0.55, 0.5, 0.6),
|
||||||
textcolor=(0.75, 0.7, 0.8),
|
textcolor=(0.75, 0.7, 0.8),
|
||||||
autoselect=True,
|
autoselect=True,
|
||||||
on_activate_call=self._on_game_service_button_press,
|
on_activate_call=self._on_game_center_button_press,
|
||||||
size=(button_width, 50),
|
size=(button_width, 50),
|
||||||
label=game_service_button_label,
|
label=game_center_button_label,
|
||||||
)
|
)
|
||||||
if first_selectable is None:
|
if first_selectable is None:
|
||||||
first_selectable = btn
|
first_selectable = btn
|
||||||
|
|
@ -911,9 +911,9 @@ class AccountSettingsWindow(bui.MainWindow):
|
||||||
edit=btn, right_widget=bui.get_special_widget('squad_button')
|
edit=btn, right_widget=bui.get_special_widget('squad_button')
|
||||||
)
|
)
|
||||||
bui.widget(edit=btn, left_widget=bbtn)
|
bui.widget(edit=btn, left_widget=bbtn)
|
||||||
v -= game_service_button_space * 0.4
|
v -= game_center_button_space * 0.4
|
||||||
else:
|
else:
|
||||||
self.game_service_button = None
|
self.game_center_button = None
|
||||||
|
|
||||||
self._achievements_text: bui.Widget | None
|
self._achievements_text: bui.Widget | None
|
||||||
if show_achievements_text:
|
if show_achievements_text:
|
||||||
|
|
@ -1214,7 +1214,7 @@ class AccountSettingsWindow(bui.MainWindow):
|
||||||
)
|
)
|
||||||
self._needs_refresh = False
|
self._needs_refresh = False
|
||||||
|
|
||||||
def _on_game_service_button_press(self) -> None:
|
def _on_game_center_button_press(self) -> None:
|
||||||
if bui.app.plus is not None:
|
if bui.app.plus is not None:
|
||||||
bui.app.plus.show_game_service_ui()
|
bui.app.plus.show_game_service_ui()
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
17
dist/ba_data/python/bauiv1lib/account/v2proxy.py
vendored
17
dist/ba_data/python/bauiv1lib/account/v2proxy.py
vendored
|
|
@ -41,6 +41,12 @@ class V2ProxySignInWindow(bui.Window):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._loading_spinner = bui.spinnerwidget(
|
||||||
|
parent=self._root_widget,
|
||||||
|
position=(self._width * 0.5, self._height * 0.5),
|
||||||
|
size=60,
|
||||||
|
style='bomb',
|
||||||
|
)
|
||||||
self._state_text = bui.textwidget(
|
self._state_text = bui.textwidget(
|
||||||
parent=self._root_widget,
|
parent=self._root_widget,
|
||||||
position=(self._width * 0.5, self._height * 0.6),
|
position=(self._width * 0.5, self._height * 0.6),
|
||||||
|
|
@ -49,10 +55,11 @@ class V2ProxySignInWindow(bui.Window):
|
||||||
size=(0, 0),
|
size=(0, 0),
|
||||||
scale=1.4,
|
scale=1.4,
|
||||||
maxwidth=0.9 * self._width,
|
maxwidth=0.9 * self._width,
|
||||||
text=bui.Lstr(
|
# text=bui.Lstr(
|
||||||
value='${A}...',
|
# value='${A}...',
|
||||||
subs=[('${A}', bui.Lstr(resource='loadingText'))],
|
# subs=[('${A}', bui.Lstr(resource='loadingText'))],
|
||||||
),
|
# ),
|
||||||
|
text='',
|
||||||
color=(1, 1, 1),
|
color=(1, 1, 1),
|
||||||
)
|
)
|
||||||
self._sub_state_text = bui.textwidget(
|
self._sub_state_text = bui.textwidget(
|
||||||
|
|
@ -141,6 +148,7 @@ class V2ProxySignInWindow(bui.Window):
|
||||||
def _set_error_state(self, error_location: str) -> None:
|
def _set_error_state(self, error_location: str) -> None:
|
||||||
msaddress = self._get_server_address()
|
msaddress = self._get_server_address()
|
||||||
addr = msaddress.removeprefix('https://')
|
addr = msaddress.removeprefix('https://')
|
||||||
|
bui.spinnerwidget(edit=self._loading_spinner, visible=False)
|
||||||
bui.textwidget(
|
bui.textwidget(
|
||||||
edit=self._state_text,
|
edit=self._state_text,
|
||||||
text=f'Unable to connect to {addr}.',
|
text=f'Unable to connect to {addr}.',
|
||||||
|
|
@ -190,6 +198,7 @@ class V2ProxySignInWindow(bui.Window):
|
||||||
self._complete = True
|
self._complete = True
|
||||||
|
|
||||||
# Clear out stuff we use to show progress/errors.
|
# Clear out stuff we use to show progress/errors.
|
||||||
|
self._loading_spinner.delete()
|
||||||
self._sub_state_text.delete()
|
self._sub_state_text.delete()
|
||||||
self._sub_state_text2.delete()
|
self._sub_state_text2.delete()
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue