From 49e4da8b4fd571c19499a547efdbe1ddc2d07f79 Mon Sep 17 00:00:00 2001 From: Eric Froemling Date: Wed, 22 Jul 2020 02:48:51 -0700 Subject: [PATCH] v1.5.23 --- .efrocachemap | 24 ++++---- .idea/dictionaries/ericf.xml | 4 ++ CHANGELOG.md | 4 +- assets/src/ba_data/python/ba/__init__.py | 2 +- assets/src/ba_data/python/ba/_app.py | 43 +++++++++++++- assets/src/ba_data/python/ba/_meta.py | 35 +++++++++--- assets/src/ba_data/python/ba/_plugin.py | 22 +++++-- assets/src/ba_data/python/bastd/gameutils.py | 10 ---- .../python/bastd/ui/settings/plugins.py | 53 +++++++++-------- docs/ba_module.md | 57 ++++++++++++------- 10 files changed, 171 insertions(+), 83 deletions(-) diff --git a/.efrocachemap b/.efrocachemap index 23462ceb..ae607fc4 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4135,16 +4135,16 @@ "assets/build/windows/x64/vc_redist.x64.exe": "https://files.ballistica.net/cache/ba1/ea/19/8b8787d81abcdce158ba608cd24f", "assets/build/windows/x64/vcruntime140_1d.dll": "https://files.ballistica.net/cache/ba1/11/d8/ff6344b429b00c24d9a1930d4338", "assets/build/windows/x64/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/20/33/0825e11e6518f87ece3009309933", - "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/26/e9/3cc27e1957894e2926093efaf419", - "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/cc/6d/523557326436bb1f2c79b40db30f", - "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/59/b5/cd299564c2795a47f19253121289", - "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/90/15/400a238c241f4baaf593338c2f0e", - "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4a/17/9c657df88fcf1aa529a6d57c1c63", - "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/2c/14/4af6ef9f9452c10d3dbc7b864045", - "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/11/05/2d5de66e04958be2440d10c96694", - "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/89/c8/2e9218badd4259e480c3b3315bf6", - "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/7e/7b/173c424cd6548670163304241161", - "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/bc/d9/52268ba89bf9e6821fd95d4b6852", - "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/e3/49/f6b920cf41705a40f7c00ad2cd26", - "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/d4/9f/365187c2ebdeffdfba74a35cf4f7" + "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b2/16/0414b009ce117f73bb2bc8991d3b", + "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/66/07/d82a3ec80ff3243321e373439210", + "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/12/5f/f87158c3281a2616897fdd2165cb", + "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/69/23/a2d029f81db60cf89ad5f6510ddb", + "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c4/e3/5aba369252ed1977be8950e5a1ce", + "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f7/ef/c28554f863485e9cd54f0d8d4e6f", + "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/68/af/f060b3463bc31c202a830fb0b20a", + "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/96/46/a239821d00e782303077192817c5", + "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/dc/15/fdd9195afe2bf299405ec49ed238", + "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/68/f9/c42b1f9b5af34cb371f80250e16b", + "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/5d/9b/213c602a9aa9cbc28872d2753f51", + "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/2f/a4/e6a6a6e0e2b64c5232cccddffdd3" } \ No newline at end of file diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index c1bf311e..9bcbca70 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -570,6 +570,7 @@ elim emitfx emoji + emojis enablexinput ename encerr @@ -1453,7 +1454,10 @@ plistname plpt plst + plugkey + plugkeys pluglist + plugstate plugstates plusbutton plvel diff --git a/CHANGELOG.md b/CHANGELOG.md index 63563696..e7a1696f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ - Fixed the shebang line in `bombsquad_server` file by using `-S` flag for `/usr/bin/env`. - Fixed a bug with hardware keyboards emitting extra characters in the in-game console (~ or F2) - Added support for 'plugin' mods and user controls to configure them in settings->advanced->plugins. -- renamed selection_loop_to_parent to selection_loops_to_parent in widget calls. +- Renamed selection_loop_to_parent to selection_loops_to_parent in widget calls. - Added 'selection_loops_to_parent', 'border', 'margin', 'claims_left_right', and 'claims_tab' args to ba.columnwidget(). - Column-widget now has a default 'border' of 0 (explicitly pass 2 to get the old look). - Column-widget now has a default 'margin' of 10 (explicitly pass 0 to get the old look). @@ -10,7 +10,7 @@ - Added 'selection_loops_to_parent', 'claims_left_right', and 'claims_tab' args to ba.rowwidget. - Added 'claims_left_right' and 'claims_tab' to ba.hscrollwidget(). - Default widget 'show_buffer' is now 20 instead of 0 (causes scrolling to stay slightly ahead of widget selection). This can be overridden with the ba.widget() call if anything breaks. -- Relocated ba.app.uiscale to ba.app.ui.uiscale +- Relocated ba.app.uiscale to ba.app.ui.uiscale. - Top level settings window now properly saves/restores its state again. - Added Emojis to the Internal Game Keyboard. - Added continuous CAPITAL letters typing feature in the Internal Game Keyboard. diff --git a/assets/src/ba_data/python/ba/__init__.py b/assets/src/ba_data/python/ba/__init__.py index 88e53a3e..6fa61a4c 100644 --- a/assets/src/ba_data/python/ba/__init__.py +++ b/assets/src/ba_data/python/ba/__init__.py @@ -38,7 +38,7 @@ from _ba import (CollideModel, Context, ContextCall, Data, InputDevice, set_analytics_screen, charstr, textwidget, time, timer, open_url, widget) from ba._activity import Activity -from ba._plugin import AvailablePlugin, Plugin +from ba._plugin import PotentialPlugin, Plugin from ba._actor import Actor from ba._player import PlayerInfo, Player, EmptyPlayer, StandLocation from ba._nodeactor import NodeActor diff --git a/assets/src/ba_data/python/ba/_app.py b/assets/src/ba_data/python/ba/_app.py index e06e3903..d3265d40 100644 --- a/assets/src/ba_data/python/ba/_app.py +++ b/assets/src/ba_data/python/ba/_app.py @@ -306,8 +306,8 @@ class App: assert isinstance(self.headless_build, bool) # Plugins. - self.loaded_plugins: List[ba.Plugin] = [] - self.available_plugins: List[ba.AvailablePlugin] = [] + self.potential_plugins: List[ba.PotentialPlugin] = [] + self.active_plugins: Dict[str, ba.Plugin] = {} # Misc. self.default_language = self._get_default_language() @@ -411,6 +411,7 @@ class App: (internal)""" # pylint: disable=too-many-locals # pylint: disable=cyclic-import + # pylint: disable=too-many-statements from ba import _apputils from ba import _appconfig from ba import _achievement @@ -520,11 +521,49 @@ class App: _ba.pushcall(do_auto_sign_in) + # Load up our plugins and go ahead and call their on_app_launch calls. + self.load_plugins() + for plugin in self.active_plugins.values(): + try: + plugin.on_app_launch() + except Exception: + from ba import _error + _error.print_exception('Error in plugin on_app_launch()') + self.ran_on_app_launch = True # from ba._dependency import test_depset # test_depset() + def load_plugins(self) -> None: + """(internal)""" + from ba._general import getclass + from ba._plugin import Plugin + + # Note: the plugins we load is purely based on what's enabled + # in the app config. Our meta-scan gives us a list of available + # plugins, but that is only used to give the user a list of plugins + # that they can enable. (we wouldn't want to look at meta-scan here + # anyway because it may not be done yet at this point in the launch) + plugstates: Dict[str, Dict] = self.config.get('Plugins', {}) + assert isinstance(plugstates, dict) + plugkeys: List[str] = sorted(key for key, val in plugstates.items() + if val.get('enabled', False)) + for plugkey in plugkeys: + try: + cls = getclass(plugkey, Plugin) + except Exception as exc: + _ba.log(f"Error loading plugin class '{plugkey}': {exc}", + to_server=False) + continue + try: + plugin = cls() + assert plugkey not in self.active_plugins + self.active_plugins[plugkey] = plugin + except Exception: + from ba import _error + _error.print_exception(f'Error loading plugin: {plugkey}') + def read_config(self) -> None: """(internal)""" from ba import _appconfig diff --git a/assets/src/ba_data/python/ba/_meta.py b/assets/src/ba_data/python/ba/_meta.py index c67bcca9..ecc5d727 100644 --- a/assets/src/ba_data/python/ba/_meta.py +++ b/assets/src/ba_data/python/ba/_meta.py @@ -66,6 +66,7 @@ def handle_scan_results(results: ScanResults) -> None: """Called in the game thread with results of a completed scan.""" from ba._lang import Lstr + from ba._plugin import PotentialPlugin # Warnings generally only get printed locally for users' benefit # (things like out-of-date scripts being ignored, etc.) @@ -87,18 +88,32 @@ def handle_scan_results(results: ScanResults) -> None: config_changed = False found_new = False plugstates: Dict[str, Dict] = _ba.app.config.setdefault('Plugins', {}) - if not isinstance(plugstates, dict): - print('Warning; found non-dict for "Plugins" in config.') - plugstates = {} - config_changed = True + assert isinstance(plugstates, dict) - for plug in results.plugins: - if plug not in plugstates: - print('found new plugin:', plug) - plugstates[plug] = {'enabled': False} + # Create a potential-plugin for each class we found in the scan. + for class_path in results.plugins: + _ba.app.potential_plugins.append( + PotentialPlugin(display_name=Lstr(value=class_path), + class_path=class_path, + available=True)) + if class_path not in plugstates: + plugstates[class_path] = {'enabled': False} config_changed = True found_new = True + # Also add a special one for any plugins set to load but *not* found + # in the scan (this way they will show up in the UI so we can disable them) + for class_path, plugstate in plugstates.items(): + enabled = plugstate.get('enabled', False) + assert isinstance(enabled, bool) + if enabled and class_path not in results.plugins: + _ba.app.potential_plugins.append( + PotentialPlugin(display_name=Lstr(value=class_path), + class_path=class_path, + available=False)) + + _ba.app.potential_plugins.sort(key=lambda p: p.class_path) + if found_new: _ba.screenmessage(Lstr(resource='pluginsDetectedText'), color=(0, 1, 0)) @@ -106,7 +121,6 @@ def handle_scan_results(results: ScanResults) -> None: if config_changed: _ba.app.config.commit() - # print(f'would check {len(results.plugins)} plugs') class ScanThread(threading.Thread): @@ -189,6 +203,9 @@ class DirectoryScan: self.results.warnings += ("Error scanning '" + str(subpath) + "': " + traceback.format_exc() + '\n') + # Sort our results + self.results.games.sort() + self.results.plugins.sort() def scan_module(self, moduledir: pathlib.Path, subpath: pathlib.Path) -> None: diff --git a/assets/src/ba_data/python/ba/_plugin.py b/assets/src/ba_data/python/ba/_plugin.py index 146d7a7f..51b9b077 100644 --- a/assets/src/ba_data/python/ba/_plugin.py +++ b/assets/src/ba_data/python/ba/_plugin.py @@ -26,23 +26,35 @@ from typing import TYPE_CHECKING from dataclasses import dataclass if TYPE_CHECKING: - pass + import ba @dataclass -class AvailablePlugin: - """Defines a plugin which can potentially be loaded. +class PotentialPlugin: + """Represents a ba.Plugin which can potentially be loaded. Category: App Classes + + These generally represent plugins which were detected by the + meta-tag scan. However they may also represent plugins which + were previously set to be loaded but which were unable to be + for some reason. In that case, 'available' will be set to False. """ - display_name: str + display_name: ba.Lstr class_path: str + available: bool class Plugin: """A plugin to alter app behavior in some way. Category: App Classes + + Plugins are discoverable by the meta-tag system + and the user can select which ones they want to activate. + Active plugins are then called at specific times as the + app is running in order to modify its behavior in some way. """ - name: str + def on_app_launch(self) -> None: + """Called when the app is being launched.""" diff --git a/assets/src/ba_data/python/bastd/gameutils.py b/assets/src/ba_data/python/bastd/gameutils.py index d756995a..de34b2bd 100644 --- a/assets/src/ba_data/python/bastd/gameutils.py +++ b/assets/src/ba_data/python/bastd/gameutils.py @@ -161,13 +161,3 @@ class SharedObjects: ), ) return self._railing_material - - -# ba _meta export plugin -class TestPlug1(ba.Plugin): - """Just Testing.""" - - -# ba _meta export plugin -class TestPlug2(ba.Plugin): - """Just Testing 2.""" diff --git a/assets/src/ba_data/python/bastd/ui/settings/plugins.py b/assets/src/ba_data/python/bastd/ui/settings/plugins.py index 6e73646a..7b1993b1 100644 --- a/assets/src/ba_data/python/bastd/ui/settings/plugins.py +++ b/assets/src/ba_data/python/bastd/ui/settings/plugins.py @@ -27,20 +27,16 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Tuple, Optional + from typing import Tuple, Optional, Dict class PluginSettingsWindow(ba.Window): """Window for configuring plugins.""" - def __del__(self) -> None: - print('~PluginSettingsWindow()') - def __init__(self, transition: str = 'in_right', origin_widget: ba.Widget = None): - print('PluginSettingsWindow()') - + # pylint: disable=too-many-locals app = ba.app # If they provided an origin-widget, scale up from that. @@ -115,22 +111,29 @@ class PluginSettingsWindow(ba.Window): self._subcontainer = ba.columnwidget(parent=self._scrollwidget, selection_loops_to_parent=True) - pluglist = [ - ba.AvailablePlugin(display_name=f'Test {i}', - class_path='fakemodule') for i in range(10) - ] + if ba.app.metascan is None: + ba.screenmessage('Still scanning plugins; please try again.', + color=(1, 0, 0)) + ba.playsound(ba.getsound('error')) + pluglist = ba.app.potential_plugins + plugstates: Dict[str, Dict] = ba.app.config.setdefault('Plugins', {}) + assert isinstance(plugstates, dict) for i, availplug in enumerate(pluglist): - active = i % 3 < 2 - check = ba.checkboxwidget(parent=self._subcontainer, - text=availplug.display_name, - value=active, - maxwidth=self._scroll_width - 100, - size=(self._scroll_width - 40, 50), - on_value_change_call=ba.Call( - self._check_value_changed, - availplug), - textcolor=((0, 1, 0) if active else - (0.6, 0.6, 0.6))) + active = availplug.class_path in ba.app.active_plugins + + plugstate = plugstates.setdefault(availplug.class_path, {}) + checked = plugstate.get('enabled', False) + assert isinstance(checked, bool) + check = ba.checkboxwidget( + parent=self._subcontainer, + text=availplug.display_name, + value=checked, + maxwidth=self._scroll_width - 100, + size=(self._scroll_width - 40, 50), + on_value_change_call=ba.Call(self._check_value_changed, + availplug), + textcolor=((0.8, 0.3, 0.3) if not availplug.available else + (0, 1, 0) if active else (0.6, 0.6, 0.6))) # Make sure we scroll all the way to the end when using # keyboard/button nav. @@ -144,12 +147,16 @@ class PluginSettingsWindow(ba.Window): self._restore_state() - def _check_value_changed(self, plug: ba.AvailablePlugin, + def _check_value_changed(self, plug: ba.PotentialPlugin, value: bool) -> None: ba.screenmessage( ba.Lstr(resource='settingsWindowAdvanced.mustRestartText'), color=(1.0, 0.5, 0.0)) - print(f'check value changed for {plug} to {value}') + plugstates: Dict[str, Dict] = ba.app.config.setdefault('Plugins', {}) + assert isinstance(plugstates, dict) + plugstate = plugstates.setdefault(plug.class_path, {}) + plugstate['enabled'] = value + ba.app.config.commit() def _save_state(self) -> None: pass diff --git a/docs/ba_module.md b/docs/ba_module.md index 6ff42acd..2c401f48 100644 --- a/docs/ba_module.md +++ b/docs/ba_module.md @@ -1,5 +1,5 @@ -

last updated on 2020-07-21 for Ballistica version 1.5.23 build 20151

+

last updated on 2020-07-22 for Ballistica version 1.5.23 build 20152

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 let me know. Happy modding!


@@ -150,10 +150,10 @@
  • ba.App
  • ba.AppConfig
  • ba.AppDelegate
  • -
  • ba.AvailablePlugin
  • ba.Campaign
  • ba.MusicPlayer
  • ba.Plugin
  • +
  • ba.PotentialPlugin
  • ba.ServerController
  • User Interface Classes

    @@ -1166,22 +1166,6 @@ when done.

    Behavior is similar to ba.gettexture()

    - - -
    -

    ba.AvailablePlugin

    -

    <top level class> -

    -

    Defines a plugin which can potentially be loaded.

    - -

    Category: App Classes -

    - -

    Methods:

    -
    -

    <constructor>

    -

    ba.AvailablePlugin(display_name: str, class_path: str)

    -

    @@ -4509,9 +4493,44 @@ the type-checker properly identifies the returned value as one.

    A plugin to alter app behavior in some way.

    -

    Category: App Classes +

    Category: App Classes

    + +

    Plugins are discoverable by the meta-tag system + and the user can select which ones they want to activate. + Active plugins are then called at specific times as the + app is running in order to modify its behavior in some way.

    +

    Methods:

    +
    +

    on_app_launch()

    +

    on_app_launch(self) -> None

    + +

    Called when the app is being launched.

    + +
    +
    +
    +

    ba.PotentialPlugin

    +

    <top level class> +

    +

    Represents a ba.Plugin which can potentially be loaded.

    + +

    Category: App Classes

    + +

    These generally represent plugins which were detected by the + meta-tag scan. However they may also represent plugins which + were previously set to be loaded but which were unable to be + for some reason. In that case, 'available' will be set to False. +

    + +

    Methods:

    +
    +

    <constructor>

    +

    ba.PotentialPlugin(display_name: ba.Lstr, class_path: str, available: bool)

    + +
    +

    ba.PowerupAcceptMessage

    <top level class>