diff --git a/assets/src/ba_data/python/ba/_account.py b/assets/src/ba_data/python/ba/_account.py index bad8a50a..ca3bd4b1 100644 --- a/assets/src/ba_data/python/ba/_account.py +++ b/assets/src/ba_data/python/ba/_account.py @@ -173,10 +173,12 @@ def have_pro_options() -> bool: # We expose pro options if the server tells us to # (which is generally just when we own pro), - # or also if we've been grandfathered in. + # or also if we've been grandfathered in or are using ballistica-core + # builds. return bool( _ba.get_account_misc_read_val_2('proOptionsUnlocked', False) - or _ba.app.config.get('lc14292', 0) > 1) + or _ba.app.config.get('lc14292', 0) > 1 + or 'ballistica' + 'core' == 'ballisticacore') def show_post_purchase_message() -> None: diff --git a/assets/src/ba_data/python/ba/_app.py b/assets/src/ba_data/python/ba/_app.py index d9a02ad8..c4235d30 100644 --- a/assets/src/ba_data/python/ba/_app.py +++ b/assets/src/ba_data/python/ba/_app.py @@ -28,7 +28,7 @@ import _ba if TYPE_CHECKING: import ba - from ba import _lang as bs_lang + from ba import _lang, _meta from bastd.actor import spazappearance from typing import (Optional, Dict, Tuple, Set, Any, List, Type, Tuple, Callable) @@ -240,16 +240,7 @@ class App: """ # pylint: disable=too-many-statements - test_https = False - if test_https: - # Testing https support (would be nice to get this working on - # our custom python builds; need to wrangle certificates somehow). - import urllib.request - try: - val = urllib.request.urlopen('https://example.com').read() - print("HTTPS TEST SUCCESS", len(val)) - except Exception as exc: - print("HTTPS TEST FAIL:", exc) + # _test_https() # Config. self.config_file_healthy = False @@ -274,7 +265,7 @@ class App: self._platform: str = env['platform'] self._subplatform: str = env['subplatform'] self._interface_type: str = env['interface_type'] - self._on_tv: bool = env['on_tv'] # + self._on_tv: bool = env['on_tv'] self._vr_mode: bool = env['vr_mode'] self.protocol_version: int = env['protocol_version'] self.toolbar_test: bool = env['toolbar_test'] @@ -282,7 +273,7 @@ class App: # Misc. self.default_language = self._get_default_language() - self.metascan: Optional[Dict[str, Any]] = None + self.metascan: Optional[_meta.ScanResults] = None self.tips: List[str] = [] self.stress_test_reset_timer: Optional[ba.Timer] = None self.suppress_debug_reports = False @@ -337,8 +328,8 @@ class App: } # Language. - self.language_target: Optional[bs_lang.AttrDict] = None - self.language_merged: Optional[bs_lang.AttrDict] = None + self.language_target: Optional[_lang.AttrDict] = None + self.language_merged: Optional[_lang.AttrDict] = None # Achievements. self.achievements: List[ba.Achievement] = [] @@ -418,7 +409,6 @@ class App: # pylint: disable=too-many-locals # pylint: disable=cyclic-import from ba import _apputils - # from ba._general import Call from ba import _appconfig from ba import ui as bsui from ba import _achievement @@ -443,6 +433,8 @@ class App: self.music_player_type = _music.InternalMusicPlayer elif _ba.env()['platform'] == 'mac' and hasattr(_ba, 'itunes_init'): self.music_player_type = _music.MacITunesMusicPlayer + + # FIXME: This should not be hard-coded. for maptype in [ stdmaps.HockeyStadium, stdmaps.FootballStadium, stdmaps.Bridgit, stdmaps.BigG, stdmaps.Roundabout, @@ -543,6 +535,7 @@ class App: # Debugging - make note if we're using the local test server so we # don't accidentally leave it on in a release. + # FIXME - move this to native layer. server_addr = _ba.get_master_server_address() if 'localhost' in server_addr: _ba.timer(2.0, @@ -574,10 +567,8 @@ class App: if self.subplatform != 'headless': _ba.timer(3.0, check_special_offer, timetype=TimeType.REAL) - _meta.startscan() - - # Start scanning for stuff available in our scripts. - # meta.get_game_types() + # Start scanning for things exposed via ba_meta. + _meta.start_scan() # Auto-sign-in to a local account in a moment if we're set to. def do_auto_sign_in() -> None: @@ -592,8 +583,6 @@ class App: from ba._dependency import test_depset test_depset() - # print('GAME TYPES ARE', meta.get_game_types()) - # _bs.quit() def read_config(self) -> None: """(internal)""" @@ -610,21 +599,21 @@ class App: activity = _ba.get_foreground_host_activity() if (activity is not None and activity.allow_pausing and not _ba.have_connected_clients()): + from ba import _gameutils, _actor, _lang # FIXME: Shouldn't be touching scene stuff here; # should just pass the request on to the host-session. - import ba - with ba.Context(activity): - globs = ba.sharedobj('globals') + with _ba.Context(activity): + globs = _gameutils.sharedobj('globals') if not globs.paused: - ba.playsound(ba.getsound('refWhistle')) + _ba.playsound(_ba.getsound('refWhistle')) globs.paused = True # FIXME: This should not be an attr on Actor. - activity.paused_text = ba.Actor( - ba.newnode( + activity.paused_text = _actor.Actor( + _ba.newnode( 'text', attrs={ - 'text': ba.Lstr(resource='pausedByHostText'), + 'text': _lang.Lstr(resource='pausedByHostText'), 'client_only': True, 'flatness': 1.0, 'h_align': 'center' @@ -821,3 +810,16 @@ class App: else: _ba.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0)) _ba.playsound(_ba.getsound('error')) + + def _test_https(self) -> None: + """Testing https support. + + (would be nice to get this working on our custom python builds; need + to wrangle certificates somehow). + """ + import urllib.request + try: + val = urllib.request.urlopen('https://example.com').read() + print("HTTPS TEST SUCCESS", len(val)) + except Exception as exc: + print("HTTPS TEST FAIL:", exc) diff --git a/assets/src/ba_data/python/ba/_meta.py b/assets/src/ba_data/python/ba/_meta.py index 8ba8088e..c09177ee 100644 --- a/assets/src/ba_data/python/ba/_meta.py +++ b/assets/src/ba_data/python/ba/_meta.py @@ -26,11 +26,12 @@ import os import pathlib import threading from typing import TYPE_CHECKING +from dataclasses import dataclass, field import _ba if TYPE_CHECKING: - from typing import (Any, Dict, List, Tuple, Union, Optional, Type, Set) + from typing import Dict, List, Tuple, Union, Optional, Type, Set import ba # The API version of this build of the game. @@ -40,7 +41,7 @@ if TYPE_CHECKING: CURRENT_API_VERSION = 6 -def startscan() -> None: +def start_scan() -> None: """Begin scanning script directories for scripts containing metadata. Should be called only once at launch.""" @@ -52,23 +53,31 @@ def startscan() -> None: thread.start() -def handle_scan_results(results: Dict[str, Any]) -> None: +@dataclass +class ScanResults: + """Final results from a metadata scan.""" + games: List[str] = field(default_factory=list) + errors: str = '' + warnings: str = '' + + +def handle_scan_results(results: ScanResults) -> None: """Called in the game thread with results of a completed scan.""" from ba import _lang # Warnings generally only get printed locally for users' benefit # (things like out-of-date scripts being ignored, etc.) # Errors are more serious and will get included in the regular log - warnings = results.get('warnings', '') - errors = results.get('errors', '') - if warnings != '' or errors != '': + # warnings = results.get('warnings', '') + # errors = results.get('errors', '') + if results.warnings != '' or results.errors != '': _ba.screenmessage(_lang.Lstr(resource='scanScriptsErrorText'), color=(1, 0, 0)) _ba.playsound(_ba.getsound('error')) - if warnings != '': - _ba.log(warnings, to_server=False) - if errors != '': - _ba.log(errors) + if results.warnings != '': + _ba.log(results.warnings, to_server=False) + if results.errors != '': + _ba.log(results.errors) class ScanThread(threading.Thread): @@ -85,7 +94,8 @@ class ScanThread(threading.Thread): scan.scan() results = scan.results except Exception as exc: - results = {'errors': 'Scan exception: ' + str(exc)} + # results = {'errors': 'Scan exception: ' + str(exc)} + results = ScanResults(errors=f'Scan exception: {exc}') # Push a call to the game thread to print warnings/errors # or otherwise deal with scan results. @@ -116,11 +126,7 @@ class DirectoryScan: 'errors': errors encountered during scan; should be fully logged """ self.paths = [pathlib.Path(p) for p in paths] - self.results: Dict[str, Any] = { - 'errors': '', - 'warnings': '', - 'games': [] - } + self.results = ScanResults() def _get_path_module_entries( self, path: pathlib.Path, subpath: Union[str, pathlib.Path], @@ -137,7 +143,7 @@ class DirectoryScan: entries = [] except Exception as exc: # Unexpected; report this. - self.results['errors'] += str(exc) + '\n' + self.results.errors += f'{exc}\n' entries = [] # Now identify python packages/modules out of what we found. @@ -158,9 +164,8 @@ class DirectoryScan: self.scan_module(moduledir, subpath) except Exception: from ba import _error - self.results['warnings'] += ("Error scanning '" + - str(subpath) + "': " + - _error.exc_str() + '\n') + self.results.warnings += ("Error scanning '" + str(subpath) + + "': " + _error.exc_str() + '\n') def scan_module(self, moduledir: pathlib.Path, subpath: pathlib.Path) -> None: @@ -187,11 +192,11 @@ class DirectoryScan: # If we find a module requiring a different api version, warn # and ignore. if required_api is not None and required_api != CURRENT_API_VERSION: - self.results['warnings'] += ('Warning: ' + str(subpath) + - ' requires api ' + str(required_api) + - ' but we are running ' + - str(CURRENT_API_VERSION) + - '; ignoring module.\n') + self.results.warnings += ('Warning: ' + str(subpath) + + ' requires api ' + str(required_api) + + ' but we are running ' + + str(CURRENT_API_VERSION) + + '; ignoring module.\n') return # Ok; can proceed with a full scan of this module. @@ -206,9 +211,8 @@ class DirectoryScan: self.scan_module(submodule[0], submodule[1]) except Exception: from ba import _error - self.results['warnings'] += ("Error scanning '" + - str(subpath) + "': " + - _error.exc_str() + '\n') + self.results.warnings += ("Error scanning '" + str(subpath) + + "': " + _error.exc_str() + '\n') def _process_module_meta_tags(self, subpath: pathlib.Path, flines: List[str], @@ -219,7 +223,7 @@ class DirectoryScan: # the ba_meta is in the right place. if mline[0] != 'ba_meta': print(f'GOT "{mline[0]}"') - self.results['warnings'] += ( + self.results.warnings += ( 'Warning: ' + str(subpath) + ': malformed ba_meta statement on line ' + str(lindex + 1) + '.\n') @@ -230,7 +234,7 @@ class DirectoryScan: elif len(mline) != 3 or mline[1] != 'export': # Currently we only support 'ba_meta export FOO'; # complain for anything else we see. - self.results['warnings'] += ( + self.results.warnings += ( 'Warning: ' + str(subpath) + ': unrecognized ba_meta statement on line ' + str(lindex + 1) + '.\n') @@ -245,9 +249,9 @@ class DirectoryScan: if export_class_name is not None: classname = modulename + '.' + export_class_name if exporttype == 'game': - self.results['games'].append(classname) + self.results.games.append(classname) else: - self.results['warnings'] += ( + self.results.warnings += ( 'Warning: ' + str(subpath) + ': unrecognized export type "' + exporttype + '" on line ' + str(lindex + 1) + '.\n') @@ -272,7 +276,7 @@ class DirectoryScan: classname = cbits[0] break # success! if classname is None: - self.results['warnings'] += ( + self.results.warnings += ( 'Warning: ' + str(subpath) + ': class definition not found' ' below "ba_meta export" statement on line ' + str(lindexorig + 1) + '.\n') @@ -296,21 +300,21 @@ class DirectoryScan: # Ok; not successful. lets issue warnings for a few error cases. if len(lines) > 1: - self.results['warnings'] += ( + self.results.warnings += ( 'Warning: ' + str(subpath) + ': multiple "# ba_meta api require " lines found;' ' ignoring module.\n') elif not lines and toplevel and meta_lines: # If we're a top-level module containing meta lines but # no valid api require, complain. - self.results['warnings'] += ( + self.results.warnings += ( 'Warning: ' + str(subpath) + ': no valid "# ba_meta api require " line found;' ' ignoring module.\n') return None -def get_scan_results() -> Dict[str, Any]: +def get_scan_results() -> ScanResults: """Return meta scan results; blocking if the scan is not yet complete.""" import time app = _ba.app @@ -326,7 +330,6 @@ def get_scan_results() -> Dict[str, Any]: time.sleep(0.05) if time.time() - starttime > 10.0: raise Exception('timeout waiting for meta scan to complete.') - print('RETURNING SCAN RESULTS') return app.metascan @@ -334,7 +337,7 @@ def get_game_types() -> List[Type[ba.GameActivity]]: """Return available game types.""" from ba import _general from ba import _gameactivity - gameclassnames = get_scan_results().get('games', []) + gameclassnames = get_scan_results().games gameclasses = [] for gameclassname in gameclassnames: try: