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-typedPluginConfig
subclass. 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 aLogger
instance scoped to its GUID. Additionally,Publish
andSubscribe
wrappers 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
,IsReady
serve 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 Log
is instantiated using the pluginβs GUID. UseLog.Info()
,Log.Warning()
, etc., for structured logging.PluginConfig
Apublic PluginConfig Config
instance is auto-instantiated. OverrideCreateConfig()
to supply a customPluginConfig
subclass.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
EventBridge
or 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.IsFull
to 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
OnInitialize
orOnLoad
. BasePlugin
automatically 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
Enabled
toggle 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 toReady
unless absolutely necessary.Leverage Hot-Reload
- Plugins automatically support hot-reload if you bind config entries and register managers correctly.
- Use
ReloadReason
to 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.