Plugins Overview
The RisingV.Core.Plugins (RisingV.Shared.Plugins) abstraction serves as the foundational entry point for building modular, self-contained feature packsβpluginsβin the RisingV ecosystem. Rather than working directly with raw BepInEx BasePlugin classes, RisingV plugins leverage a consistent, six-stage lifecycle, integrated configuration binding, and automatic manager wiring (engines, databases, systems) to ensure a robust, maintainable, and hot-swappable modding framework.
API Documentation
For full details on available classes, interfaces, and methods within the Plugins namespace, refer to the official API reference:
1. Core Abstraction
At its core, RisingVβs plugin system wraps BepInExβs BasePlugin into a richer abstraction:
Uniform Six-Stage Lifecycle
Each plugin follows a strict sequence:- Initialize
- Load
- Ready
- Reload
- Unload
- Terminate
Configuration Binding
Built atopBaseConfig, each plugin can define a strongly-typedPluginConfigsubclass. The abstraction queues upConfigEntry<T>bindings and automatically reads or hot-reloads them at the appropriate lifecycle stage.Manager Wiring
RisingV encourages separation of concerns by dividing functionality into managers such as:- EngineManager (coordinates Engines)
- DatabaseManager (coordinates Databases)
- SystemManager (coordinates custom systems or processors)
All managers are registered during Initialize and automatically invoked during Load and Ready.
Logging & EventBridge Integration
Each plugin receives aLoggerinstance scoped to its GUID. Additionally,PublishandSubscribewrappers simplify event handling through the sharedEventBridge.
2. Key Contracts
IPlugin
Defines the minimal contract for any RisingV plugin:
public interface IPlugin
{
string Guid { get; }
string Name { get; }
Version Version { get; }
string Author { get; }
bool IsInitialized { get; }
bool IsLoaded { get; }
bool IsReady { get; }
void Initialize();
void Load();
void Ready();
void Reload(ReloadReason reason);
void Unload();
void Terminate();
}
GUID, Name, Version, Author
Read-only metadata for plugin identification.Lifecycle State Flags
IsInitialized,IsLoaded,IsReadyserve as idempotent guards; any override must invokebase.*()to keep them in sync.Lifecycle Methods
Initialize(): Register configuration, managers, default data.Load(): Connect to external resources, allocate heavy objects.Ready(): Safe point for inter-plugin communication (all plugins have completed Load).Reload(ReloadReason): Triggered on Hot-Reload or manual reload commands.Unload(): Dispose caches, detach from events, stop threads.Terminate(): Final cleanup before server shutdown.
RisingBasePlugin
A concrete, opinionated base class that implements IPlugin and extends BepInExβs BasePlugin:
Logger
ProtectedLogger Logis instantiated using the pluginβs GUID. UseLog.Info(),Log.Warning(), etc., for structured logging.PluginConfig
Apublic PluginConfig Configinstance is auto-instantiated. OverrideCreateConfig()to supply a customPluginConfigsubclass.Manager Accessors
Provides helper properties to retrieve common managers:EngineManager Engines { get; }DatabaseManager Databases { get; }SystemManager Systems { get; }- Additional managers (e.g.,
AspectManager,ItemManager) as needed.
EventBridge Wrappers
Simplifies usage ofEventBridge.Publish(...)andEventBridge.Subscribe<TEvent>(handler).
3. Lifecycle Flow
ββββββββββββ ββββββββββββ ββββββββββββ
βInitializeβ βββΊ β Load β βββΊ β Ready β
ββββββββββββ ββββββββββββ ββββββββββββ
β² β β
β βΌ βΌ
ββββββββββββ ββββββββββββ ββββββββββββ
βTerminate β βββ β Unload β βββ β Reload β
ββββββββββββ ββββββββββββ ββββββββββββ
Initialize
- Instantiate and register all necessary managers (Engines, Databases, Systems).
- Create or bind configuration entries (hot-reloadable by default).
- Seed any default data or static references.
Load
- Connect to external services (e.g., network resources, file I/O).
- Allocate heavy or long-lived objects (caches, background threads).
- Register event handlers (through
EventBridgeor game hooks).
Ready
- All other plugins have completed Load.
- Safe to perform cross-plugin interactions (subscribe to events published by other plugins).
- Begin gameplay-related logic (hook into game state, spawn objects).
Reload
- Called on manual βreloadallβ or BepInEx hot-reload.
- Plugin should reload modified configurations or refresh resources.
- Use
ReloadReason.IsFullto discriminate between full reload and partial reloads.
Unload
- Dispose of large caches, stop or signal threads to exit.
- Detach all event listeners to prevent memory leaks.
- Prepare for potential re-initialization on the next reload.
Terminate
- Final cleanup: close file handles, save critical data, remove persistent references.
- Called when the server or application is shutting down.
4. QuickβStart Skeleton
Below is a minimal plugin template demonstrating how to leverage the RisingV plugin abstraction:
using BepInEx;
using RisingV.Shared.Plugins;
using RisingV.Shared.Engines;
using RisingV.Core.Engines;
public class MyFeatureConfig()
: PluginConfig(MyPluginInfo.PLUGIN_GUID, MyPluginInfo.PLUGIN_GUID)
{
public ConfigEntry<float> AttackModifier { get; set; }
public override void Load()
{
base.Load();
AttackModifier = Bind("Gameplay", "AttackModifier", 1f,
"Multiplier applied to all player attack damage");
}
}
public class MyFeatureContext() : PluginContext(typeof(MyPluginInfo), new MyFeatureConfig());
[BepInPlugin(MyPluginInfo.PLUGIN_GUID, MyPluginInfo.PLUGIN_NAME, MyPluginInfo.PLUGIN_VERSION)]
[BepInDependency("RisingV.Core")] // Ensures RisingV.Core binaries are loaded first
[BepInDependency("RisingV.Scripting")] // If your plugin uses scripting features
public class MyFeaturePlugin : RisingPlugin<MyFeatureContext>
{
protected override void OnInitialize()
{
EngineManager.AddEngine<DamageEngine>(this);
EngineManager.AddEngine<BuffEngine>(this);
EngineManager.AddEngine<DeathEngine>(this);
}
protected override void OnLoad()
{
// Pre-Initialize logic (before managers are created)
}
protected override void OnUnload()
{
// Return true to allow unload, or false to veto
return true;
}
public override void Ready()
{
// All managers and other plugins are ready: hook game logic here
if (Context.Config.AttackModifier.Value != 1f)
{
// Apply a global damage multiplier or patch methods accordingly
}
}
}
Tips:
- Most plugins only need to use
OnInitializeorOnLoad. BasePluginautomatically invokesLoad()andReady()on all registered managers, so custom logic can often be placed solely inReady().- Declare
[BepInDependency]attributes to enforce load order for dependent plugins or RisingV subsystems.
5. PluginConfig Pattern
Every RisingV plugin typically defines a PluginConfig subclass:
public sealed class LootTweaksConfig()
: PluginConfig(LootTweaksInfo.PLUGIN_GUID, LootTweaksInfo.PLUGIN_GUID)
{
public ConfigEntry<float>? Multiplier { get; private set; }
public override void Load()
{
base.Load();
Multiplier = Bind("General", "Multiplier", 2f,
"Multiply boss loot counts by this factor.");
}
}
- Inherits
BaseConfig, so you automatically get:- An
Enabledtoggle for your entire plugin. - Queued binding of all
ConfigEntry<T>fields. - Hot-reload support: when the underlying configuration file changes,
Reload()will rebind updated values.
- An
Best Practice: Always perform Bind() calls inside Load()βnot in the constructorβto avoid NullReferenceException issues.
6. PluginManager
- Located in
RisingV.Shared.Plugins. - Discover and register all plugins (both via reflection on
[BepInPlugin]attributes and manually instantiated ones used in unit tests). - Resolves dependencies using
[PluginDependency("SomeOtherGUID")]attributes.
PluginManager ensures that plugins are loaded, unloaded, and reloaded in a deterministic order based on declared dependencies.
7. Best Practices & Patterns
One Feature per Plugin
Keep each plugin focused on a single responsibility. This makes it easier to manage dependencies and reduces coupling.Register Managers Early
OverrideOnInitialize(viaRisingPlugin<TContext>). Avoid delaying manager registration toReadyunless absolutely necessary.Leverage Hot-Reload
- Plugins automatically support hot-reload if you bind config entries and register managers correctly.
- Use
ReloadReasonto distinguish between a simple config change (IsConfigChange) and a full reload (IsFull).
Use EventBridge
- Prefer publishing and subscribing to events rather than writing direct Harmony patches across plugins.
- Events decouple plugin logic and allow multiple subscribers to respond independently.
Cleanup on Unload
- Detach all event handlers in
Unload(). - Dispose of or null out large data structures to prevent memory leaks.
- Stop any custom background tasks or threads.
- Detach all event handlers in
8. Troubleshooting
| Symptom | Possible Cause | Fix |
|---|---|---|
| Plugin stuck in βLoadedβ state | Forgot to call Ready() on a manager or in BasePlugin |
Ensure you call base.Ready() and/or register managers before calling Ready(). |
NullReferenceException on Config access |
Attempted to read ConfigEntry<T> in constructor |
Move all Bind() calls to Load() method. |
| Circular plugin dependency detected | Two plugins depend on each other via [PluginDependency] |
Break one dependency or mark it optional. |
| Hot-reloaded plugin fails to reinitialize | Managers were not designed for hotβreload | Implement idempotent Reload() logic and reset internal state properly. |
9. Summary
RisingVβs plugin abstraction layer builds on BepInEx to provide:
- A uniform sixβstage lifecycle to manage initialization, loading, readiness, reloads, unloading, and termination.
- Automatic wiring of key subsystems (Engines, Databases, Systems) via helper managers.
- Firstβclass configuration binding through
PluginConfig(hot-reloadable). - Integrated logging and event publishing/subscribing via
EventBridge. - A well-defined PluginManager to discover, sort, and control plugins based on declared dependencies.
By using this system, plugin authors achieve consistency, robustness, and hotβswappability, enabling rapid development of feature-rich, modular add-ons for the RisingV framework.