More work on plugins

This commit is contained in:
Eric Froemling 2020-07-21 23:27:41 -07:00
parent 42c7bb17f5
commit 0356b0b0e0
12 changed files with 170 additions and 30 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/ff/36/ca09cc3913ae40382d73340f62ab",
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ca/30/613d0c5acf97d7eb7770c4138a3b",
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/cb/58/e668468c6ebe7b3b1bacc7079f16",
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/3a/93/215ba8579ffc8bc5fc67aa6d1bfc",
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a1/c5/b0be277a5324eccf97218644f429",
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/7c/f3/f82f3a29652ed279447dc5b1c4a5",
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/21/aa/312efa59677ebd9a97b8f7f48796",
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/7c/de/c926cf39c972f1084762c917bb9c",
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/bd/bb/bfb59d70d101eefd14987007225d",
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/df/d3/3e91ec5117fe359c2e94818b9788",
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/20/c0/cad3a08df13d190bbeff26bb901f",
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/ea/28/efe665eddea710871da829ad2dce"
"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"
}

View File

@ -137,6 +137,7 @@
<w>autoretain</w>
<w>autoselect</w>
<w>autotools</w>
<w>availplug</w>
<w>aval</w>
<w>axismotion</w>
<w>bacfg</w>
@ -628,6 +629,7 @@
<w>extrascale</w>
<w>exts</w>
<w>factoryclass</w>
<w>fakemodule</w>
<w>fallbacks</w>
<w>farthestpt</w>
<w>fback</w>
@ -1452,6 +1454,7 @@
<w>plpt</w>
<w>plst</w>
<w>pluglist</w>
<w>plugstates</w>
<w>plusbutton</w>
<w>plvel</w>
<w>pmats</w>

View File

@ -41,6 +41,7 @@
"ba_data/python/ba/__pycache__/_nodeactor.cpython-37.opt-1.pyc",
"ba_data/python/ba/__pycache__/_player.cpython-37.opt-1.pyc",
"ba_data/python/ba/__pycache__/_playlist.cpython-37.opt-1.pyc",
"ba_data/python/ba/__pycache__/_plugin.cpython-37.opt-1.pyc",
"ba_data/python/ba/__pycache__/_powerup.cpython-37.opt-1.pyc",
"ba_data/python/ba/__pycache__/_profile.cpython-37.opt-1.pyc",
"ba_data/python/ba/__pycache__/_score.cpython-37.opt-1.pyc",
@ -99,6 +100,7 @@
"ba_data/python/ba/_nodeactor.py",
"ba_data/python/ba/_player.py",
"ba_data/python/ba/_playlist.py",
"ba_data/python/ba/_plugin.py",
"ba_data/python/ba/_powerup.py",
"ba_data/python/ba/_profile.py",
"ba_data/python/ba/_score.py",

View File

@ -190,6 +190,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \
build/ba_data/python/ba/_nodeactor.py \
build/ba_data/python/ba/_player.py \
build/ba_data/python/ba/_playlist.py \
build/ba_data/python/ba/_plugin.py \
build/ba_data/python/ba/_powerup.py \
build/ba_data/python/ba/_profile.py \
build/ba_data/python/ba/_score.py \
@ -423,6 +424,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
build/ba_data/python/ba/__pycache__/_nodeactor.cpython-37.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_player.cpython-37.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_playlist.cpython-37.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_plugin.cpython-37.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_powerup.cpython-37.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_profile.cpython-37.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_score.cpython-37.opt-1.pyc \

View File

@ -38,6 +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._actor import Actor
from ba._player import PlayerInfo, Player, EmptyPlayer, StandLocation
from ba._nodeactor import NodeActor

View File

@ -37,7 +37,7 @@ if TYPE_CHECKING:
class App:
"""A class for high level app functionality and state.
category: App Classes
Category: App Classes
Use ba.app to access the single shared instance of this class.
@ -305,6 +305,10 @@ class App:
self.headless_build: bool = env['headless_build']
assert isinstance(self.headless_build, bool)
# Plugins.
self.loaded_plugins: List[ba.Plugin] = []
self.available_plugins: List[ba.AvailablePlugin] = []
# Misc.
self.default_language = self._get_default_language()
self.metascan: Optional[_meta.ScanResults] = None

View File

@ -45,6 +45,7 @@ CURRENT_API_VERSION = 6
class ScanResults:
"""Final results from a metadata scan."""
games: List[str] = field(default_factory=list)
plugins: List[str] = field(default_factory=list)
errors: str = ''
warnings: str = ''
@ -64,6 +65,8 @@ def start_scan() -> None:
def handle_scan_results(results: ScanResults) -> None:
"""Called in the game thread with results of a completed scan."""
from ba._lang import Lstr
# 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
@ -71,7 +74,6 @@ def handle_scan_results(results: ScanResults) -> None:
# errors = results.get('errors', '')
if results.warnings != '' or results.errors != '':
import textwrap
from ba._lang import Lstr
_ba.screenmessage(Lstr(resource='scanScriptsErrorText'),
color=(1, 0, 0))
_ba.playsound(_ba.getsound('error'))
@ -81,6 +83,31 @@ def handle_scan_results(results: ScanResults) -> None:
if results.errors != '':
_ba.log(textwrap.indent(results.errors, 'Error (meta-scan): '))
# Handle plugins.
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
for plug in results.plugins:
if plug not in plugstates:
print('found new plugin:', plug)
plugstates[plug] = {'enabled': False}
config_changed = True
found_new = True
if found_new:
_ba.screenmessage(Lstr(resource='pluginsDetectedText'),
color=(0, 1, 0))
_ba.playsound(_ba.getsound('ding'))
if config_changed:
_ba.app.config.commit()
# print(f'would check {len(results.plugins)} plugs')
class ScanThread(threading.Thread):
"""Thread to scan script dirs for metadata."""
@ -90,7 +117,7 @@ class ScanThread(threading.Thread):
self._dirs = dirs
def run(self) -> None:
from ba import _general
from ba._general import Call
try:
scan = DirectoryScan(self._dirs)
scan.scan()
@ -100,7 +127,7 @@ class ScanThread(threading.Thread):
# Push a call to the game thread to print warnings/errors
# or otherwise deal with scan results.
_ba.pushcall(_general.Call(handle_scan_results, results),
_ba.pushcall(Call(handle_scan_results, results),
from_other_thread=True)
# We also, however, immediately make results available.
@ -117,14 +144,6 @@ class DirectoryScan:
It is assumed that these paths are also in PYTHONPATH.
It is also assumed that any subdirectories are Python packages.
The returned dict contains the following:
'powerups': list of ba.Powerup classes found.
'campaigns': list of ba.Campaign classes found.
'modifiers': list of ba.Modifier classes found.
'maps': list of ba.Map classes found.
'games': list of ba.GameActivity classes found.
'warnings': warnings from scan; should be printed for local feedback
'errors': errors encountered during scan; should be fully logged
"""
# Skip non-existent paths completely.
@ -251,6 +270,8 @@ class DirectoryScan:
classname = modulename + '.' + export_class_name
if exporttype == 'game':
self.results.games.append(classname)
elif exporttype == 'plugin':
self.results.plugins.append(classname)
else:
self.results.warnings += (
'Warning: ' + str(subpath) +

View File

@ -0,0 +1,48 @@
# Copyright (c) 2011-2020 Eric Froemling
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# -----------------------------------------------------------------------------
"""Plugin related functionality."""
from __future__ import annotations
from typing import TYPE_CHECKING
from dataclasses import dataclass
if TYPE_CHECKING:
pass
@dataclass
class AvailablePlugin:
"""Defines a plugin which can potentially be loaded.
Category: App Classes
"""
display_name: str
class_path: str
class Plugin:
"""A plugin to alter app behavior in some way.
Category: App Classes
"""
name: str

View File

@ -161,3 +161,13 @@ 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

@ -164,7 +164,7 @@ class ControlsSettingsWindow(ba.Window):
on_activate_call=self._back)
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
# need these vars to exist even if the buttons don't
# We need these vars to exist even if the buttons don't.
self._gamepads_button: Optional[ba.Widget] = None
self._touch_button: Optional[ba.Widget] = None
self._keyboard_button: Optional[ba.Widget] = None

View File

@ -33,9 +33,13 @@ if TYPE_CHECKING:
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()')
app = ba.app
@ -111,11 +115,22 @@ class PluginSettingsWindow(ba.Window):
self._subcontainer = ba.columnwidget(parent=self._scrollwidget,
selection_loops_to_parent=True)
pluglist = [f'Test {i}' for i in range(10)]
for i, plug in enumerate(pluglist):
pluglist = [
ba.AvailablePlugin(display_name=f'Test {i}',
class_path='fakemodule') for i in range(10)
]
for i, availplug in enumerate(pluglist):
active = i % 3 < 2
check = ba.checkboxwidget(parent=self._subcontainer,
text=plug,
size=(self._scroll_width - 40, 50))
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)))
# Make sure we scroll all the way to the end when using
# keyboard/button nav.
@ -129,6 +144,13 @@ class PluginSettingsWindow(ba.Window):
self._restore_state()
def _check_value_changed(self, plug: ba.AvailablePlugin,
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}')
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 20150</em></h4>
<h4><em>last updated on 2020-07-21 for Ballistica version 1.5.23 build 20151</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,8 +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_ServerController">ba.ServerController</a></li>
</ul>
<h4><a name="class_category_User_Interface_Classes">User Interface Classes</a></h4>
@ -1164,6 +1166,22 @@ 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>
@ -4485,6 +4503,15 @@ the type-checker properly identifies the returned value as one.</p>
</dd>
</dl>
<hr>
<h2><strong><a name="class_ba_Plugin">ba.Plugin</a></strong></h3>
<p><em>&lt;top level class&gt;</em>
</p>
<p>A plugin to alter app behavior in some way.</p>
<p>Category: <a href="#class_category_App_Classes">App Classes</a>
</p>
<hr>
<h2><strong><a name="class_ba_PowerupAcceptMessage">ba.PowerupAcceptMessage</a></strong></h3>
<p><em>&lt;top level class&gt;</em>