SC服务端插件接口文档

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

1
2
3
4
5
using Game.Server;
using Game.Server.Event;
using Game.Server.PlayerEvent;
using Game.Server.Terminal;
using Engine;

插件加载规则

插件基类为 Game.Server.ServerPlugin

1
2
3
4
5
6
7
8
9
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") 或当前世界目录,避免写入程序目录失败。

最小插件示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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)

1
2
3
4
5
public override void Initialize()
{
MessageEventManager.AddObject(this);
PlayerEnterGameEventManager.AddObject(this);
}

返回值约定:

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

消息与告示牌接口

IMessageEventHandle

命名空间:Game.Server.Event

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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

1
2
3
4
5
6
7
8
9
public interface IMutableMessageEventHandle
{
void ReceiveMessage(
string playerName,
NetNode netNode,
Client From,
ref string message,
byte messageType);
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 拒绝连接。

注册:

1
BanEventManager.AddObject(this);

IPasswordValidationEventHandle

命名空间:Game.Server.Event

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 会作为失败提示。

注册:

1
PasswordValidationEventManager.AddObject(this);

玩家生命周期接口

IPlayerEnterGameEventHandle

命名空间:Game.Server.PlayerEvent

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

注册:

1
PlayerEnterGameEventManager.AddObject(this);

IPlayerIntoPlayingHandle

命名空间:Game.Server.PlayerEvent

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

注册:

1
PlayerIntoPlayingEventManager.AddObject(this);

玩家行为接口

IPlayerBreakAndPlaceHandle

命名空间:Game.Server.PlayerEvent

1
2
3
4
5
6
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 可阻止放置或破坏方块。

注册:

1
PlayerBreakAndPlaceBlockEventManager.AddObject(this);

IPlayerInteractEventHandle

命名空间:Game.Server.PlayerEvent

1
2
3
4
5
6
7
8
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 可拦截对应行为。

注册:

1
PlayerInteractEventManager.AddObject(this);

IPlayerNetInteractEventHandle

命名空间:Game.Server.PlayerEvent

1
2
3
4
5
6
7
8
9
10
public interface IPlayerNetInteractEventHandle
{
byte FirstLevel { get; }

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

注册:

1
PlayerNetInteractEventManager.AddObject(this);

IPlayerMoveHandle

命名空间:Game.Server.PlayerEvent

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

注册:

1
PlayerMoveEventManager.AddObject(this);

IPlayerPositionSetHandle

命名空间:Game.Server.PlayerEvent

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

注册:

1
PlayerPositionSetEventManager.AddObject(this);

背包与容器接口

IPlayerInventoryHandle

命名空间:Game.Server.PlayerEvent

1
2
3
4
5
6
7
8
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 可阻止对应背包操作。

注册:

1
PlayerInventoryEventManager.AddObject(this);

IPlayerInventoryOpenHandle

命名空间:Game.Server.PlayerEvent

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

注册:

1
PlayerInventoryOpenEventManager.AddObject(this);

方块、地形与环境接口

IBlockChangeEventHandle

命名空间:Game.Server.Event

1
2
3
4
5
6
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 在地形内容生成后触发,可用于调整区块内容。

注册:

1
BlockChangeEventManager.AddObject(this);

IExplodeEventHandle

命名空间:Game.Server.Event

1
2
3
4
5
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 通常可取消爆炸破坏效果。

注册:

1
ExplodeEventManager.AddObject(this);

IFireEventHandle

命名空间:Game.Server.Event

1
2
3
4
5
6
7
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 可阻止对应点火或火焰生成。

注册:

1
FireEventManager.AddObject(this);

生物接口

ICreatureHealthEventHandle

命名空间:Game.Server.Event

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

注册:

1
CreatureHealthEventManager.AddObject(this);

ICreatureSpawnEventHandle

命名空间:Game.Server.Event

1
2
3
4
5
6
7
8
9
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 可阻止原刷新更新逻辑继续执行。

注册:

1
CreatureSpawnEventManager.AddObject(this);

服务器命令接口

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

1
2
3
4
5
6
7
8
9
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:不在帮助中显示。

命令示例:

1
2
3
4
5
6
7
8
9
10
11
12
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

1
2
3
4
5
6
public interface IPromptProvider
{
int Priority { get; }
IEnumerable<ConsolePart> GetPromptParts(TerminalContextForPart ctx);
float RefreshInterval => 10f;
}

注册:

1
TerminalManager.RegisterPrompt(new MyPromptProvider());

IStatusProvider

命名空间:Game.Server.Terminal

1
2
3
4
5
6
7
public interface IStatusProvider
{
int Priority { get; }
IEnumerable<ConsolePart> GetStatusParts(TerminalContextForPart ctx);
float RefreshInterval => 10f;
bool IsFullLine => false;
}

注册:

1
TerminalManager.RegisterStatus(new MyStatusProvider());

插件工程建议

外部插件项目通常引用:

1
2
3
<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 扩展实现。
  • 若插件提示找不到接口,优先检查引用的服务端程序集版本,以及是否添加正确命名空间:
1
2
using Game.Server.Event;
using Game.Server.PlayerEvent;