Update documentation.

This commit is contained in:
Mikaël Capelle
2020-07-29 20:49:23 +02:00
parent 62a4c61a18
commit ab562cc9db
3 changed files with 225 additions and 20 deletions
+1
View File
@@ -16,6 +16,7 @@ This documentation is dedicated to writting MO2 **Python** plugins.
setup-tools
plugin-types
writing-plugins
faq
mobase
+23 -20
View File
@@ -1,3 +1,5 @@
.. _type-of-plugins:
Type of Plugins
===============
@@ -16,8 +18,9 @@ to MO.
Installers
----------
**Interface:** :class:`IPluginInstaller`, :class:`IPluginInstallerSimple`, `IPluginInstallerCustom`
**Examples:** ``installer_bain``, ``installer_bundle``, ``installer_fomod``, ``installer_ncc``, ``installer_quick``, ``installer_manual``.
| **Interface:** :class:`IPluginInstaller<mobase.IPluginInstaller>`, :class:`IPluginInstallerSimple<mobase.IPluginInstallerSimple>`,
:class:`IPluginInstallerCustom<mobase.IPluginInstallerCustom>`
| **Examples:** ``installer_bain``, ``installer_bundle``, ``installer_fomod``, ``installer_ncc``, ``installer_quick``, ``installer_manual``.
An installer is invoked when the user tries to install a mod, either by double clicking in the download
view or through the "Install Mod.." button or "Reinstall mod" item from the mod lists context menu.
@@ -30,8 +33,8 @@ There are actually two ways to write an installer, *simple* or *complex*:
Previewers
----------
**Interface:** :class:`IPluginPreview`
**Examples:** ``preview_base``
| **Interfaces:** :class:`IPluginPreview<mobase.IPluginPreview>`
| **Examples:** ``preview_base``
These plugins add support for previewing files in the data pane.
Right now all image formats supported by Qt are implemented (including `.dds`) but no audio files and
@@ -42,8 +45,8 @@ Mod Page
*WIP*
**Interface:** :class:`IPluginModPage`
**Examples:** ``page_tesalliance``
| **Interfaces:** :class:`IPluginModPage<mobase.IPluginModPage>`
| **Examples:** ``page_tesalliance``
Mod Page plugins implement interfaces to modding communities where mods can be downloaded, checked
for updates and so on.
@@ -54,8 +57,8 @@ from the core application. This is a task for the distant future, unless someone
Game
----
**Interface:** :class:`IPluginGame`
**Examples:** ``game_oblivion``, ``game_fallout3``, ``game_falloutnv``, ``game_skyrim``, ...
| **Interfaces:** :class:`IPluginGame<mobase.IPluginGame>`
| **Examples:** ``game_oblivion``, ``game_fallout3``, ``game_falloutnv``, ``game_skyrim``, ...
These plugins (shall eventually) implement all the game specific features and further game plugins are
able to add support for further games.
@@ -72,16 +75,16 @@ Wherever the core can support BSA invalidation it will query whether the current
query the implementation on specifics (like "How should the invalidation BSA be called" and "what's the right bsa version").
Of course, the goal is for feature interfaces to be as generic as possible without limiting usefulness.
**Note:** The :class:`IPluginGame` interface is complex, and mostly designed for Gamebryo games. If you plan
**Note:** The :class:`IPluginGame<mobase.IPluginGame>` interface is complex, and mostly designed for Gamebryo games. If you plan
on writting a game plugin for another type of games, you might be interested by a simpler interface and might
want to check out the `Basic Games <https://github.com/Holt59/modorganizer-basic_games>`_ meta-plugin.
want to check out the `Basic Games <https://github.com/ModOrganizer2/modorganizer-basic_games>`_ meta-plugin.
Tool
----
**Interface:** :class:`IPluginTool`
**Examples:** ``tool_configurator``, ``tool_inieditor``, ``fnistool``
| **Interfaces:** :class:`IPluginTool<mobase.IPluginTool>`
| **Examples:** ``tool_configurator``, ``tool_inieditor``, ``fnistool``
This is the simplest of plugin interfaces. Such plugins simply place an icon inside the tools submenu and get
invoked when the user clicks it. They are expected to have a user interface of some sort.
@@ -93,8 +96,8 @@ windows application itself.
Proxies
-------
**Interface:** :class:`IPluginProxy`
**Examples:** ``plugin_python``
| **Interfaces:** :class:`IPluginProxy<mobase.IPluginProxy>`
| **Examples:** ``plugin_python``
Proxy Plugins expose the plugin api to foreign languages. This is what allows you to write plugins using python
in the first place.
@@ -106,8 +109,8 @@ And no, you can not write a proxy for a third language in Python, do not be sill
*Free Plugins*
--------------
**Interface:** :class:`IPlugin`
**Examples:** ``check_fnis``, ``bsa_extractor``, ``diagnose_basic``, ``tool_inibakery``
| **Interfaces:** :class:`IPlugin<mobase.IPlugin>`
| **Examples:** ``check_fnis``, ``bsa_extractor``, ``diagnose_basic``, ``tool_inibakery``
"Free" plugins implement none of the interfaces and thus initially do not integrate with MO at all.
They are initialized by MO and get access to the MO interface.
@@ -123,8 +126,8 @@ that plugins can implement one or more of these interfaces, in addition to a nor
Diagnose
........
**Interface:** :class:`IPluginDiagnose`
**Examples:** ``diagnose_basic``, ``installer_ncc``, ``plugin_python``, ``script_extender_plugin_checker``
| **Interfaces:** :class:`IPluginDiagnose<mobase.IPluginDiagnose>`
| **Examples:** ``diagnose_basic``, ``installer_ncc``, ``plugin_python``, ``script_extender_plugin_checker``
This interface lets the plugin report issues that are then listed in the "Problems" icon in the main window.
If possible the plugin can also provide an automatic or guided fix to the problem.
@@ -139,8 +142,8 @@ An empty problem list should always be achievable.
File Mappings
.............
**Interface:** :class:`IPluginFileMapper`
**Examples:** ``tool_inibakery``, ``game_gamebryo``
| **Interfaces:** :class:`IPluginFileMapper<mobase.IPluginFileMapper>`
| **Examples:** ``tool_inibakery``, ``game_gamebryo``
This interface allows plugins to add virtual file (or directory) links to the virtual file system in addition to the
mod files.
+201
View File
@@ -0,0 +1,201 @@
Writing Plugins
===============
Getting started
---------------
Now that you know which type of plugin you need, you can start writing your own plugin.
There is two way to write a Python plugin:
- :ref:`single-file-plugin`: You use a single ``.py`` file that you put directly in the ``plugins``
folder of the MO2 installation.
- :ref:`module-plugin`: You create a Python package (folder) with a ``__init__.py`` file that you
put in the ``plugins`` folder of the MO2 installation.
Most examples of plugins will be module plugins.
.. _single-file-plugin:
Single file plugins
...................
Prior to version 2.3, this was the only way of creating a Python plugin. You simply need
to create a ``myplugin.py`` file in the ``plugins`` folder of Mod Organizer 2 with a content
similar to:
.. code-block:: python
import mobase
class MyPlugin(...):
...
def createPlugin() -> mobase.IPlugin:
return MyPlugin()
We will see later on how to create the actual ``MyPlugin`` class.
The ``createPlugin`` function is the function that is called by Mod Organizer 2 to instantiate
the plugin.
You can also provide multiple plugins by using ``createPlugins`` instead of ``createPlugin``:
.. code-block:: python
from typing import List
import mobase
class MyPlugin1(...):
...
class MyPlugin2(...):
...
def createPlugins() -> List[mobase.IPlugin]:
return [MyPlugin1(), MyPlugin2()]
**Note:** If you provide neither ``createPlugin()`` nor ``createPlugins``, MO2 will display
an error message in the logs.
**Note:** If you add a return type-hint to ``createPlugin()`` or ``createPlugins`` (``->``), ``mypy``
will type-check the function and warn you if one of your plugins is invalid, e.g. if you
forgot to implement a required method.
If you need to provide other files with your ``.py`` (assets or other Python files), you can
put them in the ``plugins/data``, but this is deprecated since MO2 2.3, and you should instead
create a Python module plugin.
.. _module-plugin:
Module Plugins
..............
Module plugins were introduced in MO2 2.3 and are shipped as whole folder containg a python module.
The minimum content of the folder is a ``__init__.py`` file with ``createPlugin`` or ``createPlugins``
function.
A minimal module plugin could be as follows:
.. code-block:: bash
plugins/ # MO2 plugins folder
myplugin/
__init__.py
plugin.py
In ``plugin.py``, you could define your plugin:
.. code-block:: python
# plugin.py
import mobase
class MyPlugin(...):
...
And in ``__init__.py``, you should write ``createPlugin``:
.. code-block:: python
# __init__.py
import mobase # For type-checking createPlugin().
from .plugin import MyPlugin # Always use relative import:
def createPlugin() -> mobase.IPlugin:
return MyPlugin()
Similar to single-file plugins, you can expose ``createPlugins`` instead of ``createPlugin``
to instantiate multiple plugins.
**Note:** The name of the folder does not have to be a valid python package, and you should
always use relative imports within the module (``import .xxx``) instead of absolute ones.
Writing the plugin
------------------
``IPlugin`` interface
.....................
In the code snippets above, the ``MyPlugin`` class was not implemented.
Depending on the :ref:`type of plugins<type-of-plugins>` that you want to create, you will
need to extend a different class.
.. code-block:: python
class MyTool(mobase.IPluginTool): # Create a Tool plugin
...
class MyPreview(mobase.IPluginPreview): # Create a preview plugin
...
Each plugin class has its own abstract methods that you need to implement but all the classes
also extend ``IPlugin``, so you need to implement the methods from :class:`IPlugin<mobase.IPlugin>`:
.. code-block:: python
from typing import List
import mobase
class MyPlugin(...): # The base class depends on the actual type of plugin
_organizer: mobase.IOrganizer
def __init__(self):
super().__init__() # You need to call this manually.
def init(self, organizer: mobase.IOrganizer):
self._organizer = organizer
return True
def name(self) -> str:
return ""
def author(self) -> str:
return "Tannin"
def description(self) -> str:
return self._tr("Gives a friendly greeting")
def version(self) -> mobase.VersionInfo:
return mobase.VersionInfo(1, 0, 0, mobase.ReleaseType.FINAL)
def isActive(self) -> bool:
return self._organizer.pluginSetting(self.name(), "enabled")
def settings(self) -> List[mobase.PluginSetting]:
return [
mobase.PluginSetting("enabled", "enable this plugin", True)
]
Most of these are pretty simple to understand:
- ``name``: Returns the name of the plugin. The name of the plugin is used to
fetch settings, and in many places, so this should not change between versions.
- ``author``: Returns the name of the plugin author (you!).
- ``description``: Returns the description of the plugin.
- ``version``: Returns the version of the plugin. See :class:`VersionInfo<mobase.VersionInfo>` for
more details.
- ``isActive``: Returns ``True`` if the plugin is active, ``False`` otherwise. This
usually returns ``True``, unless you want to check for something to dynamically
enable the plugin. You can also use a plugin setting to allow users to disable
your plugins.
- ``settings``: Returns the list of settings (that user can modify) for this plugin.
Settings can be ``int```, ``bool``, ``str`` or list of ``str``. Here we indicate
that we have a "enabled" setting that user could use to disable the plugin (and
we use it in ``isActive``).
The ``__init__`` method is the normal Python constructor for our plugin, called when doing
``MyPlugin()``.
You should always call ``super().__init__()`` explicitly when extending MO2 classes (due to
a "bug" in ``boost.python``).
The ``init`` method is called by MO2 to initialize the plugin. The given argument, ``organizer``,
is an instance of :class:`IOrganizer<mobase.IOrganizer>` which is the class used to interface with MO2.
Here, we use it in the ``isActive()`` method to retrieve the "enabled" setting for our plugin.
See :class:`IOrganizer<mobase.IOrganizer>` for more details.