本文档以当前源码为准,整理服务端插件开发时常用的生命周期、加载规则、命令接口、事件接口与注册方式。所有服务端插件代码需要在 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 ; 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 ) ; }
注册:
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 ) ; }
注册:
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 ) ; }
注册:
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;