Table of Contents

Event subscribers

An event subscriber reacts to something that happens in the world โ€” a player double-clicks, a container opens, the world finishes loading. When your plugin's job is a reaction rather than owning a resource, a subscriber is the natural fit: its handlers already run on the game loop, so they can touch world state directly.

The interface

public interface IEventSubscriberRegistration
{
    void Subscribe(IEventBus eventBus);
}

In Subscribe, attach a handler for each event you care about. A handler is Task Handler(TEvent message, CancellationToken cancellationToken).

using Moongate.Server.Abstractions.Data.Events;
using Moongate.Server.Abstractions.Interfaces.Events;
using Serilog;
using SquidStd.Core.Interfaces.Events;

namespace MyShard.Live.Subscribers;

/// <summary>Logs a banner once the world has finished loading.</summary>
public sealed class WorldReadySubscriber : IEventSubscriberRegistration
{
    private readonly ILogger _logger = Log.ForContext<WorldReadySubscriber>();

    public void Subscribe(IEventBus eventBus)
        => eventBus.Subscribe<WorldReadyEvent>(OnWorldReady);

    private Task OnWorldReady(WorldReadyEvent message, CancellationToken cancellationToken)
    {
        _logger.Information("=== MyShard is open for business ===");

        return Task.CompletedTask;
    }
}

WorldReadyEvent lives in Moongate.Server.Abstractions.Data.Events, alongside the other domain events you can subscribe to.

Registering

Register the subscriber in your plugin's Configure:

container.RegisterEventSubscriber<WorldReadySubscriber>();

The event-subscriber service resolves every registration and attaches it to the bus at startup.

Loop affinity

Domain-event handlers run on the game loop, so it is safe to read and mutate world state directly inside them โ€” no IMainThreadDispatcher needed. That is the difference from a hosted service, whose background loop runs off the game thread. A subscriber can still inject any service it needs through its constructor.