Controller Plugins

The Nexus Controller supports extending functionality through plugins. Plugins are .NET assemblies that inherit from ControllerPluginAPI and can integrate custom services, UI components, and logic into the controller application.

Overview link

Controller plugins are loaded at startup and can:

Plugins can be distributed as either:

Getting Started link

Prerequisites link

Creating a Plugin Project link

  1. Create a new Class Library project targeting .NET 8:
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
  <PropertyGroup>
    <TargetFramework>net8.0-windows</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

  <ItemGroup>
    <!-- Reference the NGController project or NuGet packages -->
    <ProjectReference Include="path/to/NGController/NGController.csproj" />
  </ItemGroup>
</Project>
  1. Create a plugin class that inherits from ControllerPluginAPI:
using NGController.PluginAPI;
using System;
using System.Windows.Controls;

namespace MyPlugin
{
    public class MyControllerPlugin : ControllerPluginAPI
    {
        public override Version PluginVersion => new Version(1, 0, 0);

        public override UserControl UserControl => new MyPluginUserControl();

        public override async Task StartService()
        {
            // Initialize your plugin here
            await base.StartService();
        }

        public override async Task StopService()
        {
            // Cleanup when the controller shuts down
            await base.StopService();
        }

        public override void StartNetworking()
        {
            // Set up networking if needed
            base.StartNetworking();
        }
    }
}
  1. Create a manifest file at the root of your plugin folder:
<?xml version="1.0"?>
<ControllerPluginManifest xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <PluginName>My Plugin</PluginName>
  <PluginVersion>1.0.0</PluginVersion>
  <MinControllerVersion>3.0.0</MinControllerVersion>
  <MaxControllerVersion>9.9.9</MaxControllerVersion>
  <PluginGUID>12345678-1234-1234-1234-123456789012</PluginGUID>
  <IsBeta>false</IsBeta>
  <Author>Your Name</Author>
  <Description>A description of what your plugin does</Description>
  <AuthorURL>https://example.com</AuthorURL>
  <PluginURL>https://github.com/your-repo</PluginURL>
</ControllerPluginManifest>

Plugin Structure link

ControllerPluginAPI link

The base class all plugins must inherit from. It extends ControllerServiceAPI and provides the core plugin functionality.

Key Properties:

Property Type Description
PluginVersion Version The version of your plugin
UserControl UserControl Optional WPF control for your plugin’s UI

Key Methods:

Method Description
StartService() Called when the controller starts. Use for initialization.
StopService() Called when the controller shuts down. Use for cleanup.
StartNetworking() Called to set up any network listeners or subscriptions.

Manifest File link

The manifest.xml file describes your plugin. Required fields:

Field Type Description
PluginName string Display name of your plugin
PluginVersion Version Current version (format: Major.Minor.Patch)
MinControllerVersion Version Minimum controller version required
MaxControllerVersion Version Maximum controller version supported
PluginGUID Guid Unique identifier for your plugin
IsBeta bool Whether this is a beta release
Author string Plugin author name
Description string Plugin description
AuthorURL string Author’s website
PluginURL string Plugin repository or download page

Installation link

Folder Plugin link

  1. Create a folder in the controller’s Plugins directory (e.g., Plugins/MyPlugin)
  2. Copy your plugin’s DLLs into that folder
  3. Add a manifest.xml to the folder root
  4. Restart the controller

Example structure:

Plugins/
└── MyPlugin/
    ├── manifest.xml
    ├── MyPlugin.dll
    └── MyPlugin.pdb

Zip Plugin link

  1. Create a .zip file containing your DLLs and manifest.xml
  2. Place the .zip in the controller’s Plugins directory
  3. Restart the controller

Example structure:

Plugins/
└── MyPlugin.zip
    ├── manifest.xml
    ├── MyPlugin.dll
    └── MyPlugin.pdb

Advanced Features link

Dependency Injection link

Your plugin can use the dependency injection container to access other services:

public class MyControllerPlugin : ControllerPluginAPI
{
    private readonly DiscordService _discordService;

    public MyControllerPlugin(DiscordService discordService)
    {
        _discordService = discordService;
    }

    public override async Task StartService()
    {
        // Use injected service
        await base.StartService();
    }
}

Adding a UI Component link

Create a WPF UserControl for your plugin:

using System.Windows.Controls;

namespace MyPlugin
{
    public partial class MyPluginUserControl : UserControl
    {
        public MyPluginUserControl()
        {
            InitializeComponent();
        }
    }
}

Then return it from your plugin class:

public override UserControl UserControl => new MyPluginUserControl();

Accessing the Database link

If your plugin needs database access, you can inject ControllerSQLService:

using NGController.Services;

public class MyControllerPlugin : ControllerPluginAPI
{
    private readonly ControllerSQLService _sqlService;

    public MyControllerPlugin(ControllerSQLService sqlService)
    {
        _sqlService = sqlService;
    }

    public override async Task StartService()
    {
        if (_sqlService.TryOpenConnection(out var connection))
        {
            using (connection)
            {
                // Access your database tables
            }
        }
        await base.StartService();
    }
}

Network Communication link

Subscribe to network events:

using NexusAPI.Networking;

public override void StartNetworking()
{
    NetSubscriber.TrySubscribe(
        MessageType.ChatSync, 
        HandleChatMessage
    );
    base.StartNetworking();
}

private void HandleChatMessage(Msg message)
{
    // Handle the incoming message
}

Version Compatibility link

Plugins specify minimum and maximum controller versions they support:

<MinControllerVersion>3.0.0</MinControllerVersion>
<MaxControllerVersion>3.5.0</MaxControllerVersion>

The controller will only load plugins that support its version. Use appropriate version ranges to ensure compatibility.

Best Practices link

  1. Use meaningful GUIDs — Generate a unique GUID for each plugin using System.Guid.NewGuid()
  2. Version your plugin — Follow semantic versioning (Major.Minor.Patch)
  3. Include symbol files — Ship .pdb files with your DLLs for better debugging
  4. Document dependencies — Clearly state what controller version and features your plugin requires
  5. Handle errors gracefully — Log exceptions and provide meaningful error messages
  6. Clean up resources — Implement StopService() to release resources
  7. Test thoroughly — Test your plugin on the minimum and maximum controller versions you support

Example Plugin link

A complete minimal example:

manifest.xml:

<?xml version="1.0"?>
<ControllerPluginManifest xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <PluginName>Hello World Plugin</PluginName>
  <PluginVersion>1.0.0</PluginVersion>
  <MinControllerVersion>3.0.0</MinControllerVersion>
  <MaxControllerVersion>9.9.9</MaxControllerVersion>
  <PluginGUID>a1b2c3d4-a1b2-a1b2-a1b2-a1b2c3d4e5f6</PluginGUID>
  <IsBeta>false</IsBeta>
  <Author>Example Author</Author>
  <Description>A simple hello world plugin</Description>
  <AuthorURL>https://example.com</AuthorURL>
  <PluginURL>https://github.com/example/hello-world-plugin</PluginURL>
</ControllerPluginManifest>

HelloWorldPlugin.cs:

using NGController.PluginAPI;
using NLog;
using System;
using System.Threading.Tasks;
using System.Windows.Controls;

namespace HelloWorldPlugin
{
    public class HelloWorldPlugin : ControllerPluginAPI
    {
        private static Logger log = LogManager.GetCurrentClassLogger();

        public override Version PluginVersion => new Version(1, 0, 0);

        public override UserControl UserControl => new HelloWorldControl();

        public override async Task StartService()
        {
            log.Info("Hello World Plugin started!");
            await base.StartService();
        }

        public override async Task StopService()
        {
            log.Info("Hello World Plugin stopping!");
            await base.StopService();
        }
    }
}

HelloWorldControl.xaml:

<UserControl x:Class="HelloWorldPlugin.HelloWorldControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel Margin="10">
        <TextBlock Text="Hello from My Plugin!" FontSize="16" FontWeight="Bold"/>
        <TextBlock Text="This is my custom UI" Margin="0,10,0,0"/>
    </StackPanel>
</UserControl>

Available Services link

The controller provides several built-in services that you can inject into your plugin to access common functionality. These services are registered in the dependency injection container and available for constructor injection.

Service Reference link

Service Purpose
ControllerConfigService Access and manage controller configuration (database settings, Discord config, application paths)
ControllerSQLService Query and manage the PostgreSQL database; execute migrations and manage database connections
NexusService Core Nexus networking; access network poller, subscriber, publisher, and request-reply systems
DiscordService Integrate with Discord; send messages, manage commands, and interact with Discord guilds and channels
ScriptService Manage Nexus scripts and prefabs; access script configuration and prefab data
ISnackbarService Display notifications and UI feedback to the user through snackbar messages
PageService Navigate to and retrieve WPF pages from the UI layer
ApplicationHostService Access application-level information and lifecycle management
AutoUpdateService Check for and manage application updates

Usage Examples link

Database Access link

Inject ControllerSQLService to access the database:

using NGController.Services;
using NGController.PluginAPI;

public class MyControllerPlugin : ControllerPluginAPI
{
    private readonly ControllerSQLService _sqlService;

    public MyControllerPlugin(ControllerSQLService sqlService)
    {
        _sqlService = sqlService;
    }

    public override async Task StartService()
    {
        if (_sqlService.TryOpenConnection(out var connection))
        {
            using (connection)
            {
                // Execute queries or use Entity Framework Core
                var command = connection.CreateCommand();
                command.CommandText = "SELECT * FROM your_table LIMIT 10";
                // ... execute command
            }
        }
        await base.StartService();
    }
}

Discord Integration link

Inject DiscordService to send messages or perform Discord actions:

using NGController.Services;
using NGController.PluginAPI;
using Discord.WebSocket;

public class MyControllerPlugin : ControllerPluginAPI
{
    private readonly DiscordService _discordService;

    public MyControllerPlugin(DiscordService discordService)
    {
        _discordService = discordService;
    }

    public override async Task StartService()
    {
        // Access Discord client for sending messages, managing roles, etc.
        var discordClient = DiscordService.dClient;
        if (discordClient?.ConnectionState == Discord.ConnectionState.Connected)
        {
            // Send messages to channels
            var channel = discordClient.GetChannel(channelId) as SocketTextChannel;
            await channel?.SendMessageAsync("Plugin started!");
        }
        await base.StartService();
    }
}

Configuration Access link

Inject ControllerConfigService to access application configuration:

using NGController.Services;
using NGController.PluginAPI;

public class MyControllerPlugin : ControllerPluginAPI
{
    private readonly ControllerConfigService _configService;

    public MyControllerPlugin(ControllerConfigService configService)
    {
        _configService = configService;
    }

    public override async Task StartService()
    {
        var config = _configService.MainConfigs;
        var scriptsFolder = ControllerConfigService.ScriptsFolder;
        var configStoragePath = ControllerConfigService.ConfigStoragePath;

        // Use configuration values for your plugin
        await base.StartService();
    }
}

Nexus Networking link

Inject NexusService to access network communication:

using NGController.Services;
using NGController.PluginAPI;
using NexusAPI.Networking;

public class MyControllerPlugin : ControllerPluginAPI
{
    private readonly NexusService _nexusService;

    public MyControllerPlugin(NexusService nexusService)
    {
        _nexusService = nexusService;
    }

    public override void StartNetworking()
    {
        // Subscribe to network messages
        NexusService.msgSubscriber?.TrySubscribe(
            MessageType.ChatSync,
            HandleChatMessage
        );
        base.StartNetworking();
    }

    private void HandleChatMessage(Msg message)
    {
        // Handle incoming network message
    }
}

Publishing Messages link

To send data to the Nexus network, create a message object, populate it with data, and publish it using .Publish(). Inject ControllerConfigService to access configuration data like gates, clusters, or servers:

using NGController.Services;
using NGController.PluginAPI;
using NexusAPI.Messages;
using NexusAPI.Structures;

public class MyControllerPlugin : ControllerPluginAPI
{
    private readonly ControllerConfigService _configService;

    public MyControllerPlugin(ControllerConfigService configService)
    {
        _configService = configService;
    }

    public override async Task StartService()
    {
        // Example: Publish a gate configuration update
        var msg = new GateUpdateMessage();
        msg.allGates = _configService.MainConfigs.ConfiguredGates.Select(x => x.Serializable).ToArray();

        // Inject my own custom gates or modify existing ones before publishing. You can also save these changes back to the config file if desired.

        msg.Publish(MessageType.GateSync);

        await base.StartService();
    }
}

Real-world example from the Gates configuration page: The message will be distributed to all connected nodes in the cluster, allowing them to receive and process the updated data.

Multiple Service Injection link

You can inject multiple services into a single plugin:

using NGController.Services;
using NGController.PluginAPI;
using Wpf.Ui;

public class AdvancedPlugin : ControllerPluginAPI
{
    private readonly ControllerSQLService _sqlService;
    private readonly DiscordService _discordService;
    private readonly ControllerConfigService _configService;

    public AdvancedPlugin(
        ControllerSQLService sqlService,
        DiscordService discordService,
        ControllerConfigService configService
    )
    {
        _sqlService = sqlService;
        _discordService = discordService;
        _configService = configService;
    }

    public override async Task StartService()
    {
        await base.StartService();
    }
}

Troubleshooting link

Plugin Won’t Load link

  1. Check that manifest.xml is valid XML and in the correct location
  2. Verify the manifest is not malformed (missing required fields)
  3. Check the controller logs for specific error messages
  4. Ensure the plugin’s minimum/maximum controller versions match

Version Incompatibility link

If you see an error like “Plugin min controller version is 3.5.0”:

Missing Dependencies link

If your plugin fails to load:

  1. Verify all required DLLs are in the plugin folder
  2. Check that dependency versions match what’s expected
  3. Include .pdb files for better error diagnostics

See Also link