mirror of
https://github.com/ModOrganizer2/pystubs-generation.git
synced 2026-07-27 14:07:13 -07:00
Update documentation.
This commit is contained in:
@@ -16,6 +16,7 @@ This documentation is dedicated to writting MO2 **Python** plugins.
|
||||
|
||||
setup-tools
|
||||
plugin-types
|
||||
writing-plugins
|
||||
faq
|
||||
mobase
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user