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/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/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", "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/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/a1/20/f75ee36d80a99dbfe1ff79db2093", "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/94/91/af4a3be510e2570651fbb8d4c297", "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/cd/6c/73657b342cb1666dd3ac734a93c8", "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/a6/9d/8830abe356b106005b793952f60c", "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/93/e7/063a5a038904ef0641d405a5e66d", "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/69/e8/77839309e3301d62bbf77624d810", "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/95/4e/9e5dbd0b19acddc2cd056eca123c", "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/0c/bb/3990f398178b924de0b493db14d8", "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/85/14/604f8855cfa461797c5e3c5b5aab", "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/e1/83/9d2ffd1a9f149a18c005be57db29", "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/f6/63/9aaf6704f8dcb6e32808d09989a4", "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/15/54/bfba7d740c7221a5d46e8e21c756", "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/4b/1f/ca36bea671a5b88a7e2ccf2e4c4a", "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/57/2d/e4b9a67cb21131cdcdfb8287f9e7", "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/59/b6/6ffc20f2c0253180496d2dae968c", "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/cd/4f/d760d9fce637b61efeed648063cc", "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/7c/5a/d63a634b3886c9cf1b3697d24b75", "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/46/80/98efbaeed954d2b008a9bfb77e12", "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/89/24/6aae1e666373c46b409e44d7cdf7" "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>getplayer</w>
<w>getpt</w> <w>getpt</w>
<w>getremote</w> <w>getremote</w>
<w>getres</w>
<w>getscanresults</w> <w>getscanresults</w>
<w>getscoreconfig</w> <w>getscoreconfig</w>
<w>getsession</w> <w>getsession</w>
@ -1015,6 +1016,7 @@
<w>intex</w> <w>intex</w>
<w>intp</w> <w>intp</w>
<w>introspectable</w> <w>introspectable</w>
<w>iobj</w>
<w>ipaddress</w> <w>ipaddress</w>
<w>ipos</w> <w>ipos</w>
<w>iprof</w> <w>iprof</w>
@ -1359,6 +1361,7 @@
<w>nline</w> <w>nline</w>
<w>nlines</w> <w>nlines</w>
<w>nntplib</w> <w>nntplib</w>
<w>noassets</w>
<w>nodeactor</w> <w>nodeactor</w>
<w>nodepos</w> <w>nodepos</w>
<w>nodpi</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. - Simplified licensing header on python scripts.
- General project refactoring in order to open source most of the C++ layer. - 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__/_hooks.cpython-38.opt-1.pyc",
"ba_data/python/ba/__pycache__/_input.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__/_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__/_level.cpython-38.opt-1.pyc",
"ba_data/python/ba/__pycache__/_lobby.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", "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/_hooks.py",
"ba_data/python/ba/_input.py", "ba_data/python/ba/_input.py",
"ba_data/python/ba/_keyboard.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/_level.py",
"ba_data/python/ba/_lobby.py", "ba_data/python/ba/_lobby.py",
"ba_data/python/ba/_map.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/_hooks.py \
build/ba_data/python/ba/_input.py \ build/ba_data/python/ba/_input.py \
build/ba_data/python/ba/_keyboard.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/_level.py \
build/ba_data/python/ba/_lobby.py \ build/ba_data/python/ba/_lobby.py \
build/ba_data/python/ba/_map.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__/_hooks.cpython-38.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_input.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__/_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__/_level.cpython-38.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_lobby.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 \ 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._gameresults import GameResults
from ba._settings import (Setting, IntSetting, FloatSetting, ChoiceSetting, from ba._settings import (Setting, IntSetting, FloatSetting, ChoiceSetting,
BoolSetting, IntChoiceSetting, FloatChoiceSetting) 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._map import Map, getmaps
from ba._session import Session from ba._session import Session
from ba._ui import UISubsystem
from ba._servermode import ServerController from ba._servermode import ServerController
from ba._score import ScoreType, ScoreConfig from ba._score import ScoreType, ScoreConfig
from ba._stats import PlayerScoredMessage, PlayerRecord, Stats from ba._stats import PlayerScoredMessage, PlayerRecord, Stats
@ -70,7 +71,8 @@ from ba._messages import (UNHANDLED, OutOfBoundsMessage, DeathType, DieMessage,
ShouldShatterMessage, ImpactDamageMessage, ShouldShatterMessage, ImpactDamageMessage,
FreezeMessage, ThawMessage, HitMessage, FreezeMessage, ThawMessage, HitMessage,
CelebrateMessage) 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._powerup import PowerupMessage, PowerupAcceptMessage
from ba._multiteamsession import MultiTeamSession from ba._multiteamsession import MultiTeamSession
from ba.ui import Window, UIController, uicleanupcheck from ba.ui import Window, UIController, uicleanupcheck

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ import _ba
if TYPE_CHECKING: if TYPE_CHECKING:
import ba import ba
from ba import _lang, _meta from ba import _language, _meta
from bastd.actor import spazappearance from bastd.actor import spazappearance
from typing import Optional, Dict, Set, Any, Type, Tuple, Callable, List 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) assert isinstance(self._env['config_file_path'], str)
return self._env['config_file_path'] 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 @property
def user_agent_string(self) -> str: def user_agent_string(self) -> str:
"""String containing various bits of info about OS/device/etc.""" """String containing various bits of info about OS/device/etc."""
@ -254,7 +173,8 @@ class App:
""" """
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
from ba._music import MusicSubsystem from ba._music import MusicSubsystem
from ba._ui import UI from ba._language import LanguageSubsystem
from ba._ui import UISubsystem
# Config. # Config.
self.config_file_healthy = False self.config_file_healthy = False
@ -283,7 +203,6 @@ class App:
self.active_plugins: Dict[str, ba.Plugin] = {} self.active_plugins: Dict[str, ba.Plugin] = {}
# Misc. # Misc.
self.default_language = self._get_default_language()
self.metascan: Optional[_meta.ScanResults] = None self.metascan: Optional[_meta.ScanResults] = None
self.tips: List[str] = [] self.tips: List[str] = []
self.stress_test_reset_timer: Optional[ba.Timer] = None self.stress_test_reset_timer: Optional[ba.Timer] = None
@ -326,8 +245,7 @@ class App:
self.music = MusicSubsystem() self.music = MusicSubsystem()
# Language. # Language.
self.language_target: Optional[_lang.AttrDict] = None self.lang = LanguageSubsystem()
self.language_merged: Optional[_lang.AttrDict] = None
# Achievements. # Achievements.
self.achievements: List[ba.Achievement] = [] self.achievements: List[ba.Achievement] = []
@ -358,7 +276,7 @@ class App:
self.coop_session_args: Dict = {} self.coop_session_args: Dict = {}
# UI. # UI.
self.ui = UI() self.ui = UISubsystem()
self.value_test_defaults: dict = {} self.value_test_defaults: dict = {}
self.first_main_menu = True # FIXME: Move to mainmenu class. 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() activity: Optional[ba.Activity] = _ba.get_foreground_host_activity()
if (activity is not None and activity.allow_pausing if (activity is not None and activity.allow_pausing
and not _ba.have_connected_clients()): and not _ba.have_connected_clients()):
from ba import _gameutils, _lang from ba import _gameutils, _language
from ba._nodeactor import NodeActor from ba._nodeactor import NodeActor
# FIXME: Shouldn't be touching scene stuff here; # FIXME: Shouldn't be touching scene stuff here;
@ -567,10 +485,14 @@ class App:
_ba.newnode( _ba.newnode(
'text', 'text',
attrs={ attrs={
'text': _lang.Lstr(resource='pausedByHostText'), 'text':
'client_only': True, _language.Lstr(resource='pausedByHostText'),
'flatness': 1.0, 'client_only':
'h_align': 'center' True,
'flatness':
1.0,
'h_align':
'center'
})) }))
def resume(self) -> None: def resume(self) -> None:
@ -705,7 +627,7 @@ class App:
def do_remove_in_game_ads_message(self) -> None: def do_remove_in_game_ads_message(self) -> None:
"""(internal)""" """(internal)"""
from ba._lang import Lstr from ba._language import Lstr
from ba._enums import TimeType from ba._enums import TimeType
# Print this message once every 10 minutes at most. # Print this message once every 10 minutes at most.
@ -730,7 +652,7 @@ class App:
def handle_deep_link(self, url: str) -> None: def handle_deep_link(self, url: str) -> None:
"""Handle a deep link URL.""" """Handle a deep link URL."""
from ba._lang import Lstr from ba._language import Lstr
from ba._enums import TimeType from ba._enums import TimeType
appname = _ba.appname() appname = _ba.appname()
if url.startswith(f'{appname}://code/'): 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: def get_remote_app_name() -> ba.Lstr:
"""(internal)""" """(internal)"""
from ba import _lang from ba import _language
return _lang.Lstr(resource='remote_app.app_name') return _language.Lstr(resource='remote_app.app_name')
def should_submit_debug_info() -> bool: def should_submit_debug_info() -> bool:
@ -213,15 +213,15 @@ def print_live_object_warnings(when: Any,
def print_corrupt_file_error() -> None: def print_corrupt_file_error() -> None:
"""Print an error if a corrupt file is found.""" """Print an error if a corrupt file is found."""
from ba._lang import get_resource
from ba._general import Call from ba._general import Call
from ba._enums import TimeType from ba._enums import TimeType
_ba.timer( _ba.timer(2.0,
2.0, lambda: _ba.screenmessage(
lambda: _ba.screenmessage(get_resource('internal.corruptFileText'). _ba.app.lang.get_resource('internal.corruptFileText').
replace('${EMAIL}', 'support@froemling.net'), replace('${EMAIL}', 'support@froemling.net'),
color=(1, 0, 0)), color=(1, 0, 0),
timetype=TimeType.REAL) ),
timetype=TimeType.REAL)
_ba.timer(2.0, _ba.timer(2.0,
Call(_ba.playsound, _ba.getsound('error')), Call(_ba.playsound, _ba.getsound('error')),
timetype=TimeType.REAL) timetype=TimeType.REAL)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -267,7 +267,7 @@ def timestring(timeval: float,
use a 'timedisplay' Node and attribute connections. 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 # Temp sanity check while we transition from milliseconds to seconds
# based time values. # based time values.

View File

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

View File

@ -11,7 +11,7 @@ from typing import TYPE_CHECKING
import _ba import _ba
from ba._error import print_exception, print_error, NotFoundError from ba._error import print_exception, print_error, NotFoundError
from ba._gameutils import animate, animate_array 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._enums import SpecialChar, InputType
from ba._profile import get_player_profile_colors 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 Category: Asset Functions
""" """
from ba import _lang from ba import _language
return _lang.Lstr(translate=('mapsNames', name)) return _language.Lstr(translate=('mapsNames', name))
def getmaps(playtype: str) -> List[str]: def getmaps(playtype: str) -> List[str]:

View File

@ -48,7 +48,7 @@ def start_scan() -> None:
def handle_scan_results(results: ScanResults) -> None: def handle_scan_results(results: ScanResults) -> None:
"""Called in the game thread with results of a completed scan.""" """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 from ba._plugin import PotentialPlugin
# Warnings generally only get printed locally for users' benefit # 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._math import normalized_color
from ba._general import Call from ba._general import Call
from ba._gameutils import cameraflash from ba._gameutils import cameraflash
from ba._lang import Lstr from ba._language import Lstr
from ba._freeforallsession import FreeForAllSession from ba._freeforallsession import FreeForAllSession
from ba._messages import CelebrateMessage from ba._messages import CelebrateMessage
_ba.timer(delay, Call(_ba.playsound, _ba.getsound('boxingBell'))) _ba.timer(delay, Call(_ba.playsound, _ba.getsound('boxingBell')))

View File

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

View File

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

View File

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

View File

@ -131,7 +131,7 @@ class PlayerRecord:
"""Submit a kill for this player entry.""" """Submit a kill for this player entry."""
# FIXME Clean this up. # FIXME Clean this up.
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
from ba._lang import Lstr from ba._language import Lstr
from ba._general import Call from ba._general import Call
self._multi_kill_count += 1 self._multi_kill_count += 1
stats = self._stats() stats = self._stats()
@ -341,7 +341,7 @@ class Stats:
from bastd.actor.popuptext import PopupText from bastd.actor.popuptext import PopupText
from ba import _math from ba import _math
from ba._gameactivity import GameActivity from ba._gameactivity import GameActivity
from ba._lang import Lstr from ba._language import Lstr
del victim_player # Currently unused. del victim_player # Currently unused.
name = player.getname() name = player.getname()
s_player = self._player_records[name] s_player = self._player_records[name]
@ -428,7 +428,7 @@ class Stats:
killed: bool = False, killed: bool = False,
killer: ba.Player = None) -> None: killer: ba.Player = None) -> None:
"""Should be called when a player is killed.""" """Should be called when a player is killed."""
from ba._lang import Lstr from ba._language import Lstr
name = player.getname() name = player.getname()
prec = self._player_records[name] prec = self._player_records[name]
prec.streak = 0 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: def get_store_item_name_translated(item_name: str) -> ba.Lstr:
"""Return a ba.Lstr for a store item name.""" """Return a ba.Lstr for a store item name."""
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from ba import _lang from ba import _language
from ba import _map from ba import _map
item_info = get_store_item(item_name) item_info = get_store_item(item_name)
if item_name.startswith('characters.'): 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']: if item_name in ['upgrades.pro', 'pro']:
return _lang.Lstr(resource='store.bombSquadProNameText', return _language.Lstr(resource='store.bombSquadProNameText',
subs=[('${APP_NAME}', subs=[('${APP_NAME}',
_lang.Lstr(resource='titleText'))]) _language.Lstr(resource='titleText'))])
if item_name.startswith('maps.'): if item_name.startswith('maps.'):
map_type: Type[ba.Map] = item_info['map_type'] map_type: Type[ba.Map] = item_info['map_type']
return _map.get_map_display_string(map_type.name) 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'] gametype: Type[ba.GameActivity] = item_info['gametype']
return gametype.get_display_string() return gametype.get_display_string()
if item_name.startswith('icons.'): if item_name.startswith('icons.'):
return _lang.Lstr(resource='editProfileWindow.iconText') return _language.Lstr(resource='editProfileWindow.iconText')
raise ValueError('unrecognized item: ' + item_name) 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. 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, 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. for both.
""" """

View File

@ -15,8 +15,13 @@ if TYPE_CHECKING:
import ba import ba
class UI: class UISubsystem:
"""UI subsystem for the app.""" """Consolidated UI functionality for the app.
Category: App Classes
To use this class, access the single instance of it at 'ba.app.ui'.
"""
def __init__(self) -> None: def __init__(self) -> None:
env = _ba.env() 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 Any code using them should migrate to alternative methods, as
deprecated items will eventually be fully removed. 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: def run(self) -> None:
"""Run the Music.app thread.""" """Run the Music.app thread."""
from ba._general import Call from ba._general import Call
from ba._lang import Lstr from ba._language import Lstr
from ba._enums import TimeType from ba._enums import TimeType
_ba.set_thread_name('BA_MacMusicAppThread') _ba.set_thread_name('BA_MacMusicAppThread')
_ba.mac_music_app_init() _ba.mac_music_app_init()
@ -212,7 +212,6 @@ class _MacMusicAppThread(threading.Thread):
def _play_current_playlist(self) -> None: def _play_current_playlist(self) -> None:
try: try:
from ba import _lang
from ba._general import Call from ba._general import Call
assert self._current_playlist is not None assert self._current_playlist is not None
if _ba.mac_music_app_play_playlist(self._current_playlist): if _ba.mac_music_app_play_playlist(self._current_playlist):
@ -220,8 +219,8 @@ class _MacMusicAppThread(threading.Thread):
else: else:
_ba.pushcall(Call( _ba.pushcall(Call(
_ba.screenmessage, _ba.screenmessage,
_lang.get_resource('playlistNotFoundText') + ': \'' + _ba.app.lang.get_resource('playlistNotFoundText') +
self._current_playlist + '\'', (1, 0, 0)), ': \'' + self._current_playlist + '\'', (1, 0, 0)),
from_other_thread=True) from_other_thread=True)
except Exception: except Exception:
from ba import _error 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)". This is NOT a valid filesystem path; may be something like "(SD Card)".
""" """
from ba import _lang from ba import _language
app = _ba.app app = _ba.app
path: Optional[str] = app.python_directory_user path: Optional[str] = app.python_directory_user
if path is None: if path is None:
@ -32,14 +32,14 @@ def get_human_readable_user_scripts_path() -> str:
if (ext_storage_path is not None if (ext_storage_path is not None
and app.python_directory_user.startswith(ext_storage_path)): and app.python_directory_user.startswith(ext_storage_path)):
path = ('<' + path = ('<' +
_lang.Lstr(resource='externalStorageText').evaluate() + _language.Lstr(resource='externalStorageText').evaluate() +
'>' + app.python_directory_user[len(ext_storage_path):]) '>' + app.python_directory_user[len(ext_storage_path):])
return path return path
def _request_storage_permission() -> bool: def _request_storage_permission() -> bool:
"""If needed, requests storage permission from the user (& return true).""" """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 from ba._enums import Permission
if not _ba.have_permission(Permission.STORAGE): if not _ba.have_permission(Permission.STORAGE):
_ba.playsound(_ba.getsound('error')) _ba.playsound(_ba.getsound('error'))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -164,10 +164,10 @@ class AdvancedSettingsWindow(ba.Window):
def _update_lang_status(self) -> None: def _update_lang_status(self) -> None:
if self._complete_langs_list is not 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( ba.textwidget(
edit=self._lang_status_text, 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') resource=self._r + '.translationNoUpdateNeededText')
if up_to_date else ba.Lstr(resource=self._r + if up_to_date else ba.Lstr(resource=self._r +
'.translationUpdateNeededText'), '.translationUpdateNeededText'),
@ -189,6 +189,8 @@ class AdvancedSettingsWindow(ba.Window):
from bastd.ui.config import ConfigCheckBox from bastd.ui.config import ConfigCheckBox
from ba.modutils import show_user_scripts 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 # Don't rebuild if the menu is open or if our language and
# language-list hasn't changed. # language-list hasn't changed.
# NOTE - although we now support widgets updating their own # 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 # menu based on the language so still need this. ...however we could
# make this more limited to it only rebuilds that one menu instead # make this more limited to it only rebuilds that one menu instead
# of everything. # of everything.
if self._menu_open or ( if self._menu_open or (self._prev_lang == _ba.app.config.get(
self._prev_lang == _ba.app.config.get('Lang', None) 'Lang', None) and self._prev_lang_list == available_languages):
and self._prev_lang_list == ba.get_valid_languages()):
return return
self._prev_lang = _ba.app.config.get('Lang', None) 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. # Clear out our sub-container.
children = self._subcontainer.get_children() children = self._subcontainer.get_children()
@ -248,7 +249,7 @@ class AdvancedSettingsWindow(ba.Window):
h_align='right', h_align='right',
v_align='center') v_align='center')
languages = ba.get_valid_languages() languages = _ba.app.lang.available_languages
cur_lang = _ba.app.config.get('Lang', None) cur_lang = _ba.app.config.get('Lang', None)
if cur_lang is None: if cur_lang is None:
cur_lang = 'Auto' cur_lang = 'Auto'
@ -289,9 +290,9 @@ class AdvancedSettingsWindow(ba.Window):
button_size=(250, 60), button_size=(250, 60),
choices_display=([ choices_display=([
ba.Lstr(value=(ba.Lstr(resource='autoText').evaluate() + ' (' + ba.Lstr(value=(ba.Lstr(resource='autoText').evaluate() + ' (' +
ba.Lstr(translate=( ba.Lstr(translate=('languages',
'languages', ba.app.lang.default_language
ba.app.default_language)).evaluate() + ')')) )).evaluate() + ')'))
] + [ba.Lstr(value=langs_full[l]) for l in languages]), ] + [ba.Lstr(value=langs_full[l]) for l in languages]),
current_choice=cur_lang) current_choice=cur_lang)
@ -689,7 +690,7 @@ class AdvancedSettingsWindow(ba.Window):
self._menu_open = False self._menu_open = False
def _on_menu_choice(self, choice: str) -> None: 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() self._save_state()
ba.timer(0.1, ba.WeakCall(self._rebuild), timetype=ba.TimeType.REAL) 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) ba.widget(edit=cancel_button, down_widget=self._text_field)
def _refresh(self) -> None: def _refresh(self) -> None:
from ba.deprecated import get_resource
for widget in self._col.get_children(): for widget in self._col.get_children():
widget.delete() widget.delete()
@ -183,8 +182,9 @@ class SoundtrackEditWindow(ba.Window):
'Scores', 'Scores',
'Victory', 'Victory',
] ]
# FIXME: We should probably convert this to use translations. # 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_type_button: Optional[ba.Widget] = None
prev_test_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], position: Tuple[float, float],
data: Dict[str, Any], data: Dict[str, Any],
scale: float = None): scale: float = None):
from ba.deprecated import get_resource
self._data = data self._data = data
uiscale = ba.app.ui.uiscale uiscale = ba.app.ui.uiscale
if scale is None: if scale is None:
@ -76,7 +75,9 @@ class TrophiesWindow(popup.PopupWindow):
trophy_types = [['0a'], ['0b'], ['1'], ['2'], ['3'], ['4']] trophy_types = [['0a'], ['0b'], ['1'], ['2'], ['3'], ['4']]
sub_height = 40 + len(trophy_types) * incr 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, self._subcontainer = ba.containerwidget(parent=self._scrollwidget,
size=(sub_width, sub_height), size=(sub_width, sub_height),
@ -84,25 +85,27 @@ class TrophiesWindow(popup.PopupWindow):
total_pts = 0 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, total_pts += self._create_trophy_type_widgets(eq_text, incr, multi_txt,
sub_height, sub_width, sub_height, sub_width,
trophy_types) trophy_types)
ba.textwidget(parent=self._subcontainer, ba.textwidget(
position=(sub_width * 1.0, parent=self._subcontainer,
sub_height - 20 - incr * len(trophy_types)), position=(sub_width * 1.0,
maxwidth=sub_width * 0.5, sub_height - 20 - incr * len(trophy_types)),
scale=0.7, maxwidth=sub_width * 0.5,
color=(0.7, 0.8, 1.0), scale=0.7,
flatness=1.0, color=(0.7, 0.8, 1.0),
shadow=0.0, flatness=1.0,
text=get_resource('coopSelectWindow.totalText') + ' ' + shadow=0.0,
eq_text.replace('${NUMBER}', str(total_pts)), text=ba.Lstr(resource='coopSelectWindow.totalText').evaluate() +
size=(0, 0), ' ' + eq_text.replace('${NUMBER}', str(total_pts)),
h_align='right', size=(0, 0),
v_align='center') h_align='right',
v_align='center')
def _create_trophy_type_widgets(self, eq_text: str, incr: int, def _create_trophy_type_widgets(self, eq_text: str, incr: int,
multi_txt: str, sub_height: int, multi_txt: str, sub_height: int,

View File

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

View File

@ -1,5 +1,5 @@
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND --> <!-- 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, <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> 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> <hr>
@ -20,6 +20,7 @@
<li><a href="#class_ba_NodeActor">ba.NodeActor</a></li> <li><a href="#class_ba_NodeActor">ba.NodeActor</a></li>
</ul> </ul>
<li><a href="#class_ba_Chooser">ba.Chooser</a></li> <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_GameResults">ba.GameResults</a></li>
<li><a href="#class_ba_GameTip">ba.GameTip</a></li> <li><a href="#class_ba_GameTip">ba.GameTip</a></li>
<li><a href="#class_ba_InputDevice">ba.InputDevice</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_emitfx">ba.emitfx()</a></li>
<li><a href="#function_ba_existing">ba.existing()</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_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_getnodes">ba.getnodes()</a></li>
<li><a href="#function_ba_getsession">ba.getsession()</a></li> <li><a href="#function_ba_getsession">ba.getsession()</a></li>
<li><a href="#function_ba_newnode">ba.newnode()</a></li> <li><a href="#function_ba_newnode">ba.newnode()</a></li>
@ -84,7 +86,6 @@
<ul> <ul>
<li><a href="#function_ba_charstr">ba.charstr()</a></li> <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_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_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_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> <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_safecolor">ba.safecolor()</a></li>
<li><a href="#function_ba_screenmessage">ba.screenmessage()</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_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_storagename">ba.storagename()</a></li>
<li><a href="#function_ba_time">ba.time()</a></li> <li><a href="#function_ba_time">ba.time()</a></li>
<li><a href="#function_ba_timer">ba.timer()</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_AppDelegate">ba.AppDelegate</a></li>
<li><a href="#class_ba_Campaign">ba.Campaign</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_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_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_Plugin">ba.Plugin</a></li>
<li><a href="#class_ba_PotentialPlugin">ba.PotentialPlugin</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_ServerController">ba.ServerController</a></li>
<li><a href="#class_ba_UISubsystem">ba.UISubsystem</a></li>
</ul> </ul>
<h4><a name="class_category_User_Interface_Classes">User Interface Classes</a></h4> <h4><a name="class_category_User_Interface_Classes">User Interface Classes</a></h4>
<ul> <ul>
@ -216,14 +219,6 @@
<li><a href="#class_ba_WidgetNotFoundError">ba.WidgetNotFoundError</a></li> <li><a href="#class_ba_WidgetNotFoundError">ba.WidgetNotFoundError</a></li>
</ul> </ul>
</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> <h4><a name="class_category_Protocols">Protocols</a></h4>
<ul> <ul>
<li><a href="#class_ba_Existable">ba.Existable</a></li> <li><a href="#class_ba_Existable">ba.Existable</a></li>
@ -810,7 +805,7 @@ likely result in errors.</p>
</p> </p>
<h3>Attributes:</h3> <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> <dl>
<dt><h4><a name="attr_ba_App__api_version">api_version</a></h4></dt><dd> <dt><h4><a name="attr_ba_App__api_version">api_version</a></h4></dt><dd>
<p><span>int</span></p> <p><span>int</span></p>
@ -849,23 +844,6 @@ likely result in errors.</p>
builds due to compiler optimizations being disabled and extra builds due to compiler optimizations being disabled and extra
checks being run.</p> 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> </dd>
<dt><h4><a name="attr_ba_App__on_tv">on_tv</a></h4></dt><dd> <dt><h4><a name="attr_ba_App__on_tv">on_tv</a></h4></dt><dd>
<p><span>bool</span></p> <p><span>bool</span></p>
@ -1441,6 +1419,9 @@ mycall()</pre>
</p> </p>
<p>A class providing info about occurring collisions.</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> <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> <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> <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> 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, <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. for both.
</p> </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> 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, <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. for both.
</p> </p>
@ -3077,6 +3058,85 @@ prefs, etc.</p>
<p><span>Dict[str, Tuple[str, ...]]</span></p> <p><span>Dict[str, Tuple[str, ...]]</span></p>
<p>Extra chars like emojis.</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> </dd>
</dl> </dl>
<hr> <hr>
@ -3907,6 +3967,96 @@ signify that the default soundtrack should be used..</p>
<li>TEST</li> <li>TEST</li>
</ul> </ul>
<hr> <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> <h2><strong><a name="class_ba_MusicType">ba.MusicType</a></strong></h3>
<p>Inherits from: enum.Enum</p> <p>Inherits from: enum.Enum</p>
<p>Types of music available to play in-game.</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> <li>SMALL</li>
</ul> </ul>
<hr> <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> <h2><strong><a name="class_ba_Vec3">ba.Vec3</a></strong></h3>
<p><em>&lt;top level class&gt;</em> <p><em>&lt;top level class&gt;</em>
</p> </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: For more info, see notes on 'existables' here:
https://ballistica.net/wiki/Coding-Style-Guide</p> 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> <hr>
<h2><strong><a name="function_ba_getactivity">ba.getactivity()</a></strong></h3> <h2><strong><a name="function_ba_getactivity">ba.getactivity()</a></strong></h3>
<p><span>getactivity(doraise: bool = True) -&gt; &lt;varies&gt;</span></p> <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>Return the in-progress collision.</p>
<p>Category: <a href="#function_category_Gameplay_Functions">Gameplay Functions</a></p>
<hr> <hr>
<h2><strong><a name="function_ba_getmaps">ba.getmaps()</a></strong></h3> <h2><strong><a name="function_ba_getmaps">ba.getmaps()</a></strong></h3>
<p><span>getmaps(playtype: str) -&gt; List[str]</span></p> <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 'screen' should be a string description of an app location
('Main Menu', etc.)</p> ('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> <hr>
<h2><strong><a name="function_ba_setmusic">ba.setmusic()</a></strong></h3> <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> <p><span>setmusic(musictype: Optional[MusicType], continuous: bool = False) -&gt; None</span></p>

View File

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

View File

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