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

完整示例 ​

这里提供几个可以直接使用的完整插件代码,覆盖欢迎提示、传送命令、区域保护和每日签到。

构建与目标版本

示例以发行 DLL 构建为准:using Game.Server.EssentialCmd;、net8.0、引用 Engine.dll 等发行程序集。若你基于 sc2.2network 源码构建,把命令的 using 改为 Game.Server,目标框架改为 net10.0,并改用项目引用即可。

示例 1:欢迎插件 ​

玩家进入时发送欢迎消息。

csharp
using System;
using Game.Server;
using Game.Server.PlayerEvent;
using Game.NetWork;
using Game.NetWork.Packages;

public class WelcomePlugin : ServerPlugin, IPlayerEnterGameEventHandle
{
    public override string Name => "Welcome";
    public override int Version => 1;
    public byte FirstLevel => 0;

    public override void Initialize()
    {
        PlayerEnterGameEventManager.AddObject(this);
        Console.WriteLine("[Welcome] 插件已加载");
    }

    void IPlayerEnterGameEventHandle.PlayerEnter(PlayerData player)
    {
        SendMessage(player, "欢迎来到服务器!");
        SendMessage(player, "输入 /help 查看命令列表");
    }

    void IPlayerEnterGameEventHandle.PlayerLeave(PlayerData player) { }

    void SendMessage(PlayerData player, string message)
    {
        var pkg = new MessagePackage(null, message, 0, null);
        pkg.To = player.Client;
        CommonLib.Net.QueuePackage(pkg);
    }

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

示例 2:传送命令 ​

/tp <玩家名> 传送到指定玩家。

csharp
using System;
using Engine;
using Game.Server.EssentialCmd;
using Game.NetWork;
using Game.NetWork.Packages;

public class TpCommand : AbstractProcessCmd
{
    public override string Cmd => "tp";
    public override string Introduce => "/tp <玩家名> - 传送到玩家";
    public override int AuthLevel => 0;
    public override DisplayType Display => DisplayType.Private;

    public override void ProcessCmd()
    {
        if (GameManager.Project == null)
        {
            SendMessage("系统", "请先进入游戏");
            return;
        }

        if (m_isTerminal)
        {
            SendMessage("系统", "控制台不能传送");
            return;
        }

        if (m_messageDatas.Length < 2)
        {
            SendMessage("系统", "用法: /tp <玩家名>");
            return;
        }

        string targetName = m_messageDatas[1];
        ComponentPlayer target = FindPlayer(targetName);

        if (target == null)
        {
            SendMessage("系统", $"玩家 {targetName} 不在线");
            return;
        }

        ComponentPlayer me = m_client.PlayerData.ComponentPlayer;
        Vector3 pos = target.ComponentBody.Position;

        me.ComponentBody.Position = pos;
        CommonLib.Net.QueuePackage(new ComponentPlayerPackage(me, (PlayerAction)12));

        SendMessage("系统", $"已传送到 {targetName}");
    }

    ComponentPlayer FindPlayer(string name)
    {
        var players = GameManager.Project.FindSubsystem<SubsystemPlayers>();
        foreach (var p in players.ComponentPlayers)
        {
            if (p.PlayerData.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
                return p;
        }
        return null;
    }
}

示例 3:区域保护 ​

保护指定区域不被破坏。

csharp
using System;
using System.Collections.Generic;
using Engine;
using Game.Server;
using Game.Server.PlayerEvent;
using Game.NetWork;
using Game.NetWork.Packages;

public class ProtectPlugin : ServerPlugin, IPlayerBreakAndPlaceHandle
{
    public override string Name => "Protect";
    public override int Version => 1;
    public byte FirstLevel => 0;

    private List<Zone> zones = new List<Zone>();

    public override void Initialize()
    {
        PlayerBreakAndPlaceBlockEventManager.AddObject(this);

        zones.Add(new Zone {
            Name = "主城",
            X1 = -50, X2 = 50,
            Y1 = 0, Y2 = 100,
            Z1 = -50, Z2 = 50
        });
    }

    bool IPlayerBreakAndPlaceHandle.PlayerBreakEvent(
        ComponentPlayer player, Point3 point, int blockValue, int toolLevel)
    {
        foreach (var z in zones)
        {
            if (z.Contains(point))
            {
                SendMsg(player, $"[{z.Name}] 不能破坏方块");
                return false;
            }
        }
        return true;
    }

    bool IPlayerBreakAndPlaceHandle.PlayerPlaceEvent(
        ComponentPlayer player, Point3 point, int blockValue)
    {
        foreach (var z in zones)
        {
            if (z.Contains(point))
            {
                SendMsg(player, $"[{z.Name}] 不能放置方块");
                return false;
            }
        }
        return true;
    }

    void SendMsg(ComponentPlayer player, string msg)
    {
        var pkg = new MessagePackage(null, msg, 0, null);
        pkg.To = player.PlayerData.Client;
        CommonLib.Net.QueuePackage(pkg);
    }

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

class Zone
{
    public string Name;
    public int X1, X2, Y1, Y2, Z1, Z2;

    public bool Contains(Point3 p)
    {
        return p.X >= X1 && p.X <= X2 && p.Y >= Y1 && p.Y <= Y2 && p.Z >= Z1 && p.Z <= Z2;
    }
}

示例 4:签到命令 ​

csharp
using System;
using System.Collections.Generic;
using System.IO;
using Game.Server.EssentialCmd;
using Game.NetWork;
using Game.NetWork.Packages;
using Newtonsoft.Json;

public class SignCommand : AbstractProcessCmd
{
    public override string Cmd => "sign";
    public override string Introduce => "每日签到";
    public override int AuthLevel => 0;
    public override DisplayType Display => DisplayType.Private;

    private static Dictionary<string, SignData> data = new Dictionary<string, SignData>();
    private static string path = "Plugins/Sign/data.json";

    static SignCommand()
    {
        Directory.CreateDirectory("Plugins/Sign");
        Load();
    }

    public override void ProcessCmd()
    {
        if (m_isTerminal)
        {
            SendMessage("系统", "控制台不能签到");
            return;
        }

        string name = m_client.PlayerData.Name;

        if (!data.ContainsKey(name))
            data[name] = new SignData();

        var d = data[name];

        if (d.Last.Date == DateTime.Today)
        {
            SendMessage("系统", "今天已签到");
            return;
        }

        if (d.Last.Date == DateTime.Today.AddDays(-1))
            d.Continuous++;
        else
            d.Continuous = 1;

        d.Total++;
        d.Last = DateTime.Now;
        Save();

        SendMessage("系统", $"签到成功!连续{d.Continuous}天,累计{d.Total}天");
    }

    static void Load()
    {
        if (File.Exists(path))
        {
            string json = File.ReadAllText(path);
            data = JsonConvert.DeserializeObject<Dictionary<string, SignData>>(json);
        }
    }

    static void Save()
    {
        string json = JsonConvert.SerializeObject(data, Formatting.Indented);
        File.WriteAllText(path, json);
    }
}

class SignData
{
    public int Total;
    public int Continuous;
    public DateTime Last;
}

项目配置文件 ​

创建 MyPlugin.csproj:

xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <OutputType>Library</OutputType>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="Engine">
      <HintPath>游戏目录\Engine.dll</HintPath>
    </Reference>
    <Reference Include="GameEntitySystem">
      <HintPath>游戏目录\GameEntitySystem.dll</HintPath>
    </Reference>
    <Reference Include="LiteNetLib">
      <HintPath>游戏目录\LiteNetLib.dll</HintPath>
    </Reference>
    <Reference Include="Newtonsoft.Json">
      <HintPath>游戏目录\Newtonsoft.Json.dll</HintPath>
    </Reference>
  </ItemGroup>
</Project>

编译和安装 ​

  1. 用 Visual Studio 或 dotnet build 编译;
  2. 将 .dll 复制到服务器的 Plugins 文件夹;
  3. 重启服务器。

小贴士 ​

  1. 从简单开始:先写欢迎插件测试。
  2. 逐步添加:确认基础功能正常再加功能。
  3. 本地测试:测试没问题再放正式服。
  4. 保留日志:用 Console.WriteLine 或 Log.Information 调试。
参与共建发现错误或有建议?两种零门槛方式,无需本地环境

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