Events and event listeners
Events are used to notify components that something is happening, like a user disconnecting.
There are two types of events:
- OpenMod Events
- C# Events
This guide will be about OpenMod events.
Subscribing to events
There are two ways to subscribe to events:
- Implement the
IEventListener
interface:
public class UserConnectListener : IEventListener<UserConnectedEvent>
{
[EventListener(Priority = EventListenerPriority.Lowest)]
public async Task HandleEventAsync(object sender, UserConnectEvent @event)
{
// do something
}
}
- Use the subscribe methods from the
IEventBus
service:
public class MyPlugin : OpenModUniversalPlugin
{
private readonly IEventBus m_EventBus;
public MyPlugin(IEventBus eventBus, IServiceProvider serviceProvider) : base(serviceProvider)
{
m_EventBus = eventBus;
}
public async Task OnLoadAsync()
{
m_EventBus.Subscribe(this, (sender, @event) => {
// do something
});
}
}
Note
All OpenMod event listeners are automatically unsubscribed when your plugin unloads. You do not have to unsubscribe them manually on unload.
Event listener priority and execution order
OpenMod allows you to control in which order your event listeners are executed. Execution order is based on priority.
You can set an event listeners priority by using the [EventListener]
attribute.
Execution order is from lowest priority to highest. In other words, lowest priority is called first.
public class UserConnectListener1 : IEventListener<UserConnectedEvent>
{
[EventListener(Priority = EventListenerPriority.Lowest)]
public async Task HandleEventAsync(object sender, UserConnectEvent @event)
{
}
}
public class UserConnectListener2 : IEventListener<UserConnectedEvent>
{
[EventListener(Priority = EventListenerPriority.Low)]
public async Task HandleEventAsync(object sender, UserConnectEvent @event)
{
}
}
public class UserConnectListener3 : IEventListener<UserConnectedEvent>
{
[EventListener(Priority = EventListenerPriority.High)]
public async Task HandleEventAsync(object sender, UserConnectEvent @event)
{
}
}
In the example above, UserConnectListener1
is called first, then UserConnectListener2
and finally UserConnectListener3
.
Cancelling events and ignoring cancelled events
An event has to implement the ICancellableEvent
interface to be cancellable. If an event gets cancelled, event listeners which do not have the IgnoreCancelled
property in the [EventListener]
attribute set to true will not be notified.
UserConnectingEvent
is such a cancellable event. It will disconnect the connecting user if the event gets cancelled.
public class UserConnectingListener1 : IEventListener<UserConnectingEvent>
{
[EventListener(Priority = EventListenerPriority.Lowest)]
public async Task HandleEventAsync(object sender, UserConnectingEvent @event)
{
if(user.DisplayName.Equals("Trojaner"))
{
@event.IsCancelled = true;
}
}
}
public class UserConnectingListener2 : IEventListener<UserConnectingEvent>
{
[EventListener(Priority = EventListenerPriority.Low)]
public async Task HandleEventAsync(object sender, UserConnectingEvent @event)
{
// this event listener will not be called because it does not ignore cancellation
}
}
public class UserConnectingListener3 : IEventListener<UserConnectingEvent>
{
[EventListener(Priority = EventListenerPriority.High, IgnoreCancelled = true)]
public async Task HandleEventAsync(object sender, UserConnectingEvent @event)
{
// this event listener will be called even if the event gets cancelled
}
}
In the example above, if a user named "Trojaner" connects, UserConnectingListener1
will cancel the event. UserConnectingListener2
will not be called in this case because it does not ignore cancelled events like UserConnectingListener3
does.
Event listener lifetime
Event listeners can have three types of lifetime:
- Transient - The event listener is always be recreated on every event. If you have multiple IEventListeners, all of them will have their own instances. This is the default lifetime.
- Scoped - If you implement multiple IEventListeners in one class, all of them will share the same instance. Otherwise same as transient.
- Singleton - The event listener will have only one shared lifetime that lives until the plugin gets unloaded.
You can set the event listener lifetime by adding the [EventListenerLifetime(ServiceLifetime)]
attribute:
[EventListenerLifetime(ServiceLifetime.Transient)]
public class UserConnectBroadcaster : IEventListener<UserConnectedEvent>
// ...
Custom events
Creating a custom event is simple: just create a new class that inherits from Event
.
Here is an example:
public class SampleEvent : Event
{
public int MyValue { get; set; }
// you can also add other properties
}
You can then emit it by using the event bus:
MyPlugin myPlugin = ...;
IEventBus eventBus = ...;
ILogger<xxx> logger = ...;
var @event = new SampleEvent
{
MyValue = 20
};
await m_EventBus.EmitAsync(myPlugin, this /* sender */, @event);
logger.LogInformation($"Event value: {@event.MyValue}");
If you want your event to be cancellable, you must implement the ICancellableEvent
interface:
public class SampleEvent : Event, ICancellableEvent
{
public int MyValue { get; set; }
public bool IsCancelled { get; set; }
}
MyPlugin myPlugin = ...;
IEventBus eventBus = ...;
ILogger<xxx> logger = ...;
var @event = new SampleEvent
{
MyValue = 20
};
await m_EventBus.EmitAsync(myPlugin, this /* sender */, @event);
if(@event.IsCancelled)
{
logger.LogInformation($"Event has been cancelled!");
return;
}
logger.LogInformation($"Event value: {@event.MyValue}");
Caution
Do not forget to unsubscribe from C# events and delegates when your plugin unloads or a service gets disposed. For example, if you want to subscribe to the onEnemyConnected
event from Unturned when your plugin loads, you must also unsubscribe from it like this:
public async UniTask OnLoadAsync()
{
Provider.onEnemyConnected += OnPlayerConnected;
}
public async UniTask OnUnloadAsync()
{
// this is very important, otherwise your plugin will not properly support reloads and unloads.
Provider.onEnemyConnected -= OnPlayerConnected;
}
Warning
Avoid writing singleton event listeners. This may cause problems if the event listener has transient or scoped dependencies.