babase package

Common shared Ballistica components.

For modding purposes, this package should generally not be used directly. Instead one should use purpose-built packages such as bascenev1 or bauiv1 which themselves import various functionality from here and reexpose it in a more focused way.

class babase.AccountV2Handle[source]

Bases: object

Handle for interacting with a V2 account.

This class supports the with statement, which is how it is used with some operations such as cloud messaging.

accountid: str

The id of this account.

logins: dict[LoginType, LoginInfo]

Info about last known logins associated with this account.

tag: str

The last known tag for this account.

workspaceid: str | None

The id of the workspace being synced to this client, if any.

workspacename: str | None

The name of the workspace being synced to this client.

class babase.AccountV2Subsystem[source]

Bases: object

Subsystem for modern account handling in the app.

Access the single shared instance of this class via the accounts attr on the PlusAppSubsystem class.

have_primary_credentials() bool[source]

Are credentials currently set for the primary app account?

Note that this does not mean these credentials have been checked for validity; only that they exist. If/when credentials are validated, the primary account handle will be set.

property primary: AccountV2Handle | None

The primary account for the app, or None if not logged in.

set_primary_credentials(credentials: str | None) None[source]

Set credentials for the primary app account.

Once credentials are set, they will be verified in the cloud asynchronously. If verification is successful, the primary attr will be set to the resulting account.

exception babase.ActivityNotFoundError[source]

Bases: NotFoundError

Raised when an expected activity does not exist.

exception babase.ActorNotFoundError[source]

Bases: NotFoundError

Raised when an expected actor does not exist.

class babase.App[source]

Bases: object

High level Ballistica app functionality and state.

Access the single shared instance of this class via the app attr available on various high level modules such as babase, bauiv1, and bascenev1.

SHUTDOWN_TASK_TIMEOUT_SECONDS = 12

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.

property active: bool

Whether the app is currently front and center.

This will be False when the app is hidden, other activities are covering it, etc. (depending on the platform).

add_shutdown_task(coro: Coroutine[None, None, None]) None[source]

Add a task to be run on app shutdown.

Note that shutdown tasks will be canceled after SHUTDOWN_TASK_TIMEOUT_SECONDS if they are still running.

property asyncio_loop: AbstractEventLoop

The logic thread’s asyncio event-loop.

This allows asyncio tasks to be run in the logic thread.

Generally you should call create_async_task() to schedule async code to run instead of using this directly. That will handle retaining the task and logging errors automatically. Only 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 errors and garbage-collected tasks disappearing before their work is done.

Note that, at this time, the asyncio loop is encapsulated and explicitly stepped by the engine’s logic thread loop and thus things like asyncio.get_running_loop() will unintuitively not return this loop from most places in the logic thread; only from within a task explicitly created in this loop. Hopefully this situation will be improved in the future with a unified event loop.

property classic: ClassicAppSubsystem | None

Our classic subsystem (if available).

config: AppConfig

Config values for the app.

create_async_task(coro: Coroutine[Any, Any, T], *, name: str | None = None) None[source]

Create a fully managed asyncio 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 or otherwise need more control, schedule a task directly using asyncio_loop.

devconsole: DevConsoleSubsystem

Subsystem for wrangling the dev-console UI.

env: babase.Env

Static environment values for the app.

fg_state: int

Incremented each time the app leaves the SUSPENDED state. This can be a simple way to determine if network data should be refreshed/etc.

Handle a deep link URL.

health: AppHealthSubsystem

Subsystem for keeping tabs on app health.

lang: LanguageSubsystem

Language subsystem.

meta: MetadataSubsystem

Subsystem for wrangling metadata.

property mode: AppMode | None

The app’s current mode.

property mode_selector: babase.AppModeSelector

Controls which app-modes are used for handling given intents.

Plugins can override this to change high level app behavior and spinoff projects can change the default implementation for the same effect.

net: NetworkSubsystem

Subsystem for network functionality.

plugins: PluginSubsystem

Subsystem for wrangling plugins.

property plus: PlusAppSubsystem | None

Our plus subsystem (if available).

postinit() None[source]

Called after we’ve been inited and assigned to babase.app.

Anything that accesses babase.app as part of its init process must go here instead of __init__.

run() None[source]

Run the app to completion.

Note that this only works on builds/runs where Ballistica is managing its own event loop.

set_intent(intent: AppIntent) None[source]

Set the intent for the app.

Intent defines what the app is trying to do at a given time. This call is asynchronous; the intent switch will happen in the logic thread in the near future. If this is called repeatedly before the change takes place, the final intent to be set will be used.

state: AppState

Current app state.

stringedit: StringEditSubsystem

Subsystem for wrangling text input from various sources.

threadpool: ThreadPoolExecutorEx

Default executor which can be used for misc background processing. It should also be passed to any additional asyncio loops we create so that everything shares the same single set of worker threads.

property ui_v1: UIV1AppSubsystem

Our ui_v1 subsystem (always available).

workspaces: WorkspaceSubsystem

Subsystem for wrangling workspaces.

class babase.AppConfig[source]

Bases: dict

A special dict that holds persistent app configuration values.

It also provides methods for fetching values with app-defined fallback defaults, applying contained values to the game, and committing the config to storage.

Access the single shared instance of this config via the config attr on the App 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, float, int, bool). Be aware that tuples will be quietly converted to lists when stored.

apply() None[source]

Apply config values to the running app.

This call is thread-safe and asynchronous; changes will happen in the next logic event loop cycle.

apply_and_commit() None[source]

Shortcut to run apply() followed by commit().

This way the commit() will not occur if apply() hits invalid data, which is generally desirable.

builtin_keys() list[str][source]

Return the list of valid key names recognized by this class.

This set of keys can be used with resolve(), default_value(), etc. It does not vary across platforms and may include keys that are obsolete or not relevant on the current running version. (for instance, VR related keys on 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 config, but in that case it is up to the user to test for the existence of the key in the config dict, fall back to consistent defaults, etc.

commit() None[source]

Commits the config to local storage.

Note that this call is asynchronous so the actual write to disk may not occur immediately.

default_value(key: str) Any[source]

Given a string key, return its predefined default value.

This is the value that will be returned by resolve() if the key is not present in the config dict or of an incompatible type.

Raises an Exception for unrecognized key names. To get the list of keys supported by this method, use babase.AppConfig.builtin_keys(). Note that it is perfectly legal to store other data in the config; it just needs to be accessed through standard dict methods and missing values handled manually.

resolve(key: str) Any[source]

Given a string key, return a config value (type varies).

This will substitute application defaults for values not present in the config dict, filter some invalid values, etc. Note that these values do not represent the state of the app; simply the state of its config. Use the App class to access actual live state.

Raises an KeyError for unrecognized key names. To get the list of keys supported by this method, use builtin_keys(). Note that it is perfectly legal to store other data in the config; it just needs to be accessed through standard dict methods and missing values handled manually.

class babase.AppHealthSubsystem[source]

Bases: AppSubsystem

Subsystem for monitoring app health; logs not-responding issues, etc.

The single shared instance of this class can be found on the health attr on the App class.

class babase.AppIntent[source]

Bases: object

Base class for high level directives given to the app.

class babase.AppIntentDefault[source]

Bases: AppIntent

Tells the app to simply run in its default mode.

class babase.AppIntentExec(code: str)[source]

Bases: AppIntent

Tells the app to exec some Python code.

class babase.AppMode[source]

Bases: object

A low level mode the app can be in.

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 can_handle_intent(intent: AppIntent) bool[source]

Return whether this mode can handle the provided intent.

For this to return True, the app-mode must claim to support the provided intent (via its can_handle_intent_impl() method) AND the AppExperience associated with the app-mode must be supported by the current app and runtime environment.

classmethod can_handle_intent_impl(intent: AppIntent) bool[source]

Override this to define indent handling for an app-mode.

Note that AppExperience does not have to be considered here; that is handled automatically by the can_handle_intent() call.

classmethod get_app_experience() AppExperience[source]

Return the overall experience provided by this mode.

handle_intent(intent: AppIntent) None[source]

Handle an intent.

on_activate() None[source]

Called when the mode is becoming the active one fro the app.

on_app_active_changed() None[source]

Called when the app’s active state changes while in this app-mode.

This corresponds to the app’s active attr. 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 on_deactivate() and on_app_active_changed() (when active is False).

on_deactivate() None[source]

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 on_deactivate() and on_app_active_changed() (when active is False).

class babase.AppModeSelector[source]

Bases: object

Defines which app-modes should handle which app-intents.

The app calls an instance of this class when passed an AppIntent to determine which AppMode to use to handle it. Plugins or spinoff projects can modify high level app behavior by replacing or modifying the app’s mode_selector attr or by modifying settings used to construct the default one.

app_mode_for_intent(intent: AppIntent) type[AppMode] | None[source]

Given an app-intent, return the app-mode that should handle it.

If None is returned, the intent will be ignored.

This may be called in a background thread, so avoid any calls limited to logic thread use/etc.

class babase.AppState(*values)[source]

Bases: Enum

High level state the app can be in.

INITING = 2

Python app subsystems are being inited but should not yet interact or do any work.

LOADING = 3

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.

NATIVE_BOOTSTRAPPING = 1

The native layer is spinning up its machinery (screens, renderers, etc.). Nothing should happen in the Python layer until this completes.

NOT_STARTED = 0

The app has not yet begun starting and should not be used in any way.

RUNNING = 4

All pieces are in place and the app is now doing its thing.

SHUTDOWN_COMPLETE = 7

The app has completed shutdown. Any code running here should be basically immediate.

SHUTTING_DOWN = 6

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).

SUSPENDED = 5

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.

class babase.AppSubsystem[source]

Bases: object

Base class for an app subsystem.

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 building one out of this base class provides conveniences such as predefined callbacks during app state changes.

Subsystems must be registered with the app before it completes its transition to the ‘running’ state.

do_apply_app_config() None[source]

Called when the app config should be applied.

on_app_loading() None[source]

Called when the app reaches the loading state.

Note that subsystems created after the app switches to the loading state will not receive this callback. Subsystems created by plugins are an example of this.

on_app_running() None[source]

Called when app enters RUNNING state.

on_app_shutdown() None[source]

Called when app enters SHUTTING_DOWN state.

on_app_shutdown_complete() None[source]

Called when app enters SHUTDOWN_COMPLETE state.

on_app_suspend() None[source]

Called when app enters SUSPENDED state.

on_app_unsuspend() None[source]

Called when app exits SUSPENDED state.

on_screen_size_change() None[source]

Called when the screen size changes.

Will not be called for the initial screen size.

on_ui_scale_change() None[source]

Called when screen ui-scale changes.

Will not be called for the initial ui scale.

reset() None[source]

Reset the subsystem to a default state.

This is called when switching app modes, but may be called at other times too.

class babase.AppTimer(time: float, call: Callable[[], Any], repeat: bool = False)[source]

Bases: object

Timers are used to run code at later points in time.

This class encapsulates a timer based on app-time. The underlying timer will be destroyed when this object is no longer referenced. If you do not want to worry about keeping a reference to your timer around, use the apptimer() function instead to get a one-off timer.

Parameters:
  • time – Length of time in seconds that the timer will wait before firing.

  • call – A callable Python object. Remember that the timer will retain a strong reference to the callable for as long as it exists, so you may want to look into concepts such as WeakCall if that is not desired.

  • repeat – If True, the timer will fire repeatedly, with each successive firing having the same delay as the first.

Example: Use a timer object to print repeatedly for a few seconds:

def say_it():
    babase.screenmessage('BADGER!')

def stop_saying_it():
    global g_timer
    g_timer = None
    babase.screenmessage('MUSHROOM MUSHROOM!')

# Create our timer; it will run as long as we keep its ref alive.
g_timer = babase.AppTimer(0.3, say_it, repeat=True)

# Now fire off a one-shot timer to kill the ref.
babase.apptimer(3.89, stop_saying_it)
class babase.Call(*args: Any, **keywds: Any)

Bases: object

Wraps a callable and arguments into a single callable object.

The callable is strong-referenced so it won’t die until this object does.

Note that a bound method (ex: myobj.dosomething) contains a reference 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 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()
class babase.CloudSubscription(subscription_id: int)[source]

Bases: object

User handle to a subscription to some cloud data.

Do not instantiate these directly; use the subscribe methods in CloudSubsystem to create them.

class babase.ContextCall(call: Callable)[source]

Bases: object

A context-preserving callable.

This wraps a callable object along with a reference to the current context (see ContextRef); it handles restoring the context when run and automatically clears itself if the context it belongs to dies.

Generally you should not need to use this directly; all standard Ballistica callbacks involved with timers, materials, UI functions, etc. handle this under-the-hood so you don’t have to worry about it. The only time it may be necessary is if you are implementing your own callbacks, such as a worker thread that does some action and then runs some engine code when done. By wrapping said callback in one of these, you can ensure that you will not inadvertently be keeping the current activity alive or running code in a torn-down (expired) ContextRef.

You can also use WeakCall for similar functionality, but ContextCall has the added bonus that it will not run during ContextRef shutdown, whereas WeakCall simply looks at whether the target object instance still exists.

Example A: Code like this can inadvertently prevent our activity (self) from ending until the operation completes, since the bound method we’re passing (self.dosomething) contains a strong-reference to self):

start_some_long_action(callback_when_done=self.dosomething)

Example B: In this case our activity (self) can still die properly; the callback will clear itself when the activity starts shutting down, becoming a harmless no-op and releasing the reference to our activity:

start_long_action(
    callback_when_done=babase.ContextCall(self.mycallback))
exception babase.ContextError[source]

Bases: Exception

Raised when a call is made in an invalid context.

Examples of this include calling UI functions within an activity context or calling scene manipulation functions outside of a scene context.

class babase.ContextRef[source]

Bases: object

Store or use a Ballistica context.

Many operations such as bascenev1.newnode() or bascenev1.gettexture() operate implicitly on a current ‘context’. A context is some sort of state that functionality can implicitly use. Context determines, for example, which scene new nodes or textures get added to without having to specify that explicitly in the newnode()/gettexture() call. Contexts can also affect object lifecycles; for example a ContextCall will instantly become a no-op and release any references it is holding when the context it belongs to is destroyed.

In general, if you are a modder, you should not need to worry about contexts; mod code should mostly be getting run in the correct context and timers and other callbacks will take care of saving and restoring contexts automatically. There may be rare cases, however, where you need to deal directly with contexts, and that is where this class comes in.

Creating a context-ref will capture a reference to the current context. Other modules may provide ways to access their contexts; for example a bascenev1.Activity instance has a context attribute. You can also use the empty() classmethod to create a reference to no context. Some code such as UI calls may expect to be run with no context set and may complain if you try to use them within a context.

Usage

Context-refs are generally used with the Python with statement, which sets the context they point to as current on entry and resets it to the previous value on exit.

Example: Explicitly clear context while working with UI code from gameplay (UI stuff may complain if called within a context):

import bauiv1 as bui

def _callback_called_from_gameplay():

    # We are probably called with a game context as current, but
    # this makes UI stuff unhappy. So we clear the context while
    # doing our thing.
    with bui.ContextRef.empty():
        my_container = bui.containerwidget()
classmethod empty() ContextRef[source]

Return a context-ref pointing to no context.

This is useful when code should be run free of a context. For example, UI code generally insists on being run this way. Otherwise, callbacks set on the UI could inadvertently stop working due to a game activity ending, which would be unintuitive behavior.

is_empty() bool[source]

Whether the context was created as empty.

is_expired() bool[source]

Whether the context has expired.

Returns False for refs created as empty.

exception babase.DelegateNotFoundError[source]

Bases: NotFoundError

Raised when an expected delegate object does not exist.

class babase.DevConsoleSubsystem[source]

Bases: object

Subsystem for wrangling the dev-console.

Access the single shared instance of this class via the devconsole attr on the App class. The dev-console is a simple always-available UI intended for use by developers; not end users. Traditionally it is available by 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).

tabs: list[DevConsoleTabEntry]

All tabs in the dev-console. Add your own stuff here via plugins or whatnot to customize the console.

class babase.DevConsoleTab[source]

Bases: object

Base class for a DevConsoleSubsystem tab.

property base_scale: float

A scale value based on the app’s current UIScale.

Dev-console tabs can manually incorporate this into their UI sizes and positions if they desire. By default, dev-console tabs are uniform across all ui-scales.

button(label: str, pos: tuple[float, float], size: tuple[float, float], call: Callable[[], Any] | None = None, *, h_anchor: Literal['left', 'center', 'right'] = 'center', label_scale: float = 1.0, corner_radius: float = 8.0, style: Literal['normal', 'bright', 'red', 'red_bright', 'purple', 'purple_bright', 'yellow', 'yellow_bright', 'blue', 'blue_bright', 'white', 'white_bright', 'black', 'black_bright'] = 'normal', disabled: bool = False) None[source]

Add a button to the tab being refreshed.

property height: float

The current tab height. Only valid during refreshes.

python_terminal() None[source]

Add a Python Terminal to the tab being refreshed.

refresh() None[source]

Called when the tab should refresh itself.

Overridden by subclasses to implement tab behavior.

request_refresh() None[source]

The tab can call this to request that it be refreshed.

text(text: str, pos: tuple[float, float], *, h_anchor: Literal['left', 'center', 'right'] = 'center', h_align: Literal['left', 'center', 'right'] = 'center', v_align: Literal['top', 'center', 'bottom', 'none'] = 'center', scale: float = 1.0) None[source]

Add a button to the tab being refreshed.

property width: float

The current tab width. Only valid during refreshes.

class babase.DevConsoleTabEntry(name: str, factory: Callable[[], DevConsoleTab])[source]

Bases: object

Represents a distinct tab in the DevConsoleSubsystem.

factory: Callable[[], DevConsoleTab]
name: str
class babase.DisplayTimer(time: float, call: Callable[[], Any], repeat: bool = False)[source]

Bases: object

Timers are used to run code at later points in time.

This class encapsulates a timer based on display-time. The underlying timer will be destroyed when this object is no longer referenced. If you do not want to worry about keeping a reference to your timer around, use the displaytimer() function instead to get a one-off timer.

Display-time is a time value intended to be used for animation and other visual purposes. It will generally increment by a consistent amount each frame. It will pass at an overall similar rate to AppTime, but trades accuracy for smoothness.

Parameters:
  • time – Length of time in seconds that the timer will wait before firing.

  • call – A callable Python object. Remember that the timer will retain a strong reference to the callable for as long as it exists, so you may want to look into concepts such as WeakCall if that is not desired.

  • repeat – If True, the timer will fire repeatedly, with each successive firing having the same delay as the first.

Example: Use a Timer object to print repeatedly for a few seconds:

def say_it():
    babase.screenmessage('BADGER!')

def stop_saying_it():
    global g_timer
    g_timer = None
    babase.screenmessage('MUSHROOM MUSHROOM!')

# Create our timer; it will run as long as we keep its ref alive.
g_timer = babase.DisplayTimer(0.3, say_it, repeat=True)

# Now fire off a one-shot timer to kill the ref.
babase.displaytimer(3.89, stop_saying_it)
class babase.Env[source]

Bases: object

Unchanging values for the current running app instance. Access the single shared instance of this class through the env attr on the App class.

android: bool

Is this build targeting an Android based OS?

api_version: int

The app’s api version.

Only Python modules and packages associated with the current API version number will be detected by the game (see the babase.MetadataSubsystem). This value will change whenever substantial backward-incompatible changes are introduced to Ballistica APIs. When that happens, modules/packages should be updated accordingly and set to target the newer API version number.

arcade: bool

Whether the app is targeting an arcade-centric experience.

config_file_path: str

Where the app’s config file is stored on disk.

data_directory: str

Where bundled static app data lives.

debug: bool

Whether the app is running in debug mode.

Debug builds generally run substantially slower than non-debug builds due to compiler optimizations being disabled and extra checks being run.

demo: bool

Whether the app is targeting a demo experience.

device_name: str

Human readable name of the device running this app.

engine_build_number: int

Integer build number for the engine.

This value increases by at least 1 with each release of the engine. It is independent of the human readable version string.

engine_version: str

Human-readable version string for the engine; something like ‘1.3.24’.

This should not be interpreted as a number; it may contain string elements such as ‘alpha’, ‘beta’, ‘test’, etc. If a numeric version is needed, use build_number.

gui: bool

Whether the app is running with a gui.

This is the opposite of headless.

headless: bool

Whether the app is running headlessly (without a gui).

This is the opposite of gui.

python_directory_app: str | None

Path where the app expects its bundled modules to live.

Be aware that this value may be None if Ballistica is running in a non-standard environment, and that python-path modifications may cause modules to be loaded from other locations.

python_directory_app_site: str | None

Path where the app expects its bundled pip modules to live.

Be aware that this value may be None if Ballistica is running in a non-standard environment, and that python-path modifications may cause modules to be loaded from other locations.

python_directory_user: str | None

Path where the app expects its user scripts (mods) to live.

Be aware that this value may be None if Ballistica is running in a non-standard environment, and that python-path modifications may cause modules to be loaded from other locations.

supports_soft_quit: bool

Whether the running app supports ‘soft’ quit options.

This generally applies to mobile derived OSs, where an act of ‘quitting’ may leave the app running in the background waiting in case it is used again.

test: bool

Whether the app is running in test mode.

Test mode enables extra checks and features that are useful for release testing but which do not slow the game down significantly.

tv: bool

Whether the app is targeting a TV-centric experience.

vr: bool

Whether the app is currently running in VR.

class babase.Existable(*args, **kwargs)[source]

Bases: Protocol

A Protocol for objects supporting an exists() method.

exists() bool[source]

Whether this object exists.

exception babase.InputDeviceNotFoundError[source]

Bases: NotFoundError

Raised when an expected input-device does not exist.

class babase.InputType(*values)[source]

Bases: Enum

Types of input a controller can send to the game.

BOMB_PRESS = 8
BOMB_RELEASE = 9
DOWN_PRESS = 25
DOWN_RELEASE = 26
FLY_PRESS = 13
FLY_RELEASE = 14
HOLD_POSITION_PRESS = 17
HOLD_POSITION_RELEASE = 18
JUMP_PRESS = 4
JUMP_RELEASE = 5
LEFT_PRESS = 19
LEFT_RELEASE = 20
LEFT_RIGHT = 3
PICK_UP_PRESS = 10
PICK_UP_RELEASE = 11
PUNCH_PRESS = 6
PUNCH_RELEASE = 7
RIGHT_PRESS = 21
RIGHT_RELEASE = 22
RUN = 12
START_PRESS = 15
START_RELEASE = 16
UP_DOWN = 2
UP_PRESS = 23
UP_RELEASE = 24
class babase.LanguageSubsystem[source]

Bases: AppSubsystem

Language functionality for the app.

Access the single instance of this class at ‘babase.app.lang’.

property available_languages: list[str]

A list of all available languages.

Note that languages that may be present in game assets but which are not displayable on the running version of the game are not included here.

get_resource(resource: str, fallback_resource: str | None = None, fallback_value: Any = None) Any[source]

Return a translation resource by name.

Warning

Use Lstr instead of this function whenever possible, as it will gracefully handle displaying correctly across multiple clients in multiple languages simultaneously.

is_custom_unicode_char(char: str) bool[source]

Return whether a char is in the custom unicode range we use.

property language: str

The current active language for the app.

This can be selected explicitly by the user or may be set automatically based on locale or other factors.

property locale: str

Raw country/language code detected by the game (such as “en_US”).

Generally for language-specific code you should look at language, which is the language the game is using (which may differ from locale if the user sets a language, etc.)

setlanguage(language: str | dict | None, print_change: bool = True, store_to_config: bool = True) None[source]

Set the active app language.

Pass None to use OS default language.

testlanguage(langid: str) None[source]

Set the app to test an in-progress language.

Pass a language id from the translation editor website as ‘langid’; something like ‘Gibberish_3263’. Once set to testing, the engine will repeatedly download and apply that same test language, so changes can be made to it and observed live.

translate(category: str, strval: str, raise_exceptions: bool = False, print_errors: bool = False) str[source]

Translate a value (or return the value if no translation available)

Warning

Use Lstr instead of this function whenever possible, as it will gracefully handle displaying correctly across multiple clients in multiple languages simultaneously.

class babase.LoginAdapter(login_type: LoginType)[source]

Bases: object

Allows using implicit login types in an explicit way.

Some login types such as Google Play Game Services or Game Center are basically always present and often do not provide a way to log out from within a running app, so this adapter exists to use them in a flexible manner by ‘attaching’ and ‘detaching’ from an always-present login, allowing for its use alongside other login types. It also provides common functionality for server-side account verification and other handy bits.

class ImplicitLoginState(login_id: str, display_name: str)[source]

Bases: object

Describes the current state of an implicit login.

display_name: str
login_id: str
class SignInResult(credentials: str)[source]

Bases: object

Describes the final result of a sign-in attempt.

credentials: str
get_sign_in_token(completion_cb: Callable[[str | None], None]) None[source]

Get a sign-in token from the adapter back end.

This token is then passed to the cloud to complete the sign-in process. The adapter can use this opportunity to bring up account creation UI, call its internal sign-in function, etc. as needed. The provided completion_cb should then be called with either a token or with None if sign in failed or was cancelled.

is_back_end_active() bool[source]

Is this adapter’s back-end currently active?

on_back_end_active_change(active: bool) None[source]

Called when active state for the back-end is (possibly) changing.

Meant to be overridden by subclasses. Being active means that the implicit login provided by the back-end is actually being used by the app. It should therefore register unlocked achievements, leaderboard scores, allow viewing native UIs, etc. When not active it should ignore everything and behave as if signed out, even if it technically is still signed in.

set_implicit_login_state(state: ImplicitLoginState | None) None[source]

Keep the adapter informed of implicit login states.

This should be called by the adapter back-end when an account of their associated type gets logged in or out.

sign_in(result_cb: Callable[[LoginAdapter, SignInResult | Exception], None], description: str) None[source]

Attempt to sign in via this adapter.

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 be passed to the callback if the sign-in attempt fails.

class babase.LoginInfo(name: str)[source]

Bases: object

Info for a login used by AccountV2Handle.

name: str
class babase.Lstr(*, resource: str, fallback_resource: str = '', fallback_value: str = '', subs: Sequence[tuple[str, str | Lstr]] | None = None)[source]
class babase.Lstr(*, translate: tuple[str, str], subs: Sequence[tuple[str, str | Lstr]] | None = None)
class babase.Lstr(*, value: str, subs: Sequence[tuple[str, str | Lstr]] | None = None)

Bases: object

Used to define strings in a language-independent way.

These should be used whenever possible in place of hard-coded strings so that in-game or UI elements show up correctly on all clients in their currently active language.

To see available resource keys, look at any of the ba_data/data/languages/*.json files in the game or the translations pages at legacy.ballistica.net/translate.

Parameters:
  • resource – Pass a string to look up a translation by resource key.

  • translate – Pass a tuple consisting of a translation category and untranslated value. Any matching translation found in that category will be used. Otherwise the untranslated value will be.

  • value – Pass a regular string value to be used as-is.

  • subs – A sequence of 2-member tuples consisting of values and 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'))])
args

Basically just stores the exact args passed. However if Lstr values were passed for subs, they are replaced with that Lstr’s dict.

evaluate() str[source]

Evaluate to a flat string in the current language.

You should avoid doing this as much as possible and instead pass and store Lstr values.

static from_json(json_string: str) babase.Lstr[source]

Given a json string, returns a Lstr.

Does no validation.

is_flat_value() bool[source]

Return whether this instance represents a ‘flat’ value.

This is defined as a simple string value incorporating no translations, resources, or substitutions. In this case it may be reasonable to replace it with a raw string value, perform string manipulation on it, etc.

exception babase.MapNotFoundError[source]

Bases: NotFoundError

Raised when an expected map does not exist.

class babase.MetadataSubsystem[source]

Bases: object

Subsystem for working with script metadata in the app.

Access the single shared instance of this class via the meta attr on the App class.

load_exported_classes(cls: type[T], completion_cb: Callable[[list[type[T]]], None], completion_cb_in_bg_thread: bool = False) None[source]

High level function to load meta-exported classes.

Will wait for scanning to complete if necessary, and will load all registered classes of a particular type in a background thread before calling the passed callback in the logic thread. Errors may be logged to messaged to the user in some way but the callback will be called regardless. To run the completion callback directly in the bg thread where the loading work happens, pass completion_cb_in_bg_thread=True.

class babase.NetworkSubsystem[source]

Bases: object

Network related app subsystem.

exception babase.NodeNotFoundError[source]

Bases: NotFoundError

Raised when an expected node does not exist.

exception babase.NotFoundError[source]

Bases: Exception

Raised when a referenced object does not exist.

class babase.Permission(*values)[source]

Bases: Enum

Permissions that can be requested from the OS.

STORAGE = 0
exception babase.PlayerNotFoundError[source]

Bases: NotFoundError

Raised when an expected player does not exist.

class babase.Plugin[source]

Bases: object

A plugin to alter app behavior in some way.

Plugins are discoverable by the MetadataSubsystem system and the user can select which ones they want to enable. Enabled plugins are then called at specific times as the app is running in order to modify its behavior in some way.

has_settings_ui() bool[source]

Called to ask if we have settings UI we can show.

on_app_running() None[source]

Called when the app reaches the running state.

on_app_shutdown() None[source]

Called when the app is beginning the shutdown process.

on_app_shutdown_complete() None[source]

Called when the app has completed the shutdown process.

on_app_suspend() None[source]

Called when the app enters the suspended state.

on_app_unsuspend() None[source]

Called when the app exits the suspended state.

show_settings_ui(source_widget: Any | None) None[source]

Called to show our settings UI.

class babase.PluginSpec(class_path: str, loadable: bool)[source]

Bases: object

Represents a plugin the engine knows about.

attempt_load_if_enabled() Plugin | None[source]

Possibly load the plugin and log any errors.

attempted_load

Whether the engine has attempted to load the plugin. If this is True but the value of 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.

class_path

Fully qualified class path for the plugin.

property enabled: bool

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.

loadable

Can we attempt to load the plugin?

plugin: Plugin | None

The associated Plugin, if any.

class babase.PluginSubsystem[source]

Bases: AppSubsystem

Subsystem for wrangling plugins.

Access the single shared instance of this class via the plugins attr on the App class.

active_plugins: list[babase.Plugin]

The set of live active plugin instances.

plugin_specs: dict[str, babase.PluginSpec]

Info about plugins that we are aware of. This may include plugins discovered through meta-scanning as well as plugins registered in the app-config. This may include plugins that cannot be loaded for various reasons or that have been intentionally disabled.

class babase.QuitType(*values)[source]

Bases: Enum

Types of quit behavior that can be requested from the app.

‘soft’ may hide/reset the app but keep the process running, depending

on the platform (generally a thing on mobile).

‘back’ is a variant of ‘soft’ which may give ‘back-button-pressed’

behavior depending on the platform. (returning to some previous activity instead of dumping to the home screen, etc.)

‘hard’ leads to the process exiting. This generally should be avoided

on platforms such as mobile where apps are expected to keep running until killed by the OS.

BACK = 1
HARD = 2
SOFT = 0
exception babase.SessionNotFoundError[source]

Bases: NotFoundError

Raised when an expected session does not exist.

exception babase.SessionPlayerNotFoundError[source]

Bases: NotFoundError

Exception raised when an expected session-player does not exist.

exception babase.SessionTeamNotFoundError[source]

Bases: NotFoundError

Raised when an expected session-team does not exist.

class babase.SpecialChar(*values)[source]

Bases: Enum

Special characters the game can print.

BACK = 10
BOTTOM_BUTTON = 7
CROWN = 64
DELETE = 8
DICE_BUTTON1 = 31
DICE_BUTTON2 = 32
DICE_BUTTON3 = 33
DICE_BUTTON4 = 34
DOWN_ARROW = 0
DPAD_CENTER_BUTTON = 15
DRAGON = 69
EYE_BALL = 66
FAST_FORWARD_BUTTON = 14
FEDORA = 62
FIREBALL = 76
FLAG_ALGERIA = 81
FLAG_ARGENTINA = 92
FLAG_AUSTRALIA = 85
FLAG_BRAZIL = 50
FLAG_CANADA = 54
FLAG_CHILE = 94
FLAG_CHINA = 52
FLAG_CZECH_REPUBLIC = 84
FLAG_EGYPT = 79
FLAG_FRANCE = 57
FLAG_GERMANY = 49
FLAG_INDIA = 55
FLAG_INDONESIA = 58
FLAG_IRAN = 90
FLAG_ITALY = 59
FLAG_JAPAN = 56
FLAG_KUWAIT = 80
FLAG_MALAYSIA = 83
FLAG_MEXICO = 48
FLAG_NETHERLANDS = 61
FLAG_PHILIPPINES = 93
FLAG_POLAND = 91
FLAG_QATAR = 78
FLAG_RUSSIA = 51
FLAG_SAUDI_ARABIA = 82
FLAG_SINGAPORE = 86
FLAG_SOUTH_KOREA = 60
FLAG_UNITED_ARAB_EMIRATES = 77
FLAG_UNITED_KINGDOM = 53
FLAG_UNITED_STATES = 47
HAL = 63
HEART = 68
HELMET = 70
LEFT_ARROW = 2
LEFT_BUTTON = 5
LOCAL_ACCOUNT = 45
LOGO_FLAT = 11
MIKIROG = 95
MOON = 74
MUSHROOM = 71
NINJA_STAR = 72
OUYA_BUTTON_A = 25
OUYA_BUTTON_O = 22
OUYA_BUTTON_U = 23
OUYA_BUTTON_Y = 24
PARTY_ICON = 36
PAUSE_BUTTON = 21
PLAY_BUTTON = 20
PLAY_PAUSE_BUTTON = 13
PLAY_STATION_CIRCLE_BUTTON = 17
PLAY_STATION_CROSS_BUTTON = 16
PLAY_STATION_SQUARE_BUTTON = 19
PLAY_STATION_TRIANGLE_BUTTON = 18
REWIND_BUTTON = 12
RIGHT_ARROW = 3
RIGHT_BUTTON = 6
SHIFT = 9
SKULL = 67
SPIDER = 75
TEST_ACCOUNT = 37
TICKET = 28
TICKET_BACKING = 38
TOKEN = 26
TOP_BUTTON = 4
TROPHY0A = 42
TROPHY0B = 43
TROPHY1 = 39
TROPHY2 = 40
TROPHY3 = 41
TROPHY4 = 44
UP_ARROW = 1
VIKING_HELMET = 73
YIN_YANG = 65
class babase.StringEditAdapter(description: str, initial_text: str, max_length: int | None, screen_space_center: tuple[float, float] | None)[source]

Bases: object

Represents a string editing operation on some object.

Editable objects such as text widgets or in-app-consoles can subclass this to make their contents editable on all platforms.

There can only be one string-edit at a time for the app. New string-edits will attempt to register themselves as the globally active one in their constructor, but this may not succeed. If can_be_replaced() returns True for an adapter immediately after creating it, that means it was not able to set itself as the global one.

apply(new_text: str) None[source]

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 this adapter is no longer the globally active one).

can_be_replaced() bool[source]

Return whether this adapter can be replaced by a new one.

This is mainly a safeguard to allow adapters whose drivers have gone away without calling apply() or cancel() to time out and be replaced with new ones.

cancel() None[source]

Should be called by the owner when editing is cancelled.

class babase.StringEditSubsystem[source]

Bases: object

Full string-edit state for the app.

Access the single shared instance of this class via the stringedit attr on the App class.

exception babase.TeamNotFoundError[source]

Bases: NotFoundError

Raised when an expected team does not exist.

class babase.UIScale(*values)[source]

Bases: Enum

The overall scale the UI is being rendered for. Note that this is independent of pixel resolution. For example, a phone and a desktop PC might render the game at similar pixel resolutions but the size they display content at will vary significantly.

‘large’ is used for devices such as desktop PCs where fine details can

be clearly seen. UI elements are generally smaller on the screen and more content can be seen at once.

‘medium’ is used for devices such as tablets, TVs, or VR headsets.

This mode strikes a balance between clean readability and amount of content visible.

‘small’ is used primarily for phones or other small devices where

content needs to be presented as large and clear in order to remain readable from an average distance.

LARGE = 2
MEDIUM = 1
SMALL = 0
class babase.Vec3[source]
class babase.Vec3(value: float)
class babase.Vec3(values: Sequence[float])
class babase.Vec3(x: float, y: float, z: float)

Bases: Sequence[float]

A vector of 3 floats.

These can be created the following ways (checked in this order):
  • With no args, all values are set to 0.

  • With a single numeric arg, all values are set to that value.

  • With a three-member sequence arg, sequence values are copied.

  • Otherwise assumes individual x/y/z args (positional or keywords).

cross(other: Vec3) Vec3[source]

Returns the cross product of this vector and another.

dot(other: Vec3) float[source]

Returns the dot product of this vector and another.

length() float[source]

Returns the length of the vector.

normalized() Vec3[source]

Returns a normalized version of the vector.

x: float

The vector’s X component.

y: float

The vector’s Y component.

z: float

The vector’s Z component.

class babase.WeakCall(*args: Any, **keywds: Any)

Bases: object

Wrap a callable and arguments into a single callable object.

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.

Think of this as a handy way to tell an object to do something at some point in the future if it happens to still exist.

EXAMPLE A: This code will create a FooClass instance and 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:

foo = FooClass()
babase.apptimer(5.0, foo.bar)
foo = None

EXAMPLE B: This code will not keep our object alive; it will die when we overwrite it with None and the timer will be a no-op 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:

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.

exception babase.WidgetNotFoundError[source]

Bases: NotFoundError

Raised when an expected widget does not exist.

babase.appname() str[source]

Return current app name (all lowercase).

babase.appnameupper() str[source]

Return current app name with capitalized characters.

babase.apptime() babase.AppTime[source]

Return the current app-time in seconds.

App-time is a monotonic time value; it starts at 0.0 when the app launches and will never jump by large amounts or go backwards, even if the system time changes. Its progression will pause when the app is in a suspended state.

Note that the AppTime returned here is simply float; it just has a unique type in the type-checker’s eyes to help prevent it from being accidentally used with time functionality expecting other time types.

babase.apptimer(time: float, call: Callable[[], Any]) None[source]

Schedule a callable object to run based on app-time.

This function creates a one-off timer which cannot be canceled or modified once created. If you require the ability to do so, or need a repeating timer, use the babase.AppTimer class instead.

Parameters:
  • time – Length of time in seconds that the timer will wait before firing.

  • call – A callable Python object. Note that the timer will retain a strong reference to the callable for as long as the timer exists, so you may want to look into concepts such as WeakCall if that is not desired.

Example: Print some stuff through time:

import babase

babase.screenmessage('hello from now!')
babase.apptimer(1.0, babase.Call(babase.screenmessage,
                'hello from the future!'))
babase.apptimer(2.0, babase.Call(babase.screenmessage,
                'hello from the future 2!'))
babase.can_display_chars(text: str) bool[source]

Is this build able to display all chars in the provided string?

See also: supports_unicode_display().

babase.charstr(char_id: babase.SpecialChar) str[source]

Return a unicode string representing a special character.

Note that these utilize the private-use block of unicode characters (U+E000-U+F8FF) and are specific to the game; exporting or rendering them elsewhere will be meaningless.

See SpecialChar for the list of available characters.

babase.clipboard_get_text() str[source]

Return text currently on the system clipboard.

Ensure that clipboard_has_text() returns True before calling this function.

babase.clipboard_has_text() bool[source]

Return whether there is currently text on the clipboard.

This will return False if no system clipboard is available; no need

to call clipboard_is_supported() separately.

babase.clipboard_is_supported() bool[source]

Return whether this platform supports clipboard operations at all.

If this returns False, UIs should not show ‘copy to clipboard’ buttons, etc.

babase.clipboard_set_text(value: str) None[source]

Copy a string to the system clipboard.

Ensure that clipboard_is_supported() returns True before adding buttons/etc. that make use of this functionality.

babase.displaytime() babase.DisplayTime[source]

Return the current display-time in seconds.

Display-time is a time value intended to be used for animation and other visual purposes. It will generally increment by a consistent amount each frame. It will pass at an overall similar rate to app-time, but trades accuracy for smoothness.

Note that the value returned here is simply a float; it just has a unique type in the type-checker’s eyes to help prevent it from being accidentally used with time functionality expecting other time types.

babase.displaytimer(time: float, call: Callable[[], Any]) None[source]

Schedule a callable object to run based on display-time.

This function creates a one-off timer which cannot be canceled or modified once created. If you require the ability to do so, or need a repeating timer, use the DisplayTimer class instead.

Display-time is a time value intended to be used for animation and other visual purposes. It will generally increment by a consistent amount each frame. It will pass at an overall similar rate to app-time, but trades accuracy for smoothness.

Parameters:
  • time – Length of time in seconds that the timer will wait before firing.

  • call – A callable Python object. Note that the timer will retain a strong reference to the callable for as long as the timer exists, so you may want to look into concepts such as WeakCall if that is not desired.

Example: Print some stuff through time:

babase.screenmessage('hello from now!')
babase.displaytimer(1.0, babase.Call(babase.screenmessage,
                    'hello from the future!'))
babase.displaytimer(2.0, babase.Call(babase.screenmessage,
                    'hello from the future 2!'))
babase.do_once() bool[source]

Return whether this is the first time running a line of code.

This is used by print_once() type calls to keep from overflowing logs. The call functions by registering the filename and line where The call is made from. Returns True if this location has not been registered already, and False if it has.

Example: This print will only fire for the first loop iteration:

for i in range(10):
    if babase.do_once():
        print('HelloWorld once from loop!')
babase.existing(obj: ExistableT | None) ExistableT | None[source]

Convert invalid references to None for any babase.Existable object.

To best support type checking, it is important that invalid references not be passed around and instead get converted to values of None. That way the type checker can properly flag attempts to pass possibly-dead objects (FooType | None) into functions expecting only live ones (FooType), etc. This call can be used on any ‘existable’ object (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: https://ballistica.net/wiki/Coding-Style-Guide

babase.fatal_error(message: str) None[source]

Trigger a fatal error. Use this in situations where it is not possible for the engine to continue on in a useful way. This can sometimes help provide more clear information at the exact source of a problem as compared to raising an Exception. In the vast majority of cases, however, exceptions should be preferred.

babase.get_ip_address_type(addr: str) AddressFamily[source]

Return an address-type given an address.

Can be socket.AF_INET or socket.AF_INET6.

babase.get_type_name(cls: type) str[source]

Return a fully qualified type name for a class.

babase.get_virtual_safe_area_size() tuple[float, float][source]

Return the size of the area on screen that will always be visible.

babase.get_virtual_screen_size() tuple[float, float][source]

Return the current virtual size of the display.

babase.getclass(name: str, subclassof: type[T], check_sdlib_modulename_clash: bool = False) type[T][source]

Given a full class name such as foo.bar.MyClass, return the class.

The class will be checked to make sure it is a subclass of the provided ‘subclassof’ class, and a TypeError will be raised if not.

babase.in_logic_thread() bool[source]

Return whether the current thread is the logic thread.

The logic thread is where a large amount of app code runs, and various functionality expects to only be used from there.

babase.is_browser_likely_available() bool[source]

Return whether a browser likely exists on the current device.

If this returns False, you may want to avoid calling open_url() with any lengthy addresses. (open_url() will display an address as a string/qr-code in a window if unable to bring up a browser, but that is only reasonable for small-ish URLs.)

babase.native_stack_trace() str | None[source]

Return a native stack trace as a string, or None if not available.

Stack traces contain different data and formatting across platforms. Only use them for debugging.

babase.normalized_color(color: Sequence[float]) tuple[float, ...][source]

Scale a color so its largest value is 1.0; useful for coloring lights.

babase.open_url(address: str, force_fallback: bool = False) None[source]

Open the provided URL.

Attempts to open the provided url in a web-browser. If that is not possible (or force_fallback is True), instead displays the url as a string and/or qrcode.

babase.pushcall(call: Callable, from_other_thread: bool = False, suppress_other_thread_warning: bool = False, other_thread_use_fg_context: bool = False, raw: bool = False) None[source]

Push a call to the logic-thread’s event loop.

This function expects to be called from the logic thread, and will automatically save and restore the context to behave seamlessly.

To push a call from outside of the logic thread, pass from_other_thread=True. In that case the call will run with no context set. To instead run in whichever context is currently active on the logic thread, pass other_thread_use_fg_context=True. Passing raw=True will skip thread checks and context saves/restores altogether.

babase.quit(confirm: bool = False, quit_type: babase.QuitType | None = None) None[source]

Quit the app.

If confirm is True, a confirm dialog will be presented if conditions allow; otherwise the quit will still be immediate. See docs for QuitType for explanations of the optional quit_type arg.

babase.safecolor(color: Sequence[float], target_intensity: float = 0.6) tuple[float, ...][source]

Given a color tuple, return a color safe to display as text.

Accepts tuples of length 3 or 4. This will slightly brighten very dark colors, etc.

babase.screenmessage(message: str | babase.Lstr, color: Sequence[float] | None = None, log: bool = False) None[source]

Print a message to the local client’s screen in a given color.

Note that this function is purely for local display. To broadcast screen-messages during gameplay, look for methods such as bascenev1.broadcastmessage().

babase.storagename(suffix: str | None = None) str[source]

Generate a unique name for storing class data in shared places.

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 places such as ‘customdata’ dicts, this minimizes the chance of collisions with other similarly named classes.

Note that this will function even if called in the class definition.

Example: Generate a unique name for storage purposes:

class MyThingie:

    # This will give something like
    # '_mymodule_submodule_mythingie_data'.
    _STORENAME = babase.storagename('data')

    # Use that name to store some data in the Activity we were
    # passed.
    def __init__(self, activity):
        activity.customdata[self._STORENAME] = {}
babase.supports_unicode_display() bool[source]

Return whether we can display all unicode characters in the gui.

babase.timestring(timeval: float | int, centi: bool = True) babase.Lstr[source]

Generate a localized string for displaying a time value.

Given a time value, returns a localized string with: (hours if > 0 ) : minutes : seconds : (centiseconds if centi=True).

Warning

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.

babase.utc_now_cloud() datetime.datetime[source]

Returns estimated utc time regardless of local clock settings.

Applies offsets pulled from server communication/etc.

babase.verify_object_death(obj: object) None[source]

Warn if an object does not get freed within a short period.

This can be handy to detect and prevent memory/resource leaks.

Submodules

babase.modutils module

Functionality related to modding.

babase.modutils.create_user_system_scripts() None[source]

Set up a copy of Ballistica app scripts under user scripts dir.

(for editing and experimenting)

babase.modutils.delete_user_system_scripts() None[source]

Clean out the scripts created by create_user_system_scripts().

babase.modutils.get_human_readable_user_scripts_path() str[source]

Return a human readable location of user-scripts.

This is NOT a valid filesystem path; may be something like “(SD Card)”.

babase.modutils.show_user_scripts() None[source]

Open or nicely print the location of the user-scripts directory.