Plugin Development - 1attila/Conduit GitHub Wiki

How the Plugin systen works

Conduits offers Plugins as a simple interfaces to build complex commands that can be used in-game. Currently plugins only support commands and subcommands with permissions, but in the future they will handle background tasks, configs, permanent variables etc.

How to make a Plugin

A plugin requires 2 things:

  • metadata.json: contains the name, description, authors, versions etc
  • code: python files that contains the code

Note

The conduit_dev plugin can setup these files directly for you with a single command!

  • Install it with !!plugin install conduit_dev in-game or handler.download-plugin conduit_dev from the console

  • Join the server in which the plugin is active and type !! cdev generate <plugin-name>

Configure metadata.json

Copy this and paste it into metadata.json:

{   
    "name": "MyFirstPlugin",
    "authors": ["1attila"],
    "url": "",
    "entrypoint": "plugin.py",
    "required_plugins": [],
    "version": "0.0.0",
    "description": "My first Conduit plugin",
    "dependencies": [],
    "python_version": "",
    "supported_langs": ["en_us"]
}

And edit with your specific data.

Metadata descrpiton:

  • name: this is the name that will used to download/load etc the plugin with the commands
  • authors (optional): a list containing the names of the authors
  • url (optional): the url of the plugin (this could be something like a website or a github repo)
  • entrypoint: the file that contains the plugin class that Conduit will load
  • required_plugins (optional, unused): this will be used to install other plugins as dependencies
  • version: the plugin version, the format must be STABLE.MAJOR.MINOR
  • description: simple description of what the plugin does
  • dependencies (optional): python dependencies
  • python_version (optional, unused): the python version that the plugin can run
  • supported langs: the languages the plugins supports, en_us is required

How to write the code

Inside the entrypoint you have specified, you can define your plugin like so:

from mconduit import plugins


class MyFirstPlugin(plugins.Plugin):
    ...

Conduit is smart enough find and create an istance of your plugin.

Note

If you want to define your own __init__ method, you can only use these parameters:

def __init__(self, manager, metadata, lang) -> "Plugin"

and you must call super().__init__(manager, metadata, lang) in it.

Commands creation

After your plugin has been setup, you can start defining your own command tree. This is a very short tutorial to introduce you what you can do, to see all the details look at the APIs wiki.

Basic commands

You can define commands as functions by using the command decorator of the plugins module like so:

from mconduit import plugins, text, Context


class MyFirstPlugin(plugins.Plugin):
    
    @plugins.command
    def test(self, ctx: Context):
        ctx.reply(text.yellow(f"Hello {ctx.player.name}"))

Let's break down this code:

  • we are defining a command called test by using the decorator
  • the function must accept at least 2 parameters:
    • self that refers to the plugin
    • Context gives you important informations (like the player who invoked the command, time, etc) and provides some utility methods that you will use frequently
  • what the command does is replying to the user (equivalent to /tellraw @<player> <text>) a yellow text
  • the reply method can accept simple strings or MinecraftJson text like the one it's used here
  • we are sending some yellow text containing hello + the name of the player who invoked the command (we could have used just ctx.player since Player's class string method returns it's name)

Note

The Context parameter must be annotated!

As you can see you can do quite a lot of stuffs with very few and clear lines of code!

What i've showed about the text, Context and commands API it's very minimal, i suggest you to take a look at their own wiki to know how powerful they are!

Commands with parameters

In the same way we can declare commands with parameters like so:

from mconduit import plugins, text, Context


class MyFirstPlugin(plugins.Plugin):
    
    @plugins.command
    def test(self, ctx: Context):
        ctx.reply(text.yellow(f"Hello {ctx.player.name}"))
 
   @plugins.command
   def say(self, ctx: Context, string: str)
       ctx.say(string)

As you can see the only thing you need to do is adding the parameter into your function with the type annotation.

Conduit matches and casts the parameter already for you.

It's also very flexible and can work with lists of variable length and default parameters togheter like this:

@plugins.command
def t1(self, ctx: Context, arg1: str="a", arg2: List[str]=[], arg3: str="b"):
    print(arg1, arg2, arg3)

Conduit also provides some useful types: Flag and Range. Their name it's pretty self-explanatory.

Subcommands

If you want to create a subcommand you can still use the command decorator like so:

from mconduit import plugins, text, Context


class MyFirstPlugin(plugins.Plugin):
    
    @plugins.command
    def cmd1(self, ctx: Context):
        ctx.reply(text.yellow(f"Hello {ctx.player.name}"))
 
   @cmd1.command
   def cmd2(self, ctx: Context, string: str)
       ctx.say(string)

Let's break this down:

  • we are declaring cmd1
  • we are declaring cmd2 as subcommand of cmd1

Set command permissions

If you want to limit the command only to certain people you can use permissions. You can set a permission like so:

from mconduit import plugins, text, Context


class MyFirstPlugin(plugins.Plugin):
    
    @plugins.perms(plugins.Permissions.Owner)
    @plugins.command
    def test(self, ctx: Context):
        ctx.reply(text.yellow(f"Hello {ctx.player.name}"))

In this case we have limited the command (and all it's subcommands) to the Owner. To see what permissions are and how they works exactly look at their specific wiki.

Command name, checks, perms, docs and aliases

If you want to personalize your command further you can do that by using the command() of the plugins module method before the command, like the normal command decorator:

from mconduit import plugins, text, Context


class MyFirstPlugin(plugins.Plugin):
    
    @plugins.command(
        name="test",
        aliases=["t"],
        checks=[plugins.check_perms(plugins.Permission.Owner)]
    )
    def _test(self, ctx: Context):
        ctx.reply(text.yellow(f"Hello {ctx.player.name}"))

Let's break this down:

  • the command() function provides a decorator that transforms your function into a command
  • the name provided as argument is the new command name that will be used to invoke the command (and it's not _test anymore)
  • aliases it's an array of names that can be used to call the command
  • checks contains all the checks that the user must match to invoke the command (in this case it must be an owner)

Events

Conduit is also able to retrieve and dispatch events!

There are many events you can find in event.py inside mconduit folder.

This is a simple example on how to link a function to an event:

from mconduit import plugins, Context


class MyFirstPlugin(plugins.Plugin):
    
    @plugins.event
    def on_player_join(self, ctx: Context):
        ctx.reply("Hello!")

When the event is not specified it's always the name of the function (like commands), but you can specify it like so:

from mconduit import plugins, Context, Event


class MyFirstPlugin(plugins.Plugin):
    
    @plugins.event(event=Event.PlayerJoin)
    def greet(self, ctx: Context):
        ctx.reply("Hello!")

Note that there are three type of events:

  • Player events: the ones we just saw
  • Server events: server start/stop
  • Conduit events: conduit start/stop

Based on the type of the event, the function you want to link must have different parameters:

  • Player events: Context
  • Server evenst: Server
  • Conduit events: Handler

Here there are a couple examples:

from mconduit import plugins, Context, Event, Handler, Server


class MyFirstPlugin(plugins.Plugin):

    # Player Event
    @plugins.event(Event.PlayerDeath)
    def on_death(self, ctx: Context):
        print("Player death")

    # Conduit Event
    @plugins.event(Event.ConduitStart)
    def on_cstart(self, ctx: Handler):
        print("Conduit start")

    # Server Event
    @plugins.event(Event.ServerStart)
    def on_sstart(self, ctx: Server):
        print("Server start")

Note that you can link multiple functions with the same event:

from mconduit import plugins, Context, Event


class MyFirstPlugin(plugins.Plugin):

    def __init__(self, manager, metadata, lang) -> "MyFirstPlugin":

        self.__online_players = []
        super().__init__(manager, metadata, lang)
    
    @plugins.event(event=Event.PlayerJoin)
    def greet(self, ctx: Context):
        ctx.reply("Hello!")

    @plugins.event(event=Event.PlayerJoin)
    def add_online_players(self, ctx: Context):
        self.__online_players.append(ctx.player)

Even more...

What it's shown here are just the most important features for plugins, but it's not even 1/10 of what you can do! Conduit also provides APIs to load custom settings, manage persistant variables, communicate between other plugins, execute any type of command, fetch a lot of data and a lot more only with a single line of code each! I encorage you to look at the full wiki to get a good idea of what you can do!

Publish your plugin

When you plugin is ready you can publish it on the Catalogue!

Before doing that i recomend to take a look at the other plugins present in the Catalogue, to give you an idea of how they are made/strucutred.

If you want to publish your plugin into the Catalogue, you can contact attila via discord and if everything is ok the Catalogue will be updated.

⚠️ **GitHub.com Fallback** ⚠️