跳到主要内容
版本:Next

Server events

Services run every tick, and RPC handlers run when a client asks for something. Server events are the third way a server mod gets to run code: the server tells you when a player connects or leaves, when an area or cell comes into use, and when a player moves between them. They come from IServerEvents, which you ask for in the constructor of a server service.

MyMod.Server/Greeter.cs
using Microsoft.Extensions.Logging;
using MyMod.Common;
using ReadyM.Api.Idents;
using ReadyM.Relay.Server.Sdk.Ecs;
using ReadyM.Relay.Server.Sdk.Events;
using ReadyM.SDK.Archetypes.Core;
using ReadyM.SDK.Attributes;
using ReadyM.SDK.Server.Entities;

namespace MyMod.Server;

[Service]
public sealed partial class Greeter(IServerEvents events, IEntities entities, ILogger logger)
{
private const int StartingGold = 50;

// Subscribe when the service starts...
private void Start()
{
events.OnPlayerConnected += OnPlayerConnected;
events.OnPlayerJoinedArea += OnPlayerJoinedArea;
}

// ...and unsubscribe when it stops.
private void Stop()
{
events.OnPlayerConnected -= OnPlayerConnected;
events.OnPlayerJoinedArea -= OnPlayerJoinedArea;
}

private void OnPlayerConnected(PlayerId playerId, Entity _)
{
if (entities.TryLookup(playerId, out Player player))
player.Balance = StartingGold;
}

private void OnPlayerJoinedArea(PlayerId playerId, AreaId areaId)
=> logger.LogInformation("{Player} entered {Area}.", playerId, areaId);
}

Balance is the mod template's Wallet mixin on every player (see Shapes).

How it works​

Subscribe in a service's Start and unsubscribe in its Stop, as above. Your handlers run in the server's update, at the same point as a service's Update, so they can read and change entities directly. A slow handler delays the whole tick, so keep them short. If a handler throws, the server logs the error and carries on.

EventHands youFires when
OnWorldEntityCreatedthe world entityOnce, when the world exists. Your Init runs before that, so this is the earliest point at which values on the world can be written.
OnPlayerConnectedplayer, entityA player has connected and their player entity exists.
OnPlayerDisconnectedplayer, entity, reasonA player has left or dropped. A player still in an area leaves it first, so OnPlayerLeftArea comes before this.
OnAreaCreatedarea, entityAn area comes into use: the first player enters it.
OnPlayerJoinedAreaplayer, areaAny player enters an area, the first one included. For the first one it comes after OnAreaCreated.
OnPlayerLeftAreaplayer, areaAny player leaves an area.
OnAreaDeletedarea, entityThe last player has left an area and the server does not keep it. An OblivionMP server keeps every area, so this does not fire today.
OnCellCreatedarea, cell, entityA cell comes into use: the first player loads it.
OnPlayerActivatedCellplayer, area, cellAny player loads a cell, the first one included. For the first one it comes after OnCellCreated.
OnPlayerDeactivatedCellplayer, area, cellAny player unloads a cell.
OnCellDeletedarea, cell, entityThe last player has unloaded a cell and the server does not keep it. An OblivionMP server keeps every cell, so this does not fire today.

An area is a world space. A cell is one of the pieces of an area a player has loaded around them. A cell id is only unique inside its area, which is why the cell events hand you both. See Scopes and World and cells.

The ids are PlayerId, AreaId and CellId. Use them to find the shape you want through IEntities: TryLookup(playerId, out Player player) for a player, TryLookup(areaId, out Area area) for an area, and TryLookup(new FullCellId(areaId, cellId), out Cell cell) for a cell. See Entities and queries.

备注

Not in 1.0 yet: turning the entity an event hands you into a shape. The events pass the server's raw Entity, and no 1.0 API turns it into a Player, Area, Cell or World. Find the shape from the event's ids instead, as above.

Reference​