mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Document Lua plugin APIs.
Trying to fix the singleton-ness in the plugin manager because it prevents injecting settings until the game is fully committed (new game wizard completed). Added the docs describing what I think I've been able to discover.
This commit is contained in:
parent
b6059f692e
commit
664efa3ace
@ -4,7 +4,7 @@ import logging
|
||||
import os
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
from dcs import Mission
|
||||
from dcs.action import DoScript, DoScriptFile
|
||||
@ -17,7 +17,6 @@ from game.plugins import LuaPluginManager
|
||||
from game.theater import TheaterGroundObject
|
||||
from game.theater.iadsnetwork.iadsrole import IadsRole
|
||||
from game.utils import escape_string_for_lua
|
||||
|
||||
from .missiondata import MissionData
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -207,16 +206,26 @@ class LuaGenerator:
|
||||
self.mission.triggerrules.triggers.append(trigger)
|
||||
|
||||
def inject_lua_trigger(self, contents: str, comment: str) -> None:
|
||||
"""Creates the trigger for running the text script at mission start."""
|
||||
trigger = TriggerStart(comment=comment)
|
||||
trigger.add_action(DoScript(String(contents)))
|
||||
self.mission.triggerrules.triggers.append(trigger)
|
||||
|
||||
def bypass_plugin_script(self, mnemonic: str) -> None:
|
||||
"""Records a script has having been intentionally ignored.
|
||||
|
||||
It's not clear why this is needed. It looks like this might be a holdover from
|
||||
when mission generation was driven by a singleton and we needed to avoid
|
||||
double-loading plugins if the generator ran twice (take off, cancel, take off)?
|
||||
|
||||
For now, this prevents duplicates from being handled twice.
|
||||
"""
|
||||
self.plugin_scripts.append(mnemonic)
|
||||
|
||||
def inject_plugin_script(
|
||||
self, plugin_mnemonic: str, script: str, script_mnemonic: str
|
||||
) -> None:
|
||||
""" "Creates a trigger for running the script file at mission start."""
|
||||
if script_mnemonic in self.plugin_scripts:
|
||||
logging.debug(f"Skipping already loaded {script} for {plugin_mnemonic}")
|
||||
return
|
||||
|
||||
@ -14,6 +14,19 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class LuaPluginWorkOrder:
|
||||
"""A script to be loaded at mision start.
|
||||
|
||||
Typically, a work order is used for the main plugin script, and another for
|
||||
configuration. The main script is added to scriptsWorkOrders and the configuration
|
||||
to configurationWorkOrders. As far as I can tell, there's absolutely no difference
|
||||
between those two lists and that could be merged.
|
||||
|
||||
Other scripts can also be run by being added to either of these lists.
|
||||
|
||||
A better name for this is probably just "LuaPluginScript", since that appears to be
|
||||
all they are.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, parent_mnemonic: str, filename: str, mnemonic: str, disable: bool
|
||||
) -> None:
|
||||
@ -23,6 +36,7 @@ class LuaPluginWorkOrder:
|
||||
self.disable = disable
|
||||
|
||||
def work(self, lua_generator: LuaGenerator) -> None:
|
||||
"""Inject the script for this work order into the mission, or ignores it."""
|
||||
if self.disable:
|
||||
lua_generator.bypass_plugin_script(self.mnemonic)
|
||||
else:
|
||||
@ -32,6 +46,8 @@ class LuaPluginWorkOrder:
|
||||
|
||||
|
||||
class PluginSettings:
|
||||
"""A common base for plugin configuration and per-plugin option configuration."""
|
||||
|
||||
def __init__(self, identifier: str, enabled_by_default: bool) -> None:
|
||||
self.identifier = identifier
|
||||
self.enabled_by_default = enabled_by_default
|
||||
@ -57,6 +73,8 @@ class PluginSettings:
|
||||
|
||||
|
||||
class LuaPluginOption(PluginSettings):
|
||||
"""A boolean option for the plugin."""
|
||||
|
||||
def __init__(self, identifier: str, name: str, enabled_by_default: bool) -> None:
|
||||
super().__init__(identifier, enabled_by_default)
|
||||
self.name = name
|
||||
@ -64,6 +82,8 @@ class LuaPluginOption(PluginSettings):
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LuaPluginDefinition:
|
||||
"""Object mapping for plugin.json."""
|
||||
|
||||
identifier: str
|
||||
name: str
|
||||
present_in_ui: bool
|
||||
@ -74,6 +94,7 @@ class LuaPluginDefinition:
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, name: str, path: Path) -> LuaPluginDefinition:
|
||||
"""Loads teh plugin definitions from the given plugin.json path."""
|
||||
data = json.loads(path.read_text())
|
||||
|
||||
options = []
|
||||
@ -120,6 +141,22 @@ class LuaPluginDefinition:
|
||||
|
||||
|
||||
class LuaPlugin(PluginSettings):
|
||||
"""A Liberation lua plugin.
|
||||
|
||||
A plugin is a mod that is able to inject Lua code into the Liberation mission start
|
||||
up. Some of these are bundled (Skynet, mist, EWRS, etc), but users can add their own
|
||||
as well.
|
||||
|
||||
A plugin is defined by a plugin.json file in resources/plugins/<name>/plugin.json.
|
||||
That file defines the name to be shown in the settings UI, whether it should be
|
||||
enabled by default, the scripts to run, and (optionally) boolean options for
|
||||
controlling plugin behavior.
|
||||
|
||||
The plugin identifier is defined by the name of the directory containing it.
|
||||
|
||||
Plugin options have their own set of default settings, UI names, and IDs.
|
||||
"""
|
||||
|
||||
def __init__(self, definition: LuaPluginDefinition) -> None:
|
||||
self.definition = definition
|
||||
super().__init__(self.definition.identifier, self.definition.enabled_by_default)
|
||||
@ -147,15 +184,22 @@ class LuaPlugin(PluginSettings):
|
||||
return cls(definition)
|
||||
|
||||
def set_settings(self, settings: Settings) -> None:
|
||||
"""Attaches the plugin to a settings object."""
|
||||
super().set_settings(settings)
|
||||
for option in self.definition.options:
|
||||
option.set_settings(self.settings)
|
||||
|
||||
def inject_scripts(self, lua_generator: LuaGenerator) -> None:
|
||||
"""Injects the plugin's scripts into the mission."""
|
||||
for work_order in self.definition.work_orders:
|
||||
work_order.work(lua_generator)
|
||||
|
||||
def inject_configuration(self, lua_generator: LuaGenerator) -> None:
|
||||
"""Injects the plugin's options and configuration scripts into the mission.
|
||||
|
||||
It's not clear why the script portion of this needs to exist, and could probably
|
||||
instead be the same as inject_scripts.
|
||||
"""
|
||||
# inject the plugin options
|
||||
if self.options:
|
||||
option_decls = []
|
||||
|
||||
@ -4,11 +4,12 @@ from pathlib import Path
|
||||
from typing import Dict, List
|
||||
|
||||
from game.settings import Settings
|
||||
|
||||
from .luaplugin import LuaPlugin
|
||||
|
||||
|
||||
class LuaPluginManager:
|
||||
"""Manages available and loaded lua plugins."""
|
||||
|
||||
_plugins_loaded = False
|
||||
_plugins: Dict[str, LuaPlugin] = {}
|
||||
|
||||
@ -48,5 +49,12 @@ class LuaPluginManager:
|
||||
|
||||
@classmethod
|
||||
def load_settings(cls, settings: Settings) -> None:
|
||||
"""Attaches all loaded plugins to the given settings object.
|
||||
|
||||
The LuaPluginManager singleton can only be attached to a single Settings object
|
||||
at a time, and plugins will update the Settings object directly, so attaching
|
||||
the plugin manager to a detached Settings object (say, during the new game
|
||||
wizard, but then canceling the new game) will break the settings UI.
|
||||
"""
|
||||
for plugin in cls.plugins():
|
||||
plugin.set_settings(settings)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user