Cleaned up some subsystems such as ba.app.lang and ba.app.ui

This commit is contained in:
Eric Froemling 2020-10-15 22:08:06 -07:00
parent 819d58d681
commit 2d5c2dda9a
51 changed files with 1027 additions and 788 deletions

View File

@ -3932,24 +3932,24 @@
"assets/build/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/b5/85/f8b6d0558ddb87267f34254b1450",
"assets/build/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/1c/e1/4a1a2eddda2f4aebd5f8b64ab08e",
"assets/build/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/50/8d/bc2600ac9491f1b14d659709451f",
"build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/a3/b4/d47b1f9ca27dc994d225efc9b652",
"build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/a1/20/f75ee36d80a99dbfe1ff79db2093",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/94/91/af4a3be510e2570651fbb8d4c297",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/cd/6c/73657b342cb1666dd3ac734a93c8",
"build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/a6/9d/8830abe356b106005b793952f60c",
"build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/93/e7/063a5a038904ef0641d405a5e66d",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/69/e8/77839309e3301d62bbf77624d810",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/95/4e/9e5dbd0b19acddc2cd056eca123c",
"build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/0c/bb/3990f398178b924de0b493db14d8",
"build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/85/14/604f8855cfa461797c5e3c5b5aab",
"build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/e1/83/9d2ffd1a9f149a18c005be57db29",
"build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/f6/63/9aaf6704f8dcb6e32808d09989a4",
"build/prefab/lib/linux_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/15/54/bfba7d740c7221a5d46e8e21c756",
"build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/4b/1f/ca36bea671a5b88a7e2ccf2e4c4a",
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/57/2d/e4b9a67cb21131cdcdfb8287f9e7",
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/59/b6/6ffc20f2c0253180496d2dae968c",
"build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/cd/4f/d760d9fce637b61efeed648063cc",
"build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7c/5a/d63a634b3886c9cf1b3697d24b75",
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/46/80/98efbaeed954d2b008a9bfb77e12",
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/89/24/6aae1e666373c46b409e44d7cdf7"
"build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/68/70/ecdec08c0236f5bebbf298c9f4cd",
"build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/85/7f/53fb1e3f1414b4865315d815b17a",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/9e/ae/586229f233660b6b49ca655b7c1b",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e5/a5/c6b90e3629a8041a68827aafec3f",
"build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/d3/66/82b2cab9b1438c30cfd33eefba5e",
"build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/52/37/253765404b79740cd4a7ac5fe325",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c7/69/ecdbcee9df40225475391beb4cb2",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/34/51/cb528ea6eb5ac853794922a0cc34",
"build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/d4/b8/d3a690970ca379d805432bde8533",
"build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/4d/16/b9014f5d983481731b54f7045dd9",
"build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/1e/11/d3c478923937f376eecb25561ae9",
"build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/d6/b4/b7854676ec826e42aa3ecb394b49",
"build/prefab/lib/linux_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7a/76/bf185c1ea65f3c25cfa63666511b",
"build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/49/30/5acd0d56b736a3729e7689cd4e18",
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f7/e8/197874ac6d756c341628ea4bb518",
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/50/6f/140a3d77288e2355605c9d0c942a",
"build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/1c/ca/62d29425e0ad0b17c145ecbc97fe",
"build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c0/fa/52a38fc153714bff3bcda077ed75",
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/75/5b/e84edb24f5df313bb6ebafa91b19",
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/0f/2e/af28c0026c0379e5441e789e9721"
}

View File

@ -850,6 +850,7 @@
<w>getplayer</w>
<w>getpt</w>
<w>getremote</w>
<w>getres</w>
<w>getscanresults</w>
<w>getscoreconfig</w>
<w>getsession</w>
@ -1015,6 +1016,7 @@
<w>intex</w>
<w>intp</w>
<w>introspectable</w>
<w>iobj</w>
<w>ipaddress</w>
<w>ipos</w>
<w>iprof</w>
@ -1359,6 +1361,7 @@
<w>nline</w>
<w>nlines</w>
<w>nntplib</w>
<w>noassets</w>
<w>nodeactor</w>
<w>nodepos</w>
<w>nodpi</w>

View File

@ -1,4 +1,8 @@
### 1.5.26 (20178)
### 1.5.27 (20218)
- Language functionality has been consolidated into a LanguageSubsystem object at ba.app.lang
- ba.get_valid_languages() is now an attr: ba.app.lang.available_languages
### 1.5.26 (20217)
- Simplified licensing header on python scripts.
- General project refactoring in order to open source most of the C++ layer.

View File

@ -30,7 +30,7 @@
"ba_data/python/ba/__pycache__/_hooks.cpython-38.opt-1.pyc",
"ba_data/python/ba/__pycache__/_input.cpython-38.opt-1.pyc",
"ba_data/python/ba/__pycache__/_keyboard.cpython-38.opt-1.pyc",
"ba_data/python/ba/__pycache__/_lang.cpython-38.opt-1.pyc",
"ba_data/python/ba/__pycache__/_language.cpython-38.opt-1.pyc",
"ba_data/python/ba/__pycache__/_level.cpython-38.opt-1.pyc",
"ba_data/python/ba/__pycache__/_lobby.cpython-38.opt-1.pyc",
"ba_data/python/ba/__pycache__/_map.cpython-38.opt-1.pyc",
@ -91,7 +91,7 @@
"ba_data/python/ba/_hooks.py",
"ba_data/python/ba/_input.py",
"ba_data/python/ba/_keyboard.py",
"ba_data/python/ba/_lang.py",
"ba_data/python/ba/_language.py",
"ba_data/python/ba/_level.py",
"ba_data/python/ba/_lobby.py",
"ba_data/python/ba/_map.py",

View File

@ -161,7 +161,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \
build/ba_data/python/ba/_hooks.py \
build/ba_data/python/ba/_input.py \
build/ba_data/python/ba/_keyboard.py \
build/ba_data/python/ba/_lang.py \
build/ba_data/python/ba/_language.py \
build/ba_data/python/ba/_level.py \
build/ba_data/python/ba/_lobby.py \
build/ba_data/python/ba/_map.py \
@ -399,7 +399,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
build/ba_data/python/ba/__pycache__/_hooks.cpython-38.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_input.cpython-38.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_keyboard.cpython-38.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_lang.cpython-38.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_language.cpython-38.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_level.cpython-38.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_lobby.cpython-38.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_map.cpython-38.opt-1.pyc \

View File

@ -42,9 +42,10 @@ from ba._gameactivity import GameActivity
from ba._gameresults import GameResults
from ba._settings import (Setting, IntSetting, FloatSetting, ChoiceSetting,
BoolSetting, IntChoiceSetting, FloatChoiceSetting)
from ba._lang import Lstr, setlanguage, get_valid_languages
from ba._language import Lstr, LanguageSubsystem
from ba._map import Map, getmaps
from ba._session import Session
from ba._ui import UISubsystem
from ba._servermode import ServerController
from ba._score import ScoreType, ScoreConfig
from ba._stats import PlayerScoredMessage, PlayerRecord, Stats
@ -70,7 +71,8 @@ from ba._messages import (UNHANDLED, OutOfBoundsMessage, DeathType, DieMessage,
ShouldShatterMessage, ImpactDamageMessage,
FreezeMessage, ThawMessage, HitMessage,
CelebrateMessage)
from ba._music import setmusic, MusicPlayer, MusicType, MusicPlayMode
from ba._music import (setmusic, MusicPlayer, MusicType, MusicPlayMode,
MusicSubsystem)
from ba._powerup import PowerupMessage, PowerupAcceptMessage
from ba._multiteamsession import MultiTeamSession
from ba.ui import Window, UIController, uicleanupcheck

View File

@ -18,7 +18,7 @@ def handle_account_gained_tickets(count: int) -> None:
(internal)
"""
from ba._lang import Lstr
from ba._language import Lstr
_ba.screenmessage(Lstr(resource='getTicketsWindow.receivedTicketsText',
subs=[('${COUNT}', str(count))]),
color=(0, 1, 0))
@ -166,7 +166,7 @@ def have_pro_options() -> bool:
def show_post_purchase_message() -> None:
"""(internal)"""
from ba._lang import Lstr
from ba._language import Lstr
from ba._enums import TimeType
app = _ba.app
cur_time = _ba.time(TimeType.REAL)
@ -183,14 +183,15 @@ def show_post_purchase_message() -> None:
def on_account_state_changed() -> None:
"""(internal)"""
import time
from ba import _lang
from ba import _language
app = _ba.app
# Run any pending promo codes we had queued up while not signed in.
if _ba.get_account_state() == 'signed_in' and app.pending_promo_codes:
for code in app.pending_promo_codes:
_ba.screenmessage(_lang.Lstr(resource='submittingPromoCodeText'),
color=(0, 1, 0))
_ba.screenmessage(
_language.Lstr(resource='submittingPromoCodeText'),
color=(0, 1, 0))
_ba.add_transaction({
'type': 'PROMO_CODE',
'expire_time': time.time() + 5,

View File

@ -269,7 +269,7 @@ class Achievement:
@property
def display_name(self) -> ba.Lstr:
"""Return a ba.Lstr for this Achievement's name."""
from ba._lang import Lstr
from ba._language import Lstr
name: Union[ba.Lstr, str]
try:
if self._level_name != '':
@ -289,16 +289,18 @@ class Achievement:
@property
def description(self) -> ba.Lstr:
"""Get a ba.Lstr for the Achievement's brief description."""
from ba._lang import Lstr, get_resource
if 'description' in get_resource('achievements')[self._name]:
from ba._language import Lstr
if 'description' in _ba.app.lang.get_resource('achievements')[
self._name]:
return Lstr(resource='achievements.' + self._name + '.description')
return Lstr(resource='achievements.' + self._name + '.descriptionFull')
@property
def description_complete(self) -> ba.Lstr:
"""Get a ba.Lstr for the Achievement's description when completed."""
from ba._lang import Lstr, get_resource
if 'descriptionComplete' in get_resource('achievements')[self._name]:
from ba._language import Lstr
if 'descriptionComplete' in _ba.app.lang.get_resource('achievements')[
self._name]:
return Lstr(resource='achievements.' + self._name +
'.descriptionComplete')
return Lstr(resource='achievements.' + self._name +
@ -307,7 +309,7 @@ class Achievement:
@property
def description_full(self) -> ba.Lstr:
"""Get a ba.Lstr for the Achievement's full description."""
from ba._lang import Lstr
from ba._language import Lstr
return Lstr(
resource='achievements.' + self._name + '.descriptionFull',
@ -318,7 +320,7 @@ class Achievement:
@property
def description_full_complete(self) -> ba.Lstr:
"""Get a ba.Lstr for the Achievement's full desc. when completed."""
from ba._lang import Lstr
from ba._language import Lstr
return Lstr(
resource='achievements.' + self._name + '.descriptionFullComplete',
subs=[('${LEVEL}',
@ -353,7 +355,7 @@ class Achievement:
Shows the Achievement icon, name, and description.
"""
# pylint: disable=cyclic-import
from ba._lang import Lstr
from ba._language import Lstr
from ba._enums import SpecialChar
from ba._coopsession import CoopSession
from bastd.actor.image import Image
@ -657,7 +659,7 @@ class Achievement:
from bastd.actor.text import Text
from bastd.actor.image import Image
from ba._general import WeakCall
from ba._lang import Lstr
from ba._language import Lstr
from ba._messages import DieMessage
from ba._enums import TimeType, SpecialChar
app = _ba.app

View File

@ -167,7 +167,7 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]):
def on_begin(self) -> None:
# pylint: disable=cyclic-import
from bastd.actor.text import Text
from ba import _lang
from ba import _language
super().on_begin()
# Pop up a 'press any button to continue' statement after our
@ -176,9 +176,9 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]):
if _ba.app.ui.uiscale is UIScale.LARGE:
# FIXME: Need a better way to determine whether we've probably
# got a keyboard.
sval = _lang.Lstr(resource='pressAnyKeyButtonText')
sval = _language.Lstr(resource='pressAnyKeyButtonText')
else:
sval = _lang.Lstr(resource='pressAnyButtonText')
sval = _language.Lstr(resource='pressAnyButtonText')
Text(self._custom_continue_message
if self._custom_continue_message is not None else sval,

View File

@ -11,7 +11,7 @@ import _ba
if TYPE_CHECKING:
import ba
from ba import _lang, _meta
from ba import _language, _meta
from bastd.actor import spazappearance
from typing import Optional, Dict, Set, Any, Type, Tuple, Callable, List
@ -49,87 +49,6 @@ class App:
assert isinstance(self._env['config_file_path'], str)
return self._env['config_file_path']
@property
def locale(self) -> str:
"""Raw country/language code detected by the game (such as 'en_US').
Generally for language-specific code you should look at
ba.App.language, which is the language the game is using
(which may differ from locale if the user sets a language, etc.)
"""
assert isinstance(self._env['locale'], str)
return self._env['locale']
def can_display_language(self, language: str) -> bool:
"""Tell whether we can display a particular language.
(internal)
On some platforms we don't have unicode rendering yet
which limits the languages we can draw.
"""
# We don't yet support full unicode display on windows or linux :-(.
if (language in {
'Chinese', 'ChineseTraditional', 'Persian', 'Korean', 'Arabic',
'Hindi', 'Vietnamese'
} and not _ba.can_display_full_unicode()):
return False
return True
def _get_default_language(self) -> str:
languages = {
'de': 'German',
'es': 'Spanish',
'sk': 'Slovak',
'it': 'Italian',
'nl': 'Dutch',
'da': 'Danish',
'pt': 'Portuguese',
'fr': 'French',
'el': 'Greek',
'ru': 'Russian',
'pl': 'Polish',
'sv': 'Swedish',
'eo': 'Esperanto',
'cs': 'Czech',
'hr': 'Croatian',
'hu': 'Hungarian',
'be': 'Belarussian',
'ro': 'Romanian',
'ko': 'Korean',
'fa': 'Persian',
'ar': 'Arabic',
'zh': 'Chinese',
'tr': 'Turkish',
'id': 'Indonesian',
'sr': 'Serbian',
'uk': 'Ukrainian',
'vi': 'Vietnamese',
'vec': 'Venetian',
'hi': 'Hindi'
}
# Special case for Chinese: map specific variations to traditional.
# (otherwise will map to 'Chinese' which is simplified)
if self.locale in ('zh_HANT', 'zh_TW'):
language = 'ChineseTraditional'
else:
language = languages.get(self.locale[:2], 'English')
if not self.can_display_language(language):
language = 'English'
return language
@property
def language(self) -> str:
"""The name of the language the game is running in.
This can be selected explicitly by the user or may be set
automatically based on ba.App.locale or other factors.
"""
assert isinstance(self.config, dict)
return self.config.get('Lang', self.default_language)
@property
def user_agent_string(self) -> str:
"""String containing various bits of info about OS/device/etc."""
@ -254,7 +173,8 @@ class App:
"""
# pylint: disable=too-many-statements
from ba._music import MusicSubsystem
from ba._ui import UI
from ba._language import LanguageSubsystem
from ba._ui import UISubsystem
# Config.
self.config_file_healthy = False
@ -283,7 +203,6 @@ class App:
self.active_plugins: Dict[str, ba.Plugin] = {}
# Misc.
self.default_language = self._get_default_language()
self.metascan: Optional[_meta.ScanResults] = None
self.tips: List[str] = []
self.stress_test_reset_timer: Optional[ba.Timer] = None
@ -326,8 +245,7 @@ class App:
self.music = MusicSubsystem()
# Language.
self.language_target: Optional[_lang.AttrDict] = None
self.language_merged: Optional[_lang.AttrDict] = None
self.lang = LanguageSubsystem()
# Achievements.
self.achievements: List[ba.Achievement] = []
@ -358,7 +276,7 @@ class App:
self.coop_session_args: Dict = {}
# UI.
self.ui = UI()
self.ui = UISubsystem()
self.value_test_defaults: dict = {}
self.first_main_menu = True # FIXME: Move to mainmenu class.
@ -551,7 +469,7 @@ class App:
activity: Optional[ba.Activity] = _ba.get_foreground_host_activity()
if (activity is not None and activity.allow_pausing
and not _ba.have_connected_clients()):
from ba import _gameutils, _lang
from ba import _gameutils, _language
from ba._nodeactor import NodeActor
# FIXME: Shouldn't be touching scene stuff here;
@ -567,10 +485,14 @@ class App:
_ba.newnode(
'text',
attrs={
'text': _lang.Lstr(resource='pausedByHostText'),
'client_only': True,
'flatness': 1.0,
'h_align': 'center'
'text':
_language.Lstr(resource='pausedByHostText'),
'client_only':
True,
'flatness':
1.0,
'h_align':
'center'
}))
def resume(self) -> None:
@ -705,7 +627,7 @@ class App:
def do_remove_in_game_ads_message(self) -> None:
"""(internal)"""
from ba._lang import Lstr
from ba._language import Lstr
from ba._enums import TimeType
# Print this message once every 10 minutes at most.
@ -730,7 +652,7 @@ class App:
def handle_deep_link(self, url: str) -> None:
"""Handle a deep link URL."""
from ba._lang import Lstr
from ba._language import Lstr
from ba._enums import TimeType
appname = _ba.appname()
if url.startswith(f'{appname}://code/'):

View File

@ -40,8 +40,8 @@ def is_browser_likely_available() -> bool:
def get_remote_app_name() -> ba.Lstr:
"""(internal)"""
from ba import _lang
return _lang.Lstr(resource='remote_app.app_name')
from ba import _language
return _language.Lstr(resource='remote_app.app_name')
def should_submit_debug_info() -> bool:
@ -213,15 +213,15 @@ def print_live_object_warnings(when: Any,
def print_corrupt_file_error() -> None:
"""Print an error if a corrupt file is found."""
from ba._lang import get_resource
from ba._general import Call
from ba._enums import TimeType
_ba.timer(
2.0,
lambda: _ba.screenmessage(get_resource('internal.corruptFileText').
replace('${EMAIL}', 'support@froemling.net'),
color=(1, 0, 0)),
timetype=TimeType.REAL)
_ba.timer(2.0,
lambda: _ba.screenmessage(
_ba.app.lang.get_resource('internal.corruptFileText').
replace('${EMAIL}', 'support@froemling.net'),
color=(1, 0, 0),
),
timetype=TimeType.REAL)
_ba.timer(2.0,
Call(_ba.playsound, _ba.getsound('error')),
timetype=TimeType.REAL)

View File

@ -151,13 +151,14 @@ def run_media_reload_benchmark() -> None:
def delay_add(start_time: float) -> None:
def doit(start_time_2: float) -> None:
from ba import _lang
_ba.screenmessage(
_lang.get_resource('debugWindow.totalReloadTimeText').replace(
'${TIME}', str(_ba.time(TimeType.REAL) - start_time_2)))
_ba.app.lang.get_resource(
'debugWindow.totalReloadTimeText').replace(
'${TIME}',
str(_ba.time(TimeType.REAL) - start_time_2)))
_ba.print_load_info()
if _ba.app.config.resolve('Texture Quality') != 'High':
_ba.screenmessage(_lang.get_resource(
_ba.screenmessage(_ba.app.lang.get_resource(
'debugWindow.reloadBenchmarkBestResultsText'),
color=(1, 1, 0))

View File

@ -14,7 +14,10 @@ if TYPE_CHECKING:
class Collision:
"""A class providing info about occurring collisions."""
"""A class providing info about occurring collisions.
Category: Gameplay Classes
"""
@property
def position(self) -> ba.Vec3:
@ -62,5 +65,8 @@ _collision = Collision()
def getcollision() -> Collision:
"""Return the in-progress collision."""
"""Return the in-progress collision.
Category: Gameplay Functions
"""
return _collision

View File

@ -139,7 +139,7 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
def _show_remaining_achievements(self) -> None:
# pylint: disable=cyclic-import
from ba._achievement import get_achievements_for_coop_level
from ba._lang import Lstr
from ba._language import Lstr
from bastd.actor.text import Text
ts_h_offs = 30
v_offs = -200

View File

@ -229,7 +229,7 @@ class CoopSession(Session):
# pylint: disable=too-many-statements
# pylint: disable=cyclic-import
from ba._activitytypes import JoinActivity, TransitionActivity
from ba._lang import Lstr
from ba._language import Lstr
from ba._general import WeakCall
from ba._coopgame import CoopGameActivity
from ba._gameresults import GameResults

View File

@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, TypeVar
from ba._activity import Activity
from ba._score import ScoreConfig
from ba._lang import Lstr
from ba._language import Lstr
from ba._messages import PlayerDiedMessage, StandMessage
from ba._error import NotFoundError, print_error, print_exception
from ba._general import Call, WeakCall

View File

@ -106,7 +106,7 @@ class GameResults:
(properly formatted for the score type.)
"""
from ba._gameutils import timestring
from ba._lang import Lstr
from ba._language import Lstr
from ba._enums import TimeFormat
from ba._score import ScoreType
if not self._game_set:

View File

@ -267,7 +267,7 @@ def timestring(timeval: float,
use a 'timedisplay' Node and attribute connections.
"""
from ba._lang import Lstr
from ba._language import Lstr
# Temp sanity check while we transition from milliseconds to seconds
# based time values.

View File

@ -40,31 +40,31 @@ def set_config_fullscreen_off() -> None:
def not_signed_in_screen_message() -> None:
from ba._lang import Lstr
from ba._language import Lstr
_ba.screenmessage(Lstr(resource='notSignedInErrorText'))
def connecting_to_party_message() -> None:
from ba._lang import Lstr
from ba._language import Lstr
_ba.screenmessage(Lstr(resource='internal.connectingToPartyText'),
color=(1, 1, 1))
def rejecting_invite_already_in_party_message() -> None:
from ba._lang import Lstr
from ba._language import Lstr
_ba.screenmessage(
Lstr(resource='internal.rejectingInviteAlreadyInPartyText'),
color=(1, 0.5, 0))
def connection_failed_message() -> None:
from ba._lang import Lstr
from ba._language import Lstr
_ba.screenmessage(Lstr(resource='internal.connectionFailedText'),
color=(1, 0.5, 0))
def temporarily_unavailable_message() -> None:
from ba._lang import Lstr
from ba._language import Lstr
_ba.playsound(_ba.getsound('error'))
_ba.screenmessage(
Lstr(resource='getTicketsWindow.unavailableTemporarilyText'),
@ -72,20 +72,20 @@ def temporarily_unavailable_message() -> None:
def in_progress_message() -> None:
from ba._lang import Lstr
from ba._language import Lstr
_ba.playsound(_ba.getsound('error'))
_ba.screenmessage(Lstr(resource='getTicketsWindow.inProgressText'),
color=(1, 0, 0))
def error_message() -> None:
from ba._lang import Lstr
from ba._language import Lstr
_ba.playsound(_ba.getsound('error'))
_ba.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
def purchase_not_valid_error() -> None:
from ba._lang import Lstr
from ba._language import Lstr
_ba.playsound(_ba.getsound('error'))
_ba.screenmessage(Lstr(resource='store.purchaseNotValidError',
subs=[('${EMAIL}', 'support@froemling.net')]),
@ -93,28 +93,28 @@ def purchase_not_valid_error() -> None:
def purchase_already_in_progress_error() -> None:
from ba._lang import Lstr
from ba._language import Lstr
_ba.playsound(_ba.getsound('error'))
_ba.screenmessage(Lstr(resource='store.purchaseAlreadyInProgressText'),
color=(1, 0, 0))
def gear_vr_controller_warning() -> None:
from ba._lang import Lstr
from ba._language import Lstr
_ba.playsound(_ba.getsound('error'))
_ba.screenmessage(Lstr(resource='usesExternalControllerText'),
color=(1, 0, 0))
def orientation_reset_cb_message() -> None:
from ba._lang import Lstr
from ba._language import Lstr
_ba.screenmessage(
Lstr(resource='internal.vrOrientationResetCardboardText'),
color=(0, 1, 0))
def orientation_reset_message() -> None:
from ba._lang import Lstr
from ba._language import Lstr
_ba.screenmessage(Lstr(resource='internal.vrOrientationResetText'),
color=(0, 1, 0))
@ -133,8 +133,8 @@ def launch_main_menu_session() -> None:
def language_test_toggle() -> None:
from ba._lang import setlanguage
setlanguage('Gibberish' if _ba.app.language == 'English' else 'English')
_ba.app.lang.setlanguage('Gibberish' if _ba.app.lang.language ==
'English' else 'English')
def award_in_control_achievement() -> None:
@ -156,7 +156,7 @@ def launch_coop_game(name: str) -> None:
def purchases_restored_message() -> None:
from ba._lang import Lstr
from ba._language import Lstr
_ba.screenmessage(Lstr(resource='getTicketsWindow.purchasesRestoredText'),
color=(0, 1, 0))
@ -168,7 +168,7 @@ def dismiss_wii_remotes_window() -> None:
def unavailable_message() -> None:
from ba._lang import Lstr
from ba._language import Lstr
_ba.screenmessage(Lstr(resource='getTicketsWindow.unavailableText'),
color=(1, 0, 0))
@ -185,7 +185,7 @@ def set_last_ad_network(sval: str) -> None:
def no_game_circle_message() -> None:
from ba._lang import Lstr
from ba._language import Lstr
_ba.screenmessage(Lstr(resource='noGameCircleText'), color=(1, 0, 0))
@ -250,7 +250,7 @@ def read_config() -> None:
def ui_remote_press() -> None:
"""Handle a press by a remote device that is only usable for nav."""
from ba._lang import Lstr
from ba._language import Lstr
# Can be called without a context; need a context for getsound.
with _ba.Context('ui'):

View File

@ -1,464 +0,0 @@
# Released under the MIT License. See LICENSE for details.
#
"""Language related functionality."""
from __future__ import annotations
import json
import os
from typing import TYPE_CHECKING, overload
import _ba
if TYPE_CHECKING:
import ba
from typing import Any, Dict, List, Optional, Tuple, Union, Sequence
class Lstr:
"""Used to define strings in a language-independent way.
category: General Utility Classes
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 bs_language_*.py files
in the game or the translations pages at bombsquadgame.com/translate.
# EXAMPLE 1: specify a string from a resource path
mynode.text = ba.Lstr(resource='audioSettingsWindow.titleText')
# EXAMPLE 2: specify a translated string via a category and english value;
# 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 = ba.Lstr(translate=('gameDescriptions', 'Defeat all enemies'))
# EXAMPLE 3: specify a raw value and some substitutions. Substitutions can
# be used with resource and translate modes as well.
mynode.text = ba.Lstr(value='${A} / ${B}',
subs=[('${A}', str(score)), ('${B}', str(total))])
# EXAMPLE 4: Lstrs can be nested. This example would display the resource
# at res_a but replace ${NAME} with the value of the resource at res_b
mytextnode.text = ba.Lstr(resource='res_a',
subs=[('${NAME}', ba.Lstr(resource='res_b'))])
"""
# pylint: disable=redefined-outer-name, dangerous-default-value
# noinspection PyDefaultArgument
@overload
def __init__(self,
*,
resource: str,
fallback_resource: str = '',
fallback_value: str = '',
subs: Sequence[Tuple[str, Union[str, Lstr]]] = []) -> None:
"""Create an Lstr from a string resource."""
...
# noinspection PyShadowingNames,PyDefaultArgument
@overload
def __init__(self,
*,
translate: Tuple[str, str],
subs: Sequence[Tuple[str, Union[str, Lstr]]] = []) -> None:
"""Create an Lstr by translating a string in a category."""
...
# noinspection PyDefaultArgument
@overload
def __init__(self,
*,
value: str,
subs: Sequence[Tuple[str, Union[str, Lstr]]] = []) -> None:
"""Create an Lstr from a raw string value."""
...
# pylint: enable=redefined-outer-name, dangerous-default-value
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
if args:
raise TypeError('Lstr accepts only keyword arguments')
# Basically just store the exact args they passed.
# However if they passed any Lstr values for subs,
# replace them with that Lstr's dict.
self.args = keywds
our_type = type(self)
if isinstance(self.args.get('value'), our_type):
raise TypeError("'value' must be a regular string; not an Lstr")
if 'subs' in self.args:
subs_new = []
for key, value in keywds['subs']:
if isinstance(value, our_type):
subs_new.append((key, value.args))
else:
subs_new.append((key, value))
self.args['subs'] = subs_new
# As of protocol 31 we support compact key names
# ('t' instead of 'translate', etc). Convert as needed.
if 'translate' in keywds:
keywds['t'] = keywds['translate']
del keywds['translate']
if 'resource' in keywds:
keywds['r'] = keywds['resource']
del keywds['resource']
if 'value' in keywds:
keywds['v'] = keywds['value']
del keywds['value']
if 'fallback' in keywds:
from ba import _error
_error.print_error(
'deprecated "fallback" arg passed to Lstr(); use '
'either "fallback_resource" or "fallback_value"',
once=True)
keywds['f'] = keywds['fallback']
del keywds['fallback']
if 'fallback_resource' in keywds:
keywds['f'] = keywds['fallback_resource']
del keywds['fallback_resource']
if 'subs' in keywds:
keywds['s'] = keywds['subs']
del keywds['subs']
if 'fallback_value' in keywds:
keywds['fv'] = keywds['fallback_value']
del keywds['fallback_value']
def evaluate(self) -> str:
"""Evaluate the Lstr and returns a flat string in the current language.
You should avoid doing this as much as possible and instead pass
and store Lstr values.
"""
return _ba.evaluate_lstr(self._get_json())
def is_flat_value(self) -> bool:
"""Return whether the Lstr is 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.
"""
return bool('v' in self.args and not self.args.get('s', []))
def _get_json(self) -> str:
try:
return json.dumps(self.args, separators=(',', ':'))
except Exception:
from ba import _error
_error.print_exception('_get_json failed for', self.args)
return 'JSON_ERR'
def __str__(self) -> str:
return '<ba.Lstr: ' + self._get_json() + '>'
def __repr__(self) -> str:
return '<ba.Lstr: ' + self._get_json() + '>'
@staticmethod
def from_json(json_string: str) -> ba.Lstr:
"""Given a json string, returns a ba.Lstr. Does no data validation."""
lstr = Lstr(value='')
lstr.args = json.loads(json_string)
return lstr
def setlanguage(language: Optional[str],
print_change: bool = True,
store_to_config: bool = True) -> None:
"""Set the active language used for the game.
category: General Utility Functions
Pass None to use OS default language.
"""
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
# pylint: disable=too-many-branches
cfg = _ba.app.config
cur_language = cfg.get('Lang', None)
# Store this in the config if its changing.
if language != cur_language and store_to_config:
if language is None:
if 'Lang' in cfg:
del cfg['Lang'] # Clear it out for default.
else:
cfg['Lang'] = language
cfg.commit()
switched = True
else:
switched = False
with open('ba_data/data/languages/english.json') as infile:
lenglishvalues = json.loads(infile.read())
# None implies default.
if language is None:
language = _ba.app.default_language
try:
if language == 'English':
lmodvalues = None
else:
lmodfile = 'ba_data/data/languages/' + language.lower() + '.json'
with open(lmodfile) as infile:
lmodvalues = json.loads(infile.read())
except Exception:
from ba import _error
_error.print_exception('Exception importing language:', language)
_ba.screenmessage("Error setting language to '" + language +
"'; see log for details",
color=(1, 0, 0))
switched = False
lmodvalues = None
# Create an attrdict of *just* our target language.
_ba.app.language_target = AttrDict()
langtarget = _ba.app.language_target
assert langtarget is not None
_add_to_attr_dict(langtarget,
lmodvalues if lmodvalues is not None else lenglishvalues)
# Create an attrdict of our target language overlaid on our base (english).
languages = [lenglishvalues]
if lmodvalues is not None:
languages.append(lmodvalues)
lfull = AttrDict()
for lmod in languages:
_add_to_attr_dict(lfull, lmod)
_ba.app.language_merged = lfull
# Pass some keys/values in for low level code to use;
# start with everything in their 'internal' section.
internal_vals = [
v for v in list(lfull['internal'].items()) if isinstance(v[1], str)
]
# Cherry-pick various other values to include.
# (should probably get rid of the 'internal' section
# and do everything this way)
for value in [
'replayNameDefaultText', 'replayWriteErrorText',
'replayVersionErrorText', 'replayReadErrorText'
]:
internal_vals.append((value, lfull[value]))
internal_vals.append(
('axisText', lfull['configGamepadWindow']['axisText']))
internal_vals.append(('buttonText', lfull['buttonText']))
lmerged = _ba.app.language_merged
assert lmerged is not None
random_names = [
n.strip() for n in lmerged['randomPlayerNamesText'].split(',')
]
random_names = [n for n in random_names if n != '']
_ba.set_internal_language_keys(internal_vals, random_names)
if switched and print_change:
_ba.screenmessage(Lstr(resource='languageSetText',
subs=[('${LANGUAGE}',
Lstr(translate=('languages', language)))
]),
color=(0, 1, 0))
def _add_to_attr_dict(dst: AttrDict, src: Dict) -> None:
for key, value in list(src.items()):
if isinstance(value, dict):
try:
dst_dict = dst[key]
except Exception:
dst_dict = dst[key] = AttrDict()
if not isinstance(dst_dict, AttrDict):
raise RuntimeError("language key '" + key +
"' is defined both as a dict and value")
_add_to_attr_dict(dst_dict, value)
else:
if not isinstance(value, (float, int, bool, str, str, type(None))):
raise TypeError("invalid value type for res '" + key + "': " +
str(type(value)))
dst[key] = value
class AttrDict(dict):
"""A dict that can be accessed with dot notation.
(so foo.bar is equivalent to foo['bar'])
"""
def __getattr__(self, attr: str) -> Any:
val = self[attr]
assert not isinstance(val, bytes)
return val
def __setattr__(self, attr: str, value: Any) -> None:
raise Exception()
def get_resource(resource: str,
fallback_resource: str = None,
fallback_value: Any = None) -> Any:
"""Return a translation resource by name."""
try:
# If we have no language set, go ahead and set it.
if _ba.app.language_merged is None:
language = _ba.app.language
try:
setlanguage(language,
print_change=False,
store_to_config=False)
except Exception:
from ba import _error
_error.print_exception('exception setting language to',
language)
# Try english as a fallback.
if language != 'English':
print('Resorting to fallback language (English)')
try:
setlanguage('English',
print_change=False,
store_to_config=False)
except Exception:
_error.print_exception(
'error setting language to english fallback')
# If they provided a fallback_resource value, try the
# target-language-only dict first and then fall back to trying the
# fallback_resource value in the merged dict.
if fallback_resource is not None:
try:
values = _ba.app.language_target
splits = resource.split('.')
dicts = splits[:-1]
key = splits[-1]
for dct in dicts:
assert values is not None
values = values[dct]
assert values is not None
val = values[key]
return val
except Exception:
# FIXME: Shouldn't we try the fallback resource in the merged
# dict AFTER we try the main resource in the merged dict?
try:
values = _ba.app.language_merged
splits = fallback_resource.split('.')
dicts = splits[:-1]
key = splits[-1]
for dct in dicts:
assert values is not None
values = values[dct]
assert values is not None
val = values[key]
return val
except Exception:
# If we got nothing for fallback_resource, default to the
# normal code which checks or primary value in the merge
# dict; there's a chance we can get an english value for
# it (which we weren't looking for the first time through).
pass
values = _ba.app.language_merged
splits = resource.split('.')
dicts = splits[:-1]
key = splits[-1]
for dct in dicts:
assert values is not None
values = values[dct]
assert values is not None
val = values[key]
return val
except Exception:
# Ok, looks like we couldn't find our main or fallback resource
# anywhere. Now if we've been given a fallback value, return it;
# otherwise fail.
from ba import _error
if fallback_value is not None:
return fallback_value
raise _error.NotFoundError(
f"Resource not found: '{resource}'") from None
def translate(category: str,
strval: str,
raise_exceptions: bool = False,
print_errors: bool = False) -> str:
"""Translate a value (or return the value if no translation available)
Generally you should use ba.Lstr which handles dynamic translation,
as opposed to this which returns a flat string.
"""
try:
translated = get_resource('translations')[category][strval]
except Exception as exc:
if raise_exceptions:
raise
if print_errors:
print(('Translate error: category=\'' + category + '\' name=\'' +
strval + '\' exc=' + str(exc) + ''))
translated = None
translated_out: str
if translated is None:
translated_out = strval
else:
translated_out = translated
assert isinstance(translated_out, str)
return translated_out
def get_valid_languages() -> List[str]:
"""Return a list containing names of all available languages.
category: General Utility Functions
Languages that may be present but are not displayable on the running
version of the game are ignored.
"""
langs = set()
app = _ba.app
try:
names = os.listdir('ba_data/data/languages')
names = [n.replace('.json', '').capitalize() for n in names]
# FIXME: our simple capitalization fails on multi-word names;
# should handle this in a better way...
for i, name in enumerate(names):
if name == 'Chinesetraditional':
names[i] = 'ChineseTraditional'
except Exception:
from ba import _error
_error.print_exception()
names = []
for name in names:
if app.can_display_language(name):
langs.add(name)
return sorted(name for name in names if app.can_display_language(name))
def is_custom_unicode_char(char: str) -> bool:
"""Return whether a char is in the custom unicode range we use."""
assert isinstance(char, str)
if len(char) != 1:
raise ValueError('Invalid Input; must be length 1')
return 0xE000 <= ord(char) <= 0xF8FF

View File

@ -0,0 +1,562 @@
# Released under the MIT License. See LICENSE for details.
#
"""Language related functionality."""
from __future__ import annotations
import json
import os
from typing import TYPE_CHECKING, overload
import _ba
if TYPE_CHECKING:
import ba
from typing import Any, Dict, List, Optional, Tuple, Union, Sequence
class LanguageSubsystem:
"""Wraps up language related app functionality.
Category: App Classes
To use this class, access the single instance of it at 'ba.app.lang'.
"""
def __init__(self) -> None:
self.language_target: Optional[AttrDict] = None
self.language_merged: Optional[AttrDict] = None
self.default_language = self._get_default_language()
def _can_display_language(self, language: str) -> bool:
"""Tell whether we can display a particular language.
On some platforms we don't have unicode rendering yet
which limits the languages we can draw.
"""
# We don't yet support full unicode display on windows or linux :-(.
if (language in {
'Chinese', 'ChineseTraditional', 'Persian', 'Korean', 'Arabic',
'Hindi', 'Vietnamese'
} and not _ba.can_display_full_unicode()):
return False
return True
@property
def locale(self) -> str:
"""Raw country/language code detected by the game (such as 'en_US').
Generally for language-specific code you should look at
ba.App.language, which is the language the game is using
(which may differ from locale if the user sets a language, etc.)
"""
env = _ba.env()
assert isinstance(env['locale'], str)
return env['locale']
def _get_default_language(self) -> str:
languages = {
'de': 'German',
'es': 'Spanish',
'sk': 'Slovak',
'it': 'Italian',
'nl': 'Dutch',
'da': 'Danish',
'pt': 'Portuguese',
'fr': 'French',
'el': 'Greek',
'ru': 'Russian',
'pl': 'Polish',
'sv': 'Swedish',
'eo': 'Esperanto',
'cs': 'Czech',
'hr': 'Croatian',
'hu': 'Hungarian',
'be': 'Belarussian',
'ro': 'Romanian',
'ko': 'Korean',
'fa': 'Persian',
'ar': 'Arabic',
'zh': 'Chinese',
'tr': 'Turkish',
'id': 'Indonesian',
'sr': 'Serbian',
'uk': 'Ukrainian',
'vi': 'Vietnamese',
'vec': 'Venetian',
'hi': 'Hindi'
}
# Special case for Chinese: map specific variations to traditional.
# (otherwise will map to 'Chinese' which is simplified)
if self.locale in ('zh_HANT', 'zh_TW'):
language = 'ChineseTraditional'
else:
language = languages.get(self.locale[:2], 'English')
if not self._can_display_language(language):
language = 'English'
return language
@property
def language(self) -> str:
"""The name of the language the game is running in.
This can be selected explicitly by the user or may be set
automatically based on ba.App.locale or other factors.
"""
assert isinstance(_ba.app.config, dict)
return _ba.app.config.get('Lang', self.default_language)
@property
def available_languages(self) -> 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.
"""
langs = set()
try:
names = os.listdir('ba_data/data/languages')
names = [n.replace('.json', '').capitalize() for n in names]
# FIXME: our simple capitalization fails on multi-word names;
# should handle this in a better way...
for i, name in enumerate(names):
if name == 'Chinesetraditional':
names[i] = 'ChineseTraditional'
except Exception:
from ba import _error
_error.print_exception()
names = []
for name in names:
if self._can_display_language(name):
langs.add(name)
return sorted(name for name in names
if self._can_display_language(name))
def setlanguage(self,
language: Optional[str],
print_change: bool = True,
store_to_config: bool = True) -> None:
"""Set the active language used for the game.
Pass None to use OS default language.
"""
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
# pylint: disable=too-many-branches
cfg = _ba.app.config
cur_language = cfg.get('Lang', None)
# Store this in the config if its changing.
if language != cur_language and store_to_config:
if language is None:
if 'Lang' in cfg:
del cfg['Lang'] # Clear it out for default.
else:
cfg['Lang'] = language
cfg.commit()
switched = True
else:
switched = False
with open('ba_data/data/languages/english.json') as infile:
lenglishvalues = json.loads(infile.read())
# None implies default.
if language is None:
language = self.default_language
try:
if language == 'English':
lmodvalues = None
else:
lmodfile = 'ba_data/data/languages/' + language.lower(
) + '.json'
with open(lmodfile) as infile:
lmodvalues = json.loads(infile.read())
except Exception:
from ba import _error
_error.print_exception('Exception importing language:', language)
_ba.screenmessage("Error setting language to '" + language +
"'; see log for details",
color=(1, 0, 0))
switched = False
lmodvalues = None
# Create an attrdict of *just* our target language.
self.language_target = AttrDict()
langtarget = self.language_target
assert langtarget is not None
_add_to_attr_dict(
langtarget,
lmodvalues if lmodvalues is not None else lenglishvalues)
# Create an attrdict of our target language overlaid
# on our base (english).
languages = [lenglishvalues]
if lmodvalues is not None:
languages.append(lmodvalues)
lfull = AttrDict()
for lmod in languages:
_add_to_attr_dict(lfull, lmod)
self.language_merged = lfull
# Pass some keys/values in for low level code to use;
# start with everything in their 'internal' section.
internal_vals = [
v for v in list(lfull['internal'].items())
if isinstance(v[1], str)
]
# Cherry-pick various other values to include.
# (should probably get rid of the 'internal' section
# and do everything this way)
for value in [
'replayNameDefaultText', 'replayWriteErrorText',
'replayVersionErrorText', 'replayReadErrorText'
]:
internal_vals.append((value, lfull[value]))
internal_vals.append(
('axisText', lfull['configGamepadWindow']['axisText']))
internal_vals.append(('buttonText', lfull['buttonText']))
lmerged = self.language_merged
assert lmerged is not None
random_names = [
n.strip() for n in lmerged['randomPlayerNamesText'].split(',')
]
random_names = [n for n in random_names if n != '']
_ba.set_internal_language_keys(internal_vals, random_names)
if switched and print_change:
_ba.screenmessage(Lstr(resource='languageSetText',
subs=[('${LANGUAGE}',
Lstr(translate=('languages',
language)))]),
color=(0, 1, 0))
def get_resource(self,
resource: str,
fallback_resource: str = None,
fallback_value: Any = None) -> Any:
"""Return a translation resource by name.
DEPRECATED; use ba.Lstr functionality for these purposes.
"""
try:
# If we have no language set, go ahead and set it.
if self.language_merged is None:
language = self.language
try:
self.setlanguage(language,
print_change=False,
store_to_config=False)
except Exception:
from ba import _error
_error.print_exception('exception setting language to',
language)
# Try english as a fallback.
if language != 'English':
print('Resorting to fallback language (English)')
try:
self.setlanguage('English',
print_change=False,
store_to_config=False)
except Exception:
_error.print_exception(
'error setting language to english fallback')
# If they provided a fallback_resource value, try the
# target-language-only dict first and then fall back to trying the
# fallback_resource value in the merged dict.
if fallback_resource is not None:
try:
values = self.language_target
splits = resource.split('.')
dicts = splits[:-1]
key = splits[-1]
for dct in dicts:
assert values is not None
values = values[dct]
assert values is not None
val = values[key]
return val
except Exception:
# FIXME: Shouldn't we try the fallback resource in the
# merged dict AFTER we try the main resource in the
# merged dict?
try:
values = self.language_merged
splits = fallback_resource.split('.')
dicts = splits[:-1]
key = splits[-1]
for dct in dicts:
assert values is not None
values = values[dct]
assert values is not None
val = values[key]
return val
except Exception:
# If we got nothing for fallback_resource, default
# to the normal code which checks or primary
# value in the merge dict; there's a chance we can
# get an english value for it (which we weren't
# looking for the first time through).
pass
values = self.language_merged
splits = resource.split('.')
dicts = splits[:-1]
key = splits[-1]
for dct in dicts:
assert values is not None
values = values[dct]
assert values is not None
val = values[key]
return val
except Exception:
# Ok, looks like we couldn't find our main or fallback resource
# anywhere. Now if we've been given a fallback value, return it;
# otherwise fail.
from ba import _error
if fallback_value is not None:
return fallback_value
raise _error.NotFoundError(
f"Resource not found: '{resource}'") from None
def translate(self,
category: str,
strval: str,
raise_exceptions: bool = False,
print_errors: bool = False) -> str:
"""Translate a value (or return the value if no translation available)
DEPRECATED; use ba.Lstr functionality for these purposes.
"""
try:
translated = self.get_resource('translations')[category][strval]
except Exception as exc:
if raise_exceptions:
raise
if print_errors:
print(('Translate error: category=\'' + category +
'\' name=\'' + strval + '\' exc=' + str(exc) + ''))
translated = None
translated_out: str
if translated is None:
translated_out = strval
else:
translated_out = translated
assert isinstance(translated_out, str)
return translated_out
def is_custom_unicode_char(self, char: str) -> bool:
"""Return whether a char is in the custom unicode range we use."""
assert isinstance(char, str)
if len(char) != 1:
raise ValueError('Invalid Input; must be length 1')
return 0xE000 <= ord(char) <= 0xF8FF
class Lstr:
"""Used to define strings in a language-independent way.
category: General Utility Classes
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 bs_language_*.py files
in the game or the translations pages at bombsquadgame.com/translate.
# EXAMPLE 1: specify a string from a resource path
mynode.text = ba.Lstr(resource='audioSettingsWindow.titleText')
# EXAMPLE 2: specify a translated string via a category and english value;
# 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 = ba.Lstr(translate=('gameDescriptions', 'Defeat all enemies'))
# EXAMPLE 3: specify a raw value and some substitutions. Substitutions can
# be used with resource and translate modes as well.
mynode.text = ba.Lstr(value='${A} / ${B}',
subs=[('${A}', str(score)), ('${B}', str(total))])
# EXAMPLE 4: Lstrs can be nested. This example would display the resource
# at res_a but replace ${NAME} with the value of the resource at res_b
mytextnode.text = ba.Lstr(resource='res_a',
subs=[('${NAME}', ba.Lstr(resource='res_b'))])
"""
# pylint: disable=dangerous-default-value
# noinspection PyDefaultArgument
@overload
def __init__(self,
*,
resource: str,
fallback_resource: str = '',
fallback_value: str = '',
subs: Sequence[Tuple[str, Union[str, Lstr]]] = []) -> None:
"""Create an Lstr from a string resource."""
...
# noinspection PyShadowingNames,PyDefaultArgument
@overload
def __init__(self,
*,
translate: Tuple[str, str],
subs: Sequence[Tuple[str, Union[str, Lstr]]] = []) -> None:
"""Create an Lstr by translating a string in a category."""
...
# noinspection PyDefaultArgument
@overload
def __init__(self,
*,
value: str,
subs: Sequence[Tuple[str, Union[str, Lstr]]] = []) -> None:
"""Create an Lstr from a raw string value."""
...
# pylint: enable=redefined-outer-name, dangerous-default-value
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
if args:
raise TypeError('Lstr accepts only keyword arguments')
# Basically just store the exact args they passed.
# However if they passed any Lstr values for subs,
# replace them with that Lstr's dict.
self.args = keywds
our_type = type(self)
if isinstance(self.args.get('value'), our_type):
raise TypeError("'value' must be a regular string; not an Lstr")
if 'subs' in self.args:
subs_new = []
for key, value in keywds['subs']:
if isinstance(value, our_type):
subs_new.append((key, value.args))
else:
subs_new.append((key, value))
self.args['subs'] = subs_new
# As of protocol 31 we support compact key names
# ('t' instead of 'translate', etc). Convert as needed.
if 'translate' in keywds:
keywds['t'] = keywds['translate']
del keywds['translate']
if 'resource' in keywds:
keywds['r'] = keywds['resource']
del keywds['resource']
if 'value' in keywds:
keywds['v'] = keywds['value']
del keywds['value']
if 'fallback' in keywds:
from ba import _error
_error.print_error(
'deprecated "fallback" arg passed to Lstr(); use '
'either "fallback_resource" or "fallback_value"',
once=True)
keywds['f'] = keywds['fallback']
del keywds['fallback']
if 'fallback_resource' in keywds:
keywds['f'] = keywds['fallback_resource']
del keywds['fallback_resource']
if 'subs' in keywds:
keywds['s'] = keywds['subs']
del keywds['subs']
if 'fallback_value' in keywds:
keywds['fv'] = keywds['fallback_value']
del keywds['fallback_value']
def evaluate(self) -> str:
"""Evaluate the Lstr and returns a flat string in the current language.
You should avoid doing this as much as possible and instead pass
and store Lstr values.
"""
return _ba.evaluate_lstr(self._get_json())
def is_flat_value(self) -> bool:
"""Return whether the Lstr is 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.
"""
return bool('v' in self.args and not self.args.get('s', []))
def _get_json(self) -> str:
try:
return json.dumps(self.args, separators=(',', ':'))
except Exception:
from ba import _error
_error.print_exception('_get_json failed for', self.args)
return 'JSON_ERR'
def __str__(self) -> str:
return '<ba.Lstr: ' + self._get_json() + '>'
def __repr__(self) -> str:
return '<ba.Lstr: ' + self._get_json() + '>'
@staticmethod
def from_json(json_string: str) -> ba.Lstr:
"""Given a json string, returns a ba.Lstr. Does no data validation."""
lstr = Lstr(value='')
lstr.args = json.loads(json_string)
return lstr
def _add_to_attr_dict(dst: AttrDict, src: Dict) -> None:
for key, value in list(src.items()):
if isinstance(value, dict):
try:
dst_dict = dst[key]
except Exception:
dst_dict = dst[key] = AttrDict()
if not isinstance(dst_dict, AttrDict):
raise RuntimeError("language key '" + key +
"' is defined both as a dict and value")
_add_to_attr_dict(dst_dict, value)
else:
if not isinstance(value, (float, int, bool, str, str, type(None))):
raise TypeError("invalid value type for res '" + key + "': " +
str(type(value)))
dst[key] = value
class AttrDict(dict):
"""A dict that can be accessed with dot notation.
(so foo.bar is equivalent to foo['bar'])
"""
def __getattr__(self, attr: str) -> Any:
val = self[attr]
assert not isinstance(val, bytes)
return val
def __setattr__(self, attr: str, value: Any) -> None:
raise Exception()

View File

@ -62,8 +62,8 @@ class Level:
@property
def displayname(self) -> ba.Lstr:
"""The localized name for this Level."""
from ba import _lang
return _lang.Lstr(
from ba import _language
return _language.Lstr(
translate=('coopLevelNames', self._displayname
if self._displayname is not None else self._name),
subs=[('${GAME}',

View File

@ -11,7 +11,7 @@ from typing import TYPE_CHECKING
import _ba
from ba._error import print_exception, print_error, NotFoundError
from ba._gameutils import animate, animate_array
from ba._lang import Lstr
from ba._language import Lstr
from ba._enums import SpecialChar, InputType
from ba._profile import get_player_profile_colors

View File

@ -48,8 +48,8 @@ def get_map_display_string(name: str) -> ba.Lstr:
Category: Asset Functions
"""
from ba import _lang
return _lang.Lstr(translate=('mapsNames', name))
from ba import _language
return _language.Lstr(translate=('mapsNames', name))
def getmaps(playtype: str) -> List[str]:

View File

@ -48,7 +48,7 @@ def start_scan() -> None:
def handle_scan_results(results: ScanResults) -> None:
"""Called in the game thread with results of a completed scan."""
from ba._lang import Lstr
from ba._language import Lstr
from ba._plugin import PotentialPlugin
# Warnings generally only get printed locally for users' benefit

View File

@ -238,7 +238,7 @@ class MultiTeamSession(Session):
from ba._math import normalized_color
from ba._general import Call
from ba._gameutils import cameraflash
from ba._lang import Lstr
from ba._language import Lstr
from ba._freeforallsession import FreeForAllSession
from ba._messages import CelebrateMessage
_ba.timer(delay, Call(_ba.playsound, _ba.getsound('boxingBell')))

View File

@ -120,6 +120,8 @@ class MusicSubsystem:
"""Subsystem for music playback in the app.
Category: App Classes
To use this class, access the single instance of it at 'ba.app.music'.
"""
def __init__(self) -> None:

View File

@ -291,7 +291,7 @@ class EmptyPlayer(Player['ba.EmptyTeam']):
defining a ba.Activity that does not need custom types of its own.
Note that EmptyPlayer defines its team type as EmptyTeam and vice versa,
so if you want to define your own class for one of them you must do so
so if you want to define your own class for one of them you should do so
for both.
"""

View File

@ -166,7 +166,7 @@ class ServerController:
return False
def _execute_shutdown(self) -> None:
from ba._lang import Lstr
from ba._language import Lstr
if self._executing_shutdown:
return
self._executing_shutdown = True

View File

@ -8,7 +8,7 @@ from typing import TYPE_CHECKING
import _ba
from ba._error import print_error, print_exception, NodeNotFoundError
from ba._lang import Lstr
from ba._language import Lstr
from ba._player import Player
if TYPE_CHECKING:

View File

@ -131,7 +131,7 @@ class PlayerRecord:
"""Submit a kill for this player entry."""
# FIXME Clean this up.
# pylint: disable=too-many-statements
from ba._lang import Lstr
from ba._language import Lstr
from ba._general import Call
self._multi_kill_count += 1
stats = self._stats()
@ -341,7 +341,7 @@ class Stats:
from bastd.actor.popuptext import PopupText
from ba import _math
from ba._gameactivity import GameActivity
from ba._lang import Lstr
from ba._language import Lstr
del victim_player # Currently unused.
name = player.getname()
s_player = self._player_records[name]
@ -428,7 +428,7 @@ class Stats:
killed: bool = False,
killer: ba.Player = None) -> None:
"""Should be called when a player is killed."""
from ba._lang import Lstr
from ba._language import Lstr
name = player.getname()
prec = self._player_records[name]
prec.streak = 0

View File

@ -21,15 +21,16 @@ def get_store_item(item: str) -> Dict[str, Any]:
def get_store_item_name_translated(item_name: str) -> ba.Lstr:
"""Return a ba.Lstr for a store item name."""
# pylint: disable=cyclic-import
from ba import _lang
from ba import _language
from ba import _map
item_info = get_store_item(item_name)
if item_name.startswith('characters.'):
return _lang.Lstr(translate=('characterNames', item_info['character']))
return _language.Lstr(translate=('characterNames',
item_info['character']))
if item_name in ['upgrades.pro', 'pro']:
return _lang.Lstr(resource='store.bombSquadProNameText',
subs=[('${APP_NAME}',
_lang.Lstr(resource='titleText'))])
return _language.Lstr(resource='store.bombSquadProNameText',
subs=[('${APP_NAME}',
_language.Lstr(resource='titleText'))])
if item_name.startswith('maps.'):
map_type: Type[ba.Map] = item_info['map_type']
return _map.get_map_display_string(map_type.name)
@ -37,7 +38,7 @@ def get_store_item_name_translated(item_name: str) -> ba.Lstr:
gametype: Type[ba.GameActivity] = item_info['gametype']
return gametype.get_display_string()
if item_name.startswith('icons.'):
return _lang.Lstr(resource='editProfileWindow.iconText')
return _language.Lstr(resource='editProfileWindow.iconText')
raise ValueError('unrecognized item: ' + item_name)

View File

@ -207,6 +207,6 @@ class EmptyTeam(Team['ba.EmptyPlayer']):
defining a ba.Activity that does not need custom types of its own.
Note that EmptyPlayer defines its team type as EmptyTeam and vice versa,
so if you want to define your own class for one of them you must do so
so if you want to define your own class for one of them you should do so
for both.
"""

View File

@ -15,8 +15,13 @@ if TYPE_CHECKING:
import ba
class UI:
"""UI subsystem for the app."""
class UISubsystem:
"""Consolidated UI functionality for the app.
Category: App Classes
To use this class, access the single instance of it at 'ba.app.ui'.
"""
def __init__(self) -> None:
env = _ba.env()

View File

@ -6,8 +6,3 @@ Classes or functions can be relocated here when they are deprecated.
Any code using them should migrate to alternative methods, as
deprecated items will eventually be fully removed.
"""
# pylint: disable=unused-import
# The Lstr class should be used for all string resources.
from ba._lang import get_resource, translate

View File

@ -68,7 +68,7 @@ class _MacMusicAppThread(threading.Thread):
def run(self) -> None:
"""Run the Music.app thread."""
from ba._general import Call
from ba._lang import Lstr
from ba._language import Lstr
from ba._enums import TimeType
_ba.set_thread_name('BA_MacMusicAppThread')
_ba.mac_music_app_init()
@ -212,7 +212,6 @@ class _MacMusicAppThread(threading.Thread):
def _play_current_playlist(self) -> None:
try:
from ba import _lang
from ba._general import Call
assert self._current_playlist is not None
if _ba.mac_music_app_play_playlist(self._current_playlist):
@ -220,8 +219,8 @@ class _MacMusicAppThread(threading.Thread):
else:
_ba.pushcall(Call(
_ba.screenmessage,
_lang.get_resource('playlistNotFoundText') + ': \'' +
self._current_playlist + '\'', (1, 0, 0)),
_ba.app.lang.get_resource('playlistNotFoundText') +
': \'' + self._current_playlist + '\'', (1, 0, 0)),
from_other_thread=True)
except Exception:
from ba import _error

View File

@ -17,7 +17,7 @@ def get_human_readable_user_scripts_path() -> str:
This is NOT a valid filesystem path; may be something like "(SD Card)".
"""
from ba import _lang
from ba import _language
app = _ba.app
path: Optional[str] = app.python_directory_user
if path is None:
@ -32,14 +32,14 @@ def get_human_readable_user_scripts_path() -> str:
if (ext_storage_path is not None
and app.python_directory_user.startswith(ext_storage_path)):
path = ('<' +
_lang.Lstr(resource='externalStorageText').evaluate() +
_language.Lstr(resource='externalStorageText').evaluate() +
'>' + app.python_directory_user[len(ext_storage_path):])
return path
def _request_storage_permission() -> bool:
"""If needed, requests storage permission from the user (& return true)."""
from ba._lang import Lstr
from ba._language import Lstr
from ba._enums import Permission
if not _ba.have_permission(Permission.STORAGE):
_ba.playsound(_ba.getsound('error'))

View File

@ -62,9 +62,9 @@ class OSMusicPlayer(MusicPlayer):
def _on_play_folder_cb(self,
result: Union[str, List[str]],
error: Optional[str] = None) -> None:
from ba import _lang
from ba import _language
if error is not None:
rstr = (_lang.Lstr(
rstr = (_language.Lstr(
resource='internal.errorPlayingMusicText').evaluate())
if isinstance(result, str):
err_str = (rstr.replace('${MUSIC}', os.path.basename(result)) +
@ -103,7 +103,7 @@ class _PickFolderSongThread(threading.Thread):
self._path = path
def run(self) -> None:
from ba import _lang
from ba import _language
from ba._general import Call
do_print_error = True
try:
@ -119,8 +119,8 @@ class _PickFolderSongThread(threading.Thread):
if not all_files:
do_print_error = False
raise RuntimeError(
_lang.Lstr(resource='internal.noMusicFilesInFolderText').
evaluate())
_language.Lstr(resource='internal.noMusicFilesInFolderText'
).evaluate())
_ba.pushcall(Call(self._callback, all_files, None),
from_other_thread=True)
except Exception as exc:

View File

@ -23,7 +23,6 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
assert isinstance(self._winner, ba.SessionTeam)
def on_begin(self) -> None:
from ba.deprecated import get_resource
ba.set_analytics_screen('Teams Score Screen')
super().on_begin()
@ -37,7 +36,7 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
# 'First to 4'.
session = self.session
assert isinstance(session, ba.MultiTeamSession)
if get_resource('bestOfUseFirstToInstead'):
if ba.app.lang.get_resource('bestOfUseFirstToInstead'):
best_txt = ba.Lstr(resource='firstToSeriesText',
subs=[('${COUNT}',
str(session.get_series_length() / 2 + 1))

View File

@ -33,7 +33,6 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
# pylint: disable=too-many-statements
from bastd.actor.text import Text
from bastd.actor.image import Image
from ba.deprecated import get_resource
ba.set_analytics_screen('FreeForAll Series Victory Screen' if self.
_is_ffa else 'Teams Series Victory Screen')
if ba.app.ui.uiscale is ba.UIScale.LARGE:
@ -72,7 +71,8 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
tval = 6.4
t_incr = 0.12
always_use_first_to = get_resource('bestOfUseFirstToInstead')
always_use_first_to = ba.app.lang.get_resource(
'bestOfUseFirstToInstead')
session = self.session
if self._is_ffa:

View File

@ -492,7 +492,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
ba.getmodel('logoTransparent'))
# If language has changed, recreate our logo text/graphics.
lang = app.language
lang = app.lang.language
if lang != self._language:
self._language = lang
y = 20
@ -511,7 +511,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
# We draw higher in kiosk mode (make sure to test this
# when making adjustments) for now we're hard-coded for
# a few languages.. should maybe look into generalizing this?..
if app.language == 'Chinese':
if app.lang.language == 'Chinese':
base_x = -270.0
x = base_x - 20.0
spacing = 85.0 * base_scale

View File

@ -1575,7 +1575,7 @@ class GatherWindow(ba.Window):
{
'type': 'PUBLIC_PARTY_QUERY',
'proto': app.protocol_version,
'lang': app.language
'lang': app.lang.language
},
callback=ba.WeakCall(self._on_public_party_query_result))
_ba.run_transactions()

View File

@ -22,7 +22,6 @@ class HelpWindow(ba.Window):
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
from ba.internal import get_remote_app_name
from ba.deprecated import get_resource
ba.set_analytics_screen('Help Window')
# If they provided an origin-widget, scale up from that.
@ -38,6 +37,8 @@ class HelpWindow(ba.Window):
self._r = 'helpWindow'
getres = ba.app.lang.get_resource
self._main_menu = main_menu
uiscale = ba.app.ui.uiscale
width = 950 if uiscale is ba.UIScale.SMALL else 750
@ -111,8 +112,8 @@ class HelpWindow(ba.Window):
label=ba.charstr(ba.SpecialChar.BACK))
self._sub_width = 660
self._sub_height = 1590 + get_resource(
self._r + '.someDaysExtraSpace') + get_resource(
self._sub_height = 1590 + ba.app.lang.get_resource(
self._r + '.someDaysExtraSpace') + ba.app.lang.get_resource(
self._r + '.orPunchingSomethingExtraSpace')
self._subcontainer = ba.containerwidget(parent=self._scrollwidget,
@ -212,8 +213,7 @@ class HelpWindow(ba.Window):
color=paragraph,
v_align='center',
flatness=1.0)
v -= (spacing * 25.0 +
get_resource(self._r + '.someDaysExtraSpace'))
v -= (spacing * 25.0 + getres(self._r + '.someDaysExtraSpace'))
txt_scale = 0.66
txt = ba.Lstr(resource=self._r +
'.orPunchingSomethingText').evaluate()
@ -228,7 +228,7 @@ class HelpWindow(ba.Window):
v_align='center',
flatness=1.0)
v -= (spacing * 27.0 +
get_resource(self._r + '.orPunchingSomethingExtraSpace'))
getres(self._r + '.orPunchingSomethingExtraSpace'))
txt_scale = 1.0
txt = ba.Lstr(resource=self._r + '.canHelpText',
subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))
@ -387,7 +387,7 @@ class HelpWindow(ba.Window):
texture=ba.gettexture('buttonPunch'),
color=(1, 0.7, 0.3))
txt_scale = get_resource(self._r + '.punchInfoTextScale')
txt_scale = getres(self._r + '.punchInfoTextScale')
txt = ba.Lstr(resource=self._r + '.punchInfoText').evaluate()
ba.textwidget(parent=self._subcontainer,
position=(h - sep - 185 + 70, v + 120),
@ -409,7 +409,7 @@ class HelpWindow(ba.Window):
color=(1, 0.3, 0.3))
txt = ba.Lstr(resource=self._r + '.bombInfoText').evaluate()
txt_scale = get_resource(self._r + '.bombInfoTextScale')
txt_scale = getres(self._r + '.bombInfoTextScale')
ba.textwidget(parent=self._subcontainer,
position=(h + sep + 50 + 60, v - 35),
size=(0, 0),
@ -431,7 +431,7 @@ class HelpWindow(ba.Window):
color=(0.5, 0.5, 1))
txtl = ba.Lstr(resource=self._r + '.pickUpInfoText')
txt_scale = get_resource(self._r + '.pickUpInfoTextScale')
txt_scale = getres(self._r + '.pickUpInfoTextScale')
ba.textwidget(parent=self._subcontainer,
position=(h + 60 + 120, v + sep + 50),
size=(0, 0),
@ -452,7 +452,7 @@ class HelpWindow(ba.Window):
color=(0.4, 1, 0.4))
txt = ba.Lstr(resource=self._r + '.jumpInfoText').evaluate()
txt_scale = get_resource(self._r + '.jumpInfoTextScale')
txt_scale = getres(self._r + '.jumpInfoTextScale')
ba.textwidget(parent=self._subcontainer,
position=(h - 250 + 75, v - sep - 15 + 30),
size=(0, 0),
@ -464,7 +464,7 @@ class HelpWindow(ba.Window):
v_align='top')
txt = ba.Lstr(resource=self._r + '.runInfoText').evaluate()
txt_scale = get_resource(self._r + '.runInfoTextScale')
txt_scale = getres(self._r + '.runInfoTextScale')
ba.textwidget(parent=self._subcontainer,
position=(h, v - sep - 100),
size=(0, 0),
@ -503,7 +503,7 @@ class HelpWindow(ba.Window):
texture=logo_tex)
v -= spacing * 50.0
txt_scale = get_resource(self._r + '.powerupsSubtitleTextScale')
txt_scale = getres(self._r + '.powerupsSubtitleTextScale')
txt = ba.Lstr(resource=self._r + '.powerupsSubtitleText').evaluate()
ba.textwidget(parent=self._subcontainer,
position=(h, v),

View File

@ -22,9 +22,7 @@ class LeagueRankWindow(ba.Window):
transition: str = 'in_right',
modal: bool = False,
origin_widget: ba.Widget = None):
# pylint: disable=too-many-statements
from ba.internal import get_cached_league_rank_data
from ba.deprecated import get_resource
ba.set_analytics_screen('League Rank Window')
self._league_rank_data: Optional[Dict[str, Any]] = None
@ -46,7 +44,7 @@ class LeagueRankWindow(ba.Window):
self._height = (657 if uiscale is ba.UIScale.SMALL else
710 if uiscale is ba.UIScale.MEDIUM else 800)
self._r = 'coopSelectWindow'
self._rdict = get_resource(self._r)
self._rdict = ba.app.lang.get_resource(self._r)
top_extra = 20 if uiscale is ba.UIScale.SMALL else 0
self._league_url_arg = ''

View File

@ -164,10 +164,10 @@ class AdvancedSettingsWindow(ba.Window):
def _update_lang_status(self) -> None:
if self._complete_langs_list is not None:
up_to_date = (ba.app.language in self._complete_langs_list)
up_to_date = (ba.app.lang.language in self._complete_langs_list)
ba.textwidget(
edit=self._lang_status_text,
text='' if ba.app.language == 'Test' else ba.Lstr(
text='' if ba.app.lang.language == 'Test' else ba.Lstr(
resource=self._r + '.translationNoUpdateNeededText')
if up_to_date else ba.Lstr(resource=self._r +
'.translationUpdateNeededText'),
@ -189,6 +189,8 @@ class AdvancedSettingsWindow(ba.Window):
from bastd.ui.config import ConfigCheckBox
from ba.modutils import show_user_scripts
available_languages = ba.app.lang.available_languages
# Don't rebuild if the menu is open or if our language and
# language-list hasn't changed.
# NOTE - although we now support widgets updating their own
@ -196,12 +198,11 @@ class AdvancedSettingsWindow(ba.Window):
# menu based on the language so still need this. ...however we could
# make this more limited to it only rebuilds that one menu instead
# of everything.
if self._menu_open or (
self._prev_lang == _ba.app.config.get('Lang', None)
and self._prev_lang_list == ba.get_valid_languages()):
if self._menu_open or (self._prev_lang == _ba.app.config.get(
'Lang', None) and self._prev_lang_list == available_languages):
return
self._prev_lang = _ba.app.config.get('Lang', None)
self._prev_lang_list = ba.get_valid_languages()
self._prev_lang_list = available_languages
# Clear out our sub-container.
children = self._subcontainer.get_children()
@ -248,7 +249,7 @@ class AdvancedSettingsWindow(ba.Window):
h_align='right',
v_align='center')
languages = ba.get_valid_languages()
languages = _ba.app.lang.available_languages
cur_lang = _ba.app.config.get('Lang', None)
if cur_lang is None:
cur_lang = 'Auto'
@ -289,9 +290,9 @@ class AdvancedSettingsWindow(ba.Window):
button_size=(250, 60),
choices_display=([
ba.Lstr(value=(ba.Lstr(resource='autoText').evaluate() + ' (' +
ba.Lstr(translate=(
'languages',
ba.app.default_language)).evaluate() + ')'))
ba.Lstr(translate=('languages',
ba.app.lang.default_language
)).evaluate() + ')'))
] + [ba.Lstr(value=langs_full[l]) for l in languages]),
current_choice=cur_lang)
@ -689,7 +690,7 @@ class AdvancedSettingsWindow(ba.Window):
self._menu_open = False
def _on_menu_choice(self, choice: str) -> None:
ba.setlanguage(None if choice == 'Auto' else choice)
ba.app.lang.setlanguage(None if choice == 'Auto' else choice)
self._save_state()
ba.timer(0.1, ba.WeakCall(self._rebuild), timetype=ba.TimeType.REAL)

View File

@ -157,7 +157,6 @@ class SoundtrackEditWindow(ba.Window):
ba.widget(edit=cancel_button, down_widget=self._text_field)
def _refresh(self) -> None:
from ba.deprecated import get_resource
for widget in self._col.get_children():
widget.delete()
@ -183,8 +182,9 @@ class SoundtrackEditWindow(ba.Window):
'Scores',
'Victory',
]
# FIXME: We should probably convert this to use translations.
type_names_translated = get_resource('soundtrackTypeNames')
type_names_translated = ba.app.lang.get_resource('soundtrackTypeNames')
prev_type_button: Optional[ba.Widget] = None
prev_test_button: Optional[ba.Widget] = None

View File

@ -20,7 +20,6 @@ class TrophiesWindow(popup.PopupWindow):
position: Tuple[float, float],
data: Dict[str, Any],
scale: float = None):
from ba.deprecated import get_resource
self._data = data
uiscale = ba.app.ui.uiscale
if scale is None:
@ -76,7 +75,9 @@ class TrophiesWindow(popup.PopupWindow):
trophy_types = [['0a'], ['0b'], ['1'], ['2'], ['3'], ['4']]
sub_height = 40 + len(trophy_types) * incr
eq_text = get_resource('coopSelectWindow.powerRankingPointsEqualsText')
eq_text = ba.Lstr(
resource='coopSelectWindow.powerRankingPointsEqualsText').evaluate(
)
self._subcontainer = ba.containerwidget(parent=self._scrollwidget,
size=(sub_width, sub_height),
@ -84,25 +85,27 @@ class TrophiesWindow(popup.PopupWindow):
total_pts = 0
multi_txt = get_resource('coopSelectWindow.powerRankingPointsMultText')
multi_txt = ba.Lstr(
resource='coopSelectWindow.powerRankingPointsMultText').evaluate()
total_pts += self._create_trophy_type_widgets(eq_text, incr, multi_txt,
sub_height, sub_width,
trophy_types)
ba.textwidget(parent=self._subcontainer,
position=(sub_width * 1.0,
sub_height - 20 - incr * len(trophy_types)),
maxwidth=sub_width * 0.5,
scale=0.7,
color=(0.7, 0.8, 1.0),
flatness=1.0,
shadow=0.0,
text=get_resource('coopSelectWindow.totalText') + ' ' +
eq_text.replace('${NUMBER}', str(total_pts)),
size=(0, 0),
h_align='right',
v_align='center')
ba.textwidget(
parent=self._subcontainer,
position=(sub_width * 1.0,
sub_height - 20 - incr * len(trophy_types)),
maxwidth=sub_width * 0.5,
scale=0.7,
color=(0.7, 0.8, 1.0),
flatness=1.0,
shadow=0.0,
text=ba.Lstr(resource='coopSelectWindow.totalText').evaluate() +
' ' + eq_text.replace('${NUMBER}', str(total_pts)),
size=(0, 0),
h_align='right',
v_align='center')
def _create_trophy_type_widgets(self, eq_text: str, incr: int,
multi_txt: str, sub_height: int,

View File

@ -353,6 +353,7 @@
<w>getpublicpartyenabled</w>
<w>getpublicpartymaxsize</w>
<w>getqrcodetexture</w>
<w>getres</w>
<w>getsession</w>
<w>getsound</w>
<w>gettexture</w>
@ -415,6 +416,7 @@
<w>internalformat</w>
<w>interuptions</w>
<w>invote</w>
<w>iobj</w>
<w>iserverget</w>
<w>iserverput</w>
<w>isinst</w>
@ -552,6 +554,7 @@
<w>nitpicky</w>
<w>nlpos</w>
<w>nmemb</w>
<w>noassets</w>
<w>nodetype</w>
<w>nofilename</w>
<w>noglobs</w>

View File

@ -1,5 +1,5 @@
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
<h4><em>last updated on 2020-10-15 for Ballistica version 1.5.26 build 20213</em></h4>
<h4><em>last updated on 2020-10-15 for Ballistica version 1.5.27 build 20218</em></h4>
<p>This page documents the Python classes and functions in the 'ba' module,
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p>
<hr>
@ -20,6 +20,7 @@
<li><a href="#class_ba_NodeActor">ba.NodeActor</a></li>
</ul>
<li><a href="#class_ba_Chooser">ba.Chooser</a></li>
<li><a href="#class_ba_Collision">ba.Collision</a></li>
<li><a href="#class_ba_GameResults">ba.GameResults</a></li>
<li><a href="#class_ba_GameTip">ba.GameTip</a></li>
<li><a href="#class_ba_InputDevice">ba.InputDevice</a></li>
@ -62,6 +63,7 @@
<li><a href="#function_ba_emitfx">ba.emitfx()</a></li>
<li><a href="#function_ba_existing">ba.existing()</a></li>
<li><a href="#function_ba_getactivity">ba.getactivity()</a></li>
<li><a href="#function_ba_getcollision">ba.getcollision()</a></li>
<li><a href="#function_ba_getnodes">ba.getnodes()</a></li>
<li><a href="#function_ba_getsession">ba.getsession()</a></li>
<li><a href="#function_ba_newnode">ba.newnode()</a></li>
@ -84,7 +86,6 @@
<ul>
<li><a href="#function_ba_charstr">ba.charstr()</a></li>
<li><a href="#function_ba_do_once">ba.do_once()</a></li>
<li><a href="#function_ba_get_valid_languages">ba.get_valid_languages()</a></li>
<li><a href="#function_ba_getclass">ba.getclass()</a></li>
<li><a href="#function_ba_is_browser_likely_available">ba.is_browser_likely_available()</a></li>
<li><a href="#function_ba_is_point_in_box">ba.is_point_in_box()</a></li>
@ -100,7 +101,6 @@
<li><a href="#function_ba_safecolor">ba.safecolor()</a></li>
<li><a href="#function_ba_screenmessage">ba.screenmessage()</a></li>
<li><a href="#function_ba_set_analytics_screen">ba.set_analytics_screen()</a></li>
<li><a href="#function_ba_setlanguage">ba.setlanguage()</a></li>
<li><a href="#function_ba_storagename">ba.storagename()</a></li>
<li><a href="#function_ba_time">ba.time()</a></li>
<li><a href="#function_ba_timer">ba.timer()</a></li>
@ -153,10 +153,13 @@
<li><a href="#class_ba_AppDelegate">ba.AppDelegate</a></li>
<li><a href="#class_ba_Campaign">ba.Campaign</a></li>
<li><a href="#class_ba_Keyboard">ba.Keyboard</a></li>
<li><a href="#class_ba_LanguageSubsystem">ba.LanguageSubsystem</a></li>
<li><a href="#class_ba_MusicPlayer">ba.MusicPlayer</a></li>
<li><a href="#class_ba_MusicSubsystem">ba.MusicSubsystem</a></li>
<li><a href="#class_ba_Plugin">ba.Plugin</a></li>
<li><a href="#class_ba_PotentialPlugin">ba.PotentialPlugin</a></li>
<li><a href="#class_ba_ServerController">ba.ServerController</a></li>
<li><a href="#class_ba_UISubsystem">ba.UISubsystem</a></li>
</ul>
<h4><a name="class_category_User_Interface_Classes">User Interface Classes</a></h4>
<ul>
@ -216,14 +219,6 @@
<li><a href="#class_ba_WidgetNotFoundError">ba.WidgetNotFoundError</a></li>
</ul>
</ul>
<h4><a name="class_category_Misc_Classes">Misc Classes</a></h4>
<ul>
<li><a href="#class_ba_Collision">ba.Collision</a></li>
</ul>
<h4><a name="function_category_Misc_Functions">Misc Functions</a></h4>
<ul>
<li><a href="#function_ba_getcollision">ba.getcollision()</a></li>
</ul>
<h4><a name="class_category_Protocols">Protocols</a></h4>
<ul>
<li><a href="#class_ba_Existable">ba.Existable</a></li>
@ -810,7 +805,7 @@ likely result in errors.</p>
</p>
<h3>Attributes:</h3>
<h5><a href="#attr_ba_App__api_version">api_version</a>, <a href="#attr_ba_App__build_number">build_number</a>, <a href="#attr_ba_App__config">config</a>, <a href="#attr_ba_App__config_file_path">config_file_path</a>, <a href="#attr_ba_App__debug_build">debug_build</a>, <a href="#attr_ba_App__language">language</a>, <a href="#attr_ba_App__locale">locale</a>, <a href="#attr_ba_App__on_tv">on_tv</a>, <a href="#attr_ba_App__platform">platform</a>, <a href="#attr_ba_App__python_directory_app">python_directory_app</a>, <a href="#attr_ba_App__python_directory_app_site">python_directory_app_site</a>, <a href="#attr_ba_App__python_directory_user">python_directory_user</a>, <a href="#attr_ba_App__subplatform">subplatform</a>, <a href="#attr_ba_App__test_build">test_build</a>, <a href="#attr_ba_App__ui_bounds">ui_bounds</a>, <a href="#attr_ba_App__user_agent_string">user_agent_string</a>, <a href="#attr_ba_App__version">version</a>, <a href="#attr_ba_App__vr_mode">vr_mode</a></h5>
<h5><a href="#attr_ba_App__api_version">api_version</a>, <a href="#attr_ba_App__build_number">build_number</a>, <a href="#attr_ba_App__config">config</a>, <a href="#attr_ba_App__config_file_path">config_file_path</a>, <a href="#attr_ba_App__debug_build">debug_build</a>, <a href="#attr_ba_App__on_tv">on_tv</a>, <a href="#attr_ba_App__platform">platform</a>, <a href="#attr_ba_App__python_directory_app">python_directory_app</a>, <a href="#attr_ba_App__python_directory_app_site">python_directory_app_site</a>, <a href="#attr_ba_App__python_directory_user">python_directory_user</a>, <a href="#attr_ba_App__subplatform">subplatform</a>, <a href="#attr_ba_App__test_build">test_build</a>, <a href="#attr_ba_App__ui_bounds">ui_bounds</a>, <a href="#attr_ba_App__user_agent_string">user_agent_string</a>, <a href="#attr_ba_App__version">version</a>, <a href="#attr_ba_App__vr_mode">vr_mode</a></h5>
<dl>
<dt><h4><a name="attr_ba_App__api_version">api_version</a></h4></dt><dd>
<p><span>int</span></p>
@ -849,23 +844,6 @@ likely result in errors.</p>
builds due to compiler optimizations being disabled and extra
checks being run.</p>
</dd>
<dt><h4><a name="attr_ba_App__language">language</a></h4></dt><dd>
<p><span>str</span></p>
<p>The name of the language the game is running in.</p>
<p> This can be selected explicitly by the user or may be set
automatically based on <a href="#attr_ba_App__locale">ba.App.locale</a> or other factors.</p>
</dd>
<dt><h4><a name="attr_ba_App__locale">locale</a></h4></dt><dd>
<p><span>str</span></p>
<p>Raw country/language code detected by the game (such as 'en_US').</p>
<p> Generally for language-specific code you should look at
<a href="#attr_ba_App__language">ba.App.language</a>, which is the language the game is using
(which may differ from locale if the user sets a language, etc.)</p>
</dd>
<dt><h4><a name="attr_ba_App__on_tv">on_tv</a></h4></dt><dd>
<p><span>bool</span></p>
@ -1441,6 +1419,9 @@ mycall()</pre>
</p>
<p>A class providing info about occurring collisions.</p>
<p>Category: <a href="#class_category_Gameplay_Classes">Gameplay Classes</a>
</p>
<h3>Attributes:</h3>
<h5><a href="#attr_ba_Collision__opposingbody">opposingbody</a>, <a href="#attr_ba_Collision__opposingnode">opposingnode</a>, <a href="#attr_ba_Collision__position">position</a>, <a href="#attr_ba_Collision__sourcenode">sourcenode</a></h5>
<dl>
@ -2119,7 +2100,7 @@ its time with lingering corpses, sound effects, etc.</p>
defining a <a href="#class_ba_Activity">ba.Activity</a> that does not need custom types of its own.</p>
<p> Note that EmptyPlayer defines its team type as EmptyTeam and vice versa,
so if you want to define your own class for one of them you must do so
so if you want to define your own class for one of them you should do so
for both.
</p>
@ -2182,7 +2163,7 @@ its time with lingering corpses, sound effects, etc.</p>
defining a <a href="#class_ba_Activity">ba.Activity</a> that does not need custom types of its own.</p>
<p> Note that EmptyPlayer defines its team type as EmptyTeam and vice versa,
so if you want to define your own class for one of them you must do so
so if you want to define your own class for one of them you should do so
for both.
</p>
@ -3077,6 +3058,85 @@ prefs, etc.</p>
<p><span>Dict[str, Tuple[str, ...]]</span></p>
<p>Extra chars like emojis.</p>
</dd>
</dl>
<hr>
<h2><strong><a name="class_ba_LanguageSubsystem">ba.LanguageSubsystem</a></strong></h3>
<p><em>&lt;top level class&gt;</em>
</p>
<p>Wraps up language related app functionality.</p>
<p>Category: <a href="#class_category_App_Classes">App Classes</a></p>
<p> To use this class, access the single instance of it at 'ba.app.lang'.
</p>
<h3>Attributes:</h3>
<h5><a href="#attr_ba_LanguageSubsystem__available_languages">available_languages</a>, <a href="#attr_ba_LanguageSubsystem__language">language</a>, <a href="#attr_ba_LanguageSubsystem__locale">locale</a></h5>
<dl>
<dt><h4><a name="attr_ba_LanguageSubsystem__available_languages">available_languages</a></h4></dt><dd>
<p><span>List[str]</span></p>
<p>A list of all available languages.</p>
<p> 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.</p>
</dd>
<dt><h4><a name="attr_ba_LanguageSubsystem__language">language</a></h4></dt><dd>
<p><span>str</span></p>
<p>The name of the language the game is running in.</p>
<p> This can be selected explicitly by the user or may be set
automatically based on <a href="#class_ba_App">ba.App</a>.locale or other factors.</p>
</dd>
<dt><h4><a name="attr_ba_LanguageSubsystem__locale">locale</a></h4></dt><dd>
<p><span>str</span></p>
<p>Raw country/language code detected by the game (such as 'en_US').</p>
<p> Generally for language-specific code you should look at
<a href="#class_ba_App">ba.App</a>.language, which is the language the game is using
(which may differ from locale if the user sets a language, etc.)</p>
</dd>
</dl>
<h3>Methods:</h3>
<h5><a href="#method_ba_LanguageSubsystem____init__">&lt;constructor&gt;</a>, <a href="#method_ba_LanguageSubsystem__get_resource">get_resource()</a>, <a href="#method_ba_LanguageSubsystem__is_custom_unicode_char">is_custom_unicode_char()</a>, <a href="#method_ba_LanguageSubsystem__setlanguage">setlanguage()</a>, <a href="#method_ba_LanguageSubsystem__translate">translate()</a></h5>
<dl>
<dt><h4><a name="method_ba_LanguageSubsystem____init__">&lt;constructor&gt;</a></dt></h4><dd>
<p><span>ba.LanguageSubsystem()</span></p>
</dd>
<dt><h4><a name="method_ba_LanguageSubsystem__get_resource">get_resource()</a></dt></h4><dd>
<p><span>get_resource(self, resource: str, fallback_resource: str = None, fallback_value: Any = None) -&gt; Any</span></p>
<p>Return a translation resource by name.</p>
<p>DEPRECATED; use <a href="#class_ba_Lstr">ba.Lstr</a> functionality for these purposes.</p>
</dd>
<dt><h4><a name="method_ba_LanguageSubsystem__is_custom_unicode_char">is_custom_unicode_char()</a></dt></h4><dd>
<p><span>is_custom_unicode_char(self, char: str) -&gt; bool</span></p>
<p>Return whether a char is in the custom unicode range we use.</p>
</dd>
<dt><h4><a name="method_ba_LanguageSubsystem__setlanguage">setlanguage()</a></dt></h4><dd>
<p><span>setlanguage(self, language: Optional[str], print_change: bool = True, store_to_config: bool = True) -&gt; None</span></p>
<p>Set the active language used for the game.</p>
<p>Pass None to use OS default language.</p>
</dd>
<dt><h4><a name="method_ba_LanguageSubsystem__translate">translate()</a></dt></h4><dd>
<p><span>translate(self, category: str, strval: str, raise_exceptions: bool = False, print_errors: bool = False) -&gt; str</span></p>
<p>Translate a value (or return the value if no translation available)</p>
<p>DEPRECATED; use <a href="#class_ba_Lstr">ba.Lstr</a> functionality for these purposes.</p>
</dd>
</dl>
<hr>
@ -3907,6 +3967,96 @@ signify that the default soundtrack should be used..</p>
<li>TEST</li>
</ul>
<hr>
<h2><strong><a name="class_ba_MusicSubsystem">ba.MusicSubsystem</a></strong></h3>
<p><em>&lt;top level class&gt;</em>
</p>
<p>Subsystem for music playback in the app.</p>
<p>Category: <a href="#class_category_App_Classes">App Classes</a></p>
<p> To use this class, access the single instance of it at 'ba.app.music'.
</p>
<h3>Methods:</h3>
<h5><a href="#method_ba_MusicSubsystem____init__">&lt;constructor&gt;</a>, <a href="#method_ba_MusicSubsystem__do_play_music">do_play_music()</a>, <a href="#method_ba_MusicSubsystem__get_music_player">get_music_player()</a>, <a href="#method_ba_MusicSubsystem__get_soundtrack_entry_name">get_soundtrack_entry_name()</a>, <a href="#method_ba_MusicSubsystem__get_soundtrack_entry_type">get_soundtrack_entry_type()</a>, <a href="#method_ba_MusicSubsystem__have_music_player">have_music_player()</a>, <a href="#method_ba_MusicSubsystem__music_volume_changed">music_volume_changed()</a>, <a href="#method_ba_MusicSubsystem__on_app_launch">on_app_launch()</a>, <a href="#method_ba_MusicSubsystem__on_app_resume">on_app_resume()</a>, <a href="#method_ba_MusicSubsystem__on_app_shutdown">on_app_shutdown()</a>, <a href="#method_ba_MusicSubsystem__set_music_play_mode">set_music_play_mode()</a>, <a href="#method_ba_MusicSubsystem__supports_soundtrack_entry_type">supports_soundtrack_entry_type()</a></h5>
<dl>
<dt><h4><a name="method_ba_MusicSubsystem____init__">&lt;constructor&gt;</a></dt></h4><dd>
<p><span>ba.MusicSubsystem()</span></p>
</dd>
<dt><h4><a name="method_ba_MusicSubsystem__do_play_music">do_play_music()</a></dt></h4><dd>
<p><span>do_play_music(self, musictype: Union[MusicType, str, None], continuous: bool = False, mode: MusicPlayMode = &lt;MusicPlayMode.REGULAR: regular&gt;, testsoundtrack: Dict[str, Any] = None) -&gt; None</span></p>
<p>Plays the requested music type/mode.</p>
<p>For most cases, setmusic() is the proper call to use, which itself
calls this. Certain cases, however, such as soundtrack testing, may
require calling this directly.</p>
</dd>
<dt><h4><a name="method_ba_MusicSubsystem__get_music_player">get_music_player()</a></dt></h4><dd>
<p><span>get_music_player(self) -&gt; MusicPlayer</span></p>
<p>Returns the system music player, instantiating if necessary.</p>
</dd>
<dt><h4><a name="method_ba_MusicSubsystem__get_soundtrack_entry_name">get_soundtrack_entry_name()</a></dt></h4><dd>
<p><span>get_soundtrack_entry_name(self, entry: Any) -&gt; str</span></p>
<p>Given a soundtrack entry, returns its name.</p>
</dd>
<dt><h4><a name="method_ba_MusicSubsystem__get_soundtrack_entry_type">get_soundtrack_entry_type()</a></dt></h4><dd>
<p><span>get_soundtrack_entry_type(self, entry: Any) -&gt; str</span></p>
<p>Given a soundtrack entry, returns its type, taking into
account what is supported locally.</p>
</dd>
<dt><h4><a name="method_ba_MusicSubsystem__have_music_player">have_music_player()</a></dt></h4><dd>
<p><span>have_music_player(self) -&gt; bool</span></p>
<p>Returns whether a music player is present.</p>
</dd>
<dt><h4><a name="method_ba_MusicSubsystem__music_volume_changed">music_volume_changed()</a></dt></h4><dd>
<p><span>music_volume_changed(self, val: float) -&gt; None</span></p>
<p>Should be called when changing the music volume.</p>
</dd>
<dt><h4><a name="method_ba_MusicSubsystem__on_app_launch">on_app_launch()</a></dt></h4><dd>
<p><span>on_app_launch(self) -&gt; None</span></p>
<p>Should be called by app on_app_launch().</p>
</dd>
<dt><h4><a name="method_ba_MusicSubsystem__on_app_resume">on_app_resume()</a></dt></h4><dd>
<p><span>on_app_resume(self) -&gt; None</span></p>
<p>Should be run when the app resumes from a suspended state.</p>
</dd>
<dt><h4><a name="method_ba_MusicSubsystem__on_app_shutdown">on_app_shutdown()</a></dt></h4><dd>
<p><span>on_app_shutdown(self) -&gt; None</span></p>
<p>Should be called when the app is shutting down.</p>
</dd>
<dt><h4><a name="method_ba_MusicSubsystem__set_music_play_mode">set_music_play_mode()</a></dt></h4><dd>
<p><span>set_music_play_mode(self, mode: MusicPlayMode, force_restart: bool = False) -&gt; None</span></p>
<p>Sets music play mode; used for soundtrack testing/etc.</p>
</dd>
<dt><h4><a name="method_ba_MusicSubsystem__supports_soundtrack_entry_type">supports_soundtrack_entry_type()</a></dt></h4><dd>
<p><span>supports_soundtrack_entry_type(self, entry_type: str) -&gt; bool</span></p>
<p>Return whether provided soundtrack entry type is supported here.</p>
</dd>
</dl>
<hr>
<h2><strong><a name="class_ba_MusicType">ba.MusicType</a></strong></h3>
<p>Inherits from: enum.Enum</p>
<p>Types of music available to play in-game.</p>
@ -5777,6 +5927,69 @@ self.t = <a href="#class_ba_Timer">ba.Timer</a>(0.3, say_it, repeat=True)
<li>SMALL</li>
</ul>
<hr>
<h2><strong><a name="class_ba_UISubsystem">ba.UISubsystem</a></strong></h3>
<p><em>&lt;top level class&gt;</em>
</p>
<p>Consolidated UI functionality for the app.</p>
<p>Category: <a href="#class_category_App_Classes">App Classes</a></p>
<p> To use this class, access the single instance of it at 'ba.app.ui'.
</p>
<h3>Attributes:</h3>
<dl>
<dt><h4><a name="attr_ba_UISubsystem__uiscale">uiscale</a></h4></dt><dd>
<p><span><a href="#class_ba_UIScale">ba.UIScale</a></span></p>
<p>Current ui scale for the app.</p>
</dd>
</dl>
<h3>Methods:</h3>
<h5><a href="#method_ba_UISubsystem____init__">&lt;constructor&gt;</a>, <a href="#method_ba_UISubsystem__clear_main_menu_window">clear_main_menu_window()</a>, <a href="#method_ba_UISubsystem__get_main_menu_location">get_main_menu_location()</a>, <a href="#method_ba_UISubsystem__has_main_menu_window">has_main_menu_window()</a>, <a href="#method_ba_UISubsystem__on_app_launch">on_app_launch()</a>, <a href="#method_ba_UISubsystem__set_main_menu_location">set_main_menu_location()</a>, <a href="#method_ba_UISubsystem__set_main_menu_window">set_main_menu_window()</a></h5>
<dl>
<dt><h4><a name="method_ba_UISubsystem____init__">&lt;constructor&gt;</a></dt></h4><dd>
<p><span>ba.UISubsystem()</span></p>
</dd>
<dt><h4><a name="method_ba_UISubsystem__clear_main_menu_window">clear_main_menu_window()</a></dt></h4><dd>
<p><span>clear_main_menu_window(self, transition: str = None) -&gt; None</span></p>
<p>Clear any existing 'main' window with the provided transition.</p>
</dd>
<dt><h4><a name="method_ba_UISubsystem__get_main_menu_location">get_main_menu_location()</a></dt></h4><dd>
<p><span>get_main_menu_location(self) -&gt; Optional[str]</span></p>
<p>Return the current named main menu location, if any.</p>
</dd>
<dt><h4><a name="method_ba_UISubsystem__has_main_menu_window">has_main_menu_window()</a></dt></h4><dd>
<p><span>has_main_menu_window(self) -&gt; bool</span></p>
<p>Return whether a main menu window is present.</p>
</dd>
<dt><h4><a name="method_ba_UISubsystem__on_app_launch">on_app_launch()</a></dt></h4><dd>
<p><span>on_app_launch(self) -&gt; None</span></p>
<p>Should be run on app launch.</p>
</dd>
<dt><h4><a name="method_ba_UISubsystem__set_main_menu_location">set_main_menu_location()</a></dt></h4><dd>
<p><span>set_main_menu_location(self, location: str) -&gt; None</span></p>
<p>Set the location represented by the current main menu window.</p>
</dd>
<dt><h4><a name="method_ba_UISubsystem__set_main_menu_window">set_main_menu_window()</a></dt></h4><dd>
<p><span>set_main_menu_window(self, window: <a href="#class_ba_Widget">ba.Widget</a>) -&gt; None</span></p>
<p>Set the current 'main' window, replacing any existing.</p>
</dd>
</dl>
<hr>
<h2><strong><a name="class_ba_Vec3">ba.Vec3</a></strong></h3>
<p><em>&lt;top level class&gt;</em>
</p>
@ -6259,17 +6472,6 @@ 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</p>
<hr>
<h2><strong><a name="function_ba_get_valid_languages">ba.get_valid_languages()</a></strong></h3>
<p><span>get_valid_languages() -&gt; List[str]</span></p>
<p>Return a list containing names of all available languages.</p>
<p>Category: <a href="#function_category_General_Utility_Functions">General Utility Functions</a></p>
<p>Languages that may be present but are not displayable on the running
version of the game are ignored.</p>
<hr>
<h2><strong><a name="function_ba_getactivity">ba.getactivity()</a></strong></h3>
<p><span>getactivity(doraise: bool = True) -&gt; &lt;varies&gt;</span></p>
@ -6317,6 +6519,8 @@ in the background if necessary.</p>
<p>Return the in-progress collision.</p>
<p>Category: <a href="#function_category_Gameplay_Functions">Gameplay Functions</a></p>
<hr>
<h2><strong><a name="function_ba_getmaps">ba.getmaps()</a></strong></h3>
<p><span>getmaps(playtype: str) -&gt; List[str]</span></p>
@ -6732,16 +6936,6 @@ are applied to the Widget.</p>
'screen' should be a string description of an app location
('Main Menu', etc.)</p>
<hr>
<h2><strong><a name="function_ba_setlanguage">ba.setlanguage()</a></strong></h3>
<p><span>setlanguage(language: Optional[str], print_change: bool = True, store_to_config: bool = True) -&gt; None</span></p>
<p>Set the active language used for the game.</p>
<p>Category: <a href="#function_category_General_Utility_Functions">General Utility Functions</a></p>
<p>Pass None to use OS default language.</p>
<hr>
<h2><strong><a name="function_ba_setmusic">ba.setmusic()</a></strong></h3>
<p><span>setmusic(musictype: Optional[MusicType], continuous: bool = False) -&gt; None</span></p>

View File

@ -29,8 +29,8 @@
namespace ballistica {
// These are set automatically via script; don't change here.
const int kAppBuildNumber = 20216;
const char* kAppVersion = "1.5.26";
const int kAppBuildNumber = 20218;
const char* kAppVersion = "1.5.27";
// Our standalone globals.
// These are separated out for easy access.

View File

@ -10,7 +10,7 @@ def get_binding_values() -> object:
import json
import copy
import ba
from ba import _lang
from ba import _language
from ba import _music
from ba import _input
from ba import _apputils
@ -99,8 +99,8 @@ def get_binding_values() -> object:
party.handle_party_invite, # kHandlePartyInviteCall
_music.do_play_music, # kDoPlayMusicCall
ba.app.handle_deep_link, # kDeepLinkCall
_lang.get_resource, # kGetResourceCall
_lang.translate, # kTranslateCall
ba.app.lang.get_resource, # kGetResourceCall
ba.app.lang.translate, # kTranslateCall
ba.Lstr, # kLStrClass
ba.Call, # kCallClass
_apputils.garbage_collect, # kGarbageCollectCall
@ -122,5 +122,5 @@ def get_binding_values() -> object:
_enums.SpecialChar, # kSpecialCharClass
_player.Player, # kPlayerClass
_hooks.get_player_icon, # kGetPlayerIconCall
_lang.Lstr.from_json, # kLstrFromJsonCall
_language.Lstr.from_json, # kLstrFromJsonCall
) # yapf: disable