-
Notifications
You must be signed in to change notification settings - Fork 25
Modifiers
Modifiers are gameplay effects that can be applied to players on top of their role. They are distinct from roles: a player keeps their role and receives one or more modifiers on top of it. Mira handles registration, assignment, networking, and UI display.
There are three modifier base classes depending on the behavior you need:
| Class | Use case |
|---|---|
BaseModifier |
Any modifier applied manually at runtime. |
GameModifier |
A modifier the game automatically assigns at round start based on chance and count. |
TimedModifier |
A modifier that has a countdown timer and optionally removes itself when it expires. |
Create a class that inherits from BaseModifier, GameModifier, or TimedModifier. Mira automatically discovers and registers any modifier class found in your plugin assembly.
public class CaptainModifier : GameModifier
{
public override string ModifierName => "Captain";
public override LoadableAsset<Sprite>? ModifierIcon => ExampleAssets.CaptainIcon;
public override string GetDescription() => "You can call a meeting from anywhere on the map.";
public override int GetAssignmentChance() => 50;
public override int GetAmountPerGame() => 1;
public override void OnDeath(DeathReason reason)
{
Player.RemoveModifier(this);
}
}| Member | Type | Default | Description |
|---|---|---|---|
ModifierName |
string |
(abstract) | The display name of the modifier. |
ModifierIcon |
LoadableAsset<Sprite>? |
null |
Icon shown on the HUD. Has no effect if HideOnUi is true. |
HideOnUi |
bool |
true when GetDescription() is empty |
Whether the modifier is hidden on the HUD. |
Unique |
bool |
true |
If true, only one instance of this modifier can be on a player at a time. |
ShowInFreeplay |
bool |
false |
Whether this modifier appears in the freeplay modifier selection menu. |
FreeplayFileColor |
Color |
Color.gray |
Color of the entry in the freeplay menu. |
Player |
PlayerControl |
Set by Mira | The player this modifier is attached to. |
UniqueId |
Guid |
Set by Mira | A unique instance ID for this specific modifier. |
TypeId |
uint |
Set by Mira | The registered type ID shared by all instances of this modifier class. |
ParentMod |
MiraPluginInfo |
Resolved automatically | The plugin that registered this modifier. |
GetDescription() |
string |
"" |
Description shown on the HUD. Must be non-empty for the modifier to be visible on the UI. |
OnActivate() |
void |
— | Called when the modifier is first added to the player. |
OnDeactivate() |
void |
— | Called when the modifier is removed from the player. |
Update() |
void |
— | Called each frame while the modifier is active. |
FixedUpdate() |
void |
— | Called each physics tick while the modifier is active. |
OnDeath(DeathReason) |
void |
— | Called when the player dies. |
OnMeetingStart() |
void |
— | Called when a meeting starts. |
CanVent() |
bool? |
null |
Override vent permission. null means no effect. |
GameModifier extends BaseModifier for modifiers that should be assigned by the game at round start. ShowInFreeplay defaults to true.
| Member | Type | Default | Description |
|---|---|---|---|
GetAssignmentChance() |
int |
(abstract) | Chance (0–100) of this modifier being assigned per eligible player. |
GetAmountPerGame() |
int |
(abstract) | Maximum number of players that can have this modifier in one game. |
Priority() |
int |
-1 |
Assignment priority. Higher values are assigned first. |
IsModifierValidOn(RoleBehaviour) |
bool |
true |
Whether this modifier can be assigned to a player with the given role. |
DidWin(GameOverReason) |
bool? |
null |
Custom win condition. true = won, false = lost, null = use player's default condition. The highest-priority modifier's result takes precedence. |
CanSpawnOnCurrentMode() |
bool |
true (false in Hide and Seek) |
Whether this modifier can spawn given the current game mode. |
When you use the default GetAssignmentChance() and GetAmountPerGame() values, Mira automatically creates options in the Modifiers settings screen so the host can configure them. If you want to read those values from an options group instead (as is recommended), return the values from your options group directly:
public override int GetAssignmentChance() =>
(int)OptionGroupSingleton<CaptainModifierSettings>.Instance.Chance;
public override int GetAmountPerGame() =>
(int)OptionGroupSingleton<CaptainModifierSettings>.Instance.Amount;TimedModifier extends BaseModifier for modifiers that count down and optionally remove themselves when the timer completes.
| Member | Type | Default | Description |
|---|---|---|---|
Duration |
float |
(abstract) | How long the modifier lasts in seconds. |
AutoStart |
bool |
true |
Whether the timer starts automatically when the modifier is added. |
RemoveOnComplete |
bool |
true |
Whether the modifier removes itself when the timer reaches zero. |
TimerActive |
bool |
— | Whether the timer is currently running. |
TimeRemaining |
float |
— | Seconds remaining on the timer. |
HideOnUi |
bool |
!TimerActive |
Hides the HUD entry while the timer is not running. |
GetDescription() |
string |
"Remaining Time: Xs/Ys" |
Automatically shows remaining/total time. |
StartTimer() |
void |
— | Starts the countdown. |
StopTimer() |
void |
— | Stops the timer and calls OnTimerComplete(). Removes the modifier if RemoveOnComplete is true. |
ResetTimer() |
void |
— | Resets TimeRemaining to Duration without calling OnTimerComplete(). |
OnTimerComplete() |
void |
— | Called when the timer reaches zero. |
public class FreezeModifier : TimedModifier
{
public override string ModifierName => "Frozen";
public override float Duration => 5f;
public override string GetDescription() => $"You are frozen! {Math.Round(TimeRemaining, 0)}s remaining.";
public override void OnActivate()
{
Player.MyPhysics.body.velocity = Vector2.zero;
}
}Modifiers are applied and removed via RPC so all clients stay in sync. All methods are extensions on PlayerControl.
| Method | Description |
|---|---|
player.RpcAddModifier<T>(params object[] args) |
Adds modifier of type T to the player, synced over RPC. |
player.RpcRemoveModifier<T>(Func<BaseModifier, bool>? predicate) |
Removes a modifier of type T from the player, synced over RPC. |
player.RpcRemoveModifier(Guid uniqueId) |
Removes a specific modifier instance by its unique ID, synced over RPC. |
player.RpcRemoveModifier(Type type, Func<BaseModifier, bool>? predicate) |
Removes a modifier by type, synced over RPC. |
player.RpcRemoveModifier(uint typeId, Func<BaseModifier, bool>? predicate) |
Removes a modifier by type ID, synced over RPC. |
These modify the local ModifierComponent directly, without sending an RPC. Use these only when you already know the call is running on all clients, or for local-only state.
| Method | Description |
|---|---|
player.AddModifier<T>(params object[] args) |
Adds a modifier of type T locally. |
player.AddModifier(Type type, params object[] args) |
Adds a modifier by type locally. |
player.AddModifier(uint typeId, params object[] args) |
Adds a modifier by type ID locally. |
player.RemoveModifier<T>(Func<T, bool>? predicate) |
Removes a modifier of type T locally. |
player.RemoveModifier(Type type, Func<BaseModifier, bool>? predicate) |
Removes a modifier by type locally. |
player.RemoveModifier(uint typeId, Func<BaseModifier, bool>? predicate) |
Removes a modifier by type ID locally. |
player.RemoveModifier(Guid uniqueId) |
Removes a modifier by its unique instance ID locally. |
player.ClearModifiers() |
Removes all modifiers from the player locally. |
These extension methods on PlayerControl let you check and retrieve a player's modifiers.
| Method | Description |
|---|---|
player.HasModifier<T>(Func<T, bool>? predicate) |
Returns true if the player has a modifier of type T. |
player.HasModifier(Type type, Func<BaseModifier, bool>? predicate) |
Returns true if the player has a modifier of the given type. |
player.HasModifier(uint typeId, Func<BaseModifier, bool>? predicate) |
Returns true if the player has a modifier with the given type ID. |
player.HasModifier(Guid uniqueId) |
Returns true if the player has the specific modifier instance. |
player.GetModifier<T>(Func<T, bool>? predicate) |
Returns the first matching modifier of type T, or null. |
player.GetModifiers<T>(Func<T, bool>? predicate) |
Returns all modifiers of type T on the player. |
player.GetModifiers(Type type, Func<BaseModifier, bool>? predicate) |
Returns all modifiers of the given type on the player. |
player.GetModifiers(uint typeId, Func<BaseModifier, bool>? predicate) |
Returns all modifiers with the given type ID on the player. |
player.GetModifierComponent() |
Returns the ModifierComponent attached to the player. |
ModifierUtils provides static helpers for querying modifiers across all players.
| Method | Description |
|---|---|
ModifierUtils.GetActiveModifiers<T>(Func<T, bool>? predicate) |
Returns all active modifiers of type T across every player. |
ModifierUtils.GetPlayersWithModifier<T>(Func<T, bool>? predicate) |
Returns all players that currently have a modifier of type T. |
// Get all players who are currently frozen
var frozenPlayers = ModifierUtils.GetPlayersWithModifier<FreezeModifier>();
// Get all active freeze modifier instances
var activeFreeze = ModifierUtils.GetActiveModifiers<FreezeModifier>();ModifierManager exposes the registered modifier registry.
| Member | Description |
|---|---|
ModifierManager.Modifiers |
Read-only list of all registered modifier instances (one per type). |
ModifierManager.GetModifierType(uint id) |
Returns the Type for a given modifier type ID. |
ModifierManager.GetModifierTypeId(Type type) |
Returns the type ID (uint?) for a given modifier type. |
ModifierManager.MiraAssignsModifiers |
When set to false, Mira will skip automatic modifier assignment at round start. |