├── .github └── workflows │ └── blank.yml ├── .idea └── serialmonitor_settings.xml ├── 1-12.png ├── CNAME ├── LICENSE ├── NukkitLearn 教程的编写、排版、格式规范.md ├── README.md ├── README_en.md ├── _config.yml ├── images ├── 0-00.jpg ├── 0-00.png ├── 1-01.png ├── 1-02.png ├── 1-03.png ├── 1-04.png ├── 1-05.png ├── 1-06.png ├── 1-07.png ├── 1-08.png ├── 1-09.png ├── 1-10.png ├── 1-11.png ├── 1-12.png ├── 1-13.png ├── 1-14.png ├── 1-15.png ├── 1-16.png ├── 1-17.png ├── 1-18.png ├── 1-19.png ├── 1-20.png ├── 1-21.png ├── 1-22.png ├── 1-23.png ├── 1-24.png ├── 1-25.png ├── 1-26.png ├── 1-27.png ├── 1-28.png ├── 1-29.png ├── 1-30.png ├── 1-31.png ├── 1-32.png ├── 1-33.png ├── 1-34.png ├── 1-35.png ├── 1-36.png ├── 1-37.png ├── 1-38.png ├── 1-39.png ├── 2-01.png ├── 3-1.png ├── 3-2.png ├── 3-3.png ├── 3-4-01.png ├── 5-01.png ├── 5-02.png ├── 5-03.png ├── 5-04.png ├── chapter2-aboutEventsMethod-1.png ├── chapter2-aboutEventsMethod-10.png ├── chapter2-aboutEventsMethod-2.png ├── chapter2-aboutEventsMethod-3.png ├── chapter2-aboutEventsMethod-4.png ├── chapter2-aboutEventsMethod-5.png ├── chapter2-aboutEventsMethod-6.png ├── chapter2-aboutEventsMethod-7.png ├── chapter2-aboutEventsMethod-8.png ├── chapter2-aboutEventsMethod-9.png ├── zy3-01.png └── zy3-02.png ├── 专栏-关于我们常见的那些坑.md ├── 前言 ├── 前言.md └── 如何搭建环境.md ├── 更新日志 └── CHANGE_LOG.md ├── 章外篇 ├── 章外篇之一-用指令设置玩家实体大小(简单版).md ├── 章外篇之三-使用groovy编写您的项目.md ├── 章外篇之二-多语言解决方案.md ├── 章外篇之四-使用DSL编写您的静态配置文件.md └── 索引-常用工具的网页索引.md ├── 第一章 ├── 1-0_前言.md ├── 1-1_什么是插件.md ├── 1-2_了解PluginBase.md ├── 1-3_如何编写监听器.md ├── 1-4_如何编写命令.md ├── 1-5_如何使用配置文件.md ├── 1-6_如何编写plugin.yml.md └── 1-7_案例玩家进入信息等效果.md ├── 第三章 ├── 3-0_前言.md ├── 3-1_主要的Form种类及介绍.md ├── 3-2_使用NukkitX自带的Form创建UI.md ├── 3-3_使用外部库GUI创建UI.md ├── 3-4_介绍一个新的外部库-Easy4Form.md └── images │ └── 3-2_使用外部库GUI创建UI │ ├── 3-1.png │ ├── 3-2.png │ └── 3-3.png ├── 第不知道多少章 - 示例文档.md └── 第二章 ├── 2-0_前言.md ├── 2-1_主要的事件介绍.md ├── 2-2_事件相关方法.md ├── 2-3_计时器的介绍.md ├── 2-4_Server类和PluginManager类.md ├── 2-5_各种实体类的方法介绍.md ├── 2-6_各种工具类的介绍.md └── 2-7_如何发送数据包.md /.github/workflows/blank.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Run a one-line script 13 | run: echo Hello, world! 14 | - name: Run a multi-line script 15 | run: | 16 | echo Add other actions to build, 17 | echo test, and deploy your project. 18 | -------------------------------------------------------------------------------- /.idea/serialmonitor_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /1-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/1-12.png -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | nl.noyark.net -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 开创者小组-开发公约 v1.1 2 | 3 | 一、"六个禁止" 4 | 1. 小组内成员禁止与他人交易非商业项目, 参与者将会被永久除名。 5 | 2. 小组内成员禁止在得到原作者同意前引用或搬运文章、文段,第一次删除警告,第二次永久除名。 6 | 3. 小组内成员禁止发表损害国家利益的言论和项目,违反者删除,并承担法律责任。 7 | 4. 小组内成员和作品享用者禁止以任何形式互相侮辱。 8 | 5. 小组内成员禁止在项目PR恶意内容和恶意代码。 9 | 6. 小组内成员禁止以小组名义进行任何图利活动。 10 | 11 | 二、"两个注意" 12 | 1. 注意用规范的项目格式贡献您的内容。 13 | 2. 注意您的项目是否支持持续性的开发。 14 | 15 | 三、"四个鼓励" 16 | 1. 我们鼓励发布优秀项目和优秀想法。 17 | 2. 我们鼓励打击损害版权利益的行为。 18 | 3. 我们鼓励您宣传正面形象。 19 | 4. 我们鼓励在论坛允许的情况下,将小组或您的项目发布至各大论坛。 20 | -------------------------------------------------------------------------------- /NukkitLearn 教程的编写、排版、格式规范.md: -------------------------------------------------------------------------------- 1 | # NukkitLearn 教程的编写、排版、格式规范 2 | 3 | >> 前言:由于本教程编写缺少排版的相关规范,造成了~~包括后期在内的~~部分读者在阅读体验上的不满。为了方便众读者~~以及满足某些人想要坐牢的愿望~~,特此制定本规范,统一说明正确的姿势。 4 | 5 | ## To be continued 6 | 还没有讨论出来,快了 ~~(0%)~~ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 史上最详细的Nukkit教程 - NukkitLearn 2 | ###### ——Learning Nukkit whoever you are! 3 | 4 | # 在阅读本教程之前,请务必仔细阅读本文档 5 | 6 | 组内的各位都忙于日常,所以更新速度会降低。如果您有补充教程的意愿,欢迎推PR或入群交流! 7 | 8 | ## Choose your language: 9 | 10 | - [中文(简体)](README.md) 11 | - [English](README_en.md) 12 | 13 | ### 以下是很长一段的说明 14 | 15 | 嗨!您可以在这里以更加舒适的方式阅读本教程:[http://nl.noyark.net/](http://nl.noyark.net/) 16 | 17 | 当然,您也可以通过观看 [Bilibili - NukkitLearn插件编写教程](https://www.bilibili.com/video/BV1R34y1D7FW) 的方式学习Nukkit插件编写。 18 | 视频教程更新速度不定,我们仍然建议您阅读文字学习。 19 | 20 | 本教程遵循[发现者小组公约](LICENSE) 21 | 22 | 23 | #### 您可能需要了解的热知识 24 | 25 | - 阅读本教程之前,请您先预习 Java 基础。 26 | - 催更人请点击右上角。 27 | - 我们不参与任何与 Minecraft 有关,但与小组内所维护的项目无关的活动。 28 | - 您可以就问题发送Issues。回复间隔会长,但不会回答例如 **搭建服务器**、 **Java基础** 等与教程无关的问题。我们仍推荐您加入QQ群交流。 29 | - 实际上,作者因学业等多种原因早已淡坑,**但仍然能保证教程的质量**,请您放心阅读。如您遇到教程内任何的疏漏或 Bug ,请您及时联系作者处理,感谢您的参与。 30 | 31 | #### 作者的扯皮——关于本教程 32 | 33 | Nukkit核心作为一个服务端开发框架,虽然性能优越,吸引了大批的开发者加入开发,但教程稀少,学习难度大,使很多小白望而却步。 34 | 35 | 基于目前很多人对于教程的渴望,包括作者在初学的时候只能通过到处询问和看核心源码来了解如何使用一个东西,但并不是所有的人都能安心去看核心源码或者到处询问问题。同时,本教程会重复强调一件事情:打好基础。 36 | 37 | 本教程**已经列出您需要掌握的Java基础**,什么0基础直接学习Nukkit**都是骗人的**,作者已经见过很多这样的初学者,很少的人通过这种方式学会(无疑是一种懒散的行为), 38 | 39 | Nukkit事实上就是学习一个新的API,并无很大的入门难度。 40 | 只要您学习了基础,就能很快从这里得到启发,并参与到Nukkit的大家庭。 41 | 42 | 主作者其实很和蔼,但是对于简单的问题作者由于时间问题是不能回答的,也不能干刷屏之类的事情,**再和蔼的人也会把你给屏蔽**。 43 | 当然,作者坚持开放原则,如果有相关问题,可以发送Issue提出您的疑问。 44 | 45 | 同时,记得给项目一个star支持一下,并且将这个项目宣传给别人,在支持作者同时,造福他人。您的支持和宣传就是作者写下去的动力, 46 | 47 | 希望这个教程可以帮到您,我们由衷的感谢:) 48 | 49 | --- MagicLu550 50 | 51 | ### 转载说明 52 | 53 | >首先,感谢您对原创作品的认可与支持。您的转载可以让我们的教程受益更多的人。 54 | >在您转载之前,请您首先仔细阅读一段不长的转载说明。 55 | 56 | 您在转载本教程前,**应首先征得作者的同意**,并**在转载注明出处与作者名**。 57 | 如有需要,请联系QQ: 843983728,并**备注您的来意**。这会让效率更加高效。 58 | 59 | **未经作者许可转载本系列文章的任何内容的,将自动视为已向作者支付100元人民币稿酬。** 60 | 收款微信账号: lu843983728; 61 | 62 | 若作者或经举报发现任何形式的侵权行为的, 63 | 组内将联系相关平台进行下架的申诉,**~~并将挂上奇人共赏~~。** 64 | 65 | **本教程完全免费。请勿与他人进行任何交易!** 66 | 67 | **本教程完全免费。请勿与他人进行任何交易!** 68 | 69 | **本教程完全免费。请勿与他人进行任何交易!** 70 | 71 | 如您已与他人交易,请立刻申请退款、向我们举报、发布帖子警告他人避免上当! 72 | 73 | ### 目前由官方承认的几个宣传贴 74 | 75 | MineBBS: [[DreamServer] Nukkit教程整理计划 - 让所有人都能开发nukkit](https://www.minebbs.com/threads/dreamserver-nukkit-nukkit.2912/) 76 | 77 | MCBBS: [[DreamServer] Nukkit教程整理计划 - 让所有人都能开发nukkit](https://www.mcbbs.net/forum.php?mod=viewthread&tid=927009) 78 | 79 | MCBBS - Pioneer Group分支: [[bedrock][DreamServerGroup] Nukkit插件从入门到精通 总集篇](https://www.mcbbs.net/forum.php?mod=viewthread&tid=930861&mobile=2) 80 | 81 | 82 | ### 我们推荐您前往以下站点学习Java基础 83 | 84 | - [菜鸟教程](https://www.runoob.com/java/java-tutorial.html?tdsourcetag=s_pcqq_aiomsg) 85 | - [w3cschool](https://www.w3cschool.cn/java/java-tutorial.html) 86 | - [并发](https://blog.csdn.net/likun557/article/details/100148245) 87 | - [网络](https://www.cnblogs.com/cainiao-chuanqi/p/11338202.html#autoid-3-2-0) 88 | 89 | ### 您所需要掌握的最基本的Java基础体系一览 90 | ``` 91 | --基础部分 92 | -- 语法 93 | -- 变量定义 94 | -- 方法定义 95 | -- 基本表达式 96 | -- 逻辑表达式 97 | -- 算数表达式 98 | -- 流程控制 99 | -- 条件语句 100 | -- 循环语句 101 | -- 选择语句 102 | -- 面向对象 103 | -- 类,对象的概念 104 | -- 定义类和声明对象 105 | -- 包的概念 106 | -- 匿名内部类 107 | -- 接口 108 | -- 抽象类,抽象方法 109 | -- lambda表达式 110 | -- 面向对象的三大特征 111 | -- 继承 112 | -- 封装 113 | -- 多态 114 | -- 枚举 115 | -- 注解 116 | -- 类库 117 | -- 反射 118 | -- 多线程 119 | -- 字符串操作 120 | -- 数字包装类 121 | -- io流 122 | -- bio 123 | -- nio 124 | -- 套接字Socket 125 | -- udp 126 | -- tcp 127 | -- 时间类库 128 | -- 系统类库 129 | 130 | ``` 131 | 132 | ### 133 | 134 | #### 鸣谢 主要编写者 135 | 136 | MagicLu550 绿泡泡 LT_Name Darker 若水 innc11 MayFlyOvO zsy smartcmd ~~愉悦师柑也~~ 137 | 138 | #### 联系我们 139 | 140 | 主作者QQ:843983728 141 | > 请注明来意。 142 | 143 | NukkitLearn官方群QQ:931210534,也可扫下方码入群 144 | > 进群请阅读群规。禁止任何形式的广告。 145 | 146 | ![QQ群二维码](images/0-00.png) 147 | 148 | #### 贡献标准和须知 149 | 150 | 1. 教程要求尽量自然,易懂,符合本项目所追求的"人人可以学习Nukkit"的宗旨 151 | 2. 编写教程可以在参与编写者添加自己的名字,若没有可以自己手动加入,如第一章所同 152 | 3. 照搬其他教程原文需要得到作者同意,并且在下面注明参考文献。 153 | 4. 若发现侵权行为,与本项目领导者无关,但我们会积极配合找到侵权者 154 | 5. 请尽量以第一章为范本编写您的内容。 155 | 6. 如果有知识缺点,请在Readme里注明这个缺点出处,写在知识缺点里,知识缺点在章节中标注。 156 | 7. 教程补充首要的是解决知识缺点 157 | 8. 由于编写人数较少,教程不避免的会有错误,漏洞,不严谨,如果发现,可以发送PR参与修改 158 | 159 | #### 知识缺点: 160 | - 关于fallBackPrefix的作用 - 第二章和第四章 [1] 161 | 162 | #### 专栏 163 | 164 | - [填坑专栏](专栏-关于我们常见的那些坑.md) 165 | 166 | - [用指令设置玩家实体大小](章外篇/章外篇之一-用指令设置玩家实体大小(简单版).md) 167 | 168 | - [多语言解决方案](章外篇/章外篇之二-多语言解决方案.md) 169 | 170 | - [使用groovy编写插件](章外篇/章外篇之三-使用groovy编写您的项目.md) 171 | 172 | - [使用DSL编写配置文件](章外篇/章外篇之四-使用DSL编写您的静态配置文件.md) 173 | 174 | - [常用工具](章外篇/索引-常用工具的网页索引.md) 175 | #### 目录 176 | - 前言 177 | - [前言](前言/前言.md) 178 | - [如何搭建环境](前言/如何搭建环境.md) 179 | - [第一章 基础准备](第一章/1-0_前言.md) 180 | - [X] [如何搭建开发环境](第一章/1-1_什么是插件.md) 181 | - [X] [什么是插件](第一章/1-2_了解PluginBase.md) 182 | - [X] [如何编写监听器](第一章/1-3_如何编写监听器.md) 183 | - [X] [如何编写指令](第一章/1-4_如何编写命令.md) 184 | - [X] [如何使用配置文件](第一章/1-5_如何使用配置文件.md) 185 | - [X] [如何编写plugin.yml](第一章/1-6_如何编写plugin.yml.md) 186 | - [X] [练习案例](第一章/1-7_案例玩家进入信息等效果.md) 187 | - [第二章 nukkit的工具和各种事件介绍](第二章/2-0_前言.md) 188 | - [X] [主要的事件的介绍](第二章/2-1_主要的事件介绍.md) 189 | - [X] [事件相关方法](第二章/2-2_事件相关方法.md) 190 | - [X] [计时器的介绍](第二章/2-3_计时器的介绍.md) 191 | - [X] [Server类和PluginManager类](第二章/2-4_Server类和PluginManager类.md) 192 | - [X] [各种实体类的方法介绍](第二章/2-5_各种实体类的方法介绍.md) 193 | - [ ] [各种工具类的介绍](第二章/2-6_各种工具类的介绍.md) 194 | - [X] [如何发送数据包](第二章/2-7_如何发送数据包.md) 195 | - [第三章 nukkit的UI操作](第三章/3-0_前言.md) 196 | - [X] [主要的Form种类及介绍](第三章/3-1_主要的Form种类及介绍.md) 197 | - [X] [使用NukkitX自带的Form创建UI](第三章/3-2_使用NukkitX自带的Form创建UI.md) 198 | - [X] [使用外部库GUI创建UI](第三章/3-3_使用外部库GUI创建UI.md) 199 | - [X] [新的外部库Easy4Form](第三章/3-4_介绍一个新的外部库-Easy4Form.md) 200 | - 第四章 nukkit实用操作 201 | - [ ] 粒子 202 | - [ ] 药水 203 | - [ ] 坐标计算 204 | - [ ] AI寻路计算 205 | - [ ] 加自定义块自定义实体区块生成 206 | - [ ] 皮革染色 207 | - [ ] NBT的使用方式 208 | - [ ] 客户端的联动 209 | - [ ] 自定义数据包 210 | - 第五章 nukkit相关实例 211 | - 第六章 nukkit原理剖析 212 | 213 | 214 | #### 专栏状况 215 | ```diff 216 | + 专题 217 | + 提示信息 218 | ``` 219 | 220 | ### 友情链接 221 | 222 | 1. ~~云易插件站(升级维护中)~~ 223 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/README_en.md -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /images/0-00.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/0-00.jpg -------------------------------------------------------------------------------- /images/0-00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/0-00.png -------------------------------------------------------------------------------- /images/1-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-01.png -------------------------------------------------------------------------------- /images/1-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-02.png -------------------------------------------------------------------------------- /images/1-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-03.png -------------------------------------------------------------------------------- /images/1-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-04.png -------------------------------------------------------------------------------- /images/1-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-05.png -------------------------------------------------------------------------------- /images/1-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-06.png -------------------------------------------------------------------------------- /images/1-07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-07.png -------------------------------------------------------------------------------- /images/1-08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-08.png -------------------------------------------------------------------------------- /images/1-09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-09.png -------------------------------------------------------------------------------- /images/1-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-10.png -------------------------------------------------------------------------------- /images/1-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-11.png -------------------------------------------------------------------------------- /images/1-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-12.png -------------------------------------------------------------------------------- /images/1-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-13.png -------------------------------------------------------------------------------- /images/1-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-14.png -------------------------------------------------------------------------------- /images/1-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-15.png -------------------------------------------------------------------------------- /images/1-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-16.png -------------------------------------------------------------------------------- /images/1-17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-17.png -------------------------------------------------------------------------------- /images/1-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-18.png -------------------------------------------------------------------------------- /images/1-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-19.png -------------------------------------------------------------------------------- /images/1-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-20.png -------------------------------------------------------------------------------- /images/1-21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-21.png -------------------------------------------------------------------------------- /images/1-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-22.png -------------------------------------------------------------------------------- /images/1-23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-23.png -------------------------------------------------------------------------------- /images/1-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-24.png -------------------------------------------------------------------------------- /images/1-25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-25.png -------------------------------------------------------------------------------- /images/1-26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-26.png -------------------------------------------------------------------------------- /images/1-27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-27.png -------------------------------------------------------------------------------- /images/1-28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-28.png -------------------------------------------------------------------------------- /images/1-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-29.png -------------------------------------------------------------------------------- /images/1-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-30.png -------------------------------------------------------------------------------- /images/1-31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-31.png -------------------------------------------------------------------------------- /images/1-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-32.png -------------------------------------------------------------------------------- /images/1-33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-33.png -------------------------------------------------------------------------------- /images/1-34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-34.png -------------------------------------------------------------------------------- /images/1-35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-35.png -------------------------------------------------------------------------------- /images/1-36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-36.png -------------------------------------------------------------------------------- /images/1-37.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-37.png -------------------------------------------------------------------------------- /images/1-38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-38.png -------------------------------------------------------------------------------- /images/1-39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/1-39.png -------------------------------------------------------------------------------- /images/2-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/2-01.png -------------------------------------------------------------------------------- /images/3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/3-1.png -------------------------------------------------------------------------------- /images/3-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/3-2.png -------------------------------------------------------------------------------- /images/3-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/3-3.png -------------------------------------------------------------------------------- /images/3-4-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/3-4-01.png -------------------------------------------------------------------------------- /images/5-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/5-01.png -------------------------------------------------------------------------------- /images/5-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/5-02.png -------------------------------------------------------------------------------- /images/5-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/5-03.png -------------------------------------------------------------------------------- /images/5-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/5-04.png -------------------------------------------------------------------------------- /images/chapter2-aboutEventsMethod-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/chapter2-aboutEventsMethod-1.png -------------------------------------------------------------------------------- /images/chapter2-aboutEventsMethod-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/chapter2-aboutEventsMethod-10.png -------------------------------------------------------------------------------- /images/chapter2-aboutEventsMethod-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/chapter2-aboutEventsMethod-2.png -------------------------------------------------------------------------------- /images/chapter2-aboutEventsMethod-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/chapter2-aboutEventsMethod-3.png -------------------------------------------------------------------------------- /images/chapter2-aboutEventsMethod-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/chapter2-aboutEventsMethod-4.png -------------------------------------------------------------------------------- /images/chapter2-aboutEventsMethod-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/chapter2-aboutEventsMethod-5.png -------------------------------------------------------------------------------- /images/chapter2-aboutEventsMethod-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/chapter2-aboutEventsMethod-6.png -------------------------------------------------------------------------------- /images/chapter2-aboutEventsMethod-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/chapter2-aboutEventsMethod-7.png -------------------------------------------------------------------------------- /images/chapter2-aboutEventsMethod-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/chapter2-aboutEventsMethod-8.png -------------------------------------------------------------------------------- /images/chapter2-aboutEventsMethod-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/chapter2-aboutEventsMethod-9.png -------------------------------------------------------------------------------- /images/zy3-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/zy3-01.png -------------------------------------------------------------------------------- /images/zy3-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/images/zy3-02.png -------------------------------------------------------------------------------- /专栏-关于我们常见的那些坑.md: -------------------------------------------------------------------------------- 1 | # 填坑专栏 2 | #### 这里整理一些容易出现的坑,这里会有解决方案. 3 | ##### 大家可以把自己平时的坑写进去,写上解决方案 4 | ##### 或者等待别人写上解决方案 5 | 6 | 1. ClassCastException 7 | 这种是类转换问题的锅,可以查看下 8 | 2. EventException: null 9 | 说明在调用事件的时候,发现了空指针异常,这发生在您的编译器 10 | 3. Main类名称指错的问题 11 | 请对应一下plugin.yml 12 | 4. ClassNotFoundException的问题 13 | 一般是缺少类库或者类库版本过新/过旧 14 | 5. NullPointerException的常见问题 15 | 这是空指针异常,请注意您是否忘记判断null了 16 | 6. 忘记注册监听器的坑 17 | 您的监听器跑不起来多半是忘记注册了 18 | 7. 加载插件,加入游戏莫名其妙闪退 19 | 可以注意一下您的plugin.yml缩进是否有问题 20 | 8. 使用setPermission设置的只是对于普通玩家而言看不见,实际上还是能执行 21 | 22 | -------------------------------------------------------------------------------- /前言/前言.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 3 | 您需要掌握一定的Java基础后,再阅读本教程。 4 | 5 | 本教程提供了一套完整的Nukkit插件开发教程。学习本教程后,您就可以独自实现一个属于自己的插件了。 6 | 7 | 再次感谢您阅读本教程。 8 | 9 | [comment]: <> (本部分分为八个小节) 10 | 11 | [comment]: <> ( - [如何搭建开发环境](1-1_如何搭建环境.md)) 12 | 13 | [comment]: <> ( - [插件要素](1-2_插件要素.md)) 14 | 15 | [comment]: <> ( - [如何编写监听器](1-3_如何编写监听器.md)) 16 | 17 | [comment]: <> ( - [如何编写指令](1-4_如何编写命令.md)) 18 | 19 | [comment]: <> ( - [如何使用配置文件](1-5_如何使用配置文件.md)) 20 | 21 | [comment]: <> ( - [如何编写plugin.yml](1-6_如何编写plugin.yml.md)) 22 | 23 | [comment]: <> ( - [PluginBase类](1-7_PluginBase类.md)) 24 | 25 | [comment]: <> ( - [练习案例](1-8_案例玩家进入信息等效果.md)) 26 | 27 | [comment]: <> (每个小节之间都具有关联性,必须看完第一个才可以进行下一个,这样才能条理的学习) 28 | 29 | [下一节](如何搭建环境.md) 30 | -------------------------------------------------------------------------------- /前言/如何搭建环境.md: -------------------------------------------------------------------------------- 1 | # 前言 - 如何搭建环境 2 | 3 | 本小节目录: 4 | 5 | * [一、环境搭建](#一环境搭建) 6 | * [二、调试插件](#二调试插件) 7 | * [三、本地的导入和打包](#三本地的导入和打包) 8 | * [零、IDEA的汉化](#零idea-的汉化) 9 | 10 | --- 11 | 12 | 参与编写者:MagicLu550 13 | 14 | 后期修订:愉悦师柑也 15 | 16 | **本版本可能为非正式版本,但不影响阅读,请您悉知。** 17 | 18 | #### 本小节建议学习时间:40 分钟; 19 | ##### 学习要点: 学会使用IDEA搭建Maven环境和Nukkit开发环境,并学习调试Nukkit项目; 20 | 21 | --- 22 | ### 一、环境搭建 23 | 24 | 搭建环境是我们学习Nukkit的第一步。虽然安装IDE不是必要的一步,但这是我们能以便捷的方式进行开发的第一步。 25 | 26 | 1. 首先上 [IDEA官网](https://www.jetbrains.com/idea/download/) 下载我们所需要的版本,我们一般选择免费的Community版本,因为它已经符合我们的需求了。作者这里使用的是Ultimate版本。 27 | ![1-01](../images/1-01.png) 28 | 29 | 1. 打开IDEA后,我们这里使用 [Maven](https://maven.apache.org/) 项目进行构建。这里使用 [JDK1.8](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) ,只要是图中这样的就可以了。 30 | ![1-02](../images/1-02.png) 31 | 32 | 3.输入 *groupId* 和 *artifactId* (这些是需要你来写的,支持自定义),并且创建项目后,我们会进入到下图的界面。图中的 *artifactId* 为 **MyFirstPlugin**. 33 | ![1-03](../images/1-03.png) 34 | 35 | ``` 36 | 关于groupId为何物,Snake1999做出了非常详细的解释(转): 37 | 这里有必要做一些详细说明。 38 | GroupId 经常也是Java项目的包名。Java项目的包命名规则有这样的约定: 39 | “以网站倒着写.项目名字” 或者 “网站倒着写.项目名字.模块名字” 为结构。 40 | 必须以小写字母开头, 41 | 必须与别的Java包相区别。 42 | Nukkit内核的所有包都是遵循这个规定的, 43 | 例如Nukkit的网站是nukkit.cn,所以里面包含所有方块(block)的包的包名应该是: 44 | cn.nukkit.block 45 | 46 | 各位在编写Nukkit插件的时候,需要编写一个独特的、和他人的项目不一样的包名,以便与他人开发的插件相区别。 47 | 比如译者的网站是snake1999.com,如果译者编写了一个叫ExamplePlugin的Nukkit插件,就可以放在包名为com.snake1999.exampleplugin的包内。以下包名的结构都是可以使用的: 48 | com.snake1999.exampleplugin 49 | net.mcbbs.tutorialplugin 50 | ru.nukkit.nkexample 51 | me.fromgate.firstplugin 52 | 而以下的包名都是不被推荐使用的: 53 | main.java.plugin 54 | TestPlugin 55 | 56 | 另外,开发Nukkit插件,我们规定不能把插件的任何部分存放在cn.nukkit包下,否则后果自负。 57 | 58 | 关于主类的名称,只要能和别人的主类区分开就可以了,但是不推荐使用MainClass之类的名称。 59 | ``` 60 | 61 | 4. 在pom.xml中加入以下标签可以导入 62 | 63 | ```xml 64 | 65 | 66 | 67 | opencollab-repo-release 68 | https://repo.opencollab.dev/maven-releases/ 69 | 70 | true 71 | 72 | 73 | false 74 | 75 | 76 | 77 | opencollab-repo-snapshot 78 | https://repo.opencollab.dev/maven-snapshots/ 79 | 80 | false 81 | 82 | 83 | true 84 | 85 | 86 | 87 | 88 | 89 | 90 | cn.nukkit 91 | nukkit 92 | 1.0-SNAPSHOT 93 | 94 | 95 | ``` 96 | 97 | 如图所示 98 | ![1-04](../images/1-04.png) 99 | 100 | 5. 接下来在程序上可以进行开发了 101 | 102 | [comment]: <> (二. 项目结构) 103 | 104 | [comment]: <> (1.nukkit的基本项目结构为) 105 | 106 | [comment]: <> (```) 107 | 108 | [comment]: <> ( -- project) 109 | 110 | [comment]: <> ( -- src) 111 | 112 | [comment]: <> ( -- main-class) 113 | 114 | [comment]: <> ( -- resources) 115 | 116 | [comment]: <> ( -- plugin.yml) 117 | 118 | [comment]: <> (```) 119 | 120 | [comment]: <> (因此我们需要创建主类和plugin.yml(插件配置文件)) 121 | 122 | [comment]: <> (转自snake1999:) 123 | 124 | [comment]: <> ( - 每个插件都有 plugin.yml 文件。) 125 | 126 | [comment]: <> ( - 这里介绍一些设置,说明这个文件的格式。) 127 | 128 | [comment]: <> ( - 插件没有这个文件,就不会被 Nukkit 识别和加载。) 129 | 130 | [comment]: <> ( - 这个文件的最基本的应该是类似这样的:) 131 | 132 | [comment]: <> (```yaml) 133 | 134 | [comment]: <> ( name: FirstPlugin # nukkit运行时识别的插件名) 135 | 136 | [comment]: <> ( main: net.noyark.www.Example # 主类名称) 137 | 138 | [comment]: <> ( version: "0.0.1" # 版本号) 139 | 140 | [comment]: <> ( author: 你的名字,这里指作者名称) 141 | 142 | [comment]: <> ( api: ["1.0.8"] # 早期nukkit api为1.0.0,) 143 | 144 | [comment]: <> ( # 目前大概为1.0.8) 145 | 146 | [comment]: <> ( description: My first plugin #介绍) 147 | 148 | [comment]: <> (```) 149 | 150 | [comment]: <> (目前大概为这种结构) 151 | 152 | [comment]: <> (![1-05](../images/1-05.png)) 153 | 154 | [comment]: <> (但是,我们项目目前这样是不够的,Example类需要继承PluginBase才可以成为真正可以加载的主类) 155 | 156 | 157 | [comment]: <> (```java) 158 | 159 | [comment]: <> (package net.noyark.www;) 160 | 161 | [comment]: <> (import cn.nukkit.plugin.PluginBase;) 162 | 163 | [comment]: <> (public class Example extends PluginBase {) 164 | 165 | [comment]: <> ( /**) 166 | 167 | [comment]: <> ( * 在服务器加载完成前执行一次) 168 | 169 | [comment]: <> ( */) 170 | 171 | [comment]: <> ( @Override) 172 | 173 | [comment]: <> ( public void onLoad() {) 174 | 175 | [comment]: <> ( }) 176 | 177 | [comment]: <> ( /**) 178 | 179 | [comment]: <> ( * 在服务器加载完成后执行一次) 180 | 181 | [comment]: <> ( */) 182 | 183 | [comment]: <> ( @Override) 184 | 185 | [comment]: <> ( public void onEnable() {) 186 | 187 | [comment]: <> ( }) 188 | 189 | [comment]: <> ( /**) 190 | 191 | [comment]: <> ( * 服务器即将关闭前执行一次) 192 | 193 | [comment]: <> ( */) 194 | 195 | [comment]: <> ( @Override) 196 | 197 | [comment]: <> ( public void onDisable() {) 198 | 199 | [comment]: <> ( }) 200 | 201 | [comment]: <> (}) 202 | 203 | [comment]: <> (```) 204 | 205 | 206 | ### 二、调试插件 207 | 208 | ###### 其实调试插件很简单,这里我们需要以下四个过程: 209 | 210 | > 编译,打包 → 部署 → 运行加载 → 调试 211 | 212 | 1. 编译打包很简单。我们使用IDE可以轻松实现Maven打包。 213 | 214 | PS: 在打包之前,建议在pom.xml里加上以下代码,可以解决IDEA编译器版本无效等一系列问题。 215 | ```xml 216 | 217 | 218 | 219 | 220 | org.apache.maven.plugins 221 | maven-compiler-plugin 222 | 3.2 223 | 224 | 1.8 225 | 1.8 226 | UTF-8 227 | 228 | 229 | 230 | 231 | 232 | ``` 233 | 本项目所采用的JDK版本为JDK8。如果您使用的不是JDK8,则需要**修改一些地方**。 234 | 235 | **JDK9** 236 | ```xml 237 | 238 | 239 | 240 | 241 | org.apache.maven.plugins 242 | maven-compiler-plugin 243 | 3.2 244 | 245 | 1.9 246 | 1.9 247 | UTF-8 248 | 249 | 250 | 251 | 252 | 253 | ``` 254 | 255 | **JDK10** 256 | ```xml 257 | 258 | 259 | 260 | 261 | org.apache.maven.plugins 262 | maven-compiler-plugin 263 | 3.2 264 | 265 | 1.10 266 | 1.10 267 | UTF-8 268 | 269 | 270 | 271 | 272 | 273 | ``` 274 | 以此类推....如果行不通,可以尝试可以删掉前面的“1.” 275 | 276 | 比如,JDK11就是 277 | 278 | ``` 279 | 11 280 | 11 281 | ``` 282 | 283 | 如图所示,添加这些标签: 284 | ![1-08](../images/1-08.png) 285 | 286 | 如图中按钮所示: 287 | ![1-06](../images/1-06.png) 288 | 289 | 之后我们找到其中的package,点击即可。 290 | ![1-07](../images/1-07.png) 291 | 292 | 如图所示,出现 **"BUILD SUCCESS"** ,编译就大功告成了。 293 | ![1-09](../images/1-09.png) 294 | 295 | 一般来讲,**目标jar包**会产生在 target 文件夹中。 296 | ![1-10](../images/1-10.png) 297 | 298 | 1. 部署 299 | 300 | 之后把打包好的合法插件丢入plugins文件里就完美运行了,这里将不做图示 301 | 这就是插件部署的过程。 302 | 303 | 3. 运行加载 304 | 305 | 如果出现了加载$(您的插件名字)成功之类的字样,说明您的插件成功了。 306 | 307 | 4. 之后进入游戏测试您的插件是否符合您的需求,根据您插件出现的各种问题进行再次调试、修改、打包等。 308 | 309 | ### 三、本地的导入和打包 310 | 311 | 鉴于很多人的需要(因为NukkitX的Maven服务器太卡了),这里我写一下本地导入jar包的方法。 312 | 313 | 1. 导入包 314 | 315 | 其实要简单很多,您需要上 [NukkitX的Jenkins](https://ci.nukkitx.com/job/NukkitX/job/Nukkit/job/master/) 下载一个 Nukkit 的jar包, 316 | 317 | 然后将您的jar包放到您项目根目录的libs文件夹下(这里您需要自己创建) 318 | 319 | ![1-11](../images/1-11.png) 320 | 321 | 我们再把它导入我们的项目里, 322 | 323 | 这里选择 libraries: 324 | 325 | ![1-12](../images/1-12.png) 326 | 327 | 选择Java,这样可以直接导入本地jar包。 328 | 329 | ![1-13](../images/1-13.png) 330 | 331 | 之后我们选择下载好的Nukkit的jar包, 332 | 333 | ![1-14](../images/1-14.png) 334 | 335 | 导入完毕, 336 | 337 | ![1-15](../images/1-15.png) 338 | 339 | 1. 构建jar包 340 | 341 | 再然后,我们在libs下创建一个plugins文件夹(先前的点击下面的OK保存一下), 342 | 343 | ![1-16](../images/1-16.png) 344 | 345 | 打开我们的这个窗口,找到artifacts,之后点击 '+' ,找到 JAR, 346 | 347 | ![1-17](../images/1-17.png) 348 | 349 | 创建之后,是这样子(图中已经说明了compile output)。 350 | 351 | ![1-18](../images/1-18.png) 352 | 353 | 然后如图中所示,把 plugin.yml 也添加到我们的jar包里 354 | 355 | ![1-19](../images/1-19.png) 356 | 357 | ![1-20](../images/1-20.png) 358 | 359 | ![1-21](../images/1-21.png) 360 | 361 | 之后我们再修改打包目标的路径,将路径指向我们jar包的根目录 362 | 363 | ![1-24](../images/1-24.png) 364 | 365 | ![1-25](../images/1-25.png) 366 | 367 | ![1-26](../images/1-26.png) 368 | 369 | 记得设置上这个: 370 | 371 | ![1-34](../images/1-34.png) 372 | 373 | 保存。 374 | 375 | ![1-27](../images/1-27.png) 376 | 377 | 378 | 3. 设置工作文件夹 379 | 380 | 最后一步,设置我们的运行环境。如图所示,点击Add Configuration 381 | 382 | ![1-28](../images/1-28.png) 383 | 384 | 接着,我们分别设置jar包和工作文件夹 385 | 386 | ![1-29](../images/1-29.png) 387 | 388 | ![1-30](../images/1-30.png) 389 | 390 | ![1-31](../images/1-31.png) 391 | 392 | ![1-32](../images/1-32.png) 393 | 394 | 最后优雅的点击保存。 395 | 396 | ![1-33](../images/1-33.png) 397 | 398 | 4. 运行 399 | 400 | 我们在这儿先写个主类 401 | 402 | ![1-35](../images/1-35.png) 403 | 404 | 再点击 Build,打包插件 405 | 406 | ![1-36](../images/1-36.png) 407 | 408 | 之后我们把插件放进服务器中跑一遍, 409 | 410 | ![1-37](../images/1-37.png) 411 | 412 | 成功√ 413 | 414 | ![1-38](../images/1-38.png) 415 | 416 | 417 | ### 零、IDEA的汉化 418 | 419 | ~~感谢 Testfor , Itname 等人的帮助,我们找到了 IDEA 的汉化包,[点击这里下载](https://github.com/pingfangx/TranslatorX)~~ 420 | 421 | 上面的汉化包适用于2019.3, 422 | 423 | 截至2021年10月28日,IDEA Community 已经更新至2021.2.3, 424 | 425 | **且官方已支持使用插件进行页面汉化。** 426 | 427 | 以 *愉悦师柑也* 的 *CLion 2021.2.2* 为例,您可以直接前往 **Settings - Plugins** 搜素 *Chinese* 并安装。 428 | 429 | ![1-39](../images/1-39.png) 430 | 431 | ~~NTR了,但没有完全NTR~~ 432 | 433 | [下一节](../第一章/1-0_前言.md) 434 | 435 | 436 | ### 参考文献 437 | - [[教程] [官方] 使用Maven配置Nukkit开发环境的方法(部分)](https://www.mcbbs.net/thread-706178-1-1.html) 438 | - [Nukkit插件从0开始](https://www.cnblogs.com/xtypr/p/nukkit_plugin_start_from_0.html) 439 | - [[教程] [转载] 如何开始制作 Nukkit 插件? Fromgate 的插件教程来了! [长文|多图] ](https://www.mcbbs.net/forum.php?mod=viewthread&tid=552265&page=1&authorid=100001) 440 | -------------------------------------------------------------------------------- /更新日志/CHANGE_LOG.md: -------------------------------------------------------------------------------- 1 | 2020.2.23 2 | - 修改了1-2插件要素的`213`行,由 3 | ```yaml 4 | api: ["1.0.8"] 5 | ``` 6 | 改为 7 | ```yaml 8 | api: ["1.0.9"] 9 | ``` 10 | 2021.5.5 11 | - 整理了第一章的教程结构,减少了各个章节之间的依赖性,使读者更容易上手 12 | - 第三章介绍了Easy4Form库 -------------------------------------------------------------------------------- /章外篇/章外篇之一-用指令设置玩家实体大小(简单版).md: -------------------------------------------------------------------------------- 1 | ## Nukkitlearn章外篇之---用指令设置玩家实体大小(简单版) 2 | 3 | 本章外篇教程由zsy制作,本教程非常的简单,目的是为萌新们打开一个很好的思路 4 | 如果你对本篇有任何合理地建议或者疑问,可以联系笔者QQ:910223023,笔者高三 可能回复不会及时,但看到后一定处理。 5 | 1. 首先你需要按照章内的教程,创建一个项目,名称随意,我这里用的SetSize。配置好一系列的东西并创建好plugin.yml后,可以开始了。 6 | 7 | 2. 这是我的plugin.yml中的内容 8 | ``` 9 | name: SetSize 10 | main: xyz.zsy.SetSize.SetSize 11 | version: "1.0.0" 12 | api: ["1.0.0"] 13 | 14 | load: POSTWORLD 15 | 16 | author: zsy 17 | website: xyz.zsy 18 | ``` 19 | 20 | 这里再次强调一个坑,main中的路径应是主类的所在的位置,***精确到文件名***,我这里xyz.zsy.SetSize.SetSize的意思是xyz\zsy\SetSize目录下的SetSize.java,读者应该根据自己的包和主类名字修改!否则Nukkit不会加载到插件的主类,插件是无法运行的,因为Nukkit无从下手。 21 | 22 | 3.之后在主类中添加内容,添加的内容如下 23 | 24 | ```java 25 | 26 | @Override 27 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 28 | 29 | if (command.getName().equals("ss")) { //判断斜杠后的指令名字 /ss,用法/ss 玩家名字 大小 30 | if (sender instanceof Player) { //判断发令者是否是玩家 31 | if (args.length>0) { //判断指令/ss 后是否有参数 32 | Player p = this.getServer().getPlayer(args[0]); //获取/ss 后的第一个参数,应该是String类 33 | if (args.length>1) { //同上 判断是否有第二个参数 也就是是否有 大小 这个参数,应该是个数字 34 | Float size = Float.parseFloat(args[1]); //获取大小,并将它转化为Float型,因为Nukkit中设置实体大小的方法,参数要求是Float 35 | p.setScale(size); //调用setScale()方法来设置玩家大小 36 | return true; 37 | } 38 | } 39 | } 40 | } 41 | return false; 42 | } 43 | 44 | ``` 45 | 46 | 4.不要忘了在plugin.yml加入权限组,不然这个指令将会是非法的。 47 | ```yaml 48 | commands: 49 | ss: 50 | description: Set A Player's Size. 51 | usage: "/ss [name] [size]" 52 | permission: SetSize.ss 53 | permissions: 54 | SetSize.ss: 55 | default: op 56 | ``` 57 | 5.检查没有丢步骤后,就可以编译并且测试了! 当然,设置的玩家大小,当玩家重新进入服务器或者死亡重生后将会变为正常大小,因为本篇设置的只是一个临时变量,实体消失后它也会一起被清理,如果想要永久的保存,则可以利用config储存下来大小,然后在进服和复活事件中进行设置,感兴趣的可以进一步咨询笔者,那么本教程就到此结束了!祝你在胜利之地越战越勇! 58 | -------------------------------------------------------------------------------- /章外篇/章外篇之三-使用groovy编写您的项目.md: -------------------------------------------------------------------------------- 1 | # NukkitLearn章外篇之三-Groovy 2 | 3 | 参与编写者: MagicLu550 4 | 5 | ##### 知识点: groovy 6 | 7 | ### 关于groovy 8 | 9 | Groovy是一种基于JVM(Java虚拟机)的敏捷开发语言,它结合了Python、Ruby和Smalltalk的许多强大的特性 10 | ,Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码。由于其运行在 JVM 上的特性,Groovy也可以使用其他非Java语言编写的库。 11 | 12 | 1、 构建在强大的Java语言之上 并 添加了从Python,Ruby和Smalltalk等语言中学到的 诸多特征,例如动态类型转换、闭包和元编程(metaprogramming)支持。 13 | 14 | 2、为Java开发者提供了 现代最流行的编程语言特性,而且学习成本很低(几乎为零)。 15 | 16 | 3、 支持DSL(Domain Specific Languages领域特定语言)和其它简洁的语法,让代码变得易于阅读和维护。 17 | 18 | 4、受检查类型异常(Checked Exception)也可以不用捕获。 19 | 20 | 5、 Groovy拥有处理原生类型,面向对象以及一个Ant DSL,使得创建Shell Scripts变得非常简单。 21 | 22 | 6、在开发Web,GUI,数据库或控制台程序时 通过 减少框架性代码 大大提高了开发者的效率。 23 | 24 | 7、支持单元测试和模拟(对象),可以 简化测试。 25 | 26 | 8、无缝集成 所有已经存在的 Java对象和类库。 27 | 28 | 9、直接编译成Java字节码,这样可以在任何使用Java的地方 使用Groovy。 29 | 30 | 10、支持函数式编程,不需要main函数。 31 | 32 | 11、一些新的运算符。 33 | 34 | 12、默认导入常用的包。 35 | 36 | 13、断言不支持jvm的-ea参数进行开关。 37 | 38 | 14、支持对对象进行布尔求值。 39 | 40 | 15、类不支持default作用域,且默认作用域为public。 41 | 42 | 16、groovy中基本类型也是对象,可以直接调用对象的方法。 43 | 44 | groovy的最吸引人的特性是闭包,动态和静态类型的兼容,流行语法。 45 | java的大部分语法几乎可以直接在groovy上完美编译。 46 | 47 | ### 为什么我推荐使用groovy? 48 | 有些人可能认为,我们既然可以用java写,为什么还要费劲的再学个groovy呢? 49 | 原因很简单,但是问题的本身就是错的,因为再学会java的基础上,您几乎不需要 50 | 再去学习它了。`java的大部分语法几乎可以直接在groovy上完美编译。`这句话 51 | 是十分有用的,并且,最主要的是,他的语法不冗长,且表达力强。例如 52 | ```java 53 | class Test{ 54 | public Map map = new HashMap(); 55 | } 56 | 57 | ``` 58 | 等同于 59 | ```groovy 60 | def map = [:] //动态类型 61 | //或者 62 | Map map1 = [:] //静态类型 63 | 64 | ``` 65 | 很显然,我们可以看出,groovy就如同简化的java一样。而且,令人欣喜的, 66 | 它可以**无缝**的调用java。 67 | 68 | ### 导入环境 69 | 首先像往常一样创建maven项目(未来我们也会教授gradle)。 70 | 71 | 我们依然推荐使用groovy,在idea中,groovy的插件是直接支持的,所以我们不需要 72 | 另外安装插件。我们同时也不需要上官网安装groovy环境,因为官方已经提供了groovy-all 73 | 的jar包和编译的插件 74 | jar包 75 | ```xml 76 | 77 | compile 78 | org.codehaus.groovy 79 | groovy-all 80 | 2.3.11 81 | 82 | ``` 83 | ```xml 84 | 85 | org.spockframework 86 | spock-core 87 | 0.6-groovy-1.8 88 | test 89 | 90 | ``` 91 | 编译的插件 92 | ```xml 93 | 94 | org.codehaus.gmaven 95 | gmaven-plugin 96 | 1.3 97 | 98 | 99 | 100 | 101 | generateTestStubs 102 | testCompile 103 | 104 | 105 | 106 | true 107 | true 108 | true 109 | .groovy 110 | 1.7 111 | 112 | 113 | 114 | 115 | 116 | ``` 117 | 如果先前存在了这个插件,请以此为模板添加 118 | ```xml 119 | 120 | org.apache.maven.plugins 121 | maven-compiler-plugin 122 | 3.2 123 | 124 | 1.8 125 | 1.8 126 | UTF-8 127 | 128 | 129 | 130 | org.codehaus.groovy 131 | groovy-eclipse-compiler 132 | 2.9.1-01 133 | 134 | 135 | 136 | org.codehaus.groovy 137 | groovy-eclipse-batch 138 | 2.3.7-01 139 | 140 | 141 | 142 | ``` 143 | 这样,我们的groovy开发环境就建好了。 144 | 145 | ### 创建groovy类 146 | 147 | 创建groovy类和创建java类基本是一样的,只是我们需要选择groovy的文件创建 148 | ![zy3-01](images/zy3-01.png) 149 | 即可创建了groovy类 150 | 151 | ### groovy的语法标准 152 | [groovy的官方网站](http://www.groovy-lang.org)里面有groovy的文档(Document), 153 | 可以参见[语法规则](http://www.groovy-lang.org/syntax.html)等。 154 | 155 | ### 在nukkit上使用 156 | 如同往常一样创建类,只是语法上可以简略一些.并且可以和java代码共存 157 | ```groovy 158 | package net.noyark.nukkit 159 | 160 | import cn.nukkit.plugin.PluginBase 161 | 162 | class ShareInventoryPlugin extends PluginBase { 163 | 164 | 165 | @Override 166 | void onLoad() { 167 | this.logger.info("hello") 168 | } 169 | 170 | @Override 171 | void onEnable() { 172 | 173 | } 174 | 175 | @Override 176 | void onDisable() { 177 | 178 | } 179 | } 180 | ``` 181 | 另外,为了杜绝找不到类的相关异常,可以下载[依赖整合包](https://github.com/Server-Founder/Nukkit_GroovyLib), 182 | 将前置插件设置为groovylib即可,在安装您的插件时,配套它使用。 183 | -------------------------------------------------------------------------------- /章外篇/章外篇之二-多语言解决方案.md: -------------------------------------------------------------------------------- 1 | # NukkitLearn章外篇之二-插件中的多语言解决方案 2 | 3 | 参与编写者: [innc11](https://github.com/innc11) 4 | 5 | ##### 知识点: 配置文件、HashMap、枚举、文本替换、可变参数、正则表达式 6 | 7 | 本章外篇教程innc11制作,目的尽可能地简化插件开发过程中对多语言的处理。不得不说对多语言的支持是插件的一大加分项,特别是将插件发布到Nukkit的国际论坛上更是如此,如果有任何的疑问欢迎随时提issue。 8 | 9 | ## 0.HashMap 10 | 11 | 所谓HashMap就是保存着一个个键值对的类,一个存储数据的类,形象的描述HashMap就相当于中药铺的药材柜子,柜子上有着数不清的小屉子,每个小屉子里都放着不同的药材,比如胖大海、金银花、荷叶等等(值),每个小屉子上都有一个标签,写着这个屉子里放着什么药材(键),这样就形成了一个关系,一个标签对应一个药材,这就是键值对的关系,一对一的关系,也是HashMap的存储结构,但也有一些限制,比如不能出现两个一模一样的标签,不然HashMap就无法判断到底需要哪一个小屉子的药材;但可以允许两个屉子里的东西一模一样;屉子可以是空的,但标签绝对不能为空。 12 | 13 | HashMap能够处理一些数组无法处理的数据,比如一个统计玩家击杀数的插件,当某个玩家干掉10个怪物以后就给其发放奖励,这时候就需要对每个玩家进行记录,把玩家名作为标签,击杀数作为屉子里的东西,每次只需要根据玩家名把对应屉子打开,把里面的数据拿出来+1然后放回去关上屉子即可。这时候便出现了一个关系,每一个玩家名对应一个击杀数。 14 | ``` 15 | zhangsan = 5 16 | lisi = 3 17 | wangwu = 8 18 | ``` 19 | 玩家名就是键,击杀数就是值,大致就是这个结构,可以随时根据玩家名获取到对应击杀数,这是HashMap的工作方式。 20 | 21 | ## 1. 原理 22 | 23 | 插件首先从配置文件加载所有语言到一个HashMap里,在需要时从这个HashMap里读出来,再进行相应变量替换后,显示给玩家。 24 | 25 | ## 2. 创建项目 26 | 27 | 1. 首先创建一个项目,在这里我使用一个当玩家破坏方块时,给玩家发送一个消息,告诉玩家破坏的方块的ID,这里插件名字就叫 Tips,配置好依赖后,首先是```plugin.yml``` 28 | 29 | ```yaml 30 | name: Tips 31 | main: exam.miner.TipsPlugin 32 | version: "1.0" 33 | api: ["1.0.0"] 34 | ``` 35 | 36 | ## 3. 使用语言类 37 | 38 | 3. 首先我们声明一个语言类,这个类非常简单,仅仅包含一个HashMap、构造方法、获取语言的方法 39 | 40 | ```lang```负责存储所有的语言文本,```String getLang()```负责从```lang```里面获取对应的文本并做参数替换,在构造方法里我们往```lang```里面添加2个语言,其中```BROKE_MESSAGE```和```PLACE_MESSAGE```是关键字,我们通过传递给```getLang()```一个关键字来获取对应的文本,```getLang()```会在```lang```里面用关键字去进行查找,并返回对应的文本 41 | 42 | ```java 43 | public class MyLang 44 | { 45 | HashMap lang = new HashMap(); 46 | 47 | public MyLang() 48 | { 49 | lang.put("BROKE_MESSAGE", "你破坏了一个方块"); 50 | lang.put("PLACE_MESSAGE", "你放置了一个方块"); 51 | } 52 | 53 | public String getLang(String key) 54 | { 55 | return lang.get(key); 56 | } 57 | } 58 | ``` 59 | 60 | 接下来是主类: 61 | 62 | 在主类中使用刚才的```MyLang```类,并注册监听器,当玩家在破坏或者放置一个方块时,去获取对应的文本,然后发送给玩家 63 | 64 | ```java 65 | public class TipsPlugin extends PluginBase implements Listener 66 | { 67 | MyLang lang; 68 | 69 | @Override 70 | public void onEnable() 71 | { 72 | lang = new MyLang(); 73 | 74 | getServer().getPluginManager().registerEvents(this, this); 75 | } 76 | 77 | @EventHandler 78 | public void onPlayerBrokeBlock(BlockBreakEvent e) 79 | { 80 | String message = lang.getLang("BROKE_MESSAGE"); // 你放置了一个方块 81 | e.getPlayer().sendMessage(message); 82 | } 83 | 84 | @EventHandler 85 | public void onPlayerPlaceBlock(BlockPlaceEvent e) 86 | { 87 | String message = lang.getLang("PLACE_MESSAGE"); // 你破坏了一个方块 88 | e.getPlayer().sendMessage(message); 89 | } 90 | 91 | } 92 | ``` 93 | 94 | 这就是最简单的方式,但实际开发中语言往往是从配置文件进行加载的,而不是写死在代码里,接下来就是如何从yml文件进行读取加载 95 | 96 | ## 3. 从yaml配置文件加载语言 97 | 98 | 1. 我们使用language.yml文件用来保存语言文本 99 | 100 | ```yaml 101 | # language.yml 102 | PLACE_MESSAGE: "你放置了一个方块" 103 | BROKE_MESSAGE: "你破坏了一个方块" 104 | ``` 105 | 106 | 1. 接着我们需要修改我们的语言文件,使其从配置文件进行加载,首先需要在```MyLang```类里额外添加一个```Config config```变量,和一个```void reload()```方法,我们手动调用```reload()```方法来从配置文件加载语言文本。 107 | 108 | 2. 构造方法我们需要添加一个参数,用来告诉```MyLang```类应该读取哪一个yml文件,不建议在构造方法中立即调用```reload()```,因为当对象构造的时候```language.yml```可能根本就不存在。非常建议在插件主类中保存默认配置文件后手动调用```reload()``` 109 | 110 | 3. 在新添加的```void reload()```方法中,首先是命令```config```(重新)加载一下,然后把```lang```中已经存在的数据全部删除掉,接着就是使用```getKeys(false)```来获取```config```中所有的key,就是上面yml中的```PLACE_MESSAGE```和```BROKE_MESSAGE```,这个方法会以```Set```的形式返回,我们使用foreach进行遍历即可,需要说明的是参数中的```false```指```boolean child```,我们只需要根节点上的key不需要子节点上的key,传```false```即可 111 | 4. 在foreach中我们定义一个变量value来放置获取到的"key对应的值"也就是```你放置了一个方块```和```你破坏了一个方块```接下来我们需要进行一个判断,如果这个值是```String```类型的,我们就把它添加到```lang```里面,如果不是,比如```int```,```bool```,或者```list```类型,则跳过。 112 | 113 | ```java 114 | public class MyLang 115 | { 116 | Config config; 117 | HashMap lang = new HashMap(); 118 | 119 | public MyLang(String languageFileName) 120 | { 121 | config = new Config(new File(getDataFolder(), languageFileName), Config.YAML); 122 | } 123 | 124 | public void reload() 125 | { 126 | config.reload(); 127 | lang.clear(); 128 | 129 | for(String key : config.getKeys(false)) 130 | { 131 | Object value = config.get(key.name()); 132 | 133 | if(value instanceof String) 134 | { 135 | lang.put(key, (String) value); 136 | } 137 | } 138 | } 139 | 140 | public String getLang(String key) 141 | { 142 | return lang.get(key); 143 | } 144 | } 145 | ``` 146 | 147 | 5. 主类需要在```new MyLang()```时传递文件名。当然也要把```language.yml```以前打包进插件里。在onEnable()里要调用```saveResource("language.yml", false)```把```language.yml```写入到插件**DataFolder**里 148 | 149 | ```java 150 | public class TipsPlugin extends PluginBase implements Listener 151 | { 152 | MyLang lang; 153 | 154 | @Override 155 | public void onEnable() 156 | { 157 | saveResource("language.yml", false); 158 | 159 | lang = new MyLang("language.yml"); 160 | lang.reload(); 161 | 162 | getServer().getPluginManager().registerEvents(this, this); 163 | } 164 | 165 | @EventHandler 166 | public void onPlayerBrokeBlock(BlockBreakEvent e) 167 | { 168 | String message = lang.getLang("BROKE_MESSAGE"); // 你放置了一个方块 169 | e.getPlayer().sendMessage(message); 170 | } 171 | 172 | @EventHandler 173 | public void onPlayerPlaceBlock(BlockPlaceEvent e) 174 | { 175 | String message = lang.getLang("PLACE_MESSAGE"); // 你破坏了一个方块 176 | e.getPlayer().sendMessage(message); 177 | } 178 | 179 | } 180 | ``` 181 | 182 | 7. 在实际使用中,我们只需要修改```language.yml```中文字,然后使用指令调用```MyLang.reload()```重新加载即可,但在复杂的插件中,只有这些功能时远远不够的,语言文件不能一成不变,有时候需要将文字中的一部分字符替换成各种实际数据,比如商店插件在交易完成时会显示这笔交易花费了多少多少钱,玩家死亡时会显示被谁谁谁干掉了,其中的"钱"和"击杀者"就是实际的数据,需要根据实际情景来决定具体应该是什么。这就涉及到参数化的问题,将文本中一部分文字使用实际数据进行替换。 183 | 184 | ## 4. 参数化 185 | 186 | 1. 参数化必然会涉及到**占位符**这个概念,拿一个例子来说 187 | 188 | ```yaml 189 | PLACE_MESSAGE: "你放置了ID为 ${BLOCK_ID} 的方块" 190 | ``` 191 | 192 | 其中的**${BLOCK_ID}**就是占位符,他只是给实际的数据占个位置而已,并不会被显示出来。当然风格可以自己定义,在这个例子中,我们使用```${占位符名字}```这种风格。 193 | 194 | 2. 我们修改我们的```MyLang```类的```getLang()```方法,使其可以动态替换占位符,具体的调用方式为```MyLang.getLang("PLACE_MESSAGE", "{BLOCK_ID}", String.valueOf(block.getId()));``` 195 | 3. 多个参数的调用方式 196 | ``` 197 | MyLang.getLang("PLACE_MESSAGE", 198 | "{BLOCK_ID}", String.valueOf(block.getId(), 199 | "{PLAYER_NAME}", player.getName(), 200 | )); 201 | ``` 202 | 4. 无参数的调用方式 203 | ``` 204 | MyLang.getLang("PLACE_MESSAGE")); 205 | ``` 206 | 5. 后面的占位符和实际数据总是成双成对的出现,这可以大幅加快开发效率,当然这需要```MyLang.getLang()```具有对应的支持,具体看下面的示例代码。 207 | 208 | ```java 209 | public class MyLang 210 | { 211 | Config config; 212 | HashMap lang = new HashMap(); 213 | 214 | public MyLang(String languageFileName) 215 | { 216 | config = new Config(new File(getDataFolder(), languageFileName), Config.YAML); 217 | } 218 | 219 | public void reload() 220 | { 221 | config.reload(); 222 | lang.clear(); 223 | 224 | for(String key : config.getKeys(false)) 225 | { 226 | Object value = config.get(key.name()); 227 | 228 | if(value instanceof String) 229 | { 230 | lang.put(key, (String) value); 231 | } 232 | } 233 | } 234 | 235 | public String getLang(String key, String... argsPair) // 这里使用可变参数,当做数组一样处理即可 236 | { 237 | String rawStr = lang.get(key); 238 | 239 | int argCount = argsPair.length / 2; // 计算出有多少"对"参数,末尾的孤立参数会被舍弃 240 | 241 | for(int i=0;i 将MyLang.L作为键(key)以提高效率 340 | HashMap lang = new HashMap(); 341 | 342 | public MyLang(String languageFileName) 343 | { 344 | config = new Config(new File(getDataFolder(), languageFileName), Config.YAML); 345 | } 346 | 347 | public void reload() 348 | { 349 | config.reload(); 350 | lang.clear(); 351 | 352 | // 一个标志,如果有缺少的关键字,会被补全,然后保存config,以便调试 353 | boolean supplement = false; 354 | 355 | // 现在是以Lang.values()进行遍历,而不是config.getKeys(),注意 356 | for(L key : L.values()) 357 | { 358 | Object value = config.get(key.name()); 359 | 360 | // 如果这个关键字不存在,会自动补齐,并设置标志位 361 | if(v==null) 362 | { 363 | config.set(key.name(), key.getDefaultLangText()); 364 | supplement = true; 365 | lang.put(key, key.getDefaultLangText()); 366 | } 367 | 368 | if(value instanceof String) 369 | { 370 | lang.put(key, (String) value); 371 | } 372 | } 373 | 374 | // 如果有补齐,则需要保存这个config,以便用户可以在config内查看到以定位问题 375 | if(supplement) 376 | { 377 | config.save(); 378 | } 379 | 380 | } 381 | 382 | public String getLang(L key, String... argsPair) // 这里使用可变参数,当做数组一样处理即可 383 | { 384 | String rawStr = lang.get(key); 385 | 386 | int argCount = argsPair.length / 2; // 计算出有多少"对"参数,末尾的孤立参数会被舍弃 387 | 388 | for(int i=0;i 执行动作 10 | ``` 11 | 所以说监听器是插件中非常重要的部分 12 | 13 | 其实看一看这章,没有啥可讲的。监听器的内容很简单,很多人认识它的困难主要是概念上, 14 | 而不是使用上。 15 | 16 | 注册监听器有两个步骤: 1.定义监听器 2.注册监听器 17 | 18 | nukkit监听器的构成: 事件监听和优先级 19 | 20 | nukkit的监听器是通过 **反射(reflect)** 实现的,因此基于它,开发者容易上手,且上手 21 | 更简便。nukkit的监听器设计形同bukkit,也易于bukkit上手 22 | 23 | nukkit声明一个事件的监听管理器是通过注解实现的,即@EventHandler 24 | 25 | @EventHandler的源码如图 26 | ```java 27 | package cn.nukkit.event; 28 | 29 | import java.lang.annotation.ElementType; 30 | import java.lang.annotation.Retention; 31 | import java.lang.annotation.RetentionPolicy; 32 | import java.lang.annotation.Target; 33 | 34 | /** 35 | * 定义一个事件的处理器的注解。
36 | * Annotation that defines a handler. 37 | * 38 | *

一个处理器的重要程度被称作处理器的优先级,优先级高的处理器有更多的决定权。参见:{@link #priority()}
39 | * The importance of a handler is called its priority, handlers with higher priority speaks louder then 40 | * lower ones. See: {@link #priority()}

41 | * 42 | *

处理器可以选择忽略或不忽略被取消的事件,这种特性可以在{@link #ignoreCancelled()}中定义。
43 | * A handler can choose to ignore a cancelled event or not, that can be defined in {@link #ignoreCancelled()}.

44 | * 45 | * @author MagicDroidX(code) @ Nukkit Project 46 | * @author 粉鞋大妈(javadoc) @ Nukkit Project 47 | * @see cn.nukkit.event.Listener 48 | * @see cn.nukkit.event.Event 49 | * @since Nukkit 1.0 | Nukkit API 1.0.0 50 | */ 51 | 52 | @Target(ElementType.METHOD) 53 | @Retention(RetentionPolicy.RUNTIME) 54 | public @interface EventHandler { 55 | /** 56 | * 定义这个处理器的优先级。
57 | * Define the priority of the handler. 58 | * 59 | *

Nukkit调用处理器时会按照优先级从低到高的顺序调用,这样保证了高优先级的监听器能覆盖低优先级监听器做出的处理。 60 | * 调用的先后顺序如下:

61 | * When Nukkit calls all handlers, ones with lower priority is called earlier, 62 | * that make handlers with higher priority can replace the decisions made by lower ones. 63 | * The order that Nukkit call handlers is from the first to the last as: 64 | *
    65 | *
  1. EventPriority.LOWEST 66 | *
  2. EventPriority.LOW 67 | *
  3. EventPriority.NORMAL 68 | *
  4. EventPriority.HIGH 69 | *
  5. EventPriority.HIGHEST 70 | *
  6. EventPriority.MONITOR 71 | *
72 | * 73 | * @return 这个处理器的优先级。
The priority of this handler. 74 | * @see cn.nukkit.event.EventHandler 75 | */ 76 | EventPriority priority() default EventPriority.NORMAL; 77 | 78 | /** 79 | * 定义这个处理器是否忽略被取消的事件。
80 | * Define if the handler ignores a cancelled event. 81 | * 82 | *

如果为{@code true}而且事件发生,这个处理器不会被调用,反之相反。
83 | * If ignoreCancelled is {@code true} and the event is cancelled, the method is 84 | * not called. Otherwise, the method is always called.

85 | * 86 | * @return 这个处理器是否忽略被取消的事件。
Whether cancelled events should be ignored. 87 | * @see cn.nukkit.event.EventHandler 88 | */ 89 | boolean ignoreCancelled() default false; 90 | } 91 | 92 | ``` 93 | 第一个是优先级,第二个是是否忽略事件被取消 94 | 95 | 默认的优先级是normal,从注释可以知道,这是他们的先后顺序,LOWEST会最先被调用,其次是LOW,最后是MONITOR,如果在LOWEST监听器中调用了Event.setCancelled(true),Nukkit则会忽略掉后面的 ignoreCancelled 被设置为true或者保持默认的优先级更高的监听器 96 | 97 | - EventPriority.LOWEST 98 | 99 | - EventPriority.LOW 100 | 101 | - EventPriority.NORMAL 102 | 103 | - EventPriority.HIGH 104 | 105 | - EventPriority.HIGHEST 106 | 107 | - EventPriority.MONITOR 108 | 109 | 而实现监听器的优先级标记是通过 **枚举(Enum)** 实现的 110 | 111 | 优先级的目的是为了保证监听器按照顺序执行,以使得一个监听器操作完会 112 | 进入下一个监听器继续执行,以确保执行的有序性。下一个监听器的相同操作 113 | 会覆盖之前监听器的相同操作。 114 | 115 | 同时,事件可以被我手动取消的,但是有时候事件虽然取消,但依然需要操作, 116 | 那么ignoreCancelled可以发挥作用了,当然默认是忽略掉取消的事件,也就是 117 | 说取消的事件默认不会被监听(这里可能有所错误,我这个不太常用) 118 | 119 | 一个EventHandler基本都是监听一个事件的(我只尝试过一个事件的),代码中 120 | 这样使用 121 | ```java 122 | package net.noyark.www; 123 | 124 | import cn.nukkit.event.EventHandler; 125 | import cn.nukkit.event.Listener; 126 | import cn.nukkit.event.player.PlayerJoinEvent; 127 | 128 | public class OtherListener implements Listener { 129 | 130 | @EventHandler 131 | public void onPlayerJoin(PlayerJoinEvent e){ 132 | //执行代码 133 | } 134 | } 135 | 136 | ``` 137 | 这里的意思就是当玩家进入服务器时,就会触发PlayerJoinEvent事件,服务器会 138 | 形成一个PlayerJoinEvent对象,并且调用先前注册的有关PlayerJoinEvent的 139 | EventHandler,将对象传入,这样就实现了一个 **事件的调用** 140 | 获取对象的内容,则通过e调用即可,我们不需要很明白它的具体细节,只需要知道, 141 | 使用EventHandler注解的方法(且有一个Event的子类类型参数,并且它所在的监听器被注册), 142 | 就会在事件发生时,相应的被调用,如上文所讲,这里的代码案例就是当发生玩家加入时, 143 | 这个onPlayerJoin方法会被服务端调用。 144 | ```java 145 | package net.noyark.www; 146 | 147 | import cn.nukkit.event.EventHandler; 148 | import cn.nukkit.event.Listener; 149 | import cn.nukkit.event.player.PlayerJoinEvent; 150 | 151 | public class OtherListener implements Listener { 152 | 153 | @EventHandler 154 | public void onPlayerJoin(PlayerJoinEvent e){ 155 | e.getPlayer().sendMessage("你好 "+e.getPlayer()); 156 | } 157 | } 158 | ``` 159 | 例如这个代码,在玩家加入时,将会向玩家发送一个"你好 玩家的名字",这就是 160 | 我们监听器的作用 161 | ```java 162 | package net.noyark.www; 163 | 164 | import cn.nukkit.event.EventHandler; 165 | import cn.nukkit.event.EventPriority; 166 | import cn.nukkit.event.Listener; 167 | import cn.nukkit.event.player.PlayerJoinEvent; 168 | 169 | public class OtherListener implements Listener { 170 | 171 | @EventHandler(priority = EventPriority.HIGH,ignoreCancelled = true) 172 | public void onPlayerJoin(PlayerJoinEvent e){ 173 | e.getPlayer().sendMessage("你好 "+e.getPlayer()); 174 | } 175 | } 176 | ``` 177 | 如果要使用我们之前所说的参数,则这样使用。 178 | 179 | 事实上,监听器的基本使用方式也就这些,nukkit也提供了很多事件给予我们使用,也允许我们 180 | 自己制作事件自己使用,在第二部分中,我们将会讲解提供了哪些事件 181 | 182 | 如何自己定义一个事件 183 | 184 | 事实上,nukkit的事件是通过callEvent调用的,所以,我们同样可以通过callEvent实现我们自己 185 | 的事件。 186 | ``` 187 | this.getServer().getPluginManager().callEvent(Event e); 188 | ``` 189 | 首先我们先定义一个事件类 190 | ```java 191 | package net.noyark.www; 192 | 193 | import cn.nukkit.event.Event; 194 | 195 | public class MyEvent extends Event { 196 | } 197 | 198 | ``` 199 | 之后,我们在监听器使用我们的事件 200 | ```java 201 | package net.noyark.www; 202 | 203 | import cn.nukkit.event.EventHandler; 204 | import cn.nukkit.event.Listener; 205 | 206 | 207 | public class OtherListener implements Listener { 208 | 209 | @EventHandler 210 | public void onMy(MyEvent e){ 211 | 212 | } 213 | } 214 | ``` 215 | 之后,我们就可以使用我们的事件了。 216 | ``` 217 | this.getServer().getPluginManager().registerEvents(new OtherListener(),this); 218 | ``` 219 | 如何触发我们的事件? 220 | 事件的触发则通过callEvent触发,假如,我们写一个玩家假如时,如果他的 221 | 名字叫abc,就触发MyEvent事件 222 | ```java 223 | package net.noyark.www; 224 | 225 | import cn.nukkit.event.EventHandler; 226 | import cn.nukkit.event.Listener; 227 | import cn.nukkit.event.player.PlayerJoinEvent; 228 | 229 | 230 | public class OtherListener implements Listener { 231 | 232 | @EventHandler 233 | public void onMy(MyEvent e){ 234 | 235 | } 236 | 237 | @EventHandler 238 | public void onPLayerJoin(PlayerJoinEvent e){ 239 | if("abc".equals(e.getPlayer().getName())){ 240 | Example.getPlugin().getServer() 241 | .getPluginManager() 242 | .callEvent(new MyEvent()); 243 | } 244 | } 245 | } 246 | 247 | ``` 248 | 这里,我们完成了我们的自定义事件 249 | 250 | 假如我们要注册一个EventHandler,不去注册其他的该如何。 251 | 事实上,可以实现这个,nukkit提供了registerEvent方法, 252 | 可以注册单个EventHandler,但不太常用,我这里也不会再过多 253 | 阐述了,如果想了解,可以发issue,我将会添加这方面的内容 254 | 255 | [上一节](1-2_了解PluginBase.md) [下一节](1-4_如何编写命令.md) 256 | -------------------------------------------------------------------------------- /第一章/1-4_如何编写命令.md: -------------------------------------------------------------------------------- 1 | [上一节](1-3_如何编写监听器.md) [下一节](1-5_如何使用配置文件.md) 2 | 3 | # 第一章 第四节 如何编写命令 4 | 参与编写者: MagicLu550 5 | #### 建议学习时间: 30分钟 6 | ##### 学习要点: 学习自己创建一个简易的命令,了解指令和指令映射,了解SimpleCommand的使用 7 | 8 | 其实创建一个简易的命令很简单,我们可以在`PluginBase`中重写`onCommand`方法,进行`equals`识别,比如 9 | ```java 10 | public class PluginMain extends PluginBase{ 11 | //... 其他 12 | @Override 13 | // args指后面的那一串参数,直接取出即可,注意先检查length,label其实就是指令名称,也可以用command的信息,这个是个人习惯 14 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 15 | if("hello".equals(command.getName())){ // 识别指令名称 16 | this.getLogger().info("hello!"); 17 | return true; 18 | } 19 | return false; 20 | } 21 | } 22 | 23 | ``` 24 | 25 | nukkit的原生命令也是很多基于Command创建的.很多项目是需求指令 26 | 以使得用户和您的项目操作,也就是作为一个 **接口(Interface)** ,接下来我们介绍以下nukkit的命令。 27 | 28 | nukkit的命令的父类是Command,它有很多的构造方法 29 | ``` 30 | public Command(String name) { 31 | this(name, "", null, new String[0]); 32 | } 33 | 34 | public Command(String name, String description) { 35 | this(name, description, null, new String[0]); 36 | } 37 | 38 | public Command(String name, String description, String usageMessage) { 39 | this(name, description, usageMessage, new String[0]); 40 | } 41 | 42 | public Command(String name, String description, String usageMessage, String[] aliases) { 43 | this.commandData = new CommandData(); 44 | this.name = name.toLowerCase(); // Uppercase letters crash the client?!? 45 | this.nextLabel = name; 46 | this.label = name; 47 | this.description = description; 48 | this.usageMessage = usageMessage == null ? "/" + name : usageMessage; 49 | this.aliases = aliases; 50 | this.activeAliases = aliases; 51 | this.timing = Timings.getCommandTiming(this); 52 | this.commandParameters.put("default", new CommandParameter[]{new CommandParameter("args", CommandParamType.RAWTEXT, true)}); 53 | } 54 | ``` 55 | 我们可以知道,nukkit的命令都是toLowerCase的,即小写,也就是说,命令不区分大小写。 56 | 对于命令如何存储,服务端如何识别一个命令,我们从源码中找到以下资料: 57 | 58 | -- 59 | 60 | 命令的区分标识是fallBackPrefix+:+label,默认为指令的名称,一般fallBackPrefix都写"" 61 | 62 | 这一块知识缺点将会有其他人补充[1] 63 | 64 | 代码依据 - SimpleCommandMap.java 65 | ``` 66 | 159 private boolean registerAlias(Command command, boolean isAlias, String fallbackPrefix, String label) { 67 | 160 this.knownCommands.put(fallbackPrefix + ":" + label, command); 68 | 69 | ``` 70 | -- 71 | 72 | 73 | 存储指令的容器是实现CommandMap接口的,即SimpleCommandMap,我们可以通过 74 | ``` 75 | this.getServer().getCommandMap(); 76 | ``` 77 | 得到CommandMap. 78 | 79 | Command,CommandMap有很多方法 80 | 81 | 1. Command 82 | 1. 根构造方法的参数为 83 | ``` 84 | String name, String description, String usageMessage, String[] aliases 85 | 86 | 第一个name是指令名称,最终会转换为全小写 87 | 88 | 第二个description是指令介绍,用于给玩家查看使用的 89 | 90 | 第三个usageMessage就是当玩家对命令使用错误,返回的信息 91 | 92 | 第四个aliases就是指令的别名,指令可以有多个别名 93 | ``` 94 | 当然,也有简化的构造方法,可以根据你的需求任意调用,这里不多阐述,其他的都是为默认值 95 | 2. Command的主要属性 96 | 如同command的构造方法一样,command我们需要了解的属性基本也就是这四个。其他属性将会 97 | 在nukkit原理解析的时候讲解 98 | 3. Command的比较常用的方法 99 | 1. boolean execute(CommandSender commandSender, String label, String[] strings) 100 | 101 | 这个方法是需要开发者自行实现的方法,当指令被触发,就会执行execute里的代码 102 | 它的参数我们在[第二章](1-2_插件要素.md)提到了 103 | 104 | 2. String getName() 105 | 106 | 这个可以获取指令的名称 107 | 108 | 其余方法我们将会在后期附件讲解到,如果有想要知悉的其他方法,我们会另外在这里做补充, 109 | 或者您认为常用的,也可以pull request添加进去 110 | 111 | 2. CommandMap 112 | 1. boolean register(String fallbackPrefix, Command command) 113 | 可以注册指令,fallbackPrefix是前缀,用于服务端存储命令对象的标识 114 | nukkit的本地命令的fallbackPrefix为nukkit 115 | command则为你的自定义命令对象 116 | 2. void registerAll(String fallbackPrefix, List commands) 117 | 这个可以一次性注册多个指令 118 | 3. boolean dispatch(CommandSender sender, String cmdLine) 119 | 这个调用一个命令,cmdLine就是日常所输入的命令 120 | 4. void registerSimpleCommands(Object object) 121 | 这个是调用简单指令,通过注解实现的指令对象,我们后面将会演示如何使用它。 122 | 123 | nukkit官方后来推出一系列简化操作,如SimpleCommand,SimpleConfig等,我们这里解释以下SimpleCommand 124 | 125 | SimpleCommand运用了注解,同样通过 **反射** 实现的,我们可以看到官方的源码来探讨它的使用 126 | 127 | SimpleCommandMap.java 128 | ``` 129 | @Override 130 | public void registerSimpleCommands(Object object) { 131 | for (Method method : object.getClass().getDeclaredMethods()) { 132 | cn.nukkit.command.simple.Command def = method.getAnnotation(cn.nukkit.command.simple.Command.class); 133 | if (def != null) { 134 | SimpleCommand sc = new SimpleCommand(object, method, def.name(), def.description(), def.usageMessage(), def.aliases()); 135 | 136 | Arguments args = method.getAnnotation(Arguments.class); 137 | if (args != null) { 138 | sc.setMaxArgs(args.max()); 139 | sc.setMinArgs(args.min()); 140 | } 141 | 142 | CommandPermission perm = method.getAnnotation(CommandPermission.class); 143 | if (perm != null) { 144 | sc.setPermission(perm.value()); 145 | } 146 | 147 | if (method.isAnnotationPresent(ForbidConsole.class)) { 148 | sc.setForbidConsole(true); 149 | } 150 | 151 | CommandParameters commandParameters = method.getAnnotation(CommandParameters.class); 152 | if (commandParameters != null) { 153 | Map map = Arrays.stream(commandParameters.parameters()) 154 | .collect(Collectors.toMap(Parameters::name, parameters -> Arrays.stream(parameters.parameters()) 155 | .map(parameter -> new CommandParameter(parameter.name(), parameter.type(), parameter.optional())) 156 | .distinct() 157 | .toArray(CommandParameter[]::new))); 158 | 159 | sc.commandParameters.putAll(map); 160 | } 161 | 162 | this.register(def.name(), sc); 163 | } 164 | } 165 | } 166 | ``` 167 | 很显然,简易命令必须要有@Command注解在方法上,方法上标记一些内容,当然,最终只是把一个类拆解,分为多个命令 168 | 对象注册(SimpleCommand),最终也继承自Command。SimpleCommand提供了对于参数最大和最小的限制。 169 | 170 | SimpleCommand.java 171 | ``` 172 | @Override 173 | public boolean execute(CommandSender sender, String commandLabel, String[] args) { 174 | if (this.forbidConsole && sender instanceof ConsoleCommandSender) { 175 | this.sendInGameMessage(sender); 176 | return false; 177 | } else if (!this.testPermission(sender)) { 178 | return false; 179 | } else if (this.maxArgs != 0 && args.length > this.maxArgs) { 180 | this.sendUsageMessage(sender); 181 | return false; 182 | } else if (this.minArgs != 0 && args.length < this.minArgs) { 183 | this.sendUsageMessage(sender); 184 | return false; 185 | } 186 | 187 | boolean success = false; 188 | 189 | try { 190 | //这里执行我们的命令 191 | success = (Boolean) this.method.invoke(this.object, sender, commandLabel, args); 192 | } catch (Exception exception) { 193 | Server.getInstance().getLogger().logException(exception); 194 | } 195 | 196 | if (!success) { 197 | this.sendUsageMessage(sender); 198 | } 199 | 200 | return success; 201 | } 202 | ``` 203 | 这段代码我们可以知道方法的参数有规范要求的 204 | Object object,CommandSender sender,String label,String[] args 205 | object就是我们的命令对象了,通过registerSimpleCommand注册进去的命令对象 206 | 其他显而易见,不再多讲,具体如何使用,其实很简单 207 | ```java 208 | package net.noyark.www; 209 | 210 | 211 | import cn.nukkit.command.CommandSender; 212 | import cn.nukkit.command.simple.Arguments; 213 | import cn.nukkit.command.simple.Command; 214 | 215 | public class MySimpleCommand { 216 | 217 | @Command(name = "hello",description = "233",usageMessage = "/hello") 218 | @Arguments(max = 10,min = 0) 219 | public boolean onHelloCommand(CommandSender sender, String label, String[] args){ 220 | //这里写指令处理代码 221 | return true;//为布尔类型,否则会发生空指针异常,在先前源码可以分析其返回类型 222 | } 223 | } 224 | ``` 225 | 最终通过registerSimpleCommand注册即可.事实上是对Command的封装 226 | 227 | command的用户组(这里参考自snake1999的文章) 228 | 229 | ```yaml 230 | permissions: #这个标签我们只写一次就ok了 231 | plugin.hello: 232 | description: 你好 233 | default: op #权限组,后期会讲到 234 | commands: #这个标签我们只写一次就ok了 235 | hello: 236 | description: 你好 237 | usage: "/hello" 238 | permission: plugin.hello 239 | ``` 240 | 这里我们发现了default,这个的选项有以下几种 241 | 242 | - op,代表服务器管理员,在ops.txt中规定。 243 | - notop,代表除服务器管理员外的所有玩家。 244 | - true,代表所有玩家。 245 | - false,代表空集。如果某个命令对应这个权限,那就没有人能够使用这个命令(控制台除外)。 246 | 247 | 大家可以根据这些选项来控制指令的使用范围了 248 | 249 | 参考文献: 250 | - [Nukkit插件从0开始](https://www.cnblogs.com/xtypr/p/nukkit_plugin_start_from_0.html) 251 | 252 | [上一节](1-3_如何编写监听器.md) [下一节](1-5_如何使用配置文件.md) 253 | -------------------------------------------------------------------------------- /第一章/1-5_如何使用配置文件.md: -------------------------------------------------------------------------------- 1 | [上一节](1-4_如何编写命令.md) [下一节](1-6_如何编写plugin.yml.md) 2 | 3 | # 第一章 第五节 如何编写配置文件 4 | 参与编写者: MagicLu550 5 | #### 建议学习时间: 40分钟 6 | ##### 学习要点: 了解如何创建配置文件,了解使用SimpleConfig 7 | 8 | 配置文件用于存储插件的配置信息,供用户自定义和修改,以及存储一些永久数据,我们称 9 | 配置文件属于 **持久层**。 10 | 11 | nukkit提供了多种配置文件格式,如yaml,json,properties等,其中最常用的是yaml, 12 | 我们主要讲解这个配置文件格式,其他如果想要了解,可以发送issues,我们可以补充。 13 | 14 | 一. 关于yaml 15 | 16 | nukkit的yaml框架基于[snakeYaml](https://github.com/bmoliveira/snake-yaml)实现的, 17 | snakeYaml是一款使用广泛的yaml解析库,我们可以从它的[官网](https://yaml.org/type/index.html)了解 18 | 他们的语法,在nukkit开发中,我们更多使用key: value的映射形式 19 | 20 | 这是一个yaml的文件案例 21 | ```yaml 22 | server: 23 | name: 12 24 | player: 25 | - nihao 26 | - xiaoming 27 | - xiaogang 28 | time: 29 | year: 2019 30 | ``` 31 | yaml的标准语法是使用空格来划分级别,前面为键,后面为值,且值和冒号之间有空格, 32 | 虽然yaml的语法不止如此简单,但是我们最常用的也就这些很简单的东西。 33 | 34 | yaml的数组有两个表示形式 35 | ```yaml 36 | array1: ["1","2"] #yaml的注释 37 | array2: [1,2] #yaml不允许有重复的键 38 | array3: 39 | - 1 40 | - 2 41 | - 3 42 | 43 | ``` 44 | 如果还要了解其他,可以上yaml官网查看他们语法,这里只讲解这些语法 45 | 46 | 二. 如何使用nukkit的配置文件库 47 | 48 | 原本的snakeYaml使用起来比较复杂,因此nukkit官方提供了简化,同一 49 | 使用Config对象来表达和操作。 50 | 51 | 使用默认的配置 52 | 53 | 默认配置文件为config.yml,可以通过saveDefaultConfig()方法来实现, 54 | 前提是你的resources下面要创建一个config.yml,这个方法会默认在 55 | plugins/${你的插件名字}下创建一个config.yml,并且会把resources下面的那个 56 | config.yml内容复制过来. 57 | ![5-01](../images/5-01.png) 58 | 之后我们调用这个方法即可 59 | ![5-02](../images/5-02.png) 60 | 我们这里打开一个服务器,做一个实验可以看看,插件名为FirstPlugin 61 | ![5-03](../images/5-03.png) 62 | 我们插件被加载成功,我们看看我们的文件夹 63 | ![5-04](../images/5-04.png) 64 | 发现.../plugins/FirstPlugin下面出现了一模一样的config.yml 65 | 66 | 使用自定义的配置 67 | 68 | 自定义配置有两种方式,一种是已经初始化的文件,一种是空白文件,元素后期添加 69 | 70 | 1. 初始化的文件 71 | saveResource(String fileName, boolean replace) 72 | 73 | 第一个是文件名称,默认是this.getDataFolder()+"/"+fileName的路径 74 | getDataFolder()为.../plugins/你的插件名 ,不包含后面的"/",使用时记得注意 75 | 假如你的插件名称为abc,那么路径为.../plugins/abc 76 | 77 | 第二个是是否替换文件,每次重新启动服务器时,会把文件内容重新更新,如果为false, 78 | 则不会更新。或者已经存在config,将不会换掉。如果为true,那么就会换掉它。之前修改 79 | 的内容就会被替换(实际上加了true就是把文件删掉重新创建一遍) 80 | 81 | 而文件初始化的来源和之前的config.yml一样,我们可以看看saveDefaultConfig()源码会发现, 82 | 它的本质也是saveResource 83 | 84 | PluginBase.java 85 | ``` 86 | @Override 87 | public void saveDefaultConfig() { 88 | if (!this.configFile.exists()) { 89 | this.saveResource("config.yml", false); 90 | } 91 | } 92 | 93 | ``` 94 | 2. 空白文件 95 | 空白文件的文件默认没有getDataFolder(),它的默认路径是和nukkit.jar同一级别的文件目录. 96 | ``` 97 | Config config = new Config(this.getDataFolder()+"/myConfig.yml",Config.YAML); 98 | config.save();//保存文件,文件不存在就会创建 99 | ``` 100 | Config可以实现对文件的操作,例如先前的config.yml,我们想对其实现操作,可以 101 | ``` 102 | Config config = new Config(this.getDataFolder()+"/config.yml",Config.YAML); 103 | config.set("me","12");//将me修改为12,如果me不存在将创建me 104 | config.save(); 105 | ``` 106 | 当然值得注意,每次重新运行,set都会修改一次me,所以如果手动修改了me,就会被还原。 107 | 所以你可以做一个条件判断来避免还原的问题,比如判断文件是否存在。 108 | 当然这是初始化文件出现的问题,我还是建议初始化文件使用saveResource来初始化 109 | 110 | Config的常用方法 111 | 1.构造方法 112 | 113 | 我们可以看官方源码 114 | 115 | Config.java 116 | ``` 117 | /** 118 | * Constructor for Config instance with undefined file object 119 | * 120 | * @param type - Config type 121 | */ 122 | public Config(int type) { 123 | this.type = type; 124 | this.correct = true; 125 | this.config = new ConfigSection(); 126 | } 127 | 128 | /** 129 | * Constructor for Config (YAML) instance with undefined file object 130 | */ 131 | public Config() { 132 | this(Config.YAML); 133 | } 134 | 135 | public Config(String file) { 136 | this(file, Config.DETECT); 137 | } 138 | 139 | public Config(File file) { 140 | this(file.toString(), Config.DETECT); 141 | } 142 | 143 | public Config(String file, int type) { 144 | this(file, type, new ConfigSection()); 145 | } 146 | 147 | public Config(File file, int type) { 148 | this(file.toString(), type, new ConfigSection()); 149 | } 150 | 151 | 152 | ``` 153 | file为文件名称,默认路径为nukkit的根目录 154 | type为类型,主要使用的类型是 155 | 156 | Config.java 157 | ``` 158 | public static final int DETECT = -1; //Detect by file extension 159 | public static final int PROPERTIES = 0; // .properties 160 | public static final int CNF = Config.PROPERTIES; // .cnf 161 | public static final int JSON = 1; // .js, .json 162 | public static final int YAML = 2; // .yml, .yaml 163 | //public static final int EXPORT = 3; // .export, .xport 164 | //public static final int SERIALIZED = 4; // .sl 165 | public static final int ENUM = 5; // .txt, .list, .enum 166 | public static final int ENUMERATION = Config.ENUM; 167 | 168 | 169 | ``` 170 | 我们主要使用yaml,json.properties,其他大家可以自行查询。 171 | 172 | 如何实现动态内容 173 | 174 | 比如一些插件可以这样做 175 | ```yaml 176 | message: ${player}加入了信息 177 | ``` 178 | 179 | 其实可以使用replace做到 180 | ```java 181 | package net.noyark.www; 182 | 183 | import cn.nukkit.event.EventHandler; 184 | import cn.nukkit.event.Listener; 185 | import cn.nukkit.event.player.PlayerJoinEvent; 186 | 187 | 188 | public class OtherListener implements Listener { 189 | 190 | @EventHandler 191 | public void onPLayerJoin(PlayerJoinEvent e){ 192 | String message; 193 | //获取到内容后... 194 | e.getPlayer().sendMessage(message.replace("${player}",e.getPlayer().getName())); 195 | } 196 | } 197 | ``` 198 | 当然我们也可以基于反射做一个简单的解析工具 199 | 200 | ```java 201 | class Utils{ 202 | String[] vals = {"player","ip"}; 203 | Map map = new HashMap(){ 204 | { 205 | put("player","getName"); 206 | put("ip","getAddress"); 207 | } 208 | }; 209 | public String translate(Event e,String message){ 210 | try{ 211 | Set> entries = map.entrySet(); 212 | for(Map.Entry e1:entries){ 213 | message = message.replace("${"+e1.getKey()+"}",e.getClass().getDeclaredMethod(e1.getValue()).invoke(e).toString()); 214 | } 215 | }catch (Exception e2){ 216 | e2.printStackTrace(); 217 | } 218 | return message; 219 | } 220 | } 221 | 222 | ``` 223 | 224 | [上一节](1-4_如何编写命令.md) [下一节](1-6_如何编写plugin.yml.md) -------------------------------------------------------------------------------- /第一章/1-6_如何编写plugin.yml.md: -------------------------------------------------------------------------------- 1 | [上一节](1-5_如何使用配置文件.md) [下一节](1-7_案例玩家进入信息等效果.md) 2 | 3 | # 第一章 第六节 如何编写plugin.yml 4 | 参与编写者: SmallasWater MagicLu550 5 | #### 建议学习时间: 10分钟 6 | ##### 学习要点: 了解plugin.yml内容 7 | 8 | 1. 关于plugin.yml 9 | 10 | plugin.yml 是 nukkit加载插件的主要文件 在加载插件前必先加载plugin.yml 11 | 12 | 2. plugin.yml构成 13 | 14 | ```yaml 15 | name: FirstPlugin # nukkit运行时识别的插件名 16 | main: net.noyark.www.Example # 主类名称,不能以cn.nukkit开头 17 | version: "0.0.1" # 版本号 18 | author: 你的名字,这里指作者名称 # 感谢qq1586235767的提议,作者名称不能使用纯数字,否则会发生类型转换异常,您可以使用字符串避免这个问题 19 | api: ["1.0.9"] # 早期nukkit api为1.0.0, 20 | # 目前大概为1.0.9 21 | depend: ["EconomyAPI"] # 依赖的插件名称 添加后如果Plugins文件夹不存在添加的插件则关闭本插件 22 | loadbefore: ["EconomyAPI"] # 在xx插件之后加载 一般用作解决调用依赖库出现的ClassCastExpection 23 | description: My first plugin # 介绍 24 | commands: # Commands指令列表 25 | fp: # 指令名称 不要加 / 26 | usage: "/fp help" # 指令的用法 当onCommand返回false时 输出 usage内容 27 | description: "指令介绍" # 指令的介绍 28 | permission: FirstPlugin.fp # 指令权限 如果你希望插件仅允许 op执行 可以尝试这个 29 | aliases: [] # 指令别名 可以增加中文名称 30 | permissions: 31 | FirstPlugin.fp: # 权限名称 32 | description: "" # 权限的介绍 33 | default: op # 权限限制 op / notop notop为非op可执行 op 为仅限op执行 34 | ``` 35 | 3. 其他构成 36 | 37 | 我们通过拆解PluginDescription类,可以知道 38 | 39 | PluginDescription.java 40 | ``` 41 | private String name; 42 | private String main; 43 | private List api; 44 | private List depend = new ArrayList<>(); 45 | private List softDepend = new ArrayList<>(); 46 | private List loadBefore = new ArrayList<>(); 47 | private String version; 48 | private Map commands = new HashMap<>(); 49 | private String description; 50 | private final List authors = new ArrayList<>(); 51 | private String website; 52 | private String prefix; 53 | private PluginLoadOrder order = PluginLoadOrder.POSTWORLD; 54 | ``` 55 | 其中load属性分为POSTWORLD和STARTUP,他们区别官方在注释说明了 56 | 57 | PluginLoadOrder.java 58 | ``` 59 | /** 60 | * 表示这个插件在服务器启动时就开始加载。
61 | * Indicates that the plugin will be loaded at startup. 62 | * 63 | * @see cn.nukkit.plugin.PluginLoadOrder 64 | * @since Nukkit 1.0 | Nukkit API 1.0.0 65 | */ 66 | STARTUP, 67 | /** 68 | * 表示这个插件在第一个世界加载完成后开始加载。
69 | * Indicates that the plugin will be loaded after the first/default world was created. 70 | * 71 | * @see cn.nukkit.plugin.PluginLoadOrder 72 | * @since Nukkit 1.0 | Nukkit API 1.0.0 73 | */ 74 | POSTWORLD 75 | ``` 76 | config里有一个load,分别为STARUP与POSTWORLD,前者使插件加载在地图之前,后者为使插件加载在地图之后,如果对地图加载有需求的话,必须填写POSTWORLD,否则将无法获取level 77 | 78 | 您也可以添加自己的网站: website属性 79 | 80 | [上一节](1-5_如何使用配置文件.md) [下一节](1-7_案例玩家进入信息等效果.md) 81 | -------------------------------------------------------------------------------- /第一章/1-7_案例玩家进入信息等效果.md: -------------------------------------------------------------------------------- 1 | [上一节](1-7_PluginBase类.md) [下一节](../第二章/2-0_前言.md) 2 | 3 | 4 | # 第一章 第七节 案例课:玩家进入案例演示 5 | 参与编写者: MagicLu550 6 | #### 建议学习时间: 30分钟 7 | ##### 学习要点: 对于之前知识的巩固和加深认识 8 | 9 | 这个项目的需求很简单.实现一个玩家加入自定义提示信息,可以手动取消信息的功能 10 | 我们列举下步骤 11 | - 创建项目 12 | - 导入nukkit包 13 | - 创建初始化配置文件set-info.yml 14 | - 创建plugin.yml 15 | - 创建主类 16 | - 创建配置 17 | 18 | - 创建监听器 19 | - 读取配置内容 20 | - 发送玩家信息 21 | - 创建命令 22 | -判断 23 | - 注册监听器和命令 24 | - 完成 25 | 26 | 1.第一到二步请自己完成 27 | 2.set-inf的内容 28 | ```yaml 29 | message: 欢迎${player}加入了服务器 30 | ``` 31 | 3.我的plugin.yml的内容 32 | ```yaml 33 | name: MyJoin 34 | main: net.noyark.www.MyJoin 35 | version: "0.0.1" 36 | author: magiclu550 37 | api: ["1.0.9"] 38 | description: My first plugin 39 | #当采用注册时,控制权限需要在这里声明 40 | commands: 41 | set: 42 | permission: myjoin.set 43 | permissions: 44 | myjoin.set: 45 | default: op 46 | ``` 47 | 4.创建主类 MyJoin 48 | ```java 49 | package net.noyark.www; 50 | 51 | import cn.nukkit.plugin.PluginBase; 52 | 53 | 54 | public class MyJoin extends PluginBase { 55 | 56 | private Map cancel; 57 | 58 | private static MyJoin instance; 59 | 60 | @Override 61 | public void onLoad() { 62 | this.getLogger().info("插件开始加载"); 63 | } 64 | 65 | @Override 66 | public void onEnable() { 67 | instance = this; 68 | this.saveDefaultConfig(); 69 | this.getLogger().info("插件初始化完毕"); 70 | 71 | } 72 | 73 | @Override 74 | public void onDisable() { 75 | this.getLogger().info("插件已经关闭"); 76 | } 77 | 78 | public static MyJoin getInstance() { 79 | return instance; 80 | } 81 | 82 | public void setCancel(String name,boolean isCancel) { 83 | cancel.put(name,isCancel); 84 | } 85 | 86 | public Map getCancel() { 87 | return cancel; 88 | } 89 | } 90 | 91 | ``` 92 | 5.创建监听器 93 | ```java 94 | package net.noyark.www; 95 | 96 | import cn.nukkit.event.EventHandler; 97 | import cn.nukkit.event.Listener; 98 | import cn.nukkit.event.player.PlayerJoinEvent; 99 | 100 | public class MyJoinListener implements Listener { 101 | 102 | @EventHandler 103 | public void onJoin(PlayerJoinEvent e){ 104 | Boolean isCancel = MyJoin.getInstance().getCancel().get(e.getPlayer().getName()); 105 | if(isCancel!=null&&!isCancel) { 106 | String message = MyJoin.getInstance().getConfig().getString("message"); 107 | e.setJoinMessage(message.replace("${player}", e.getPlayer().getName())); 108 | }else{ 109 | MyJoin.getInstance().getCancel().put(e.getPlayer().getName(),true); 110 | } 111 | } 112 | 113 | } 114 | 115 | ``` 116 | 6.创建命令 117 | ```java 118 | package net.noyark.www; 119 | 120 | import cn.nukkit.command.Command; 121 | import cn.nukkit.command.CommandSender; 122 | 123 | public class MyCommand extends Command { 124 | 125 | public MyCommand() { 126 | super("set","设置是否改变加入信息","/set"); 127 | } 128 | 129 | @Override 130 | public boolean execute(CommandSender sender, String commandLabel, String[] args) { 131 | if(args.length!=0) 132 | MyJoin.getInstance().getCancel().put(sender.getName(),Boolean.valueOf(args[0])); 133 | else 134 | //这行代码可能会吓到你,不要担心,他只是一个找玩家名字来找到是否取消 135 | //如果为空,则默认取消,如果不是空,则把玩家之前的状态取反 136 | MyJoin.getInstance().getCancel().put(sender.getName(), 137 | MyJoin.getInstance().getCancel().get( 138 | sender.getName() 139 | )==null?false:!MyJoin.getInstance() 140 | .getCancel() 141 | .get(sender.getName() 142 | ) 143 | ); 144 | return true; 145 | } 146 | } 147 | ``` 148 | 7. 注册 149 | ```java 150 | package net.noyark.www; 151 | 152 | import cn.nukkit.plugin.PluginBase; 153 | 154 | import java.util.Map; 155 | 156 | 157 | public class MyJoin extends PluginBase { 158 | 159 | private Map cancel; 160 | private static MyJoin instance; 161 | 162 | @Override 163 | public void onLoad() { 164 | this.getLogger().info("插件开始加载"); 165 | } 166 | 167 | @Override 168 | public void onEnable() { 169 | instance = this; 170 | this.saveDefaultConfig(); 171 | //一定在它的后面注册!!否则会出现一系列奇怪问题... 172 | this.getServer().getCommandMap().register("",new MyCommand()); 173 | this.getServer().getPluginManager().registerEvents(new MyJoinListener(),this); 174 | this.getLogger().info("插件初始化完毕"); 175 | 176 | } 177 | 178 | @Override 179 | public void onDisable() { 180 | this.getLogger().info("插件已经关闭"); 181 | } 182 | 183 | public static MyJoin getInstance() { 184 | return instance; 185 | } 186 | 187 | public void setCancel(String name,boolean isCancel) { 188 | cancel.put(name,isCancel); 189 | } 190 | 191 | public Map getCancel() { 192 | return cancel; 193 | } 194 | } 195 | 196 | ``` 197 | 198 | 这大概是这个项目的流程,很简单,但是大家可以练一练,扎实基本功,我们马上开始推入下一节 199 | 感谢你们的支持 200 | 201 | [上一节](1-7_PluginBase类.md) [下一节](../第二章/2-0_前言.md) -------------------------------------------------------------------------------- /第三章/3-0_前言.md: -------------------------------------------------------------------------------- 1 | # 第三章 第零节 前言 2 | 3 | 本部分将讲解nukkit插件的UI创建部分,学习完这一部分,你可以熟练创建三大种UI,感谢您开始观看这个教程。 4 | 5 | 本部分分为三个章节 6 | 7 | - [主要的Form种类及介绍](3-1_主要的Form种类及介绍.md) 8 | - [使用NukkitX自带的Form创建UI](3-2_使用NukkitX自带的Form创建UI.md) 9 | - [使用外部库GUI创建UI](3-2_使用外部库GUI创建UI.md) 10 | 11 | 每个章节都具有关联性,必须看完第一个才可以进行下一个,这样才能条理的学习 -------------------------------------------------------------------------------- /第三章/3-1_主要的Form种类及介绍.md: -------------------------------------------------------------------------------- 1 | [下一节](3-2_使用NukkitX自带的Form创建UI.md) 2 | 3 | # 第三章 第一节 主要的Form种类及介绍 4 | 5 | 参与编写者:`iGxnon` 6 | 7 | #### 建议学习时间:10分钟 8 | 9 | **学习要点:了解三个主要的Form及其各组件** 10 | 11 | UI是用户与程序交互的图形界面,Form则是Nukkit在UI方面上的一个呈现方式——**表格**。而这些可以被叫做三层架构中的**表示层**,用于接收用户传来的数据,并传递给**业务逻辑层**,最终可能会被写入**持久层(配置文件)**。 12 | 13 | Nukkit提供了三种Form以及一些组件。正由于这些简单的表格配合组件,使得Nukkit的UI图形交互界面变得十分好写! 14 | 15 | 这一节我们不解析代码,只给学者提供一个思路,具体创建代码看下一节 16 | 17 | 所有组件和Form位置均在`cn.nukkit.form`包下 18 | 19 | 一. 三种Form 20 | 21 | 1. **Simple_Form** 22 | - 特点:这种表格只能放入**按钮**这一种组件 23 | - 作用:主要用于做各种表格之间的跳转,点击执行命令等操作 24 | - 位置 `cn.nukkit.form.window.FormWindowSimple` 25 | 2. **Custom_Form** 26 | - 特点: 这种表格可以放入**除了按钮**以外的组件,不能设置content(可通过label这一组件弥补) 27 | - 作用: 接收用户传入的各种数据类型(字符串,布尔,数),用于传输数据 28 | - 位置 `cn.nukkit.form.window.FormWindowCustom` 29 | 3. **Modal_Form** 30 | - 特点: 这种表格**只能放入两个按钮(不能带有图片)** 31 | - 作用: 专门用于接收用户传入的布尔类型(true or false),常见于一些确认界面中 32 | - 位置 `cn.nukkit.form.window.FormWindowModal` 33 | 34 | 以上的三种Form各自对应着自己的一个FormResponse,例如FormWindowSimple -> FormResponseSimple 35 | 36 | FormResponse:接收玩家交互后传入的数据(例如玩家摁了哪个按钮),用于做数据传输或者处理 37 | 38 | 二. 组件 39 | 40 | 1. **Button**(按钮) 41 | - 特点:只能被放入Simple类型的Form中,可以传入图片 42 | - 位置 `cn.nukkit.form.element.ElementButton` 43 | 2. **Dropdown**(下滑块) 44 | - 特点:点击弹出一个下滑块列表,可供用户从有限的**备选类型**中选取一个 45 | - 位置 `cn.nukkit.form.element.ElementDropdown` 46 | 3. **Input** (文本输入框) 47 | - 特点:用户可以在框中键入一些字符串 48 | - 位置 `cn.nukkit.form.element.ElementInput` 49 | 4. **Label** (标签) 50 | - 特点:作用类似于content,在表格内添加一个文本标签 51 | - 位置 `cn.nukkit.form.element.ElementLabel` 52 | 5. **Slider** (水平滑块_1) 53 | - 特点:设置最小值和最大值后,可以滑动滑块获得一个数值(类似进度条) 54 | - 位置 `cn.nukkit.form.element.ElementSlider` 55 | 6. **StepSlider**(水平滑块_2) 56 | - 特点:类似于Dropdown,只不过将下滑块改成水平滑块 57 | - 位置 `cn.nukkit.form.element.ElementStepSlider` 58 | 7. **Toggle**(开关) 59 | - 特点:可以接受布尔值 60 | - 位置 `cn.nukkit.form.element.ElementToggle` 61 | 62 | 三. 创建流程 63 | 64 | **上面我们已经学习到了三种Form和组件,现在就来讲一讲它们的创建流程:** 65 | 66 | 发送一个Form 67 | 68 | - 实例化一种Form ——> 设置标题(Title)和内容(content,Custom类型除外) ——> 添加能加入的组件 ——> 将Form发送给玩家 69 | 70 | 接受玩家对Form的交互信息 71 | 72 | - 监听`PlayerFormRespondedEvent` ——> 获取到FormResponse ——> 分析FormResponse中返回的交互数据 ——> 将数据进一步传输或者直接判断处理 73 | 74 | 75 | 76 | [下一节](3-2_使用NukkitX自带的Form创建UI.md) 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /第三章/3-2_使用NukkitX自带的Form创建UI.md: -------------------------------------------------------------------------------- 1 | [上一节](3-1_主要的Form种类及介绍.md) [下一节](3-2_使用外部库GUI创建UI.md) 2 | 3 | # 第三章 第二节 使用NukkitX自带的Form创建UI 4 | 5 | 参与编写者:`iGxnon` 6 | 7 | #### 建议学习时间:40分钟 8 | 9 | **学习要点:掌握三种Form的创建方式以及对交互数据处理方式** 10 | 11 | 上一节我们已经介绍了各种Form和组件,并且梳理了一个UI从创建到处理用户交互数据的一个流程,这一节正式介绍代码 12 | 13 | 一. Simple_Form 14 | 15 | - FormWindowSimple中的构造方法 16 | 17 | ```java 18 | public FormWindowSimple(String title, String content) { 19 | this(title, content, new ArrayList<>()); 20 | } 21 | public FormWindowSimple(String title, String content, List buttons) { 22 | this.title = title; 23 | this.content = content; 24 | this.buttons = buttons; 25 | } 26 | ``` 27 | 28 | 我们可以看到有两种构造方法都要传入`title`和`content`,这两个值对应着Simple_Form的**标题**和**内容**。 29 | 30 | 第二种构造方法多了一个由`ElementButton`组成的一个List,表示了这个Simple_Form中的按钮列表 31 | 32 | 不过我们常常使用第一种构造方法,然后调用`addButton(ElementButton btn)`来添加按钮。 33 | 34 | - ElementButton的构造方法 35 | 36 | ```java 37 | public ElementButton(String text) { 38 | this.text = text; 39 | } 40 | public ElementButton(String text, ElementButtonImageData image) { 41 | this.text = text; 42 | if (!image.getData().isEmpty() && !image.getType().isEmpty()) this.image = image; 43 | } 44 | ``` 45 | 46 | 第一个构造方法不用多说,传入一个按钮上显示的文本`text`。我们发现第二个构造方法可以传入一个`ElementButtonImageData`。ElementButtonImageData,简单介绍一下,这个是按钮上图片的解释类。我 47 | 48 | 们来看一看它的构造方法`ElementButtonImageData(String type, String data)` 49 | 50 | - String `type` 51 | 52 | 只能填入"url"或者"path" 53 | 54 | "url":在`data`内填入一个url图片地址,即可解析到按钮上 55 | 56 | "path":在`data`内填入一个本地资源包内一个图片的路径,即可解析到按钮上 57 | 58 | - String `data` 59 | 60 | 填入一个url或者是一个路径,取决于`type`所填入的值 61 | 62 | 例如"textures/blocks/bedrock.png" (type是"path"下) 即可在按钮左边显示一个基岩的图片 63 | 64 | - 简单的实例: 65 | 66 | ```java 67 | class UI { 68 | public static final int MENU = 114514; //用于标记menu这个UI界面,方便监听事件时候判断 69 | 70 | public static void menu(Player player) { 71 | FormWindowSimple form = new FormWindowSimple("我是标题", "我是内容"); 72 | form.addButton(new ElementButton("我是按钮1")); 73 | form.addButton(new ElementButton("我是按钮2", new ElementButtonImageData("path","textures/blocks/bedrock.png"))); 74 | form.addButton(new ElementButton("我是按钮3")); 75 | player.showFormWindow(form, MENU); //将form发送给玩家,将MENU标记给了这个form 76 | 77 | // showFormWindow部分代码 78 | /** 79 | * Shows a new FormWindow to the player 80 | * 将一个FormWindow发送给玩家 81 | * You can find out FormWindow result by listening to PlayerFormRespondedEvent 82 | * 你可以从监听PlayerFormRespondedEvent中得到玩家的交互数据 83 | * @param window to show 84 | * @return form id to use in {@link PlayerFormRespondedEvent} 85 | */ 86 | 87 | //public int showFormWindow(FormWindow window) { 88 | // return showFormWindow(window, this.formWindowCount++); 89 | //} 90 | 91 | /** 92 | * Shows a new FormWindow to the player 93 | * You can find out FormWindow result by listening to PlayerFormRespondedEvent 94 | * 95 | * @param window to show 96 | * @param id form id 97 | * @return form id to use in {@link PlayerFormRespondedEvent} 98 | */ 99 | //public int showFormWindow(FormWindow window, int id) { 100 | // ModalFormRequestPacket packet = new ModalFormRequestPacket(); 101 | // packet.formId = id; 102 | // packet.data = window.getJSONData(); 103 | // this.formWindows.put(packet.formId, window); 104 | 105 | // this.dataPacket(packet); 106 | // return id; 107 | //} 108 | } 109 | } 110 | ``` 111 | 112 | 以上的方法可以将一个id标记为`114514`的`menu`UI界面发送给玩家。 113 | 114 | 但是玩家对Form的交互却没有任何响应(即按了三个按钮也没响应),所以我们需要监听玩家的**交互事件**并取得交互数据 115 | 116 | 交互事件:**PlayerFormRespondedEvent** 117 | 118 | - 监听器的代码: 119 | 120 | ```java 121 | @EventHandle 122 | public void onFormResponse(PlayerFormRespondedEvent event) { 123 | Player player = event.getPlayer(); 124 | int id = event.getFormID(); //这将返回一个form的唯一标识`id` 125 | if(id == UI.MENU) { //判断出这个UI界面是否是我们上面写的`menu` 126 | FormResponseSimple response = (FormResponseSimple) event.getResponse(); //这里需要强制类型转换一下 127 | //获取到被按的按钮的id(如果按"x"则返回-1) 128 | //重点: 按钮id: 我们将上面的addButton中传入的ElementButton放入一个数组,返回的id就是被按的Button的角标 129 | //例如:玩家按了"我是按钮1"这个按钮,则会返回0 130 | int clickedButtonId = response.getClickedButtonId(); 131 | //分类别讨论 132 | switch (clickedButtonId){ 133 | case 0: 134 | player.sendMessage("你摁了按钮1"); 135 | break; 136 | case 1: 137 | player.sendMessage("你摁了按钮2"); 138 | break; 139 | case 2: 140 | player.sendMessage("你摁了按钮3"); 141 | break; 142 | default: 143 | break; 144 | } 145 | } 146 | } 147 | ``` 148 | 149 | 完成上述代码后,用户将收到一个UI界面,并且按任意按钮都会发送信息提示。在外部只需要UI.menu(player)即可召唤UI 150 | 151 | 二. Custom_Form 152 | 153 | - FormWindowCustom中的构造方法 154 | 155 | ```java 156 | public FormWindowCustom(String title) { 157 | this(title, new ArrayList<>()); 158 | } 159 | public FormWindowCustom(String title, List contents) { 160 | this(title, contents, (ElementButtonImageData) null); 161 | } 162 | public FormWindowCustom(String title, List contents, String icon) { 163 | this(title, contents, icon.isEmpty() ? null : new ElementButtonImageData(ElementButtonImageData.IMAGE_DATA_TYPE_URL, icon)); 164 | } 165 | public FormWindowCustom(String title, List contents, ElementButtonImageData icon) { 166 | this.title = title; 167 | this.content = contents; 168 | this.icon = icon; 169 | } 170 | ``` 171 | 172 | 我们可以看到这有很多种构造方法,不过我们常用的只有**第一种**。另外,FormCustom没有content这一个属性,不过可以通过添加Label组件弥补 173 | 174 | 在实例化一个CustomForm之后,我们需要调用`addElement(Element element)`这个方法去添加组件 175 | 176 | 注意:无法在CustomForm中添加**ElementButton**这个组件! 177 | 178 | - 简单实例 179 | 180 | ```java 181 | class UI implements Listener { //一般实际开发中不在这个类中写监听器 182 | public static final int MENU = 114514; 183 | public static void menu(Player player) { 184 | FormWindowCustom form = new FormWindowCustom("我是标题"); 185 | // 添加一个标签组件 186 | form.addElement(new ElementLabel("我是标签")); // 组件角标: 0 (Label也占一个元素角标) 187 | // 添加一个下滑块组件 188 | form.addElement(new ElementDropdown("我是下滑块", Arrays.asList("元素1", "元素2", "元素3"))); // 组件角标: 1 189 | // 添加一个文本输入框组件 190 | form.addElement(new ElementInput("我是文本输入框")); // 组件角标: 2 191 | // 添加一个水平滑块_1 (text, 最小值, 最大值, 滑动最小步数) 192 | form.addElement(new ElementSlider("我是水平滑块", 0, 100, 1)); // 组件角标: 3 193 | // 添加一个水平滑块_2 194 | form.addElement(new ElementStepSlider("我是水平滑块", Arrays.asList("元素1", "元素2", "元素3"))); // 组件角标: 4 195 | // 添加一个开关 196 | form.addElement(new ElementToggle("我是开关")); // 组件角标: 5 197 | player.showFormWindow(form, MENU); 198 | } 199 | 200 | // 另外,CustomForm会自带一个"提交"按钮,用于交付数据,我们无需添加 201 | 202 | // 监听器部分 203 | @EventHandle 204 | public void onFormResponse(PlayerFormRespondedEvent event) { 205 | Player player = event.getPlayer(); 206 | int id = event.getFormID(); //这将返回一个form的唯一标识`id` 207 | if(id == MENU){ 208 | FormResponseCustom response = (FormResponseCustom) event.getResponse(); 209 | //我们通过 FormResponseCustom 中提供的: 210 | // - getDropdownResponse(int id) 返回Dropdown中被选择的元素的封装对象 211 | // - getInputResponse(int id) 返回文本输入框中的字符串 212 | // - getSliderResponse(int id) 返回数值水平滑块的浮点值 213 | // - getStepSliderResponse(int id) 返回StepSlider中被选择的元素的封装对象 214 | // - getToggleResponse(int id) 返回开关的布尔类型(开和关) 215 | // 注意: getDropdownResponse和getStepSliderResponse返回的都是一个FormResponseData对象,我们需要调用它的getElementContent()获得被选元素的字符串数值 216 | // id 和 SimpleForm中的按钮角标一样,从0开始往后标记 217 | String dropDown = response.getDropdownResponse(1).getElementContent(); 218 | String input = response.getInputResponse(2); 219 | float slider = response.getSliderResponse(3); 220 | String stepSlider = response.getDropdownResponse(4).getElementContent(); 221 | boolean toggle = response.getToggleResponse(5); 222 | // 获取到以上数据后就能进行一些数据传输了 223 | } 224 | } 225 | } 226 | ``` 227 | 228 | 229 | - 完成上述代码后,用户将会收到一个数据填写表格(CustomForm),并且我们可以通过监听器去获取用户填入的数据 230 | 231 | 三. Modal_Form 232 | 233 | - FormWindowModal中的构造方法 234 | 235 | ```java 236 | public FormWindowModal(String title, String content, String trueButtonText, String falseButtonText) { 237 | this.title = title; 238 | this.content = content; 239 | this.button1 = trueButtonText; 240 | this.button2 = falseButtonText; 241 | } 242 | ``` 243 | 244 | `title`和`content`不用多说,这里解释`trueButtonText`和`falseButtonText` 245 | 246 | `trueButtonText`:填入一个字符串,但应填入"确定","同意","是"或者它们的近义词,这个将会是true的选项按钮 247 | 248 | `falseButtonText`:填入一个字符串,但应填入"拒绝","否","返回"或者它们的近义词,这个将会是false的选项按钮 249 | 250 | 注意: ModalForm中只能填入这两个按钮,无法添加更多或者更少!并且按钮没有图标。 251 | 252 | PS: ModalForm的所有功能其实都是可以用SimpleForm代替,不过我们通常会使用ModalForm对于用户确定某个行为所弹出的UI。 253 | 254 | - 简单实例 255 | 256 | ```java 257 | class UI implements Listener { 258 | public static final int MENU = 1433223; 259 | public static void menu(Player player) { 260 | FormWindowModal form = new FormWindowModal("我是标题", "我是内容", "点我确定", "点我取消"); //这就实例化好了一个ModalForm 261 | player.showFormWindow(form, MENU); // 标记id并且发送 262 | } 263 | 264 | @EventHandle 265 | public void onFormResponse(PlayerFormRespondedEvent event) { 266 | Player player = event.getPlayer(); 267 | int id = event.getFormID(); //这将返回一个form的唯一标识`id` 268 | if(id == MENU) { 269 | FormResponseModal response = (FormResponseModal) event.getResponse(); 270 | int id = response.getClickedButtonId(); 271 | //注意: 返回的按钮id是从1开始的,也就是说"true"代表1,"false"代表2 272 | if(id == 1){ 273 | //这里执行确定后的代码 274 | }else{ 275 | // 这里执行取消后的代码 276 | } 277 | } 278 | } 279 | } 280 | ``` 281 | 282 | - 完成上述代码后,用户将收到一个是否确认界面,我们在监听器内写入确认或者取消后发生的代码 283 | 284 | 四. 小案例 285 | 286 | - 张三想要一个按钮,如果他是OP的话,按钮text为"撤销OP",并且点击就会撤销OP,如果他不是OP的话,按钮text为"给予OP",点击后添加OP 287 | 288 | - 我们在addButton的时候判断张三是否是op,如果是,则addButton("撤销OP"),如果不是,则addButton("给予OP"),这样就可以做到同一个位置的按钮对于不同状态的张三可以显示不同内容 289 | 290 | - 我们在事件监听中,监听到如果张三按到了切换OP状态的这一个按钮,并且判断张三此时是不是OP,然后给予OP或者撤销OP。 291 | 292 | 这固然可以完成上面的需求,但是如果有一种状态,只能在创建UI时候能获取,在监听器中无法获取呢?假如上面的例子,我们无法在监听器中获取张三是否为OP,怎么办呢? 293 | 294 | 我们可以从Event中获取Form对象,根据clickButtonId来获取被按的按钮对象,进而获取它的text属性并判断是否含有"撤销"这一个字符串。就能做到无需检测张三此时的状态,而从一个按钮上的文本来进行判断。 295 | 296 | 这里代码就不贴了,感兴趣的可以自己去写一写这个小案例。 297 | 298 | 五. 开发技巧 299 | 300 | - 我们会将一些拥有特定状态的人才会显示的按钮放到一个Form表格的最后一个。例如"OP系统",这个按钮往往是在最后一个位置上的。这样可以不打乱整体的按钮角标值。只需在监听器中扩充一个角标就行(普通用户根本不会按到OP系统) 301 | 302 | [上一节](3-1_主要的Form种类及介绍.md) [下一节](3-2_使用外部库GUI创建UI.md) 303 | -------------------------------------------------------------------------------- /第三章/3-3_使用外部库GUI创建UI.md: -------------------------------------------------------------------------------- 1 | [上一节](3-2_使用NukkitX自带的Form创建UI.md) 2 | 3 | # 第三章 第三节 使用外部库GUI创建UI 4 | 5 | 参与编写者:`iGxnon` 6 | 7 | #### 建议学习时间:20分钟 8 | 9 | **学习要点:掌握GUI创建三种Form的方式以及对交互数据的处理** 10 | 11 | 上一节中我们学习了如何使用NukkitX自带的三种Form表格和组件创建UI,三种Form创建流程都不外乎: 12 | 13 | 写UI视图 ——> 监听交互并处理。可是人类总是想着各种方法偷懒,这一节就引出外部库 GUI (作者: him188) 14 | 15 | GUI: [Github地址](https://github.com/Him188/GUI) ,使创建UI的方式变得更加简洁 注: GUI的maven仓库已经失效,~~只能下载jar依赖。~~ 16 | 17 | 有个代替的仓库地址可以用,如下: (smartcmd的仓库) 18 | ```xml 19 | 20 | 21 | ck 22 | http://finalgame.cn:51460/repository/cookiestudio/ 23 | 24 | 25 | 26 | 27 | 28 | moe.him188 29 | gui 30 | 1.15.1 31 | 32 | 33 | ``` 34 | 35 | 另外介绍一下 FormAPI [GitHub地址](https://github.com/qPexLegendary/FormAPI) 使用这个依赖也可以达到更简洁创建UI,不过它可能会有一种打不开UI的bug 36 | 37 | 如图: ![](images/3-2_使用外部库GUI创建UI/3-1.png) 38 | 39 | 这种神秘力量只能用"我代码写错了"才能解释清楚,所以建议使用GUI 40 | 41 | 注意: 学习本节之前,你得完全掌握 匿名内部类 **或者** lambda 表达式 的使用 42 | 43 | 一: GUI的简单实现原理(没有兴趣的可以直接跳过): 44 | 45 | - GUI提供了不止三种Form表格 (但往上追溯还是那三种) 46 | - ![](images/3-2_使用外部库GUI创建UI/3-3.png) 47 | 48 | - 例如:`FormSimple` 提供给了一个 `onClicked` 49 | 50 | ```java 51 | /** 52 | * 在玩家提交表单后调用
53 | * Called on submitted 54 | * 55 | * @param listener 调用的方法 56 | */ 57 | public final ResponsibleFormWindowSimple onClicked(@NotNull BiConsumer listener) { 58 | Objects.requireNonNull(listener); 59 | this.buttonClickedListener = listener; 60 | return this; 61 | } 62 | /** 63 | * 在玩家提交表单后调用
64 | * Called on submitted 65 | * 66 | * @param listener 调用的方法(无 Player) 67 | */ 68 | public final ResponsibleFormWindowSimple onClicked(@NotNull Consumer listener) { 69 | Objects.requireNonNull(listener); 70 | this.buttonClickedListener = (id, player) -> listener.accept(id); 71 | return this; 72 | } 73 | /** 74 | * 在玩家提交表单后调用
75 | * Called on submitted 76 | * 77 | * @param listener 调用的方法(无参数) 78 | */ 79 | public final ResponsibleFormWindowSimple onClicked(@NotNull Runnable listener) { 80 | Objects.requireNonNull(listener); 81 | this.buttonClickedListener = (id, player) -> listener.run(); 82 | return this; 83 | } 84 | ``` 85 | 86 | - `Consumer`和`BiConsumer`都是接口`Interface`,我们可以使用 lambda 表达式直接写它的实现类。也可以用 匿名内部类去编写。 87 | 88 | - 最后 GUI 帮我们监听事件并执行我们在 `Consumer` 或 `BiConumer`的实现类中的`accept`方法。 89 | 90 | - 综上:我们无需监听事件,在写UI视图的同时也能将用户交互后的处理写完。 91 | 92 | 二. 代码部分 93 | 94 | - `FormSimple ` 构造方法 95 | 96 | ```java 97 | public FormSimple() { 98 | } 99 | 100 | public FormSimple(String content) { 101 | super(content); 102 | } 103 | 104 | public FormSimple(String title, String content) { 105 | super(title, content); 106 | } 107 | 108 | public FormSimple(String title, String content, String... buttons) { 109 | super(title, content, buttons); 110 | } 111 | 112 | public FormSimple(String title, String content, ElementButton... buttons) { 113 | super(title, content, buttons); 114 | } 115 | 116 | public FormSimple(String title, String content, @NotNull List buttons) { 117 | super(title, content, buttons); 118 | } 119 | 120 | ``` 121 | 122 | 构造方法与NukkitX自带的FormWindowSimple差不多。所以不必赘述。 123 | 124 | `FormSimple `继承了`ResponsibleFormWindowSimple`,在 `ResponsibleFormWindowSimple` 我们常常用的方法有: 125 | 126 | - `addButton(ElementButton btn)` 添加一个按钮 127 | 128 | - `addButton(ElementButton btn, @NotNull ClickListenerSimple clickListener) `添加一个按钮,并在`ClickListenerSimple`中写入按下这个按钮的处理 129 | 130 | `ClickListenerSimple`: 最终继承了 Consumer 并且传入了Player这个泛型,我们可以获取player并且操作 131 | 132 | 这个方法可以通过添加 ResponsibleButton 来代替。ResponsibleButton是一种可传入点击后处理的特殊按钮 133 | 134 | - `setParent(FormWindow form)` 设置父表格,用于 goBack(Player player) 这个方法返回上一个表格 135 | 136 | - `onClicked(@NotNull Consumer listener)` 写入处理交互事件的匿名实现类 Integer 返回被按的按钮id- 137 | 138 | - Demo 139 | 140 | ```java 141 | import moe.him188.gui.window.FormSimple; //导入依赖 142 | 143 | class UI { 144 | public static void menu(Player player) { 145 | FormSimple form = new FormSimple("我是标题", "我是内容"); 146 | form.addButton(new ElementButton("我是普通按钮")); // 角标 0 147 | form.addButton(new ResponsibleButton("我是特殊按钮还带图标", 148 | new ElementButtonImageData("path", "textures/blocks/bedrock.png"), 149 | () -> player.sendMessage("你按了特殊按钮"))); // 这一行是lambda表达式,如果不清楚使用idea new 一下ClickListenerSimple, 会弹出提示使用匿名内部类 角标 1 150 | player.showFormWindow(form.onClicked(id -> { //这里也用了lambda 151 | if(id == 0) player.sendMessage("你按了普通按钮"); 152 | })); 153 | // 上面onClicked内只需写入第一个按钮的处理方法。因为第二个按钮的处理方法在添加的时候就写了,无需重复! 154 | } 155 | } 156 | ``` 157 | 158 | - 完成上述代码后,即可达到使用NukkitX自带的Form创建UI的所有流程 159 | - 综上: 人类的偷懒是永无止境的 160 | 161 | - `FormCustom` 构造方法 162 | 163 | ```java 164 | public FormCustom() { 165 | } 166 | 167 | public FormCustom(String title) { 168 | super(title); 169 | } 170 | 171 | public FormCustom(String title, Element... contents) { 172 | super(title, contents); 173 | } 174 | 175 | public FormCustom(String title, @NotNull List contents) { 176 | super(title, contents); 177 | } 178 | 179 | public FormCustom(String title, @NotNull List contents, @NotNull String icon) { 180 | super(title, contents, icon); 181 | } 182 | ``` 183 | 184 | 又是和NK自带的差不多,所以不用多说 185 | 186 | `FormCustom`内常用方法和`FormSimple的差不多`,不同点: 187 | 188 | - `onResponded(@NotNull Consumer listener)` 这个类似FormSimple中的onClicked。只不过泛型变成了`FormResponseCustom` 这个上一节我们已经了解,是Custom这个表格的Response,可以获得交互数据 189 | - 没有了` addButton` ,我们讲过: **无法在Custom类型的Form中添加 按钮** 190 | - `addElement(Element element)` 这个甚至在`ResponsibleFormWindowCustom`中没有重写,也就意味着这个和NK自带的Custom类型的Form添加组件是一样的。 191 | 192 | - Demo 193 | 194 | ```java 195 | import moe.him188.gui.window.FormCustom; //导入依赖 196 | 197 | class UI { 198 | public static void menu(Player player) { 199 | FormCustom form = new FormCustom("我是标题"); 200 | form.addElement(new ElementInput("文本输入框")); //角标 0 201 | player.showFormWindow(form.onResponded(response -> { //这里也用了lambda 202 | player.sendMessage(response.getInputResponse(0)); 203 | })); 204 | } 205 | } 206 | ``` 207 | 208 | - 简洁,明了 209 | 210 | - `FormModal` 构造方法 211 | 212 | ```java 213 | public FormModal(String trueButtonText, String falseButtonText) { 214 | super(trueButtonText, falseButtonText); 215 | } 216 | 217 | public FormModal(String content, String trueButtonText, String falseButtonText) { 218 | super(content, trueButtonText, falseButtonText); 219 | } 220 | 221 | public FormModal(String title, String content, String trueButtonText, String falseButtonText){ 222 | super(title, content, trueButtonText, falseButtonText); 223 | } 224 | ``` 225 | 226 | 和NK自带的也是差不多的 227 | 228 | `FormModal` 常用方法和`FormCustom`几乎一样,不同点 229 | 230 | - `onResponded(@NotNull Consumer listener)` 可以看出,这里的泛型是Boolean,于是我们可以直接放到 if 内判断 231 | - 没有 `addElement`和`addButton` 232 | 233 | - Demo 234 | 235 | ```java 236 | import moe.him188.gui.window.FormModal; //导入依赖 237 | 238 | class UI { 239 | public static void menu(Player player) { 240 | // 一行写完 241 | player.showFormWindow((new FormModal("标题","内容","确定","取消")).onResponded(bool -> player.sendMessage(bool ? "确定" : "取消"))); 242 | } 243 | } 244 | ``` 245 | 246 | - 压缩再压缩! 247 | 248 | 三. 结语 249 | 250 | - 学到此处,你应当能够熟练的操控UI,和之前学到的知识结合,写出一个漂亮的公会插件! 251 | - GUI的表格不止这三种,但是对于我们来说,这三种已经完全够用了,感兴趣的话可以去看看GUI的github上的README,里面写了更多表格的使用方式 252 | 253 | [上一节](3-2_使用NukkitX自带的Form创建UI.md) 254 | 255 | 256 | 257 | 另外附上GUI教程传播授权图 258 | 259 | ![](images/3-2_使用外部库GUI创建UI/3-2.png) 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | -------------------------------------------------------------------------------- /第三章/3-4_介绍一个新的外部库-Easy4Form.md: -------------------------------------------------------------------------------- 1 | [上一节](3-3_使用外部库GUI创建UI.md) 2 | 3 | # 第三章 第四节 介绍一个新的外部库-Easy4Form 4 | 5 | 参与编写者:smartcmd 6 | 7 | #### 建议学习时间:20分钟 8 | 9 | Easy4Form是smartcmd写的一个十分简单的外部Form库 10 | 11 | 使用Easy4Form,你可以十分方便的写出Form交互功能 12 | 13 | 要想使用它,请在你的pom中加入以下配置: 14 | ```xml 15 | 16 | 17 | ck 18 | http://finalgame.cn:1000/repository/cookiestudio/ 19 | 20 | 21 | 22 | 23 | 24 | cn.cookiestudio 25 | Easy4Form 26 | 1.0.0 27 | provided 28 | 29 | 30 | ``` 31 | 32 | 并保证你的服务器中有加载Easy4Form插件(可以自行构建,github坐标在下面) 33 | 34 | 1.EZ4F包含的类: 35 | ![../images/3-4-01.png](../images/3-4-01.png) 36 | 怎么样,是不是很简单? 37 | 38 | 2.使用 39 | 我们以simpleForm作为栗子: 40 | ```java 41 | BFormWindowSimple bf = new BFormWindowSimple(); 42 | bf.addButton(...) 43 | ... 44 | ```` 45 | ... 46 | bf.setResponseAction((e) ->{//e是此Form的ResponseEvent对象(ez4f会保证此event是”这个“form的返回事件) 47 | ...//这里不需要判空,ez4f会代替你处理这些细节 48 | }); 49 | bf.sendToPlayer(player);//注意这里 50 | 51 | BFormWindowSimple.Builder bfb = BFormWindowSimple.getBuilder();//也可以用Builder 52 | bfb.addButton(...).setResponseAction(...).setTitle(...).setContent(...).build().sendToPlayer(Player); 53 | ``` 54 | 55 | 搭配lambda表达式,是不是很方便? 56 | 使用ez4f,你可以省去创建监听器,匹配formid等十分麻烦的步骤(特别是匹配formid,这一步非常爽) 57 | 58 | Easy4Form的github链接: https://github.com/Cookie-Studio/Easy4Form 59 | 60 | 顺便补一句,如果你的的nk依赖从官方maven仓库下不下来,我的仓库里面也是有的哦qwq -------------------------------------------------------------------------------- /第三章/images/3-2_使用外部库GUI创建UI/3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/第三章/images/3-2_使用外部库GUI创建UI/3-1.png -------------------------------------------------------------------------------- /第三章/images/3-2_使用外部库GUI创建UI/3-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/第三章/images/3-2_使用外部库GUI创建UI/3-2.png -------------------------------------------------------------------------------- /第三章/images/3-2_使用外部库GUI创建UI/3-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-Founder/NukkitLearn/978410c8d513b1a052225e2fc13a055c1135bf96/第三章/images/3-2_使用外部库GUI创建UI/3-3.png -------------------------------------------------------------------------------- /第不知道多少章 - 示例文档.md: -------------------------------------------------------------------------------- 1 | # 第不知道多少章 - 示例文档 2 | 3 | 本小节目录: 4 | - [1. Example 1](#1-example-1) 5 | - [1.1 这是一段文字。](#11-这是一段文字) 6 | - [2. Example 2](#2-example-2) 7 | - [2.1 This is **an example** *in English*.](#21-this-is-an-example-in-english) 8 | - [3. Example 3](#3-example-3) 9 | - [3.1 嗯~~~~](#31-嗯) 10 | 11 | --- 12 | 13 | 参与编写者:NukkitLearn 文案组 14 | 15 | 16 | #### 本小节建议学习时间:10 分钟; 17 | 18 | #### 学习要点:参考本模板,开始你的修改、创作之旅吧! 19 | --- 20 | 21 | ### 1. Example 1 22 | 23 | #### 1.1 这是一段文字。 24 | 25 | 不知道写点啥 26 | 27 | --- 28 | 29 | ### 2. Example 2 30 | 31 | #### 2.1 This is **an example** *in English*. 32 | 33 | 我觉得你们应该不介意我随便摘一段CNN的东西过来的。 34 | 35 | > **It's a fact**: Eating **too much fat** can raise cholesterol, clog arteries, and contribute to the risk of stroke and other forms of heart disease -- *not to mention obesity, diabetes, cancer and a host of other conditions.* 36 | 37 | --- 38 | 39 | ### 3. Example 3 40 | 41 | #### 3.1 嗯~~~~ 42 | 43 | **太好听了⑧!!!!!!** 44 | 45 | 你唱歌真的好好听啊!简直就是*天籁*! 46 | 47 | 我们*一起唱歌*好不好,**一起唱!!** 48 | 49 | 一起做 [学园偶像](https://www.bilibili.com/video/BV1gL411H7y2) !!! 50 | -------------------------------------------------------------------------------- /第二章/2-0_前言.md: -------------------------------------------------------------------------------- 1 | # 第二章 第零节 前言 2 | 3 | 本部分将讲解nukkit插件的高级应用部分,学习完这一部分,你就可以独自实现一个 4 | 自己的复杂的插件了,感谢您开始观看这个教程。 5 | 6 | 本部分分为七个章节 7 | 8 | - [主要的事件的介绍](2-1_主要的事件介绍.md) 9 | - [事件相关方法](2-2_事件相关方法.md) 10 | - [计时器的介绍](2-3_计时器的介绍.md) 11 | - [Server类和PluginManager类](2-4_Server类和PluginManager类.md) 12 | - [各种实体类的方法介绍](2-5_各种实体类的方法介绍.md) 13 | - [各种工具类的介绍](2-6_各种工具类的介绍.md) 14 | - [如何发送数据包](2-7_如何发送数据包.md) 15 | 16 | 每个章节都具有关联性,必须看完第一个才可以进行下一个,这样才能条理的学习 -------------------------------------------------------------------------------- /第二章/2-1_主要的事件介绍.md: -------------------------------------------------------------------------------- 1 | [上一节](2-0_前言.md) [下一节](2-2_事件相关方法.md) 2 | # 第二章 第一节 主要的事件介绍 3 | 参与编写者: SmallasWater MagicLu550 4 | #### 建议学习时间: 40分钟 5 | ##### 学习要点: 了解基本的事件 6 | 7 | 转载于[[教程] [原创][Wiki][NPS] NukkitX 事件大全](https://www.mcbbs.net/thread-813733-1-1.html) 8 | 9 | - block: 方块类 10 | - BlockBreakEvent: 当一个方块被玩家破坏的时候调用本事件 11 | - BlockBurnEvent: 当一个方块被火烧掉的时候触发此事件 12 | - BlockFadeEvent: 当一个方块因自然原因消失或衰落时触发此事件 13 | - BlockFormEvent: 当一个方块因为自然变化被放置、更改或者蔓延时(如下雪)触发此事件 14 | - BlockFromToEvent: 液体流动/龙蛋自己传送的事件(源方块到目标方块) 15 | - BlockGrowEvent: 当一个方块在世界中自然生长的时触发此事件(如小麦生长) 16 | - BlockIgniteEvent: 当一个方块被点燃时触发 17 | - BlockPistonChangeEvent: 活塞臂状态变化事件 18 | - BlockPlaceEvent: 当一个方块被玩家放置的时候触发此事件 19 | - BlockRedstoneEvent: 当方块接受到的红石信号变化时触发此事件 20 | - BlockSpreadEvent: 当一个方块基于自然法则地蔓延时触发此事件(如菌丝的蔓延) 21 | - BlockUpdateEvent: 方块更新事件(如你操作静态水周围方块时会触发本事件,导致水流淌) 22 | - DoorToggleEvent: 开关门事件 23 | - ItemFrameDropItemEvent: 物品展示框丢出物品事件 24 | - LeavesDecayEvent: 当树叶消失时触发此事件 25 | - LiquidFlowEvent: 当液体流动时触发此事件 26 | - SignChangeEvent: 在玩家设置牌子上的内容子时触发 27 | - BlockFallEvent: 重力方块下落时触发(当类似于砾石、沙子、龙蛋、混凝土、雪层和铁砧的方块落下时调用(判断下面是不是空气,火,液体),如果被取消,方块将不会落下) 28 | - entity: 实体类 29 | - CreatureSpawnEvent: 使用生成蛋时触发该事件 30 | - CreeperPowerEvent: 当爬行者被闪电击中时触发该事件 31 | - EntityArmorChangeEvent: 实体护甲变化事件 32 | - EntityBlockChangeEvent: 实体改变方块事件 33 | - EntityCombustByBlockEvent: 当方块造成实体燃烧时触发该事件 34 | - EntityCombustByEntityEvent: 当一个实体造成另外一个实体燃烧时触发该事件 35 | - EntityCombustEvent: 当实体燃烧时触发该事件 36 | - EntityDamageByBlockEvent: 当一个实体受到来自方块的伤害时触发该事件 37 | - EntityDamageByChildEntityEvent: 当一个实体受到另一个子实体的伤害时触发该事件 38 | - EntityDamageByEntityEvent: 当一个实体受到另外一个实体伤害时触发该事件 39 | - EntityDamageEvent: 储存伤害事件的数据 40 | - EntityDeathEvent: 当任何一个实体死亡时触发本事件 41 | - EntityDespawnEvent: 实体被移除事件 42 | - EntityExplodeEvent: 当一个实体爆炸的时候触发本事件 43 | - EntityExplosionPrimeEvent: 当一个实体决定爆炸时调用(苦力怕的闪烁) 44 | - EntityInteractEvent: 当一个实体与其他物体互交时触发本事件 45 | - EntityInventoryChangeEvent: 实体存储变化事件 46 | - EntityLevelChangeEvent: 当一个实体进入另一个世界时触发该事件 47 | - EntityMotionEvent: 实体运动事件 48 | - EntityPortalEnterEvent: 当一个实体与传送门接触时触发本事件 49 | - EntityRegainHealthEvent: 实体生命回复事件 50 | - EntityShootBowEvent: 生命实体射出箭时触发 51 | - EntitySpawnEvent: 实体生成事件 52 | - EntityTeleportEvent: 当非玩家实体(如末影人)试图从一个位置传送到另一个位置时触发 53 | - EntityVehicleEnterEvent: 实体进入载具事件 54 | - EntityVehicleExitEvent: 实体退出载具事件 55 | - ExplosionPrimeEvent: 当一个实体决定爆炸时调用(TNT的闪烁) 56 | - ItemDespawnEvent: 从世界中移除掉落物时会调用此事件(因为掉落满5分钟) 57 | - ItemSpawnEvent: 世界中产生掉落物时会调用此事件 58 | - ProjectileHitEvent: 当一个抛射物击中物体时触发本事件 59 | - ProjectileLaunchEvent: 当一个抛射物被发射时触发本事件 60 | - inventory: 物品格类 61 | - BrewEvent: 当酿造完成时触发这个事件 62 | - CraftItemEvent: 当一个物品被合成的时候触发这个事件 63 | - FurnaceBurnEvent: 当一个物品作为燃料被燃烧的时候触发这个事件 64 | - FurnaceSmeltEvent: 当一个物品被熔炼完毕时触发这个事件 65 | - InventoryClickEvent: 当玩家点击物品栏中的格子时触发事件事件 66 | - InventoryCloseEvent: 当玩家关闭背包时触发本事件 67 | - InventoryMoveItemEvent: 非玩家触发格子物品传输时触发此事件(如漏斗) 68 | - InventoryOpenEvent: 当玩家打开背包时触发本事件 69 | - InventoryPickupArrowEvent: 当玩家收起发射出去的箭时触发本事件 70 | - InventoryPickupItemEvent: 当玩家收起掉落的物品时触发本事件 71 | - InventoryTransactionEvent: 村民交易事件 72 | - StartBrewEvent: 开始酿造事件 73 | - level: 世界类 74 | - ChunkLoadEvent: 当一个区块被加载时调用 75 | - ChunkPopulateEvent: 当一个新的区块填充完毕时调用 76 | - ChunkUnloadEvent: 当一个区块被卸载时调用 77 | - LevelInitEvent: 当一个世界被初始化时调用 78 | - LevelLoadEvent: 当一个世界被加载时调用 79 | - LevelSaveEvent: 当一个世界被保存时调用 80 | - LevelUnloadEvent: 当一个世界被卸载时调用 81 | - SpawnChangeEvent: 一个在世界的出生点被改变时调用的事件 82 | - ThunderChangeEvent: 同天气类下同名事件 83 | - WeatherChangeEvent: 同天气类下同名事件 84 | - player: 玩家类 85 | - PlayerAchievementAwardedEvent: 当玩家获得某个成就时触发此事件 86 | - PlayerAnimationEvent: 玩家动作事件 87 | - PlayerAsyncPreLoginEvent: 存储尝试登录的玩家的详细信息,玩家尝试登录服务器的事件 88 | - PlayerBedEnterEvent: 玩家躺在床上时触发此事件 89 | - PlayerBedLeaveEvent: 玩家离开床时触发此事件 90 | - PlayerBlockPickEvent: 玩家捡拾方块事件 91 | - PlayerBucketEmptyEvent: 玩家用完一只桶后触发此事件 92 | - PlayerBucketFillEvent: 玩家装满一只桶后触发此事件 93 | - PlayerChatEvent: 玩家聊天/使用命令会触发本事件 94 | - PlayerChunkRequestEvent: 玩家请求区块事件 95 | - PlayerCommandPreprocessEvent: 玩家执行命令时触发(/开头) 96 | - PlayerCreationEvent: 创建玩家事件 97 | - PlayerDeathEvent: 玩家死亡事件 98 | - PlayerDropItemEvent: 玩家丢出物品事件 99 | - PlayerEatFoodEvent: 玩家吃食物事件 100 | - PlayerFoodLevelChangeEvent: 玩家饥饿度变化事件 101 | - PlayerFormRespondedEvent: 玩家操作GUI(按钮/滑块/输入框/选择框)事件 102 | - PlayerGameModeChangeEvent: 当玩家游戏模式发生变化时调用此事件 103 | - PlayerInteractEntityEvent: 当玩家点击一个实体时调用此事件 104 | - PlayerInteractEvent: 当玩家对一个对象或空气进行交互时触发本事件 105 | - PlayerInvalidMoveEvent: 当玩家不正确地移动时触发 106 | - PlayerItemConsumeEvent: 当玩家使用了一个消耗品(食物/药水/牛奶桶)时触发 107 | - PlayerItemHeldEvent: 玩家手持某物品事件 108 | - PlayerJoinEvent: 玩家进入服务器事件 109 | - PlayerJumpEvent: 玩家跳跃时触发 110 | - PlayerKickEvent: 玩家被服务器踢出事件 111 | - PlayerLocallyInitializedEvent 玩家客户端初始化完成事件 112 | - PlayerLoginEvent: 玩家尝试登录的事件 113 | - PlayerMapInfoRequestEvent: 玩家请求地图信息事件 114 | - PlayerMouseOverEntityEvent: 玩家鼠标停留在实体上事件 115 | - PlayerMoveEvent: 玩家移动事件 116 | - PlayerPreLoginEvent: 玩家尝试登录服务器事件 117 | - PlayerQuitEvent: 玩家离开服务器事件 118 | - PlayerRespawnEvent: 玩家重生事件 119 | - PlayerServerSettingsRequestEvent: 玩家请求服务器设置事件 120 | - PlayerSettingsRespondedEvent: 响应玩家设置事件 121 | - PlayerTeleportEvent: 玩家传送事件 122 | - PlayerToggleFlightEvent: 玩家切换飞行状态时触发 123 | - PlayerToggleGlideEvent: 玩家切换滑翔状态时触发 124 | - PlayerToggleSneakEvent: 玩家切换潜行状态时触发 125 | - PlayerToggleSprintEvent: 玩家切换疾跑状态时触发 126 | - PlayerToggleSwimEvent: 玩家切换游泳状态时触发 127 | - plugin: 插件类 128 | - PluginDisableEvent: 插件禁用事件 129 | - PluginEnableEvent: 插件启用事件 130 | - potion: 药水类 131 | - PotionApplyEvent: 药水生效事件 132 | - PotionCollideEvent: 药水冲突事件 133 | - redstone: 红石类 134 | - RedstoneUpdateEvent: 红石更新事件 135 | - server: 服务端类 136 | - BatchPacketsEvent: 批处理数据包事件 137 | - DataPacketReceiveEvent: 数据包接收事件 138 | - DataPacketSendEvent: 数据包发送事件 139 | - PlayerDataSerializeEvent: 玩家数据序列化事件 140 | - QueryRegenerateEvent: GameSpy4协议监听事件 141 | - RemoteServerCommandEvent: 这个事件当服务器RCON收到指令时调用 142 | - ServerCommandEvent: 这个事件当服务器后台发送指令时调用. 这是命令开始处理过程之前被触发的 143 | - ServerStopEvent: 服务器关闭事件 144 | - vehicle: 载具类 145 | - EntityEnterVehicleEvent: 实体进入载具事件 146 | - EntityExitVehicleEvent: 实体退出载具事件 147 | - VehicleCreateEvent: 载具创建事件 148 | - VehicleDamageEvent: 载具被伤害的事件 149 | - VehicleDestroyEvent: 载具被损毁的事件(包含被玩家/自然损坏) 150 | - VehicleMoveEvent: 载具移动事件 151 | - VehicleUpdateEvent: 载具更新事件 152 | - weather: 天气类 153 | - LightningStrikeEvent: 雷击事件 154 | - ThunderChangeEvent: 世界打雷事件 155 | - WeatherChangeEvent: 天气改变事件 156 | 157 | 158 | 其中的方法介绍 159 | 160 | 本内容将会在[第二章](2-2_事件相关方法.md)提到,也可以参考[bukkit文档](https://bukkit.windit.net/javadoc/) 161 | 162 | 参考文献 163 | - [[教程] [原创][Wiki][NPS] NukkitX 事件大全](https://www.mcbbs.net/thread-813733-1-1.html) 164 | - [bukkit中文doc](https://bukkit.windit.net/javadoc/) 165 | 166 | [上一节](1-8_案例玩家进入信息等效果.md) [下一节](2-2_事件相关方法.md) 167 | -------------------------------------------------------------------------------- /第二章/2-2_事件相关方法.md: -------------------------------------------------------------------------------- 1 | [上一节](2-1_主要的事件介绍.md) [下一节](2-3_计时器的介绍.md) 2 | # 第二章 第二节 事件相关方法 3 | 参与编写者: MayflyOvO 4 | 5 | 建议学习时间: 40分钟 6 | 7 | 学习要点: 重点记忆 Event 下各种方法的使用。要实际去编写。光看不写是记不起来的! 8 | 9 | ### 本章目录 10 | - [前言](#1) 11 | - [Nukkit 插件初步配置](#2) 12 | - [从玩家加入开始...](#3) 13 | - [我不许你动 你就不能动](#4) 14 | - [禁止其余动作](#5) 15 | - [当玩家离开](#6) 16 | - [效果](#7) 17 | - [最后](#8) 18 | 19 | 20 | 21 | ### 一、前言 22 | 23 | 本章我并不想单纯的去讲某个Event下面都有OOO方法,看了这么多章,我们来上手实现一个登录插件吧。通过这个插件来 **深入浅出** 学习各种Event的使用。 24 | 25 | 带有 :red_circle: 符号需要重点记忆理解 26 | 27 | 28 | ### 二、Nukkit 插件初步配置 29 | 30 | 关于Nukkit插件如何开始开发,已经由@Magic Lu同学在前面讲过了,此处不再赘述。 31 | 32 | `Talk Is Cheap, Show You The Code` 33 | 34 | plugin.yml 35 | ```yml 36 | name: site.dotdream.loginplugin 37 | main: site.dotdream.LoginPlugin 38 | version: "1.0" 39 | author: "MayflyOvO" 40 | api: ["1.0.9"] 41 | description: A Simple Auth Plugin 42 | ``` 43 | 44 | LoginPlugin.java(程序入口) 45 | ```java 46 | package site.dotdream; 47 | 48 | import cn.nukkit.Player; 49 | import cn.nukkit.plugin.PluginBase; 50 | import site.dotdream.handlers.AuthEventHandler; 51 | 52 | import java.util.ArrayList; 53 | import java.util.List; 54 | 55 | public class LoginPlugin extends PluginBase { 56 | private static LoginPlugin plugin; // 插件实例 57 | private List loginPlayerList = new ArrayList<>(); // 已登录列表 58 | 59 | @Override 60 | public void onEnable() { 61 | plugin = this; 62 | 63 | this.getServer().getPluginManager().registerEvents(new AuthEventHandler(), this); // 注册Event 64 | } 65 | 66 | @Override 67 | public void onDisable() { 68 | 69 | } 70 | 71 | /** 72 | * 传入一个 Player 73 | * 返回 Boolean 判断该玩家是否在已登录列表内 74 | */ 75 | public boolean isPlayerLogin(Player player) { 76 | return loginPlayerList.contains(player); 77 | /* 78 | * 通过 contains 方法 79 | * 如果玩家已登录这里返回 true 80 | * 反之返回 false 81 | * */ 82 | } 83 | 84 | /** 85 | * 传入一个Player 86 | * 记录该玩家已经登陆 87 | */ 88 | public void playerLogin(Player player) { 89 | this.loginPlayerList.add(player); // 将该玩家加入已登录玩家的List中 90 | } 91 | 92 | /** 93 | * 传入一个Player 94 | * 记录该玩家已经登出 95 | */ 96 | public void playerLogout(Player player) { 97 | this.loginPlayerList.remove(player); // 将该玩家从已登录玩家的List中移除 98 | } 99 | 100 | /** 101 | * 通过这个方法获得Plugin实例 102 | */ 103 | public static LoginPlugin getInstance() { 104 | return plugin; 105 | } 106 | } 107 | 108 | ``` 109 | 110 | handlers/AuthEventHandler.java 111 | ```java 112 | package site.dotdream.handlers; 113 | 114 | import cn.nukkit.event.Listener; 115 | 116 | public class AuthEventHandler implements Listener { 117 | private LoginPlugin plugin = LoginPlugin.getInstance(); 118 | } 119 | ``` 120 | 121 | 添加Package 122 | 123 | ![创建Package](../images/chapter2-aboutEventsMethod-1.png) 124 | 125 | 命名 126 | 127 | ![创建Package](../images/chapter2-aboutEventsMethod-2.png) 128 | 129 | 创建类 130 | 131 | ![创建Package](../images/chapter2-aboutEventsMethod-3.png) 132 | 133 | 可以看到我们定义了一个列表`loginPlayerList`来记录已经登录的玩家,同时创建了`isPlayerLogin`,`playerLogin`,`playerLogout`方法,来获取是否登录,以及操作该列表 134 | 135 | 然后我们又注册了一个Event。为了代码简洁方便维护,我建立了一个Package并将它独立放到一个叫AuthEventHandler的类里。同时又在onEnable中将它注册,使其生效。:wink: 136 | 137 | 到这里插件的初步配置就完成了,接下来我们大部分操作都在这个AuthEventHandler.java中... 138 | 139 | ### 三、从玩家加入开始... 140 | 141 | 142 | 我们在制作插件的时候,有必要认真的考虑一下插件的逻辑。 143 | 144 | 以本教程将要编写的登录插件为例,想要实现一个 `登录前 --> 输入密码 --> 登录后` ,那么势必要判断: 145 | - 某玩家登录过了吗? 146 | - 没有登陆? 147 | - 那么我就 ~~禁止~~取消 你:走动,放置与破坏,伤害与被伤害,饥饿值变化,与其他玩家聊天。 148 | 149 | 为什么这里说是**取消**而不是**禁止**? 150 | 151 | 因为当玩家进行这些活动,Nukkit询问我们事先注册的Event 152 | 153 | `Nukkit:你!插件LoginPlugin,对这个动作有什么意见吗?` 154 | 155 | 没有插件的情况下,这些动作都是会被同意的,通过一个重要的方法`event.setCancelled()` 156 | 157 | 告诉Nukkit 158 | 159 | `LoginPlugin:我不同意!你要取消掉本次活动` 160 | 161 | ``` 162 | 伪代码如下 163 | 164 | 如果 isPlayerLogin(玩家) 等于 否: 165 | Event设置为取消 166 | 否则: 167 | 不设置取消,即是允许 168 | ``` 169 | 170 | 这些都是为了实现我们的目标而需要考虑在内的 171 | 172 | **好的,脑海中有了这些概念,我们来实际编写吧!** 173 | 174 | 向AuthEventHandler.java中加入如下代码 175 | ```java 176 | @EventHandler 177 | public void onPlayerJoin(PlayerJoinEvent event) { 178 | Player player = event.getPlayer(); // 重要方法!可以通过getPlayer来获得该动作发起的玩家 179 | 180 | /* PlayerJoinEvent 独有方法,用来设置玩家欢迎词 181 | 这里玩家刚刚加入还没有登录 182 | 我们将其设为空字符串,暂时先不欢迎 183 | */ 184 | event.setJoinMessage(""); 185 | 186 | plugin.getServer().getScheduler().scheduleDelayedTask(plugin, () -> { 187 | player.sendTitle("欢迎来到OOO服务器", "请先登录", 20, Integer.MAX_VALUE, 20); 188 | }, 2 * 20); 189 | } 190 | ``` 191 | 这里涉及了几个方法,我们来分开说: 192 | 193 | 1. :red_circle:**event.getPlayer()** 很常用的方法,也很重要,通过它,我们可以获得这个动作(Event)的发起者。 194 | 2. :red_circle:**event.setJoinMessage()** 这是一个PlayerJoinEvent独有的方法。通过它,我们可以设置这名玩家加入服务器后,发送一条向全服广播某玩家已加入游戏的消息 195 | 3. **player.sendTitle()** 这个方法可以向玩家发送两行几乎占满全屏的字,它接受5个参数:(第一行字,第二行字,渐入的时间,显示的时间,渐出的时间),需要注意的是,Nukkit使用红石刻计算时间,也就是20刻=1秒钟。将显示的时间设为Integer变量的最大值,让其一直显示,直到取消。**同时这里还涉及一个Nukkit的BUG,如果直接在玩家加入时发送Message或者Title,玩家是不显示的,所以使用`DelayedTask`做了2秒延迟** 196 | 197 | 这样我们就对新加入的玩家发送了一个要求登录的提示。 198 | 199 | 那取消未登录玩家的放置破坏啥的在哪呢? 200 | 201 | 别急!我们这就来~ 202 | 203 | 204 | ### 四、我不许你动 你就不能动 205 | 206 | 继续向AuthEventHandler.java中加入如下代码 207 | 208 | ```java 209 | @EventHandler 210 | public void onPlayerMove(PlayerMoveEvent event) { 211 | Player player = event.getPlayer(); 212 | 213 | if (!plugin.isPlayerLogin(player)) { 214 | // 当返回false,则玩家处在未登录状态 215 | Location from = event.getFrom(); 216 | Location to = event.getTo(); 217 | if (from.getX() != to.getX() || from.getZ() != to.getZ()) { 218 | // 当移动前地点 from 与 移动后地点 to 的 X,Z 坐标有任何一项不相等 219 | event.setCancelled(); 220 | } 221 | } 222 | } 223 | ``` 224 | 225 | 同样的方法不再解释,我们主要来看 :red_circle:**event.getFrom()** 与 :red_circle:**event.getTo()** 226 | 这两个方法为PlayerMoveEvent特有的方法,并且都能够得到一个类型为Location的变量,区别是from为玩家移动前的位置,to为玩家移动后的位置 227 | 228 | Location类型变量包含4个值得注意的方法 229 | ```java 230 | getX() // 玩家X坐标 231 | getY() // 玩家Y坐标(高度) 232 | getZ() // 玩家Z坐标 233 | getDirectionVector() // 玩家视角 234 | ``` 235 | 236 | 所以,当我们判断玩家的X,Z其中任何一个值不相等,就可以:red_circle:`event.setCancelled()`来告诉Nukkit,我不批准!这样玩家此次的移动就会被取消。 237 | 238 | 同理,大部分的Event都可以通过`setCancelled()`来取消。但是例如`PlayerQuitEvent`便不可以,因为你没有办法阻止玩家离开服务器。 239 | 240 | 在这里需要大家思考,为什么不 241 | ```java 242 | if(!to.equals(from)){ 243 | event.setCancelled(); 244 | } 245 | // 或者 246 | if (from.getX() != to.getX() || from.getY() != to.getY() || from.getZ() != to.getZ()){ 247 | // ... 248 | } 249 | ``` 250 | 这样会有什么缺点? 251 | 252 | 253 | ### 五、禁止其余动作 254 | 255 | 以下的代码禁止了: 256 | 1. 放置与破坏 257 | 2. 玩家模型碰撞 258 | 3. 玩家饥饿值变化 259 | 260 | 它们相比之前并没有什么特殊,无非是检查是否登录,判断是否取消,我直接放代码在这里 261 | ```java 262 | /** 263 | * 放置方块Event 264 | */ 265 | @EventHandler 266 | public void onBlockPlace(BlockPlaceEvent event) { 267 | if (!plugin.isPlayerLogin(event.getPlayer())) { 268 | event.setCancelled(); 269 | } 270 | } 271 | 272 | /** 273 | * 破坏方块Event 274 | */ 275 | @EventHandler 276 | public void onBlockBreak(BlockBreakEvent event) { 277 | if (!plugin.isPlayerLogin(event.getPlayer())) { 278 | event.setCancelled(); 279 | } 280 | } 281 | 282 | /** 283 | * 玩家模型碰撞Event 284 | */ 285 | @EventHandler 286 | public void onPlayerInteractEvent(PlayerInteractEvent event) { 287 | if (!plugin.isPlayerLogin(event.getPlayer())) { 288 | event.setCancelled(); 289 | } 290 | } 291 | 292 | /** 293 | * 玩家饥饿值变化Event 294 | */ 295 | @EventHandler 296 | public void onPlayerFoodLevelChangeEvent(PlayerFoodLevelChangeEvent event) { 297 | if (!plugin.isPlayerLogin(event.getPlayer())) { 298 | event.setCancelled(); 299 | } 300 | } 301 | ``` 302 | 303 | 除以上几个Event外,存在2个需要大家重点看的Event 304 | 1. 玩家伤害及受伤害 305 | 2. 与其他玩家聊天 306 | 307 | 先上代码吧... 308 | ```java 309 | /** 310 | * 实体伤害Event 311 | */ 312 | @EventHandler 313 | public void onEntityDamage(EntityDamageEvent event) { 314 | Player player = null; // 先定义一个Player变量 315 | if (event.getEntity() instanceof Player) { 316 | // 如果受到伤害的实体是一名玩家 317 | player = (Player) event.getEntity(); // 强制转换实体为Player类型,并赋值。 318 | } else if (event instanceof EntityDamageByEntityEvent) { 319 | // 如果本次Event属于被一个实体被另一个实体攻击的Event 320 | Entity damager = ((EntityDamageByEntityEvent) event).getDamager(); // 获得本次Event的发起者 321 | if (damager instanceof Player) 322 | // 如果发起伤害的实体是一名玩家 323 | player = (Player) damager; 324 | } else if (event instanceof EntityDamageByChildEntityEvent) { 325 | // 如果本次Event属于被一个实体被另一个实体的子实体攻击的Event 326 | Entity damager = ((EntityDamageByChildEntityEvent) event).getDamager(); // 获得本次Event的发起者 327 | if (damager instanceof Player) 328 | // 如果发起伤害的实体是一名玩家 329 | player = (Player) damager; 330 | } 331 | 332 | if (player != null && !plugin.isPlayerLogin(player)) { 333 | event.setCancelled(); 334 | } 335 | } 336 | ``` 337 | 这个EntityDamageEvent比较特殊,它没有 `getPlayer()` 方法,因为它是一个涵盖的很广,不仅仅是玩家的行为会调用这个Event。 338 | 339 | 例如: 340 | 1. 玩家对玩家造成伤害 (发起受到伤害的是玩家) 341 | 2. 玩家对生物造成伤害 (发起伤害的是玩家) 342 | 3. 生物对玩家造成伤害 (受到伤害的是玩家) 343 | 4. 玩家的箭对玩家造成伤害 (子实体) 344 | 345 | 这些都会调用这个方法。我们可以根据判断:发起伤害的实体:red_circle:`getDamager()`和受到伤害的实体:red_circle:`getEntity()`,只要其中有一个是未登录的玩家,就取消本次Event。 346 | 347 | 然后是: 348 | ```java 349 | /** 350 | * 玩家聊天Event 351 | */ 352 | @EventHandler 353 | public void onPlayerChat(PlayerChatEvent event) { 354 | Player player = event.getPlayer(); 355 | if (!plugin.isPlayerLogin(event.getPlayer())) { 356 | String message = event.getMessage(); // 获取玩家发送的内容 357 | if (message.equals("A_Bad_Password")) { 358 | /* 359 | * 这里判断是否密码正确 360 | * !但仅是为了举例,通常需要将message用md5之类的非对称加密算法加密 361 | * !然后再与之前就保存在数据库里的结果进行比对 362 | */ 363 | plugin.playerLogin(player); // 将玩家加入已登录的列表(List)中 364 | player.sendTitle("", "", 0, 0, 0); // 还记得我们给玩家发了一个占满全屏的两行提示吗?在这里我们把它取消。 365 | // 另一个BUG player.clearTitle() 在这里不起作用 366 | 367 | player.sendMessage(player.getName() + ",欢迎~"); // 给这个玩家发送欢迎消息 368 | // 或者 369 | plugin.getServer().broadcastMessage("玩家:" + player.getName() + ",已加入游戏"); // 给全服的玩家发送该玩家已加入的欢迎消息 370 | } else { 371 | player.sendMessage("抱歉,密码错误!"); 372 | } 373 | event.setCancelled(); // 在 if 外取消,确保无论密码正确与否,都不会发送给其他玩家看到 374 | } 375 | } 376 | ``` 377 | 378 | 我们在这里编写了玩家聊天与输入密码的逻辑 379 | 先判断玩家是否已登录,然后通过:red_circle:`event.getMessage()`获得消息,实际上也就是密码,来做对比。如果错误进行提示,如果正确则将其加入已登录玩家的列表中。 380 | 381 | 这时我们回头来看之前写的全部Event,如果该玩家登陆成功后,由于已被加入`loginPlayerList`中,此时`isPlayerLogin()`便会返回true,条件不成立,玩家的各种活动便不会被取消。 382 | 383 | > ——发散一下思维,也可以在这里修改玩家发送的内容。加称号等等都是如此实现的。 384 | 385 | 386 | ### 六、当玩家离开 387 | 388 | 当玩家登录,我们把他保存在`loginPlayerList`中。最后,当玩家离开,我们还需要将他移除,不然,如果一名玩家登录后下线,某人冒充顶替的话再上线的话,由于已登录列表中有他,就不需要登录了。 389 | 390 | ```java 391 | @EventHandler 392 | public void onPlayerQuit(PlayerQuitEvent event) { 393 | if (plugin.isPlayerLogin(event.getPlayer())) { 394 | // 如果玩家在线,将其移除,反之不用管 395 | plugin.playerLogout(event.getPlayer()); 396 | } 397 | } 398 | ``` 399 | 400 | ### 七、效果 401 | 效果图1 402 | 效果图2 403 | 效果图3 404 | 效果图4 405 | 效果图5 406 | 效果图6 407 | 效果图7 408 | 409 | 410 | ### 八、最后 411 | 412 | 一个简单的登录插件就编写完了。希望大家能够了解一些Event下面的方法,最重要的是去实践。无论是Eclipse还是IDEA,都自带了非常友好的提示功能。还有反编译。可以直接通过方法名,类名来了解Nukkit。 413 | 414 | 当然,这个插件还有一部分功能是残缺的,比如注册。有一些Event没有禁止掉,比如扔物品和捡起物品。这些都等待屏幕前的你来开发。 415 | 416 | [上一节](2-1_主要的事件介绍.md) [下一节](2-3_计时器的介绍.md) 417 | -------------------------------------------------------------------------------- /第二章/2-3_计时器的介绍.md: -------------------------------------------------------------------------------- 1 | [上一节](2-2_事件相关方法.md) [下一节](2-4_Server类和PluginManager类.md) 2 | # 第二章 第三节 计时器的介绍 3 | 参与编写者:`zzz1999` 4 | #### 建议学习时间:3分钟 5 | ##### 学习要点:了解计时器的功能以及如何使用 6 | 7 | 一.概念 8 | 1.计时器的作用是 __立即,延时,循环或延时循环__ 某些任务,通过计时器我们可以实现例如 *在线奖励* , *倒计时* 等插件功能, 是一些复杂插件中经常用到的功能之一. 9 | 10 | 2.执行计时器代码块的单元叫做任务,我们可以编写任务代码来使用计时器的功能. 11 | 12 | 3.任务执行分为两种类型,一种是同步,一种是异步.同步通过主线程所调度管理执行,异步通过主线程调度管理,异步线程池执行.所以,太多的任务会导致服务器卡顿. 13 | 14 | 4.计时器的功能以`minecraft刻`(tick)作为单位,一刻为现实时间的0.05秒,现实时间的1秒为20刻,服务器每秒刻数由TPS决定,最高为20. 15 | 16 | 17 | 二.代码 18 | 19 | 我们先来看一下nukkit的[Task](https://github.com/NukkitX/Nukkit/blob/master/src/main/java/cn/nukkit/scheduler/Task.java)类. 20 | 21 | ```java 22 | package cn.nukkit.scheduler; 23 | 24 | import cn.nukkit.Server; 25 | 26 | /** 27 | * 表达一个任务的类。
A class that describes a task. 28 | * 29 | *

一个任务可以被Nukkit服务器立即,延时,循环或延时循环执行。参见:{@link ServerScheduler}
30 | * A task can be executed by Nukkit server with a/an express, delay, repeat or delay&repeat. 31 | * See:{@link ServerScheduler}

32 | * 33 | *

对于插件开发者,为确保自己任务能够在安全的情况下执行(比如:在插件被禁用时不执行), 34 | * 建议让任务继承{@link PluginTask}类而不是这个类。
35 | * For plugin developers: To make sure your task will only be executed in the case of safety 36 | * (such as: prevent this task from running if its owner plugin is disabled), 37 | * it's suggested to use {@link PluginTask} instead of extend this class.

38 | * 39 | * @author MagicDroidX(code) @ Nukkit Project 40 | * @author 粉鞋大妈(javadoc) @ Nukkit Project 41 | * @since Nukkit 1.0 | Nukkit API 1.0.0 42 | */ 43 | public abstract class Task implements Runnable { 44 | private TaskHandler taskHandler = null; 45 | 46 | public final TaskHandler getHandler() { 47 | return this.taskHandler; 48 | } 49 | 50 | public final int getTaskId() { 51 | return this.taskHandler != null ? this.taskHandler.getTaskId() : -1; 52 | } 53 | 54 | public final void setHandler(TaskHandler taskHandler) { 55 | if (this.taskHandler == null || taskHandler == null) { 56 | this.taskHandler = taskHandler; 57 | } 58 | } 59 | 60 | /** 61 | * 这个任务被执行时,会调用的过程。
62 | * What will be called when the task is executed. 63 | * 64 | * @param currentTick 服务器从开始运行到现在所经过的tick数,20ticks = 1秒,1tick = 0.05秒。
65 | * The elapsed tick count from the server is started. 20ticks = 1second, 1tick = 0.05second. 66 | * @since Nukkit 1.0 | Nukkit API 1.0.0 67 | */ 68 | public abstract void onRun(int currentTick); 69 | 70 | @Override 71 | public final void run() { 72 | this.onRun(taskHandler.getLastRunTick()); 73 | } 74 | 75 | public void onCancel() { 76 | 77 | } 78 | 79 | public void cancel() { 80 | try { 81 | this.getHandler().cancel(); 82 | } catch (RuntimeException ex) { 83 | Server.getInstance().getLogger().critical("Exception while invoking onCancel", ex); 84 | } 85 | } 86 | 87 | } 88 | ``` 89 | 90 | 我们可以捕捉到几个关键信息,首先我们可以看到`onRun(int)`方法,它是一个抽象方法并且nk开发者很贴心的写上了注释示意我们这是Task中会被运行到代码块. 91 | 92 | 还有`onCancel()`和`cancel()`方法,onCancel()内部是一个空代码块,当Task被取消的时候(被任务调度器取消或者手动调用cancel()取消都会触发),这段代码块会被调用 93 | 94 | 比较常见的用途比如小游戏时间到的时候,一些判定胜负,给予奖励,关闭房间的一些代码可以用Task来实现或者调用. 95 | 96 | 我们还可以使用`cancel()`方法来强制取消一个Task,例如你想要写一个登录插件,每个玩家进入服务器时创建一个Task循环发送提示登录的信息,如果玩家输入了正确的密码并且登录成功才会取消掉这个Task.这时候就可以使用cancel方法了(仅举例,实际生产环境中,每个玩家进入创一个计时器是很费资源的,不会使用这种方法) 97 | 98 | Handler是服务器调度Task所用到的一些东西,可以不懂,不妨碍计时器的使用. 99 | 100 | 开发者可以使用`PluginBase#getServer()`方法获取到Server对象,然后使用`Server#getSchedule()`获取到计时器的实例类. 101 | ```java 102 | public class Main extends PluginBase{ 103 | @Override 104 | public void onEnable(){ 105 | ServerScheduler scheduler = this.getServer().getScheduler(); 106 | } 107 | } 108 | ``` 109 | 获取到后,可以创建 __立即,延时,循环或延时循环__ 类型的计时器 110 | 立即(ServerSchedule#scheduleTask) 111 | 延时(ServerSchedule#scheduleDelayedTask) 112 | 循环(ServerSchedule#scheduleRepeatingTask) 113 | 延时循环(ServerSchedule#scheduleDelayedRepeatingTask) 114 | 115 | 计时器是需要一个被触发的时间间隔的,以tick为单位,如果是循环计时器,则是每次循环的时间,如果是延时,则是延迟多少tick才被触发,如果是延迟循环,则会要求填写延迟间隔与循环间隔. 116 | 117 | 举例将会在下一小节讲到 118 | 119 | 3.使用 120 | 121 | 下面会使用到`PluginTask`类,你需要对`PluginTask`类的方法做一个初步的了解,以及知道它继承了Task类. 122 | ```java 123 | package cn.nukkit.scheduler; 124 | 125 | 126 | import cn.nukkit.plugin.Plugin; 127 | 128 | /** 129 | * 插件创建的任务。
Task that created by a plugin. 130 | * 131 | *

对于插件作者,通过继承这个类创建的任务,可以在插件被禁用时不被执行。
132 | * For plugin developers: Tasks that extend this class, won't be executed when the plugin is disabled.

133 | * 134 | *

另外,继承这个类的任务可以通过{@link #getOwner()}来获得这个任务所属的插件。
135 | * Otherwise, tasks that extend this class can use {@link #getOwner()} to get its owner.

136 | * 137 | * 下面是一个插件创建任务的例子:
An example for plugin create a task: 138 | *
139 |  *     public class ExampleTask extends PluginTask<ExamplePlugin>{
140 |  *         public ExampleTask(ExamplePlugin plugin){
141 |  *             super(plugin);
142 |  *         }
143 |  *
144 |  *        {@code @Override}
145 |  *         public void onRun(int currentTick){
146 |  *             getOwner().getLogger().info("Task is executed in tick "+currentTick);
147 |  *         }
148 |  *     }
149 |  * 
150 | * 151 | *

如果要让Nukkit能够延时或循环执行这个任务,请使用{@link ServerScheduler}。
152 | * If you want Nukkit to execute this task with delay or repeat, use {@link ServerScheduler}.

153 | * 154 | * @param 这个任务所属的插件。
The plugin that owns this task. 155 | * @author MagicDroidX(code) @ Nukkit Project 156 | * @author 粉鞋大妈(javadoc) @ Nukkit Project 157 | * @since Nukkit 1.0 | Nukkit API 1.0.0 158 | */ 159 | public abstract class PluginTask extends Task { 160 | 161 | protected final T owner; 162 | 163 | /** 164 | * 构造一个插件拥有的任务的方法。
Constructs a plugin-owned task. 165 | * 166 | * @param owner 这个任务的所有者插件。
The plugin object that owns this task. 167 | * @since Nukkit 1.0 | Nukkit API 1.0.0 168 | */ 169 | public PluginTask(T owner) { 170 | this.owner = owner; 171 | } 172 | 173 | /** 174 | * 返回这个任务的所有者插件。
175 | * Returns the owner of this task. 176 | * 177 | * @return 这个任务的所有者插件。
The plugin that owns this task. 178 | * @since Nukkit 1.0 | Nukkit API 1.0.0 179 | */ 180 | public final T getOwner() { 181 | return this.owner; 182 | } 183 | 184 | } 185 | ``` 186 | 187 | 我们可以通过创建一个继承PluginTask的类来创建一个PluginTask(为什么不直接继承Task类?,因为这个类添加了`getOwner()`方法,可以通过构造方法传入一份主类或任意你需要类实例的引用,方便编写代码) 188 | 189 | ```java 190 | 191 | package net.noyark.NukkitLearn; 192 | 193 | import cn.nukkit.scheduler.PluginTask; 194 | 195 | public class NukkitLearnExampleTask extends PluginTask
{ 196 | public NukkitLearnExampleTask(Main owner) { 197 | super(owner); 198 | } 199 | 200 | @Override 201 | public void onRun(int i) { 202 | 203 | } 204 | } 205 | 206 | 207 | ``` 208 | 209 | 上面代码是一个简单Task类的样例,我们现在带入一个插件情境进一步说明计时器的用法. 210 | > 小明是一个服主,他非常喜欢玩家来他的服务器玩并且希望玩家每玩45分钟能够停下来休息一下保护视力.于是他想要开发一个插件,当玩家单次进入服务器并且呆了45分钟后,能够发送一条信息提示(Player#sendMessage(String)玩家休息一会) 211 | 212 | 于是我们来头脑风暴一下,我们知道计时器有4种类型,不知道你还能不能想得起来 213 | 214 | 215 | 216 | ##### 立即(ServerSchedule#scheduleTask) 217 | ##### 延时(ServerSchedule#scheduleDelayedTask) 218 | ##### 循环(ServerSchedule#scheduleRepeatingTask) 219 | ##### 延时循环(ServerSchedule#scheduleDelayedRepeatingTask) 220 | 221 | > 小明火速写好了一个Task类 222 | 223 | ```java 224 | package net.noyark.NukkitLearn; 225 | 226 | import cn.nukkit.Player; 227 | import cn.nukkit.scheduler.PluginTask; 228 | 229 | public class PresentationRestTask extends PluginTask
{ 230 | 231 | private Player player; 232 | 233 | public PresentationRestTask(Main owner, Player player) { 234 | super(owner); 235 | this.player = player; 236 | } 237 | 238 | @Override 239 | public void onRun(int i) { 240 | player.sendMessage("你已经玩了45分钟了,快下线休息一下眼睛吧!"); 241 | } 242 | } 243 | 244 | ``` 245 | 246 | > 以及一个主类 247 | 248 | ```java 249 | 250 | package net.noyark.NukkitLearn; 251 | 252 | import cn.nukkit.event.EventHandler; 253 | import cn.nukkit.event.EventPriority; 254 | import cn.nukkit.event.Listener; 255 | import cn.nukkit.event.player.PlayerJoinEvent; 256 | import cn.nukkit.plugin.PluginBase; 257 | 258 | public class Main extends PluginBase implements Listener { 259 | 260 | @EventHandler(priority = EventPriority.LOW,ignoreCancelled = true) 261 | public void onJoin(PlayerJoinEvent event){ 262 | 263 | } 264 | 265 | } 266 | 267 | ``` 268 | 269 | ### 思考过程 270 | * 使用立刻类型的计时器 271 | - 使用立刻类型的计时器,在主类onJoin方法中填写入代码 272 | > this.getServer().getScheduler().scheduleTask(new PresentationRestTask(this,event.getPlayer())); 273 | > 小明找了个玩家小红进入服务器,小红刚进入服务器,左上角就被发了消息:"你已经玩了45分钟了,快下线休息一下眼睛吧!". 274 | * 使用延迟类型的计时器 275 | - 使用延时类型的计时器,在主类onJoin方法中填写入代码 276 | > this.getServer().getScheduler().scheduleDelayedTask(new PresentationRestTask(this,event.getPlayer()),54000); 277 | > 小明找了个玩家小红进入服务器,小红刚进入服务器45分钟后,左上角才被发了消息:"你已经玩了45分钟了,快下线休息一下眼睛吧!". 278 | * 使用循环类型的计时器 279 | - 似乎这很不符合我们的常理,并且增加了很多不必要的开销 280 | - 修改Task,加入时间作为判断依据 281 | > 282 | ```java 283 | package net.noyark.NukkitLearn; 284 | 285 | import cn.nukkit.Player; 286 | import cn.nukkit.scheduler.PluginTask; 287 | 288 | public class PresentationRestTask extends PluginTask
{ 289 | 290 | private Player player; 291 | private long stp; 292 | 293 | public PresentationRestTask(Main owner, Player player,long stp) { 294 | super(owner); 295 | this.player = player; 296 | this.stp = stp; 297 | } 298 | 299 | @Override 300 | public void onRun(int i) { 301 | if (System.currentTimeMillis() >= stp) { 302 | player.sendMessage("你已经玩了45分钟了,快下线休息一下眼睛吧!"); 303 | this.cancel(); 304 | } 305 | } 306 | } 307 | 308 | ``` 309 | - 使用循环类型的计时器,在主类onJoin方法中填写入代码 310 | > this.getServer().getScheduler().scheduleRepeatingTask(new PresentationRestTask(this,event.getPlayer(),System.currentTimeMillis() + (45 * 60 * 1000)),60*20); 311 | > 可以达成效果,但是实在是`太麻烦` 312 | - 使用延时循环类型的计时器,在主类onJoin方法中填写入代码 313 | > this.getServer().getScheduler().scheduleDelayedRepeatingTask(new PresentationRestTask(this,event.getPlayer()),45 * 60 * 20,0); 314 | > 同时计时器也需要修改,否则就会45分钟一到就会无尽发送消息 315 | > 316 | ```java 317 | package net.noyark.NukkitLearn; 318 | 319 | import cn.nukkit.Player; 320 | import cn.nukkit.scheduler.PluginTask; 321 | 322 | public class PresentationRestTask extends PluginTask
{ 323 | 324 | private Player player; 325 | 326 | public PresentationRestTask(Main owner, Player player) { 327 | super(owner); 328 | this.player = player; 329 | } 330 | 331 | @Override 332 | public void onRun(int i) { 333 | player.sendMessage("你已经玩了45分钟了,快下线休息一下眼睛吧!"); 334 | this.cancel(); 335 | } 336 | } 337 | 338 | ``` 339 | > 最终从各种角度来看,小明更应该使用`第二种`方法 340 | 341 | 342 | 343 | [上一节](2-2_事件相关方法.md) [下一节](2-4_Server类和PluginManager类.md) 344 | -------------------------------------------------------------------------------- /第二章/2-4_Server类和PluginManager类.md: -------------------------------------------------------------------------------- 1 | [上一节](2-3_计时器的介绍.md) [下一节](2-5_各种实体类的方法介绍.md) 2 | # 第二章 第四节 Server类和PluginManager类 3 | 参与编写者: MagicLu550 4 | #### 建议学习时间: 20分钟 5 | ##### 学习要点: 了解Server类和PluginManager类 6 | 7 | 一. Server 8 | 9 | 1.概述 10 | 11 | Server类是插件几乎所有接口的入口,几乎一切的接口都是基于这个类获得的,而在nukkit中,Server类 12 | 是作为一个对象单独存在,Server的实例化意味着nukkit服务器的启动,并且不允许外部调用其构造方法,但可以根据getInstance方法或者插件主类提供的 13 | getServer方法可以获得,这里我们提到了两个获得Server的方法 14 | ``` 15 | Server.getInstance(); 16 | this.getServer();//this.getClass() == mainClass 17 | ``` 18 | Server对象是在Nukkit类里完成初始化的,所以我们不需要担心Server对象是否存在的问题.加载插件 19 | 前,Server对象已经存在.一切启动的初始化操作都在Server的构造方法中完成.对于Server的复杂原理, 20 | 这里不过多赘述,可以参见第四部分的内容 21 | 22 | Nukkit.java Line. 108-115 23 | ``` 24 | 25 | try { 26 | if (TITLE) { 27 | System.out.print((char) 0x1b + "]0;Nukkit is starting up..." + (char) 0x07); 28 | } 29 | new Server(PATH, DATA_PATH, PLUGIN_PATH, language); 30 | } catch (Throwable t) { 31 | log.throwing(t); 32 | } 33 | ``` 34 | 2. Server类的常用方法 35 | * addOp(String) 可以添加op管理员的名称,name是玩家名称 36 | * addWhitelist(String) 可以添加白名单 37 | * batchPackets​(Player[], DataPacket[]) 批量发送数据包,后面[数据包发送篇](2-7_如何发送数据包.md)详细讲解 38 | * broadMessage(String) 发送服务器广播信息,所有玩家可见 39 | * addRecipe​(Recipe) 添加配方,这个配方指包括合成、炉子、炼药台等使用的配方。 40 | * broadcastPacket​(Player[], DataPacket) 向所有玩家广播数据包 41 | * forceShutdown() 强制关闭服务端 42 | * doAutoSave() 自动保存 43 | * generateLevel​(String) 产生一个level(世界),String为名字,返回创建是否成功 44 | * getAllowFlight() 获得这个服务器是否是允许飞行的 45 | * getApiVersion() 获取插件api的版本 46 | * getCommandAliases() 将返回以(一个指令名对应着多个别名)为一对的Map集合 47 | * getCommandMap() 获取指令Map,通过它可以注册一些命令,[前面已经说到过](../第一章/1-4_如何编写命令.md) 48 | * getDataPath() 获取服务端的数据目录 49 | * getDefaultGamemode() 获取服务端的默认模式(如创造模式等) 50 | * getDefaultLevel() 获取默认的世界对象,如World 51 | * getDifficulty() 获得游戏难度,如和平模式等 52 | * getIp() 获得服务端的ip地址 53 | * getIpBans() 获得封禁的信息(ban) 54 | * getLanguage() 获得服务端默认语言(如zh等) 55 | * getLevelByName(String) 通过世界名称获得世界对象 56 | * getMaxPlayers() 获得最大人数 57 | * getMotd() 获得服务端的motd 58 | * getName() 获得服务端的名称 59 | * getNameBans() 获得迸封禁的玩家表 60 | * getOfflinePlayer(String) 通过玩家名称得到不在线玩家 61 | * getOnlinePlayers() 获得在线玩家,UUID都是唯一的标识符 62 | * getPlayerExact(String) 通过名称获得一个确切的玩家​ 63 | * getPluginManager() 获得插件管理器 64 | * getOps() 获得管理员清单 65 | * getPort() 获得端口 66 | * getPluginPath() 获得插件文件夹的位置 67 | * getScheduler() 获得任务表,可以注册有关多线程之类的东西 68 | * getSubMotd() 获得附属的motd 69 | * hasWhitelist() 是否有白名单 70 | * isOp​(String) 判断一个玩家是否是op,String为玩家名称 71 | * reload() 重启服务端 72 | * reloadWhitelist() 重新加载白名单 73 | * removeOnlinePlayer​(Player) 删除在线玩家 74 | * removeOp​(String) 删除指定管理员 75 | * removeWhitelist​(String) 删除一个玩家的白名单 76 | * shutdown() 关闭服务端 77 | * unloadLevel​(Level) 卸载一个世界 78 | 79 | 二. PluginManager 80 | 1. 概述 81 | PluginManager是插件管理器,很多的插件加载和数据储存都在这里进行,如监听器 82 | 的注册等 83 | 使用这个类也可以实现动态加载插件等一系列的操作,PluginManager的加载基于JavaPluginLoader 84 | 85 | 2. 常用的方法 86 | * addPermission(Permission) 添加Permission对象 87 | * callEvent(Event) 触发一个事件 88 | * clearPlugins() 清空插件 89 | * disablePlugin(Plugin) 停止一个插件,这个插件会提前调用onDisable 90 | 并卸载 91 | * getPlugin​(String) 得到其他插件的插件对象 92 | * loadPlugin​(File) 加载一个插件,路径默认为服务端根目录 93 | * loadPlugins​(File) 加载一个文件夹的插件 94 | * registerEvents​(Listener, Plugin) 注册监听器 95 | * removePermission​(String) 删除一个Permission 96 | 97 | [上一节](2-3_计时器的介绍.md) [下一节](2-5_各种实体类的方法介绍.md) 98 | -------------------------------------------------------------------------------- /第二章/2-5_各种实体类的方法介绍.md: -------------------------------------------------------------------------------- 1 | [上一节](2-4_Server类和PluginManager类.md) [下一节](2-6_各种工具类的介绍.md) 2 | # 第二章 第五节 各种实体类的方法介绍 3 | 4 | 参与编写者: ruo_shui 5 | 6 | 建议学习时间: 20分钟 7 | 8 | ##### 学习要点: 9 | 10 | 了解Entity类 11 | 12 | 1. 概述 13 | 14 | ​ **Entity**类是所有生物的基类,任何生物实体,包括掉落物都继承自**Entity**类。掉落物为一个实体继承 自**EntityItem**类。 若要实现**自定义NPC**则需要继承**EntityHuman**类。 15 | 16 | 2. 自定义生物的实现 17 | 18 | 首先创建一个类继承 Entity / EntityHuman 19 | 20 | ```java 21 | public CustomEntity extends Entity 22 | ``` 23 | 24 | ​ 重写方法 25 | 26 | ```java 27 | /** 28 | * 注意在PowerNukkit中 此构造方法必须保留 29 | */ 30 | public CustomEntity(FullChunk chunk, CompoundTag nbt) { 31 | super(chunk, nbt); 32 | } 33 | 34 | /** 35 | * 这里填写生物的 ID 值 建议参考WIKI百科内生物的命名空间ID 36 | */ 37 | @Override 38 | public int getNetworkId() { 39 | return 0; 40 | } 41 | ``` 42 | 43 | [WIKI 百科](https://minecraft.fandom.com/zh/wiki/%E7%94%9F%E7%89%A9#.E7.94.9F.E7.89.A9.E5.88.97.E8.A1.A8) 44 | 45 | 如果我们要实现其他功能则需要设置或重写方法 46 | 47 | **需要注意一点 基于Entity 类创建的实体需要重写 getHeight() 方法与 getWidth() 方法设置生物的碰撞箱不然会出现一些生物名称向下的情况** 48 | 49 | 这里举几个常用设置 50 | 51 | ```java 52 | /** 53 | 这里是构造函数 54 | */ 55 | public CustomEntity(FullChunk chunk, CompoundTag nbt) { 56 | super(chunk, nbt); 57 | this.setHealth(20);//设置生物当前血量为20点 58 | this.setMaxHealth(20);//设置生物最大血量为20点 59 | this.setImmobile(true);//当设置为true时 生物不会进行移动 60 | this.setNameTagVisible();//设置显示名称 虽然和下面表达效果相同但是不加这个会不显示 61 | this.setNameTagAlwaysVisible();//设置生物头部名称显示 62 | this.setNameTag("生物头部显示的内容"); //设置头部显示的内容 63 | 64 | } 65 | 66 | 67 | /**碰撞箱体积*/ 68 | @Override 69 | public float getHeight() { 70 | return 0.8f; 71 | } 72 | 73 | /**碰撞箱体积*/ 74 | @Override 75 | public float getWidth() { 76 | return 1.2f; 77 | } 78 | 79 | /** 80 | 生物AI的实现方法 81 | 需要编写算法实现 82 | 此方法相当于一个定时器,在每个tick循环执行 83 | 编写算法让生物行动 84 | */ 85 | @Override 86 | public boolean onUpdate(int currentTick) { 87 | return super.onUpdate(currentTick); 88 | } 89 | ``` 90 | 91 | 生成实体 92 | 93 | ```java 94 | //生成实体时我们需要获取 Position 对象 就是实体生成的位置 95 | //获取之后实例化 自定义实体 96 | //传入对象 97 | CustomEntity custom = new CustomEntity(position.getChunk(),Entity.getDefaultNBT(position)); 98 | custom.spawnToAll();//生成实体展示给全部玩家 99 | 100 | // 如果只想给单个玩家发送实体 则调用 spawnTo(玩家对象); 101 | custom.spawnTo(Server.getInstance().getPlayer("Steve")/*玩家对象*/); 102 | ``` 103 | 104 | 关闭实体 105 | 106 | ```java 107 | //几种常用的方法 108 | 109 | //despawn方法并不会关闭实体,只是不显示给玩家 110 | custom.despawnFromAll(); //和spawnToAll()相反,取消展示实体给全部玩家 111 | custom.despawnFrom(Server.getInstance().getPlayer("Steve")/*玩家对象*/); //和spawnTo(玩家对象)相反,取消展示实体给指定玩家 112 | 113 | //关闭实体 关闭后不能再调用spawnTo等方法 114 | custom.close(); 115 | 116 | //杀死实体 和close()方法相比,使用此方法会正常掉落物品(如果有的话) 117 | //如果custom是继承EntityLiving的 还会触发EntityDeathEvent 118 | custom.kill(); 119 | 120 | ``` 121 | 122 | [上一节](2-4_Server类和PluginManager类.md) [下一节](2-6_各种工具类的介绍.md) 123 | -------------------------------------------------------------------------------- /第二章/2-6_各种工具类的介绍.md: -------------------------------------------------------------------------------- 1 | [上一节](2-5_各种实体类的方法介绍.md) [下一节](2-7_如何发送数据包.md) 2 | 3 | [上一节](2-5_各种实体类的方法介绍.md) [下一节](2-7_如何发送数据包.md) -------------------------------------------------------------------------------- /第二章/2-7_如何发送数据包.md: -------------------------------------------------------------------------------- 1 | [上一节](2-6_各种工具类的介绍.md) [下一节]() 2 | # 第二章 第七节 如何发送数据包 3 | 参与编写者: MagicLu550 4 | #### 建议学习时间: 40分钟 5 | ##### 学习要点: 了解数据包和主要的发送形式 6 | 7 | 一. 概述 8 | 9 | Nukkit实现客户端与服务端交互,是通过发送和接收数据包实现的.数据包在nukkit 10 | 的工作过程是占有很重的分量,包括玩家的移动等,都是由一个个数据包接连不断的实现这一 11 | 功能.实现收发数据包的机制是RakNet,通过UDP实现的这些功能.RakNet实现的基础是 12 | Netty框架,如下文可以看到 13 | 14 | 15 | cn/nukkit/raknet/server/UDPServerSocket.java 16 | ``` 17 | package cn.nukkit.raknet.server; 18 | 19 | import cn.nukkit.utils.ThreadedLogger; 20 | import io.netty.bootstrap.Bootstrap; 21 | import io.netty.buffer.PooledByteBufAllocator; 22 | import io.netty.buffer.Unpooled; 23 | import io.netty.channel.Channel; 24 | import io.netty.channel.ChannelHandlerContext; 25 | import io.netty.channel.ChannelInboundHandlerAdapter; 26 | import io.netty.channel.ChannelOption; 27 | import io.netty.channel.epoll.Epoll; 28 | import io.netty.channel.epoll.EpollDatagramChannel; 29 | import io.netty.channel.epoll.EpollEventLoopGroup; 30 | import io.netty.channel.nio.NioEventLoopGroup; 31 | import io.netty.channel.socket.DatagramPacket; 32 | import io.netty.channel.socket.nio.NioDatagramChannel; 33 | 34 | import java.io.IOException; 35 | import java.net.InetSocketAddress; 36 | import java.util.concurrent.ConcurrentLinkedQueue; 37 | 38 | /** 39 | * author: MagicDroidX 40 | * Nukkit Project 41 | */ 42 | public class UDPServerSocket extends ChannelInboundHandlerAdapter { 43 | 44 | ``` 45 | 46 | RakNetServer.java 47 | ``` 48 | @Override 49 | public void run() { 50 | this.setName("RakNet Thread #" + Thread.currentThread().getId()); 51 | Runtime.getRuntime().addShutdownHook(new ShutdownHandler()); 52 | UDPServerSocket socket = new UDPServerSocket(this.getLogger(), port, this.interfaz); 53 | try { 54 | new SessionManager(this, socket); 55 | } catch (Exception e) { 56 | Server.getInstance().getLogger().logException(e); 57 | } 58 | } 59 | ``` 60 | 61 | 二. Netty框架 62 | 63 | [Netty框架](https://github.com/netty/netty)是使用最广泛的java-nio框架之一,由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具, 64 | 用以快速开发高性能、高可靠1性的网络服务器和客户端程序。Netty相当于简化和流线化了网络应用的编程开发过程. 65 | 66 | 如果你想要更深入研究这个框架可以参见[Netty框架的github](https://github.com/netty/netty) 67 | 68 | 三. Nukkit的发包机制 69 | 70 | 1. Nukkit 数据包的结构 71 | 72 | Nukkit的数据包类的继承结构是 73 | ``` 74 | BinaryStream 75 | |------- DataPacket 76 | |-------- 我们主要操作的数据包 77 | ``` 78 | 79 | pid() 一般为数据包的NETWORK_ID,在Player,Server,RakNetInterface,DataPacket类中被调用过 80 | 81 | DataPacket的主要方法是decode()和encode(),数据包的传输过程中,通过这两个方法实现解码和 82 | 编码,使得数据包在服务端与客户端间相互识别. 83 | 84 | decode() 是解码方法,一般是客户端发来的数据包,解码到对象的具体属性,之后在服务端中使用这些数据, 85 | 86 | 即 客户端 -> 服务端 87 | 88 | 如代码中这样 89 | 90 | CameraPacket.java 91 | ``` 92 | package cn.nukkit.network.protocol; 93 | 94 | import lombok.ToString; 95 | 96 | @ToString 97 | public class CameraPacket extends DataPacket { 98 | 99 | public long cameraUniqueId; 100 | public long playerUniqueId; 101 | 102 | @Override 103 | public byte pid() { 104 | return ProtocolInfo.CAMERA_PACKET; 105 | } 106 | 107 | @Override 108 | public void decode() { 109 | this.cameraUniqueId = this.getVarLong(); 110 | this.playerUniqueId = this.getVarLong(); 111 | } 112 | 113 | ``` 114 | 115 | encode() 是编码方法,会在发包时被调用,将在服务端设置的数据值写入发出到客户端 116 | 117 | 即 服务端 -> 客户端 118 | 119 | 如代码这样 120 | 121 | CameraPacket.java 122 | ``` 123 | @Override 124 | public void encode() { 125 | this.reset(); 126 | this.putEntityUniqueId(this.cameraUniqueId); 127 | this.putEntityUniqueId(this.playerUniqueId); 128 | } 129 | ``` 130 | 131 | 从这里,我们就可以引入接下来的发包环节,事实上,它很简单 132 | 133 | 2. 事件 134 | 135 | 发送数据包和接收数据包的时候会触发几种事件,我们可以通过这几种事件进行 136 | 抓包 137 | 我们比较常用的是这几种 138 | - BatchPacketsEvent: 批处理数据包事件 139 | - DataPacketReceiveEvent: 数据包接收事件 140 | - DataPacketSendEvent: 数据包发送事件 141 | 这里我们主要介绍Receive和Send 142 | 143 | DataPacketSendEvent主要触发在服务端向客户端发送数据包的时候 144 | 145 | Player.java 146 | ``` 147 | public int dataPacket(DataPacket packet, boolean needACK) { 148 | if (!this.connected) { 149 | return -1; 150 | } 151 | 152 | try (Timing timing = Timings.getSendDataPacketTiming(packet)) { 153 | //There!!!!! 154 | DataPacketSendEvent ev = new DataPacketSendEvent(this, packet); 155 | this.server.getPluginManager().callEvent(ev); 156 | if (ev.isCancelled()) { 157 | return -1; 158 | } 159 | ``` 160 | 161 | DataPacketReceiveEvent主要触发在客户端向服务端发送数据包并且服务端接收到的时候. 162 | 163 | Player.java 164 | 165 | ``` 166 | public void handleDataPacket(DataPacket packet) { 167 | if (!connected) { 168 | return; 169 | } 170 | 171 | try (Timing timing = Timings.getReceiveDataPacketTiming(packet)) { 172 | //There!!!!!!!!! 173 | DataPacketReceiveEvent ev = new DataPacketReceiveEvent(this, packet); 174 | this.server.getPluginManager().callEvent(ev); 175 | if (ev.isCancelled()) { 176 | return; 177 | } 178 | 179 | ``` 180 | 3. 发包 181 | 182 | Nukkit提供了友好的数据包机制,我们可以通过需求定义,发送数据包 183 | 184 | Nukkit提供了发送数据包的方法,并允许开发者直接发送数据包和监听数据包的收发 185 | 一般的,发送数据包的方式都是使用玩家对象的dataPacket实现 186 | `player.dataPacket(DataPacket)`,这是一个最常用的方式。 187 | 188 | 当然,先前的Server类也提到了批量发包的方法(Server类) 189 | 190 | * batchPackets​(Player[], DataPacket[]) 批量发送数据包 191 | * broadcastPacket​(Player[], DataPacket) 向所有玩家广播数据包 192 | 193 | 这三个方法就是发包所常使用的方法了。 194 | 195 | 这里我们用dataPacket方法做案例 196 | 197 | 这里用MovePlayerPacket做一个样例 198 | ```java 199 | package net.noyark.www; 200 | 201 | import cn.nukkit.event.EventHandler; 202 | import cn.nukkit.event.Listener; 203 | import cn.nukkit.event.player.PlayerJoinEvent; 204 | import cn.nukkit.network.protocol.MovePlayerPacket; 205 | 206 | public class TestListener implements Listener { 207 | 208 | @EventHandler 209 | public void onPlayer(PlayerJoinEvent e){ 210 | //1. 定义数据包对象 211 | MovePlayerPacket packet = new MovePlayerPacket(); 212 | //2. 设置数据包数值,这里我随便写了几个值 213 | packet.x = 0; 214 | packet.y = 100; 215 | packet.z = 1000; 216 | //3.发出 217 | e.getPlayer().dataPacket(packet); 218 | } 219 | } 220 | ``` 221 | 222 | 四. 常用数据包的解释 223 | 224 | [上一节](2-6_各种工具类的介绍.md) [下一节]() 225 | --------------------------------------------------------------------------------