Skip to content
服务器插件与终端接口

适用范围

本文记录的是社区服务端项目接口,不是 Candy Rufus Games 官方原版客户端的公共 API。代码需要在对应源码分支的 SERVER 构建中使用;接口名称和行为可能随社区项目版本变化。

本文档以当前源码为准,整理服务端插件开发时常用的生命周期、加载规则、命令接口、事件接口与注册方式。所有服务端插件代码需要在 SERVER 构建中使用,常见命名空间包括:

csharp
using Game.Server;
using Game.Server.Event;
using Game.Server.PlayerEvent;
using Game.Server.Terminal;
using Engine;

插件加载规则

插件基类为 Game.Server.ServerPlugin

csharp
public abstract class ServerPlugin
{
    public abstract int Version { get; }
    public abstract string Name { get; }
    public abstract void Initialize();
    public abstract void Load();
    public abstract void Save();
    public virtual void Update(float dt) { }
}
  • 外部插件 DLL 放在服务端插件目录 Plugins 下,由 ServerManager.LoadPluginsDll() 扫描。
  • DLL 中所有继承 ServerPlugin 且非抽象的类会被实例化。
  • DLL 中所有继承 AbstractProcessCmd 且非抽象的命令类会自动注册到 CmdManager
  • 内置插件和外部插件都会执行 Initialize();世界加载后执行 Load(),保存时执行 Save(),服务端每帧执行 Update(dt)
  • 事件订阅建议放在 Initialize(),避免 Load() 多次执行导致重复订阅。
  • 插件配置建议存到 Storage.GetSystemPath("app:/Configs") 或当前世界目录,避免写入程序目录失败。

最小插件示例

csharp
using Game.Server;

namespace MyPlugin;

public class HelloPlugin : ServerPlugin
{
    public override int Version => 10000; // 1.0.0
    public override string Name => "示例插件";

    public override void Initialize()
    {
    }

    public override void Load()
    {
    }

    public override void Save()
    {
    }

    public override void Update(float dt)
    {
    }
}

事件注册约定

大多数事件都有对应的 XxxEventManager.AddObject(this)RemoveObject(this)

csharp
public override void Initialize()
{
    MessageEventManager.AddObject(this);
    PlayerEnterGameEventManager.AddObject(this);
}

返回值约定:

  • 多数 bool 返回值表示是否允许原行为继续执行。
  • 返回 false 通常表示拦截、取消或禁止该行为。
  • 管理器一般会遍历所有处理器,只要任意处理器返回 false,最终结果就是 false
  • FirstLevel 多数接口暂未完整实现排序,建议默认返回 0

消息与告示牌接口

IMessageEventHandle

命名空间:Game.Server.Event

该接口保持旧插件兼容,告示牌相关插件应继续实现此接口。

csharp
public interface IMessageEventHandle
{
    byte FirstLevel { get; }

    void ReceiveMessage(
        string playerName,
        NetNode netNode,
        Client From,
        string message,
        byte messageType,
        out bool External);

    bool EditSignMessage(Point3 point, ComponentPlayer componentPlayer);
}

说明:

  • ReceiveMessage 在服务端收到聊天消息时触发。
  • External 用于标记外部消息显示样式;旧插件可继续使用。
  • EditSignMessage 在玩家请求编辑告示牌时触发,返回 false 可阻止打开编辑界面。
  • 当前版本为了兼容旧插件,IMessageEventHandle 不再使用 ref string message,也不再把告示牌接口改成 void

示例:

csharp
public class SignGuardPlugin : ServerPlugin, IMessageEventHandle
{
    public override int Version => 10000;
    public override string Name => "告示牌保护";
    public byte FirstLevel => 0;

    public override void Initialize()
    {
        MessageEventManager.AddObject(this);
    }

    public bool EditSignMessage(Point3 point, ComponentPlayer componentPlayer)
    {
        return componentPlayer != null;
    }

    public void ReceiveMessage(string playerName, NetNode netNode, Client From, string message, byte messageType, out bool External)
    {
        External = false;
    }

    public override void Load() { }
    public override void Save() { }
}

IMutableMessageEventHandle

命名空间:Game.Server.Event

新插件如果需要修改玩家聊天内容,可以额外实现该扩展接口。它不会替代 IMessageEventHandle

csharp
public interface IMutableMessageEventHandle
{
    void ReceiveMessage(
        string playerName,
        NetNode netNode,
        Client From,
        ref string message,
        byte messageType);
}

示例:

csharp
public class WordReplacePlugin : ServerPlugin, IMessageEventHandle, IMutableMessageEventHandle
{
    public override int Version => 10000;
    public override string Name => "聊天替换";
    public byte FirstLevel => 0;

    public override void Initialize()
    {
        MessageEventManager.AddObject(this);
    }

    public void ReceiveMessage(string playerName, NetNode netNode, Client From, string message, byte messageType, out bool External)
    {
        External = false;
    }

    public void ReceiveMessage(string playerName, NetNode netNode, Client From, ref string message, byte messageType)
    {
        message = message.Replace("bad", "***");
    }

    public bool EditSignMessage(Point3 point, ComponentPlayer componentPlayer) => true;
    public override void Load() { }
    public override void Save() { }
}

玩家连接与验证接口

IBanEventHandle

命名空间:Game.Server

csharp
public interface IBanEventHandle
{
    byte FirstLevel { get; }

    bool IsBan(
        string nickname,
        string id,
        string password,
        string ip,
        NetNode netNode,
        Client client,
        out bool UseExternalPassword);

    bool IsBanIp(string ip, NetNode netNode, ConnectionRequest request);
}
  • IsBan 返回 true 表示拒绝玩家进入。
  • UseExternalPassword 可告知后续密码验证走外部逻辑。
  • IsBanIp 返回 true 表示按 IP 拒绝连接。

注册:

csharp
BanEventManager.AddObject(this);

IPasswordValidationEventHandle

命名空间:Game.Server.Event

csharp
public interface IPasswordValidationEventHandle
{
    byte FirstLevel { get; }

    bool ValidatePassword(
        string inputPassword,
        string serverPassword,
        bool useExternalPassword,
        string nickname,
        string communityAccountId,
        string ipAddress,
        NetNode netNode,
        Client client,
        out string errorMessage);
}
  • 用于自定义密码验证。
  • 返回值表示验证是否通过。
  • errorMessage 会作为失败提示。

注册:

csharp
PasswordValidationEventManager.AddObject(this);

玩家生命周期接口

IPlayerEnterGameEventHandle

命名空间:Game.Server.PlayerEvent

csharp
public interface IPlayerEnterGameEventHandle
{
    byte FirstLevel { get; }
    void PlayerEnter(PlayerData playerData);
    void PlayerLeave(PlayerData playerData);
}
  • PlayerEnter 在玩家进入服务器后触发。
  • PlayerLeave 在玩家离开服务器前触发。

注册:

csharp
PlayerEnterGameEventManager.AddObject(this);

IPlayerIntoPlayingHandle

命名空间:Game.Server.PlayerEvent

csharp
public interface IPlayerIntoPlayingHandle
{
    byte FirstLevel { get; }
    void PlayerIntoPlayingEvent(ComponentPlayer componentPlayer);
}
  • 玩家实体进入正式 Playing 状态时触发。

注册:

csharp
PlayerIntoPlayingEventManager.AddObject(this);

玩家行为接口

IPlayerBreakAndPlaceHandle

命名空间:Game.Server.PlayerEvent

csharp
public interface IPlayerBreakAndPlaceHandle
{
    byte FirstLevel { get; }
    bool PlayerPlaceEvent(ComponentPlayer componentPlayer, Point3 point, int placeBlockValue);
    bool PlayerBreakEvent(ComponentPlayer componentPlayer, Point3 point, int digBlockValue, int toolLevel);
}
  • 返回 false 可阻止放置或破坏方块。

注册:

csharp
PlayerBreakAndPlaceBlockEventManager.AddObject(this);

IPlayerInteractEventHandle

命名空间:Game.Server.PlayerEvent

csharp
public interface IPlayerInteractEventHandle
{
    byte FirstLevel { get; }
    bool Interact(ComponentPlayer componentPlayer, CellFace cellFace);
    bool Use(ComponentPlayer componentPlayer, object raycast, int activeBlockValue);
    bool Hit(ComponentPlayer componentPlayer, ComponentBody componentBody, Vector3 hitPoint, Vector3 hitDirection);
    bool Aim(ComponentPlayer componentPlayer, int activeBlockValue, Ray3 aim, AimState state);
}
  • 与方块交互、使用物品、击打、瞄准相关。
  • 返回 false 可拦截对应行为。

注册:

csharp
PlayerInteractEventManager.AddObject(this);

IPlayerNetInteractEventHandle

命名空间:Game.Server.PlayerEvent

csharp
public interface IPlayerNetInteractEventHandle
{
    byte FirstLevel { get; }

    bool PlayerNetInteractEvent(
        ComponentPlayer player,
        ComponentPlayer.InteractEvent m_interactEvent,
        Ray3 m_netInteractRay,
        TerrainRaycastResult? m_netInteractRaycast);
}
  • 在接收到玩家网络交互包时触发,比部分本地交互接口更靠近网络层。
  • 返回 false 可阻止该网络交互。

注册:

csharp
PlayerNetInteractEventManager.AddObject(this);

IPlayerMoveHandle

命名空间:Game.Server.PlayerEvent

csharp
public interface IPlayerMoveHandle
{
    byte FirstLevel { get; }
    bool PlayerMoveEvent(ComponentPlayer componentPlayer, Vector3 position);
}
  • 玩家移动时触发。
  • 返回 false 可阻止移动。

注册:

csharp
PlayerMoveEventManager.AddObject(this);

IPlayerPositionSetHandle

命名空间:Game.Server.PlayerEvent

csharp
public interface IPlayerPositionSetHandle
{
    byte FirstLevel { get; }
    bool OnPlayerPositionSet(ComponentPlayer componentPlayer);
}
  • 玩家位置被强制设置时触发。
  • 处理器会按 FirstLevel 从小到大排序。
  • 返回 false 可阻止位置设置。

注册:

csharp
PlayerPositionSetEventManager.AddObject(this);

背包与容器接口

IPlayerInventoryHandle

命名空间:Game.Server.PlayerEvent

csharp
public interface IPlayerInventoryHandle
{
    byte FirstLevel { get; }
    bool PlayerDrop(ComponentPlayer componentPlayer);
    bool PlayerDrapDrop(ComponentPlayer componentPlayer, Vector3 worldPos, InventoryDragData inventoryDragData, int count);
    bool PlayerHandleMoveItem(ComponentPlayer componentPlayer, IInventory sourceInventory, int sourceSlotIndex, IInventory targetInventory, int targetSlotIndex, int count);
    bool PlayerHandleDragDrop(ComponentPlayer componentPlayer, IInventory sourceInventory, int sourceSlotIndex, DragMode dragMode, IInventory targetInventory, int targetSlotIndex, bool processingOnly);
}
  • 拦截玩家丢弃、拖拽丢弃、移动物品、拖拽物品。
  • 返回 false 可阻止对应背包操作。

注册:

csharp
PlayerInventoryEventManager.AddObject(this);

IPlayerInventoryOpenHandle

命名空间:Game.Server.PlayerEvent

csharp
public interface IPlayerInventoryOpenHandle
{
    byte FirstLevel { get; }
    bool PlayerOpenInventoryEvent(PlayerData playerData, IInventory inventory);
    bool PlayerOpenPointInventoryEvent(PlayerData playerData, Point3 point, IInventory inventory);
}
  • 打开容器界面时触发。
  • PlayerOpenPointInventoryEvent 会额外提供容器位置。
  • 返回 false 可阻止打开。

注册:

csharp
PlayerInventoryOpenEventManager.AddObject(this);

方块、地形与环境接口

IBlockChangeEventHandle

命名空间:Game.Server.Event

csharp
public interface IBlockChangeEventHandle
{
    byte FirstLevel { get; }
    bool ChangeCell(int x, int y, int z, int oldValue, int newValue, ComponentMiner componentMiner);
    void OnTerrainContentsGenerated(TerrainUpdater terrainUpdater, TerrainChunk chunk);
}
  • ChangeCell 在方块值变化时触发,返回 false 可阻止变化。
  • OnTerrainContentsGenerated 在地形内容生成后触发,可用于调整区块内容。

注册:

csharp
BlockChangeEventManager.AddObject(this);

IExplodeEventHandle

命名空间:Game.Server.Event

csharp
public interface IExplodeEventHandle
{
    byte FirstLevel { get; }
    void Explode(int x, int y, int z, ref float pressure, bool isIncendiary, bool noExplosionSound, PlayerData miner);
}
  • 爆炸时触发。
  • 可修改 pressure,设置为 0 通常可取消爆炸破坏效果。

注册:

csharp
ExplodeEventManager.AddObject(this);

IFireEventHandle

命名空间:Game.Server.Event

csharp
public interface IFireEventHandle
{
    byte FirstLevel { get; }
    bool Fire(Ray3 ray, ComponentMiner componentMiner);
    bool FireTerrain(CellFace cellFace, ComponentMiner componentMiner);
    bool OnFireGeneration(int x, int y, int z, ref float spreadability);
}
  • Fire:火柴向实体等目标点火时触发。
  • FireTerrain:火柴向地形点火时触发。
  • OnFireGeneration:火方块/火实体生成时触发,可修改 spreadability
  • 返回 false 可阻止对应点火或火焰生成。

注册:

csharp
FireEventManager.AddObject(this);

生物接口

ICreatureHealthEventHandle

命名空间:Game.Server.Event

csharp
public interface ICreatureHealthEventHandle
{
    byte FirstLevel { get; }
    bool Heal(ComponentHealth componentHealth, float amount);
    bool Injure(ComponentHealth componentHealth, float amount, ComponentCreature attacker, string cause);
}
  • 生物治疗或受伤时触发。
  • 返回 false 可阻止治疗或伤害。

注册:

csharp
CreatureHealthEventManager.AddObject(this);

ICreatureSpawnEventHandle

命名空间:Game.Server.Event

csharp
public interface ICreatureSpawnEventHandle
{
    byte FirstLevel { get; }
    bool Update(SubsystemCreatureSpawn subsystemCreatureSpawn, float dt);
    void OnEntityAdded(SubsystemCreatureSpawn subsystemCreatureSpawn, Entity entity);
    void OnEntityRemoved(SubsystemCreatureSpawn subsystemCreatureSpawn, Entity entity);
    void OnPlayerSpawned(PlayerData playerData, Entity playerEntity, Vector3 position);
    void InitCreatureTypes(SubsystemCreatureSpawn subsystemCreatureSpawn, List<SubsystemCreatureSpawn.CreatureType> creatureTypes);
}
  • 可参与生物刷新、实体添加移除、玩家出生、生物类型初始化。
  • Update 返回 false 可阻止原刷新更新逻辑继续执行。

注册:

csharp
CreatureSpawnEventManager.AddObject(this);

服务器命令接口

命令类继承 Game.Server.AbstractProcessCmd。外部 DLL 中非抽象命令类会自动注册。

csharp
public abstract class AbstractProcessCmd
{
    public abstract string Cmd { get; }
    public abstract string Introduce { get; }
    public abstract int AuthLevel { get; }
    public abstract DisplayType Display { get; }
    public abstract void ProcessCmd();
    public virtual IEnumerable<string> GetSuggestions(string[] args);
}

DisplayType

  • All:所有人都可以在帮助中看到。
  • Authority:权限达到后可见。
  • NoDisplay:不在帮助中显示。

命令示例:

csharp
public class CmdHello : AbstractProcessCmd
{
    public override string Cmd => "hello";
    public override string Introduce => "/hello - 测试命令";
    public override int AuthLevel => 0;
    public override DisplayType Display => DisplayType.All;

    public override void ProcessCmd()
    {
        SendMessage("Hello", "插件命令执行成功");
    }
}

终端提示符与状态栏接口

这些接口用于增强服务端终端显示,只有增强终端模式支持;普通终端下会使用 NullPromptManager / NullStatusManager 降级。

IPromptProvider

命名空间:Game.Server.Terminal

csharp
public interface IPromptProvider
{
    int Priority { get; }
    IEnumerable<ConsolePart> GetPromptParts(TerminalContextForPart ctx);
    float RefreshInterval => 10f;
}

注册:

csharp
TerminalManager.RegisterPrompt(new MyPromptProvider());

IStatusProvider

命名空间:Game.Server.Terminal

csharp
public interface IStatusProvider
{
    int Priority { get; }
    IEnumerable<ConsolePart> GetStatusParts(TerminalContextForPart ctx);
    float RefreshInterval => 10f;
    bool IsFullLine => false;
}

注册:

csharp
TerminalManager.RegisterStatus(new MyStatusProvider());

插件工程建议

外部插件项目通常引用:

xml
<ProjectReference Include="..\..\EngineServer\EngineServer.csproj" />
<ProjectReference Include="..\..\EntitySystemServer\EntitySystemServer.csproj" />
<ProjectReference Include="..\..\SurvivalcraftServer\SurvivalcraftServer.csproj" />

建议:

  • 插件 DLL 放到服务端 Plugins 目录。
  • 插件类和命令类都必须是 public 且非抽象类。
  • 订阅事件放在 Initialize()
  • 配置读取放在 Load(),保存放在 Save()
  • Update(dt) 中避免阻塞 IO、网络请求和长耗时计算。
  • 需要世界路径时,可参考箱子锁插件按当前世界目录保存配置。
  • 不要在日志中打印敏感 token、密码、玩家隐私信息。

当前兼容性说明

  • IMessageEventHandle 已恢复旧签名,旧告示牌/聊天插件优先兼容。
  • 新的聊天内容修改能力通过 IMutableMessageEventHandle 扩展实现。
  • 若插件提示找不到接口,优先检查引用的服务端程序集版本,以及是否添加正确命名空间:
csharp
using Game.Server.Event;
using Game.Server.PlayerEvent;

玩家共建文档 · 原版、插件版、联机版与插件联机版请注意区分