EXILED Plugin Framework

repository·master·Indexed 43 days ago

https://github.com/exiled-team/exiled

A high-level, event-driven plugin framework for SCP: Secret Laboratory servers. It utilizes Harmony to allow developers to manipulate game code and implement custom functions without modifying server assemblies directly. The framework includes a dedicated installer for Windows and Linux, a plugin reload lifecycle, and a structured event system consisting of Handlers and Event Arguments.

Tokens
30.1K
Snippets
111
Records
145
Agent score
49%

What's inside EXILED

  1. Overview of EXILED

    master

    EXILED is a high-level plugin framework for SCP: Secret Laboratory servers. It provides an event system that allows developers to manipulate or change game code or implement custom features.

    Key characteristics:

    • Harmony-based: All EXILED events are coded with Harmony, meaning they do not require direct editing of server Assemblies. This allows the framework to remain compatible with minor game updates and makes it easier to update when necessary.
    • Open Source: The framework code is public, allowing developers to understand its inner workings and suggest improvements.
  2. What is EXILED?

    master

    EXILED is a high-level plugin framework for the SCP: Secret Laboratory server. It provides an event system that allows developers to hook into and modify game code or create custom functions without directly modifying the server's assemblies.

    Key characteristics:

    • Harmony-based: All events are written using Harmony, meaning the framework operates independently of the server's core assemblies.
    • Open and Extensible: The framework code is freely shared, allowing developers to understand its inner workings and suggest improvements.
    • Resilient to Updates: Because it works separately from the server assemblies, minor game updates typically have little to no impact on the framework, making it highly compatible with future updates.
  3. Implement dynamic updates using OnEnable, OnDisable, and OnReload

    master

    Exiled supports a Reload command that allows existing and new plugins to be reloaded at runtime. To prevent data loss or memory leaks during a reload, your plugin must implement dynamic updates:

    1. OnEnable: Initialize all variables, events, and coroutines here.
    2. OnDisable: Unsubscribe from all events and set objects/handlers to null to prevent memory leaks.
    3. OnReload: Use this method to persist data that should survive a reload. Since instance variables are reset during a reload, you must save their state to static variables (which are not cleared) during OnReload, and restore them from those static variables during OnEnable.

    Failure to properly unsubscribe in OnDisable or manage state in OnReload will result in duplicate event execution or lost data.

    // This static variable persists through reloads
    public static int StaticCount = 0;
    
    // This instance variable is reset on reload
    public int counter = 0;
    
    public override void OnEnable()
    {
        // Restore state from static variable
        counter = StaticCount;
        counter++;
        Info(counter);
    }
    
    public override void OnDisable()
    {
        counter++;
        Info(counter);
    }
    
    public override void OnReload()
    {
        // Save state to static variable before it is wiped
        StaticCount = counter;
    }
  4. Understand the EXILED event structure

    master

    EXILED uses an event-driven model to inform plugins when specific game actions occur. Events are organized into two main components:

    1. Handlers: Static classes found in the Exiled.Events namespace. Handlers are grouped by game feature (e.g., Exiled.Events.Scp096 for SCP-096 related actions).
    2. Event Arguments: Classes found in the Exiled.Events.EventArgs namespace that inherit from System.EventArgs. These objects contain the data related to the event and, in many cases, the ability to cancel the event.

    Disallowing Events

    Many events include an IsAllowed property within their argument class.

    • Default true: Setting IsAllowed = false prevents the event from occurring (e.g., preventing a player from dying).
    • Default false: Setting IsAllowed = true allows an event that would otherwise be blocked (e.g., allowing a player to interact with a door they normally couldn't).
  5. Implement Dynamic Updates in plugins

    master

    EXILED supports dynamic reloading of plugin assemblies using the reload plugins command (via Remote Admin or Server Console) without requiring a server restart. To prevent memory leaks, crashes, or conflicts, plugin developers must follow these rules for dynamic updates.

    Developer Requirements

    When a plugin is reloaded, EXILED calls OnDisabled() (or OnReloaded()), then loads the new assembly, and finally calls OnEnabled(). You must clean up resources in OnDisabled() or OnReloaded():

    1. Unsubscribe from Events: Cancel all subscriptions to any events your plugin was listening to.
    2. Unpatch Harmony: If using custom Harmony patches, use your Harmony instance to call UnPatchAll().
    3. Stop Coroutines: Any MEC coroutines started in OnEnabled() must be stopped/terminated.

    Assembly Naming (Crucial)

    Because of the Global Assembly Cache (GAC), if you replace an assembly with another file of the same name, the old version will still be used.

    • To support dynamic updates: You must give every new version a different assembly name (e.g., MyPlugin_v1.dll, MyPlugin_v2.dll).
    • If you do not support dynamic updates: Do not change your assembly name between versions, but instruct server owners to perform a full server restart when updating.
  6. Implement Dynamic Plugin Updates

    master

    EXILED supports dynamic assembly reloading, allowing plugins to be updated without rebooting the server using the reload plugins command in Remote Admin or the Server Console.

    Requirements for Developers

    To prevent severe issues (like memory leaks or conflicting logic), plugins supporting dynamic updates must clean up after themselves in either OnDisabled() or OnReloaded().

    You must:

    • Unsubscribe from all events you connected to.
    • Unpatch all custom Harmony patches using UnPatchAll() on your specific Harmony instance.
    • Stop all MEC coroutines started in OnEnabled().

    Assembly Naming Rules: Because of the Global Assembly Cache (GAC), you cannot simply replace an old assembly with a new one of the same name. To support dynamic updates, you must build each version with a different assembly name in your build options.

    Requirements for Hosts

    • When updating, ensure the new assembly name is different from the current one.
    • Remove the old plugin file from the Plugins folder before running the reload plugins command.
    • If a plugin does not support dynamic updates, the server must be fully rebooted to apply the update.
  7. Implement Dynamic Reloading (Hot Reloading)

    master

    EXILED supports reloading plugin assemblies without restarting the server. This can be triggered via the console using reload plugins or pmanager disable/enable <plugin_name>.

    Requirements for Developers

    To prevent memory leaks and conflicts during a reload, your plugin must follow these rules in its OnDisabled() or OnReloaded() methods:

    1. Unsubscribe from all events: Ensure no event handlers remain active from the old assembly.
    2. Unpatch Harmony: If using custom Harmony patches, use unique names for Harmony instances and call UnPatchAll() during shutdown/reload.
    3. Stop Coroutines: Any MEC coroutines started in OnEnabled() must be terminated.

    Assembly Naming Requirement

    Because of the Global Assembly Cache (GAC), simply renaming a file is not enough. To support dynamic updates, you must use a different assembly name during the build process for each version. If you use the same assembly name, the server will continue to use the old version from the cache instead of loading your new code.

    Lifecycle Order during Reload

    When a plugin is reloaded, the sequence is:

    1. OnDisabled() (Old assembly)
    2. OnReloaded() (Old assembly)
    3. New assembly is loaded
    4. OnEnabled() (New assembly)
  8. How to run repetitive code using MEC Coroutines

    master

    EXILED does not provide OnUpdate, OnFixedUpdate, or OnLateUpdate events. To run code frequently, you should use MEC (More Effective Coroutines). MEC coroutines are safe for Unity and allow you to pause execution (wait) without blocking the main game thread.

    Warning: Never create new threads to interact with Unity, as this will cause the server to crash. Use MEC instead.

    To use MEC:

    1. Reference Assembly-CSharp-firstpass.dll from the server files.
    2. Add using MEC; to your script.
    3. Use Timing.RunCoroutine() to start your coroutine.
    4. Use yield return Timing.WaitForSeconds(float) to introduce delays.
    using MEC;
    using Exiled.API.Features;
    using System.Collections;
    
    public void SomeMethod()
    {
        Timing.RunCoroutine(MyCoroutine());
    }
    
    public IEnumerator<float> MyCoroutine()
    {
        for (;;)
        {
            Log.Info("Hey I'm a infinite loop!");
            yield return Timing.WaitForSeconds(5f);
        }
    }
  9. Use OnEnabled and OnDisabled for plugin lifecycle

    master

    The Plugin<IConfig> class provides two overridable methods to manage plugin functionality during its lifecycle:

    • OnEnabled(): Executes when the plugin is loaded/enabled. Use this to initialize your plugin's logic.
    • OnDisabled(): Executes when the plugin is disabled. Use this to clean up or disable functionality.

    Important: All code that enables plugin features must be handled in OnEnabled, and all corresponding cleanup must be handled in OnDisabled to ensure the plugin responds correctly when server hosts enable or disable it.

    namespace MyPluginNamespace
    {
        using Exiled.API.Features;
    
        public class Plugin : Plugin<Config>
        {
            public override void OnEnabled()
            {
                Log.Info("My plugin has been enabled!");
            }
    
            public override void OnDisabled()
            {
                Log.Info("My plugin has been disabled!");
            }
        }
    }
  10. Implement Dynamic Updates for plugins

    master

    EXILED supports reloading plugin assemblies at runtime using the reload plugins command in RemoteAdmin/ServerConsole without restarting the server. To support this safely, developers must follow specific cleanup rules to prevent memory leaks or conflicting logic.

    Developer Requirements for Dynamic Updates

    When a plugin is reloaded, EXILED calls OnDisabled(), then OnReloaded(), loads the new assembly, and finally calls OnEnabled(). To prevent issues, your plugin must do the following in OnDisabled() or OnReloaded():

    1. Unsubscribe from Events: Unhook all events the plugin is subscribed to.
    2. Clean up Harmony Patches: If using custom Harmony patches, use a unique instance name and call UnPatchAll() on that instance.
    3. Stop Coroutines: Kill all MEC coroutines started by the plugin.

    Assembly Naming (Crucial)

    Because of the Global Assembly Cache (GAC), if you replace an assembly with one of the same name, the old code will still run alongside the new code.

    • To support dynamic updates: You must change the Assembly Name in your build settings for every new version. Simply renaming the file is not enough.
    • If you do NOT support dynamic updates: Keep the Assembly Name constant, but inform server hosts that they must perform a full server restart to update your plugin.
  11. How the EXILED plugin reload lifecycle works

    master

    When a plugin is reloaded via the reload plugins command, EXILED follows a specific sequence to ensure a clean transition between the old and new versions of an assembly:

    1. OnDisabled(): Called on the old assembly to allow for cleanup (unsubscribing events, unpatching Harmony, killing coroutines).
    2. OnReloaded(): Called on the old assembly.
    3. Load New Assemblies: The new version of the plugin is loaded into the environment.
    4. OnEnabled(): Called on the new assembly to initialize the plugin.

    Note for Hosts: When updating a plugin dynamically, you must remove the old version from the Plugins folder before adding the new version and running the reload command.

  12. Make plugins dynamically updatable using OnEnable, OnDisable, and OnReload

    master

    EXILED supports a Reload command that reloads all plugins. To prevent memory leaks and ensure state persistence, your plugin must be Dynamically Updatable.

    • OnEnable: Initialize all variables, event subscriptions, and coroutines here.
    • OnDisable: Nullify or clean up all variables, unsubscribe from all events, and stop coroutines.
    • OnReload: Use this method to pass persistent data from instance variables to static variables, as static variables are not cleared during a reload.

    Failure to unsubscribe from events in OnDisable will prevent the plugin from being truly updatable and can cause issues during reloads.

    public static int StaticCount = 0;
    public int counter = 0;
    
    public override void OnEnable()
    {
        counter = StaticCount;
        counter++;
        Info(counter);
    }
    
    public override void OnDisable()
    {
        counter++;
        Info(counter);
    }
    
    public override void OnReload()
    {
        StaticCount = counter;
    }