来源:http://nukkit.ru/threads/99 (Nukkit 俄语社区)// 原标题:С чего начать создание плагинов // 原作者:fromgate // 原语言:俄语 // 译者:Snake1999
本教程为翻译俄语社区的帖子。原帖为系列教程,我翻译时统一在本帖整理,为方便阅读和理解,本文章经过译者重新排版并分页。为适合国内朋友阅读,本教程部分语句按照国人习惯的方法来表达,可能与原文有出入,还请见谅。为便于理解对原文补充的部分,因为语法的差异必须要修改的部分,还有译者的吐槽,会用灰色字体表达。翻译不易,转载请联系作者。 —— Snake1999
更新于2019年7月21日:更换了所有图片的图床,现在图片应该可以正常显示了
因为考虑到流量党,第一页不放图,全文篇幅较长,请翻页查看。如果俄语能力好的话,可以去原帖和原文作者交流。本教程仅考虑使用电脑和 IntelliJ IDEA 作为开发环境的情况。
我写了几篇关于编写 Nukkit 教程的文章。我认为这能帮助一些 Nukkit 开发的新手,让他们可以踏出 Nukkit 插件开发的第一步。这个教程包含写作插件的教程,你可以为在公共场合发布而编写开源插件,也可以给你的服务器定制插件。
文章从这里开始:
1. 第一个插件
2. 使用配置文件
3. 事件处理
4. 命令和命令组
祝你好运!
(译者:请翻页来继续阅读。)
本文纯文本全长超过三万字,足足翻译了一下午{:10_505:}如果觉得我的文章好的话,请不要吝啬你的积分和人气{:10_509:}我会去翻译更多更棒的文章{:10_512:}谢谢各位支持,哪怕说一句“楼主辛苦了”也是对我最好的鼓励{:10_508:}
本页文章来源:http://nukkit.ru/threads/81(Nukkit 俄语社区)// 原标题:С чего начать: Первый плагин
我们来看如何创建第一个插件,对那些 Nukkit 插件的新手我们做一点插件的介绍。
1. 选择开发使用的 IDE
虽然在有些情况下你可以用记事本来写代码,但是如果我们用一个特殊的编辑器,那将是很方便的。IDE,即集成开发环境,是辅助编程开发的软件。使用合适的IDE能有效地减少代码语法错误、编译麻烦等问题,加大开发效率。以前我在写bukkit插件或者别的 Java 应用时,我用 eclipse 来开发。但是我写 Nukkit 插件时,我发现使用 IntelliJ IDEA 将是更方便的。
2. 获得 IntelliJ IDEA
至于软件,我们需要从它的官网上下载。现在需要看这个链接:https://www.jetbrains.com/idea/download/

在这里,我们下载“Community(社区)”的版本,它对于大多数的,越来越多的 Nukkit 插件的开发已经是绰绰有余。
3. 安装并运行
我们看到了类似的这样的信息:(你的左边的项目列表应该是空的)

4. 创建项目
点击“Create New Project(创建新项目)”按钮,出现一个窗口,你需要选择项目创建的类型。我通常选择 Maven 管理的项目 - 如果有必要的话,它会提供让我们可以使用 Maven 仓库来管理的优势。原则上我们需要选择适合 Java 开发的项目类型。

如果你选择了 Maven,你只需要点击“下一步”,然后填写相关的 Maven 项目的参数:

你必须指定 GroupId 和 ArtifactId 值。在 Maven 的每个项目中,必须设置有这两个独特的 GroupId 和 ArtifactId。为了避免冲突和混淆,GroupId我们通常冠名为组织或者项目的网站,而 ArtifactId 通常是项目的名称。
译者:这里有必要做一些详细说明。GroupId 经常也是 Java 项目的包名。Java 项目的包命名规则有这样的约定:
- 以 网站倒着写.项目名字 或者 网站倒着写.项目名字.模块名字 为结构
- 必须以小写字母开头
- 必须与别的 Java 包相区别
Nukkit 内核的所有包都是遵循这个规定的 Nukkit 的网站是 nukkit.cn,所以里面包含所有方块(block)的包的包名应该是:
- cn.nukkit.block
各位在编写 Nukkit 插件的时候,需要编写一个独特的、和他人的项目不一样的包名,以便与他人开发的插件相区别。比如译者的网站是 snake1999.com,如果译者编写了一个叫 ExamplePlugin 的 Nukkit 插件,就可以放在包名为 com.snake1999.exampleplugin 的包内。以下包名的结构都是可以使用的:而以下的包名都是不被推荐使用的:
- com.snake1999.exampleplugin
- net.mcbbs.tutorialplugin
- ru.nukkit.nkexample
- me.fromgate.firstplugin
另外,开发 Nukkit 插件,我们规定不能把插件的任何部分存放在 cn.nukkit 包下,否则后果自负。
main.java.pluginTestPlugin
关于主类的名称,只要能和别人的主类区分开就可以了,但是不推荐使用MainClass之类的名称。
指定完成后,我们点击“Finish”按钮,现在这个项目已经创建完成了!

5. 创建一个项目包
在左边的侧边栏中,展开项目结构,展开src文件夹 - 我们需要在 src/main/java 文件夹内编写我们的代码。鼠标右键点击,创建一个新的包:

输入包的名称:

点击“OK”,需要的创建完成了!
6. 设置依赖库
我们写 Nukkit 的插件,意味着我们在 Nukkit 原有的功能上做修改或添加。这意味着我们把 Nukkit 内核本身作为一个依赖库。要做到这一点,我们来打开“Project Structure(项目结构)”窗口:

在这个窗口中,选择“Libraries(依赖库)”,点击“+”,在弹出的菜单中点击“Java”:
(译者:原文作者写这篇教程时,Nukkit 官方尚未推送内核到 Maven 仓库。以后这里会有更简单的方法)

在此之后,系统会提示你选择磁盘上的文件 - 我们将选择 Nukkit 服务器的核心 jar 包(需要单独放置在一个文件夹):

7. 创建项目主类
我们应该在先前项目自动生成的包里面创建这个主类:

输入类的名称:

类已经创建完毕了:

8. 创建 plugin.yml
每个插件都有 plugin.yml 文件。这里介绍一些设置,说明这个文件的格式。插件没有这个文件,就不会被 Nukkit 识别和加载。
这个文件我们放进 resources(资源) 文件夹:

这个文件名当然是 plugin.yml:

这个文件的全文应该是类似这样的:
- name: FirstPlugin
- main: me.fromgate.firstplugin.FirstPlugin
- version: "0.0.1"
- author: fromgate, nukkit.ru
- api: ["1.0.0"]
- description: My first plugin
- name - 插件的名称
- main - 插件主类的完整路径,包括包名和主类名
- version - 版本号,注意这里必须用引号包围,否则会出现错误。(再次感谢 @andylizi 指出typo)
- api - 这个插件支持的 Nukkit API 版本号。目前总是写["1.0.0"]
- description - 插件的简要说明
顺便一提,这个消息出现的时候不要害怕:

它建议你安装一个 IntelliJ IDEA 的插件,来支持 yml 格式文档更高级的编辑。如果你不需要的话,可以点击"Ignore extension"。
9. 编写一个简单的代码
我们回到插件的主类。需要做这几点:在名称后面添加" extends PluginBase",这意味着我们的插件需要继承 PluginBase 类才能让 Nukkit 可以识别。
我们在主类中创建这样的方法:
- @Override
- public void onEnable(){
- this.getLogger().info(TextFormat.RED+"My first plugin enabled");
- }
我们的第一个插件已经完成了!你可以把他编译,然后就可以使用。

10. 编译插件
我们再次按下"Project Structure",转到"Artifacts"栏,点击加号再选择"jar"->"Empty":

在这个界面你需要做的有:
- 填写名称("Name")
- 指定编译输出的目录(我这里填了测试用服务器的插件目录)
- 在右边 "Available Elements(可用项)" 中双击 "FirstPlugin" 来编译输出
- 勾选"Build On Make(在编译时生成)"

点击"OK", 回到编辑器,按下 ctrl+F9,然后等待编译

编译输出的插件的路径在刚才已经制定过了。你现在可以运行服务器来检查插件!
11. 检查插件
要做到这一点,我们只需要运行服务器然后看控制台:

如果你看到了我们输出的消息,或许这就意味着我们刚才做的一切有了成果。祝贺吧!
下页:使用配置文件
本页文章来源:http://nukkit.ru/threads/85(Nukkit 俄语社区)// 原标题:С чего начать: Работа с файлами конфигурации
插件配置文件 - 它是存储设置和少量信息的一个重要途径。基本的配置文件是插件数据文件夹内的 config.yml 文件,要使用这个文件不需要通过它的名字来引用它,可以通过一些简单的方法,比如 PluginBase 类里已经定义的 getConfig()、saveConfig()、reloadConfig()。
要创建一个这样的文件,我们通常拷贝已有的配置好的模板,里面包含了默认的设置。举个不远的例子 - nukkit.yml,这就是一个配置文件,在 Nukkit 的核心中已经打包并准备好,包含着服务器的设置,并在使用前被拷贝出来。
我们将以上一节中创建的 FirstPlugin 为例子,来讲解配置文件的使用。
1. 创建一个配置模板

在这里,我们给它命名为 config.yml:

我们来添加这样的内容:
译者:这里是翻译成中文的配置,原文请看折叠:
- # 示例配置文件
- #
- # 选项:hello-message - 是在服务器启动时将要显示的消息
- #
- hello-message: 从配置文件读出来的欢迎消息!

这个模板文件已经编写完成了。你在编译插件时,它将会被包括在插件的jar文件内。
现在,你需要插件在第一次启动时自动复制jar文件内的配置文件。
2. 创建一个用于存储在插件目录的配置文件的方法
(译者:这段文字原文发布时API并未更新,所以较麻烦,我根据更新的内容重新写一下)
其实非常简单,你只需要一个方法 - saveDefaultConfig()
我们添加这个方法:
- public void initConfig(){
- saveDefaultConfig();
- }
原文的方法,有兴趣的话可以看看:
3. 读取 config.yml 中的参数
我希望在插件中定义一个变量,然后读取配置文件存入这个变量,让插件工作:
- String helloMessage;
因此,我们这样写代码来读取配置文件到 helloMessage 变量:
(译者:我翻译一下:Сообщение по умолчанию 默认消息)
- public void loadCfg(){
- this.reloadConfig();
- this.helloMessage = this.getConfig().getString("hello-message","Сообщение по умолчанию");
- }
这样一来,我们的 onEnable 方法是这样的:
- @Override
- public void onEnable(){
- this.initConfig();
- this.loadCfg();
- this.getLogger().info(TextFormat.RED+this.helloMessage);
- }
好了,整个插件看起来像这样:

4. 编译,看结果
我们按 ctrl+F9 运行服务器:

正如你所看到的,这个消息已被成功地从配置文件中读取。
如果检查服务器上的插件目录,你会看到FirstPlugin目录已经被创建,然后出现了config.yml文件。
下页:事件处理
本页文章来源:http://nukkit.ru/threads/97(Nukkit 俄语社区)// 原标题:С чего начать: Обработка событий
1. 事件的基本信息
玩家在游玩服务器时,与服务器的其他玩家和物体会发生一些事件:玩家的移动、玩家发送指令、破坏或放置方块,雪花降落到地面,爬行者的爆炸,和其它诸如此类的事件。当这样的事件发生的时候,服务器会关心那些插件需要的事件。一个插件也许能影响或改变则个事件:插件可以把闯入他人领地的玩家踢出游戏,在玩家发送广告前将其拦截,防止玩家更改领地内的方块,清除下雪天气,把残忍的苦力怕变成可爱的(译者:噗)。如果我们忽略这段文字中的文学成分,我们能说,服务器内存在某个事件的监听器 - 这是事件系统中最重要的部分,几乎所有的插件都与处理事件相关。
现在获取事件列表,最好的方法是翻找 github 页面事件处理过程本身需要这样实现:
- 插件“表达”它处理事件的愿望,为这个事件编写处理程序,并注册为监听器。事件的处理程序可以作为一个独立的类(数量不限),或者可以和插件主类共用一个类。
- 在处理程序中表达插件处理这个事件的方法。这些方法将参与到服务器处理整个事件的过程。
作为例子,我们给 FirstPlugin 插件添加一个监听器,能在玩家进入服务器时弹出消息。
2. 创建一个监听器
创建一个监听器,可以和 FirstPlugin 共用一个类,然而我更喜欢把事件处理单独写一个类。要开始创建,我们需要在包里面创建一个新的类:

我们叫它 FirstListener 好了:

要把它变为一个事件监听器类,我们需要在类名后面添加" implements Listener":

3. 注册监听器类
现在,我们已经创建了一个监听器。虽然它事实上是空的,但是我们依然可以注册这个监听器到 Nukkit。我们现在就开始做吧,以防之后的开发过程中忘记了这一步,来造成调试上不必要的麻烦。
要注册一个监听器类,在插件主类中的配置文件后面,添加一些这样的代码:
- this.getServer().getPluginManager().registerEvents(new FirstListener(),this);

现在所有的监听器已经被注册了。
顺便一提,我想重点说一个内容。我可以创建一个事件监听器的匿名内部类后马上使用 registerEvents 来注册。但是,一般情况下这个监听器类和插件是分离的,这个类只是注册事件方法的一个作为参数的单独对象。匿名累不累只能工作在一种情况下 - 那就是你的监听器需要引用插件主类的其它方法,并以某种形式使用它。在大多数情况下,我们不需要引用插件主类,所以我们就可以把事件监听器类和插件主类分开。
4. 创建处理事件的方法
要实现插件对事件的处理,你需要在监听器类中添加一个方法。我们来创建一个空的(至少现在是空的)方法:
- public void onPlayerJoin (PlayerJoinEvent event){
- }
然而,这个方法事实上并不会在事件发生的时候被调用。我们还需要一个注解 - @EventHandler:
- @EventHandler (ignoreCancelled = true, priority = EventPriority.NORMAL)
- public void onPlayerJoin (PlayerJoinEvent event){
-
- }
一般情况下,你直接在方法前添加 @EventHandler,然后就可以工作了。然而,我习惯于把所有参数都写全。这些参数是:
- ignoreCancelled = true - 意思是如果这个事件被别的插件取消了,那 Nukkit 就不会调用我们的插件的事件监听器的这个方法。
- priority = EventPriority.NORMAL - 这个决定你的事件监听器的优先级。低优先级的监听器(LOW, LOWEST)会被首先调用,然后高优先级的(HIGH, HIGHER, MONITOR)会在随后调用. 在99%的情况下用普通优先级(NORMAL)就够了.
现在让我们完善我们的监听器,让它能够在玩家加入时显示之前在配置文件中读取的字符串。
5. 从事件监听类获取插件主类的方法和变量
要处理事件,我们需要在另一个类中获取插件主类的消息。为了能够访问插件,我们给 FirstPlugin 类添加 getPlugin 的静态方法,这个方法将返回插件对象本身。要做到这一点,在 FirstPlugin 类中创建一个静态变量,这个变量将会通过这个方法返回:
- private static FirstPlugin plugin;
我们在 onEnable 中初始化这个变量:
- plugin = this;
因此,我们这样写 getPlugin 方法:
- public static FirstPlugin getPlugin(){
- return plugin;
- }

6. 实现监听器
我们回到监听器类。我想要显示“[NEWS]消息”格式并带颜色的新闻,我们可以使用 TextFormat 工具类中的 colorize 函数,这当然是一个好消息。(译者:顺便一提,Nukkit 中的 colorize 函数就是原文作者本人编写的)信息的颜色我们可以在配置文件中设置。我们这样开始:
- @EventHandler (ignoreCancelled = true, priority = EventPriority.NORMAL)
- public void onPlayerJoin (PlayerJoinEvent event){
- event.getPlayer().sendMessage(TextFormat.colorize("&3[NEWS] &b"+FirstPlugin.getPlugin().helloMessage));
- }

我们可以修改插件的默认配置文件:
- # 示例配置文件
- #
- # 选项:hello-message - 是在服务器启动时将要显示的消息
- #
- hello-message: '&6服务器已经安装了 &4FirstPlugin&6!'
(译者:原文看折叠:
要让默认配置文件生效,记得把旧的文件夹和里面的旧配置文件删除。
然后我要提到一点。字符串内直接使用一些 YAML 的特殊字符时,比如字符“&”,会引起 YAML 引擎报错。要避免这个错误发生 - 请使用引号把字符串包围。
7. 编译!
按 ctrl+F9,启动服务器!

我们看到我们的消息已经被显示了。祝贺!
下页:命令和命令组
本页文章来源:http://nukkit.ru/threads/98(Nukkit 俄语社区)// 原标题:С чего начать: Команды
我们来继续提高我们的插件 - 添加能够输出信息的能力之后,我们还要能够检查玩家是否拥有某个权限 - 来做到检查这个玩家是否有权限执行这个指令。有时候,有些指令只有某些特定的玩家可以使用。
1. 注册命令
首先你需要告诉服务器你的插件拥有一个命令。可以有两种方式完成 - 在 plugin.yml 里编写,或者在复杂的情况下可以添加一个自己的命令组。我们使用在 plugin.yml 中的方法,因为在多数情况下这已经足够了。
我们在 plugin.yml 中添加这些内容,来定义一个命令:
- commands:
- news:
- aliases: ["info"]
- description: Show (and set current news)
- usage: "/news [set <Anouncement>]"
在这里做一些解释:
- "news" - 指的是我们的命令组
- "aliases" - 指的是这个命令所有的名称。需要注意的是,这应该是一个字符串的列表,我们就必须使用["alias1", "alias2"]这样的形式。
- "description" - 指的是命令组的说明(会在服务器的 /help 菜单中显示)
- "usage" - 这个命令的使用用法
2. 添加代码
如果这个插件的 plugin.yml 定义了一些命令组,Nukkit 会解析命令,并在调用时调用插件类中的 onCommand 方法。要在命令被执行时做出操作,需要重写这个方法:
- @Override
- public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-
- }
这个方法的参数是这样的:
- sender - 是谁调用了这个命令组
- command - 这个命令组的 Command 对象
- label - 这个命令组被执行时,所使用的命令名称。这里有可能会有同一个命令组以不同的名称显示。
- args - 一个字符串数组,这将是所有的命令参数。
所以我们要加入这个指令 /news,需要在聊天窗口输入不带参数的这个指令时,显示特定的文字;在带参数时,命令的发送者可以设置显示的文字。这种情况下,我们需要设置命令的权限,来防止有玩家把文字设置成广告。此外,检查权限不会伤害到被检查的玩家。代码这样写:
- @Override
- public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
- Player player = (sender instanceof Player) ? (Player) sender : null;
- if (args.length==0){
- if (player!=null&&!player.hasPermission("firstplugn.news.show"))
- sender.sendMessage(TextFormat.RED+"You have not enough permissions!");
- else sender.sendMessage(TextFormat.colorize("&3[NEWS] &b"+this.helloMessage));
- } else {
- if (player!=null&&!player.hasPermission("firstplugn.news.set"))
- sender.sendMessage(TextFormat.RED+"You have not enough permissions!");
- else {
- StringBuilder sb = new StringBuilder();
- for (String s : args) {
- if (sb.length() > 0) sb.append(" ");
- sb.append(s);
- }
- if (player != null) sb.append(" &3(").append(player.getName()).append(")");
- this.helloMessage = sb.toString();
- this.getConfig().set("hello-message",this.helloMessage);
- this.saveConfig();
- sender.sendMessage(TextFormat.colorize("&6You configured new announcement:"));
- sender.sendMessage(TextFormat.colorize("&3[NEWS] &b"+this.helloMessage));
- }
- }
- return true;
- }
编译运行,启动,然后....

祝贺!我们的插件正常工作!
3. 多个指令
需要注意的是,如果用 plugin.yml 定义的方法,定义多个指令时下面的代码将变得及其庞大并难以阅读,不建议在指令数量多的情况下使用 plugin.yml 的方法定义指令,这个方法只适用于少量的指令,而且是最基础的方法。下面演示的只是一个例子。
- @Override
- public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
- if (command.getLabel().equalsIgnoreCase("command1")){
- // 执行指令 command1
-
- } else if (command.getLabel().equalsIgnoreCase("command2")){
- // 执行指令 command2
-
- } else return false;
- return true;
- }
插件注册大量的指令有其它的方法,这里不详细讲了,可以看 Command 类的 javadoc。我的建议是,在少量指令的情况下,可以用 plugin.yml 这种很方便的方式。
全文完
本文纯文本全长超过三万字,足足翻译了一下午{:10_505:}如果觉得我的文章好的话,请不要吝啬你的积分和人气{:10_509:}我会去翻译更多更棒的文章{:10_512:}谢谢各位支持,哪怕说一句“楼主辛苦了”也是对我最好的鼓励{:10_508:}

大爱
啤?
,不得不又来找你了,我又遇到一个问题,我想了半天了,就是:如何在监听器类的一个事件里面,如果事件被触发,如何把一些指定的数据创建到配置文件里面

