This commit is contained in:
Eric Froemling 2020-07-22 02:48:51 -07:00
parent 400488cc66
commit 49e4da8b4f
10 changed files with 171 additions and 83 deletions

View File

@ -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"
}

View File

@ -570,6 +570,7 @@
<w>elim</w>
<w>emitfx</w>
<w>emoji</w>
<w>emojis</w>
<w>enablexinput</w>
<w>ename</w>
<w>encerr</w>
@ -1453,7 +1454,10 @@
<w>plistname</w>
<w>plpt</w>
<w>plst</w>
<w>plugkey</w>
<w>plugkeys</w>
<w>pluglist</w>
<w>plugstate</w>
<w>plugstates</w>
<w>plusbutton</w>
<w>plvel</w>

View File

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

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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."""

View File

@ -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."""

View File

@ -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

View File

@ -1,5 +1,5 @@
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
<h4><em>last updated on 2020-07-21 for Ballistica version 1.5.23 build 20151</em></h4>
<h4><em>last updated on 2020-07-22 for Ballistica version 1.5.23 build 20152</em></h4>
<p>This page documents the Python classes and functions in the 'ba' module,
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p>
<hr>
@ -150,10 +150,10 @@
<li><a href="#class_ba_App">ba.App</a></li>
<li><a href="#class_ba_AppConfig">ba.AppConfig</a></li>
<li><a href="#class_ba_AppDelegate">ba.AppDelegate</a></li>
<li><a href="#class_ba_AvailablePlugin">ba.AvailablePlugin</a></li>
<li><a href="#class_ba_Campaign">ba.Campaign</a></li>
<li><a href="#class_ba_MusicPlayer">ba.MusicPlayer</a></li>
<li><a href="#class_ba_Plugin">ba.Plugin</a></li>
<li><a href="#class_ba_PotentialPlugin">ba.PotentialPlugin</a></li>
<li><a href="#class_ba_ServerController">ba.ServerController</a></li>
</ul>
<h4><a name="class_category_User_Interface_Classes">User Interface Classes</a></h4>
@ -1166,22 +1166,6 @@ when done.</p>
<p>Behavior is similar to <a href="#function_ba_gettexture">ba.gettexture</a>()</p>
</dd>
</dl>
<hr>
<h2><strong><a name="class_ba_AvailablePlugin">ba.AvailablePlugin</a></strong></h3>
<p><em>&lt;top level class&gt;</em>
</p>
<p>Defines a plugin which can potentially be loaded.</p>
<p>Category: <a href="#class_category_App_Classes">App Classes</a>
</p>
<h3>Methods:</h3>
<dl>
<dt><h4><a name="method_ba_AvailablePlugin____init__">&lt;constructor&gt;</a></dt></h4><dd>
<p><span>ba.AvailablePlugin(display_name: str, class_path: str)</span></p>
</dd>
</dl>
<hr>
@ -4509,9 +4493,44 @@ the type-checker properly identifies the returned value as one.</p>
</p>
<p>A plugin to alter app behavior in some way.</p>
<p>Category: <a href="#class_category_App_Classes">App Classes</a>
<p>Category: <a href="#class_category_App_Classes">App Classes</a></p>
<p> 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.
</p>
<h3>Methods:</h3>
<dl>
<dt><h4><a name="method_ba_Plugin__on_app_launch">on_app_launch()</a></dt></h4><dd>
<p><span>on_app_launch(self) -&gt; None</span></p>
<p>Called when the app is being launched.</p>
</dd>
</dl>
<hr>
<h2><strong><a name="class_ba_PotentialPlugin">ba.PotentialPlugin</a></strong></h3>
<p><em>&lt;top level class&gt;</em>
</p>
<p>Represents a <a href="#class_ba_Plugin">ba.Plugin</a> which can potentially be loaded.</p>
<p>Category: <a href="#class_category_App_Classes">App Classes</a></p>
<p> 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.
</p>
<h3>Methods:</h3>
<dl>
<dt><h4><a name="method_ba_PotentialPlugin____init__">&lt;constructor&gt;</a></dt></h4><dd>
<p><span>ba.PotentialPlugin(display_name: <a href="#class_ba_Lstr">ba.Lstr</a>, class_path: str, available: bool)</span></p>
</dd>
</dl>
<hr>
<h2><strong><a name="class_ba_PowerupAcceptMessage">ba.PowerupAcceptMessage</a></strong></h3>
<p><em>&lt;top level class&gt;</em>