在前面我们提到过,NMS是底层实现。现有的BukkitAPI已经能够满足我们绝大部分的需求,然而只是绝大部分。还有一小部分的功能(如NBT标签)不包含在Bukkit所提供的API中,这使得我们需要借助NMS完成这些被Bukkit遗忘的功能。
PS:本章涉及到反射机制,如果你已经学过反射机制的话,阅读这篇文章会更加轻松。没有阅读过的可能有些困难,我尽量举例说明。
法律扼制了谁
早期的Minecraft服务端都是原版服务端,放到今天来说就是个不添加任何插件的纯服(或水桶服)。
原版服务端并没有提供任何的API,也就不能写插件,于是就把开发者高兴坏了。
这时候有个叫Bukkit的项目将原版服务端的代码反编译并修改了一下,自己又提供一套API,开发者们十分的(喜)。项目逐渐在这个圈子里火了起来。
但好景不长,MOJANG(以下称作麻将)发觉不对劲:你小子用我的代码火了起来?
麻将偷偷地更新了原版服务端的 EULA(最终用户许可协议),EULA 里提到过:
一条重要规定是您不得分发我们创建的任何内容。“分发我们创建的任何内容’是指‘向其他人提供我们游戏的副本、将我们创建的任何内容用于商业用途或赚钱,或允许其他人以不公平或不合理的方式访问我们创建的任何内容”
Bukkit 也很害怕,害怕麻将哪一天突然拿着 EULA 和 DMCA(数字千年版权法案)把自己告上了法庭。
麻将还雇佣了 Bukkit 的核心人员,并和他们签合同,让他们放弃个人版权和他们的开源贡献者的权利。
在 2014 年 8 月 21 日,Bukkit 项目的主开发者:EvilSeph,宣布停止对 Bukkit 项目的开发。
事实上,麻将在两年前(2012)就买下了 Bukkit 项目,EvilSeph 是不可能决定这个项目停止的。
呃呃,扯的有些太多了。对于我们现在所处的圈子,我只想说:
什么狗屁开源精神!
重新回顾一下 NMS,现在看来也没有辣么难。
有一个东西叫 NBT 标签,这玩意不知道由于什么原因,被 Bukkit 扔了,忘了。不过原版 NMS 中是有这玩意的。
在net.minecraft.server
下有个叫x_xx_Rx
的包,它在不同版本下名称都不一样,这也导致了插件糟糕的兼容性(需要对每一个版本都编译一遍)。
另外,org.bukkit.craftbukkit
(简称OBC)下也有个叫x_xx_Rx
的包,它是对NMS进行一次封装,其实 OBC 里面都是接口,也是 Bukkit 的实现。
我们会发现,有些插件可以兼容许多版本。这一切要归功于Java的一个机制:反射。
Java的发射机制是什么?
简单讲,你可以构造任意一个类的对象,了解它们其中的属性和方法,等等。
对于一个字节码文件
.class
,虽然表面上我们对该字节码文件一无所知,但该文件本身却记录了许多信息。Java在将.class
字节码文件载入时,JVM 将产生一个java.lang.Class
对象代表该.class
字节码文件,从该 Class 对象中可以获得类的许多基本信息,这就是反射机制。所以要想完成反射操作,就必须首先认识 Class 类
——摘自百度百科《JAVA反射机制》
Class 类用来描述一个类,其中有个静态方法用来找一个类:
@CallerSensitive
public static Class<?> forName(String className) throws ClassNotFoundException
找到了就返回,找不到抛异常,很简单。
我们写插件,也是为了考虑兼容性的,不能说你这个插件只兼容一个版本,那别人用了得顺着网线真实你。
所以,我们简单写一个方法,它用来查找一个类,找到了就返回这个类,找不到抛异常。
public Class<?> getNMSClass(String className) {
String rootName = Bukkit.getServer().getClass().getName();
//这里的rootName是OBC路径,需要把它替换成NMS路径
//在NMS中找一个类,将它替换成我们要找的类就行了。
try {
return Class.forName(rootName.replace("org.bukkit.craftbukkit", "net.minecraft.server").
replace("CraftServer", className));
} catch (ClassNotFoundException e) {
return null;
}
}
接下来,创建一个Class类对象,将找到的类(如果找到的话)赋给它。
//找ShapedRecipes类
Class<?> recipe = NMSUtils.getNMSClass("Tag");
这时候肯定有人问了,我找到类了我该怎么使用它的方法呢?
有个叫Method
的类,是专门找一个类中的方法的。
//找NBTCompressedStreamTools类中的a方法(真的有a方法)
Method a = NMSUtils.getNMSClass("NBTCompressedStreamTools").getMethod("a", null);
getMethod
方法的第一个参数表示方法名,后面的参数表示这个方法的参数,没有就是null
。
由于一个方法可能有很多种,它们每一种都有不同的参数,传入参数就是为了精确找到方法了。
假设我在Test
类中有一个setMyName
方法,参数是String
类型:
public void setMyName(String name) {
this.name = name;
}
现在使用getMethod
方法,就应该这么写。
Method setMyName = NMSUtils.getNMSClass("Test").getMethod("setMyName", String.class);
可见,参数传的是该参数的类型的实例化对象。
现在,找到了方法,我们就应该调用这个方法,那么就可以使用Method
方法中的invoke
方法,用来调用该方法。
//创建一个类
Test test = new Test();
//找到了方法
Method a = NMSUtils.getNMSClass("Test").getMethod("setMyName", String.class);
//调用它
a.invoke(test, "小明");
最后一行代码等价于下面一行代码:
test.setMyName("小明");
invoke
方法的第一个参数代表哪个对象调用的方法,后面的表示传参。
大概就这么多了,Class
类还有很多方法,比如获得构造函数、类加载器等之类的方法。
总结
NMS 万不得已不要用,一般情况下还是用 Bukkit API 吧。
在下一章,我们将要学会自己自定义实体的行为特征,这就要接触到 NMS 中的一些东西了,比如触发器等等。
当然了,这些我不会考虑插件的兼容性。
说到触发器,我有点想到现在 3D 游戏中的行为树了,很像吗?
上一篇:我的世界Bukkit服务器插件开发教程(八)进度条与自定义合成表
下一篇:我的世界Bukkit服务器插件开发教程(十)实体