前言
本教程旨在指导插件开发入门,而非C#编程入门。
关于C#的基础知识,请参阅B站相关教程。
作者水平有限,文档可能存在不足之处。
本教程以Windows系统为例进行演示(建议使用Windows 10或更高版本)。
如有任何段落难以理解或存在错误,欢迎提出建议。
@TOc
Part 0.配置开发环境
本章内容:
- 安装Microsoft Visual Studio的步骤
1.下载Microsoft Visual Studio安装程序
访问Visual Studio官方下载页面:Visual Studio下载
选择社区(专业版,企业版为付费版本)
2.运行VisualStudioSetup.exe
按照提示继续,开始安装Visual Studio安装器。
3.选择.NET桌面开发并安装
注意:
请不要随意勾选组件和工作负载,以免占用过多存储空间。
修改安装位置可能导致安装问题,建议使用默认设置。
若需修改安装设置,请确保您有足够的把握。
4.等待安装完成
注意:
如提示需要重启,请按照提示操作。
5.启动Visual Studio
Visual Studio通常位于开始菜单下,如果没有快捷方式,可以按照以下方法创建。
首次启动可能需要登录,登录后可按照个人喜好选择主题(如黑色主题)。
Part 1.编写你的第一个插件
本章内容:
- 使用Visual Studio创建插件项目
- 使用NuGet程序包管理器添加插件依赖项
- 初步了解编写插件的流程
1.创建新项目
选择类库选项,然后点击下一步。
注意:
确保选择的是C#、.NET、库。
项目名称应填写插件名称,位置建议使用默认设置,具体可参考下图配置。
注意:
项目名称应仅包含字母(A-Z,a-z)、数字(0-9)、下划线(_),并具有实际意义。
请勿使用中文。
解决方案中可以包含多个插件项目,方便管理多个插件代码,并方便项目间互相引用。
必须选择.NET 6.0,否则可能会引发插件兼容性问题。
2.进入VS项目
对于熟悉C#的您,VS界面应不陌生,但请牢记下图中的代码区域。
*重命名(非必要)
在解决方案资源管理器中找到相应的.cs文件,可以右键进行重命名。
同样需要遵循命名规范,避免使用中文。
3.添加TShock插件依赖项
TShock插件依赖多个相关程序集,您可以通过NuGet程序包快速引用它们。
在解决方案资源管理器中右键依赖项,选择管理NuGet程序包。
按照下图步骤安装TShock的NuGet程序包,安装完成后点击确定、接受。
4.开始编写代码
目前您可能还不会编写代码,但可以先复制以下TShock插件模板:
//代码来源:https://github.com/chi-rei-den/PluginTemplate/blob/master/src/PluginTemplate/Program.cs
//恋恋的TShock插件模板,有改动(为了配合章节名)
//来自棱镜的插件教程
using System;
using System.Collections.Generic;
using System.Reflection;
using Terraria;
using TerrariaApi.Server;
using TShockAPI;
namespace Plugin
{
[ApiVersion(2, 1)]
public class Plugin : TerrariaPlugin
{
//定义插件的作者名称
public override string Author => "插件的作者";
//插件的一句话描述
public override string Description => "插件的描述";
//插件的名称
public override string Name => "插件的名字";
//插件的版本
public override Version Version => Assembly.GetExecutingAssembly().GetName().Version;
//插件的构造器
public Plugin(Main game) : base(game)
{
}
//插件加载时执行的代码
public override void Initialize()
{
//恋恋给出的模板代码中展示了如何为TShock添加一个指令
Commands.ChatCommands.Add(new Command(
permissions: new List<string> { "plugin.permission1", "plugin.permission2" },
cmd: this.Cmd,
"helloworld", "hw"));
}
//插件卸载时执行的代码
protected override void Dispose(bool disposing)
{
if (disposing)
{
//移除所有由本插件添加的所有指令
var asm = Assembly.GetExecutingAssembly();
Commands.ChatCommands.RemoveAll(c => c.CommandDelegate.Method?.DeclaringType?.Assembly == asm);
}
base.Dispose(disposing);
}
//执行指令时对指令进行处理的方法
private void Cmd(CommandArgs args)
{
args.Player.SendSuccessMessage("Hello world!");
}
}
}
相信你看到这串代码大概是一脸懵逼的,但是没关系,你只要知道它的功能是添加命令/helloworld,执行此命令会向执行命令的人发送一个绿色的HelloWorld!
粘贴代码后,当你发现代码编辑区左下角的错误数已经变成0之后,恭喜你可以开始编译插件了
注:如果有错误存在,可能是你复制漏了某些地方。
5.编译生成你的插件
点击工具栏处的绿色小三角(或者快捷键Ctrl+B)即可开始编译
开始编译后,输出区会打印编译的一些日志,最后打印编译结果
当出现"1 成功"的字样,说明插件代码已经成功编译了
6.找到并安装你的插件
在解决方案资源管理器右键当前项目(HelloWorld),点击在文件资源管理器中打开文件夹
此时会蹦出一个文件夹,按照下图目录找到net6.0文件夹
将所示两个文件移动或复制到TShock目录中的ServerPlugins文件夹中。
7.测试插件
启动TShock后,可以发现插件已被成功识别并安装。
使用/help命令查看命令列表,插件添加的命令应已显示在列表中。
执行/helloworld命令,控制台应显示绿色Hello world!
这表明插件功能正常。
Part 2.初步了解TShock插件模板
本章内容:
TShock插件模板各部分的作用
TShock插件的初始化顺序
插件模板
以下是一个常用的TShock插件模板,基本与Part 1中的模板相同,只是移除了HelloWorld命令。
//代码来源:https://github.com/chi-rei-den/PluginTemplate/blob/master/src/PluginTemplate/Program.cs
//恋恋的TShock插件模板,有改动(为了配合章节名)
//来自棱镜的插件教程
using System;
using System.Collections.Generic;
using System.Reflection;
using Terraria;
using TerrariaApi.Server;
using TShockAPI;
namespace Plugin
{
[ApiVersion(2, 1)]
public class Plugin : TerrariaPlugin
{
//定义插件的作者名称
public override string Author => "插件的作者";
//插件的一句话描述
public override string Description => "插件的描述";
//插件的名称
public override string Name => "插件的名字";
//插件的版本
public override Version Version => Assembly.GetExecutingAssembly().GetName().Version;
//插件的构造器
public Plugin(Main game) : base(game)
{
}
//插件加载时执行的代码
public override void Initialize()
{
}
//插件卸载时执行的代码
protected override void Dispose(bool disposing)
{
if (disposing)
{
}
base.Dispose(disposing);
}
}
}
插件基本信息
以下代码可以设置插件的一些基本信息,插件名建议按照功能命名,不要起奇怪名字(例如"炸裂")
//定义插件的作者名称
public override string Author => "插件的作者";
//插件的一句话描述
public override string Description => "插件的描述";
//插件的名称
public override string Name => "插件的名字";
//插件的版本
public override Version Version => Assembly.GetExecutingAssembly().GetName().Version;
Author(插件作者)、Name(插件名字)、Version(插件版本)将会在插件初始化完成时显示
其中,Version可以通过new Version修改,如下:
//插件的版本
public override Version Version => new Version(1, 0, 0, 0);
//public override Version Version => Assembly.GetExecutingAssembly().GetName().Version;
使用new Version()将会显示此方法参数为版本号(例如本例子中的"1.0.0.0)
而使用Assembly.GetExecutingAssembly().GetName().Version获取的是程序集的版本号,
插件版本号设置
在设置插件版本号时,你有两种方法可以选择:
- 使用
new Version()
- 使用
new Version()
将会显示此方法参数为版本号(例如本例子中的"1.0.0.0")。 - 使用
Assembly.GetExecutingAssembly().GetName().Version
- 使用
Assembly.GetExecutingAssembly().GetName().Version
获取的是程序集的版本号,程序集版本号需要在项目属性中修改。
修改程序集版本
- 右键解决方案资源管理器中的项目(这里是HelloWorld),然后点击属性。
- 在属性选项卡中依次找到包—常规—程序集版本。
- 修改程序集版本即可。
插件的构造器
插件的构造器,也叫构造函数,构造函数中的代码会比Initialize(初始化)函数更先执行。服务端会先按照读取到插件的顺序(取决于插件的文件名)执行所有插件的构造函数,再按照Order(等下会讲)由小到大执行Initialize(初始化)函数。
// 插件的构造器
public Plugin(Main game) : base(game)
{
base.Order = 1; //插件加载顺序,默认为1,数字越小越先加载
}
在构建函数中修改Order
构造函数常常会用来修改插件的初始化顺序—Order。Order是一个int类型的整数,Order可以在取int类型范围内的任何数,当然也可以是负数。Order越小插件的初始化就越优先,Order为1的插件会比Order为2的插件先初始化, TShock的Order为0,插件的Order在默认情况下(不修改)为1,而TShock的Order为0,所以TShock会比其他插件更先初始化。
插件的初始化函数
初始化函数是TShock插件中非常重要的部分,这个函数主要负责插件的初始化,可以用来注册钩子、添加命令、注册REST API命令、初始化配置文件、初始化数据库等。
// 插件加载时执行的代码
public override void Initialize()
{
ServerApi.Hooks.ServerChat.Register(this, this.OnChat); //注册钩子
Commands.ChatCommands.Add(new Command(permissions: new List<string> {""}, cmd: this.Fish, "钓鱼排行")); //添加命令
TShock.RestApi.Register(new SecureRestCommand("/XSB/GetMap", GetMap, "rest.xsb.admin")); //注册REST API命令
Config.GetConfig(); //初始化配置文件
DB.Connect(); //初始化数据库
}
插件的卸载函数
相较于初始化函数,卸载函数显得没什么存在感(你甚至可以不写卸载函数)。但是为了代码规范,还是建议你把注册过的钩子卸载,删除添加的命令。
// 插件卸载时执行的代码
protected override void Dispose(bool disposing)
{
if (disposing)
{
ServerApi.Hooks.ServerChat.Deregister(this, this.OnChat); //卸载钩子
Commands.ChatCommands.RemoveAll(c => c.CommandDelegate.Method?.DeclaringType?.Assembly == Assembly.GetExecutingAssembly());
//移除插件添加的命令
}
base.Dispose(disposing);
}
Author, Name, Version 设置
其中,Version可以通过 new Version 修改,如下:
// 插件的版本
public override Version Version => new Version(1, 0, 0, 0);
// public override Version Version => Assembly.GetExecutingAssembly().GetName().Version;
使用 new Version() 将会显示此方法参数为版本号(例如本例子中的"1.0.0.0")。而使用 Assembly.GetExecutingAssembly().GetName().Version 获取的是程序集的版本号,程序集版本号需要在项目属性中修改。
Extra Part. 一些额外的东西
本章你将学到:
- Visual Studio常见奇怪问题解决办法
- 创建新的插件项目
- 让Visual Studio自动复制插件、重载调试服务器
Visual Studio常见奇怪问题解决办法
解决方案资源管理器消失
-
在工具栏中选择视图。
-
点击解决方案资源管理,然后就会发现解决方案资源管理又回来啦!
注:当然输出、错误列表等没了也可以用这个办法。
在中间输入代码会删除后面的代码
可能是你按到INS键切换成了OVR模式,此时代码编辑区的右下角就会显示OVR。
工具栏下方出现报错横幅
如果你看到如下提示,那么恭喜你,你下载的是正版的Microsoft Visual Studio。这些报错会导致代码提示等出现故障。
解决办法:重新启动Visual Studio。
创建新的插件项目
- 在解决方案资源管理器中右键解决方案。
- 选择添加,点击新建项目,然后重复和Part1一样的步骤即可。
- 点击工具栏启动的白色小箭头,再点击配置启动项目。
- 按照下图修改配置。
生成后事件(自动复制插件/重启服务器)
-
在解决方案资源管理器中右键项目。
-
点击属性。
-
在属性选项卡找到生成—事件—生成后事件。
-
如果你只想自动复制插件在其中输入下面的命令即可。
copy bin\Debug\net6.0\*.dll C:\Users\13110\Desktop\code\TShock144\ServerPlugins copy bin\Debug\net6.0\*.pdb C:\Users\13110\Desktop\code\TShock144\ServerPlugins
注:C:\Users\13110\Desktop\code\TShock144\ServerPlugins要替换为自己调试服务器的插件目录
(可选)自动重启服务器
如果你是像我这样的终极懒鬼,你可以尝试一下下面的方法让你解放双手。
先在生成后事件加上一句
taskkill /F /IM TShock.Server.exe
新建自启动脚本(如果你已经有了可以跳过这个步骤),然后编辑个打开记事本编辑以下内容,并保存为编码UTF-8,后缀.bat的文件,放在和TShock.Server.exe同目录。
chcp 65001
@echo off
@echo.*自启动脚本 by Cai
set restart=1
:restart
set ip=0.0.0.0
set port=7777
set map_path=C:\Users\13110\Desktop\code\TShock144\测试.wld
set lang=7
@echo.*服务器准备启动: 语言: %lang%,IP: %ip%,端口: %port%,地图路径: %map_path%
TShock.Server.exe -lang %lang% -ip %ip% -port %port% -world %map_path%
@echo.*服务器崩溃准备重启, 目前重启次数%restart%
timeout /t 0 /NOBREAK
set/a restart=restart+1
goto restart
注:map_path要改成自己地图的路径(注意\使用/可能无法识别),port改成自己想要的端口,ip请不要更改(0.0.0.0是绑定本机所有IP的意思,请你不要自以为是的修改),lang 7是中文语言启动(你没必要修改)。
Part 3. 添加新命令
本章你将学到:
- 如何在TShock插件中添加新命令
- 学会处理CommandArgs参数
- 学会卸载插件添加的命令(养成好习惯)
添加新的命令
在Part 1中有提到,添加命令通常是在插件的初始化函数 Initialize()
中添加的。添加新命令的常用格式是这样的:
public override void Initialize()
{
Commands.ChatCommands.Add(new Command("mycommand", Cmd, "cmd2"));
// Command() 中第一个参数是权限名,第二个参数是回调函数,第三个参数是命令名
}
当你输入代码后由于没有相应的函数(Cmd)会报错,此时我们右键我们需要创建的函数名,然后选择快速操作和重构,然后选择生成方法 “Cmd”。
private void Cmd(CommandArgs args)
{
args.Player.SendSuccessMessage("你使用了cmd2命令!");
}
命令的权限
public override void Initialize()
{
Commands.ChatCommands.Add(new Command("mycommand", Cmd, "cmd2"));
}
上面的示例代码中,当玩家输入 “/cmd2” 命令并且拥有 “mycommand” 权限时,就会向玩家发送一条绿色的消息,内容为 “你使用了cmd2命令!”,若玩家没有相应权限则会提示玩家无权执行该命令,并且日志会记录玩家试图执行命令的行为并发送给打开接受日志的玩家。
权限也可以设为无或者多个权限,如下代码可以添加3条分别为 /cmd1(无权限)、/cmd2(单权限)、/cmd3(多权限)的命令:
public override void Initialize()
{
Commands.ChatCommands.Add(new Command(Cmd, "cmd1")); // 不设置权限
Commands.ChatCommands.Add(new Command("mycommand", Cmd, "cmd2")); // 设置一个权限
Commands.ChatCommands.Add(new Command(new List<string> { "mycommand", "mycommand2" }, Cmd, "cmd3")); // 设置多个权限
}
执行上面的命令,都会调用 “Cmd” 函数,他们的区别在于权限。
命令的别名
有时候一个命令可以由两个名字对应,例如:/off 和 /exit 他们属于同一个命令,/exit 就是 /off 的别名。我们可以通过下面的代码实现命令别名:
public override void Initialize()
{
Commands.ChatCommands.Add(new Command("cancanneed", Vme, "vme50", "v我50"));
}
csharp
private void Vme(CommandArgs args)
{
args.Player.GiveItem(74, 50, 0); // 给玩家50个铂金币
}
上述代码中,当玩家使用 /vme50 或者 /v我50 且有 “cancanneed” 权限,就会给予这个玩家50铂金币。此时 “/v我50” 就是 “/vme50” 的别名。
命令的其他属性
TShock 的命令还有 4 个属性,这些属性是 AllowServer、DoLog、HelpDesc、HelpText,你可以使用 new Command(){ 属性 = 值 } 来修改他们,也可以用如下方法一次修改多个属性:
public override void Initialize()
{
Commands.ChatCommands.Add(new Command(Vme, "giveme50", "送我50")
{
AllowServer = false,
DoLog = true,
HelpDesc = new string[]{ "帮助文档", "这是第一行" },
HelpText = "帮助文本"
});
}
CommandArgs 参数
当玩家执行我们添加的命令时,服务器会把一些相关的信息 “打包” 传给我们的回调函数,这些信息就是 CommandArgs。
private void Cmd(CommandArgs args)
{
// CommandArgs 参数处理
}
卸载你添加的命令
使用下面的代码就可以卸载本插件添加的所有命令:
protected override void Dispose(bool disposing)
{
if (disposing)
{
var asm = Assembly.GetExecutingAssembly();
Commands.ChatCommands.RemoveAll(c => c.CommandDelegate.Method?.DeclaringType?.Assembly == asm);
}
base.Dispose(disposing);
}
Part 4. 挂钩钩~
本章你将学到:
- 学习如何注册、卸载钩子
- 学会使用常用钩子修改游戏事件
- 钩子可以干啥?
- 当你注册一个事件的钩子后,在触发这个事件时,钩子就会执行你预先设置的函数(回调函数)。
代码解析:
注册钩子语句 不同类型的钩子有不同的注册语句,ServerApi.Hooks.ServerChat.Register(this, OnChat); 就是 ServerApi 钩子的注册语句,钩子要在使用前注册(一般在 Initialize 注册)。
public override void Initialize()
{
ServerApi.Hooks.ServerChat.Register(this, OnChat); // 注册聊天钩子
}
卸载钩子语句 不同类型的钩子有不同的卸载语句,ServerApi.Hooks.ServerChat.Deregister(this, OnChat); 就是 ServerApi 钩子的卸载语句,钩子要在不需要使用时卸载(一般在 Dispose 卸载)。
protected override void Dispose(bool disposing)
{
if (disposing)
{
ServerApi.Hooks.ServerChat.Deregister(this, OnChat); // 卸载聊天钩子
}
base.Dispose(disposing);
}
回调函数 回调函数是触发钩子时执行的函数,创建方法类似命令的回调函数。
private void OnChat(ServerChatEventArgs args)
{
// 回调函数逻辑
}
常用钩子
ServerApi 钩子 ServerApi 的钩子覆盖了服务器大部分可能需要挂钩的事件,同时值得注意的是 ServerApi 钩子中参数的命名和 Terraria 原版相似。
public override void Initialize()
{
ServerApi.Hooks.ServerChat.Register(this, OnChat); // 注册 ServerApi 钩子(玩家聊天)
}
TShockAPI 钩子
这些钩子主要挂钩了 TShock 中的事件,例如:新建账号、玩家进入区域等,TShockAPI 钩子分为如下几类:
AccountHooks
PlayerHooks
GeneralHooks
RegionHooks
public override void Initialize()
{
TShockAPI.Hooks.AccountHooks.AccountCreate += AccountHooks_AccountCreate; // 注册 TShockAPI 钩子(玩家创建账号)
}
GetDataHandlers 数据包钩子 服务器会收到客户端发送的数据包,而 GetDataHandlers 钩子会解析这些数据包,触发对应的数据包钩子,并且打包成参数传入回调函数,但是 GetDataHandlers 并不包含所有数据包。
public override void Initialize()
{
GetDataHandlers.KillMe.Register(OnKillPlayer); // 注册 GetDataHandlers 数据包钩子(玩家死亡)
}
钩子练习题
使用 ServerApi 钩子实现,在玩家加入服务器时 (ServerJoin, ServerLeave) 广播 “欢迎玩家 XXX 加入服务器!”,在玩家离开服务器时广播 “玩家 XXX 离开服务器!”。
使用 ServerApi 钩子实现,在服务器加载完成后 (GamePostInitialize) 在控制台打印 “服务器已经加载完啦!”。
使用 TShockAPI 钩子实现,在玩家完成登录后 (PlayerHooks.PlayerPostLogin) 给玩家发送 “您登录的 IP 是:xxx.xxx.xxx.xxx”。
使用 TShockAPI 钩子实现,在服务器使用 /reload 命令 (GeneralHooks.ReloadEvent) 后显示 “服务器正在重载!”。
当玩家在墓地环境移动时,通过 TSPlayer.DamagePlayer() 对其造成 5 点伤害。