我的世界Bukkit服务器插件开发教程(九)NMS

发布于:2023-01-04 ⋅ 阅读:(709) ⋅ 点赞:(0)

九、NMS

在前面我们提到过,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 是不可能决定这个项目停止的。

呃呃,扯的有些太多了。对于我们现在所处的圈子,我只想说:

什么狗屁开源精神!


1.NMS

重新回顾一下 NMS,现在看来也没有辣么难。

有一个东西叫 NBT 标签,这玩意不知道由于什么原因,被 Bukkit 扔了,忘了。不过原版 NMS 中是有这玩意的。

net.minecraft.server下有个叫x_xx_Rx的包,它在不同版本下名称都不一样,这也导致了插件糟糕的兼容性(需要对每一个版本都编译一遍)。

另外,org.bukkit.craftbukkit(简称OBC)下也有个叫x_xx_Rx的包,它是对NMS进行一次封装,其实 OBC 里面都是接口,也是 Bukkit 的实现。

我们会发现,有些插件可以兼容许多版本。这一切要归功于Java的一个机制:反射

2.反射

2.1.找类Class

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;
    }
}

2.2.找方法Method

接下来,创建一个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服务器插件开发教程(十)实体


网站公告

今日签到

点亮在社区的每一天
去签到