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


    九、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
    
    • 1
    • 2

    找到了就返回,找不到抛异常,很简单。

    我们写插件,也是为了考虑兼容性的,不能说你这个插件只兼容一个版本,那别人用了得顺着网线真实你。

    所以,我们简单写一个方法,它用来查找一个类,找到了就返回这个类,找不到抛异常。

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2.2.找方法Method

    接下来,创建一个Class类对象,将找到的类(如果找到的话)赋给它。

    //找ShapedRecipes类
    Class<?> recipe = NMSUtils.getNMSClass("Tag");
    
    • 1
    • 2

    这时候肯定有人问了,我找到类了我该怎么使用它的方法呢?

    有个叫Method的类,是专门找一个类中的方法的。

    //找NBTCompressedStreamTools类中的a方法(真的有a方法)
    Method a = NMSUtils.getNMSClass("NBTCompressedStreamTools").getMethod("a", null);
    
    • 1
    • 2

    getMethod方法的第一个参数表示方法名,后面的参数表示这个方法的参数,没有就是null

    由于一个方法可能有很多种,它们每一种都有不同的参数,传入参数就是为了精确找到方法了。

    假设我在Test类中有一个setMyName方法,参数是String类型:

    public void setMyName(String name) {
    	this.name = name;
    }
    
    • 1
    • 2
    • 3

    现在使用getMethod方法,就应该这么写。

    Method setMyName = NMSUtils.getNMSClass("Test").getMethod("setMyName", String.class);
    
    • 1

    可见,参数传的是该参数的类型的实例化对象

    现在,找到了方法,我们就应该调用这个方法,那么就可以使用Method方法中的invoke方法,用来调用该方法。

    //创建一个类
    Test test = new Test();
    //找到了方法
    Method a = NMSUtils.getNMSClass("Test").getMethod("setMyName", String.class);
    //调用它
    a.invoke(test, "小明");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    最后一行代码等价于下面一行代码:

    test.setMyName("小明");
    
    • 1

    invoke方法的第一个参数代表哪个对象调用的方法,后面的表示传参。

    大概就这么多了,Class类还有很多方法,比如获得构造函数、类加载器等之类的方法。


    总结

    NMS 万不得已不要用,一般情况下还是用 Bukkit API 吧。

    在下一章,我们将要学会自己自定义实体的行为特征,这就要接触到 NMS 中的一些东西了,比如触发器等等。

    当然了,这些我不会考虑插件的兼容性。

    说到触发器,我有点想到现在 3D 游戏中的行为树了,很像吗?


    上一篇:我的世界Bukkit服务器插件开发教程(八)进度条与自定义合成表
    下一篇:我的世界Bukkit服务器插件开发教程(十)实体

  • 相关阅读:
    【Spring】事务实现原理
    Artplayer视频JSON解析播放器源码|支持弹幕|json数据模式
    [centos]centos镜像ISO下载地址
    湖北移动中兴B860AV2.1_S905L_线刷固件包
    2022卡塔尔世界杯赛程直播北京时间_足球世界杯对阵表图完整全部
    测试经验总结
    【react】点击空白处隐藏
    达梦数据库,外部基表不能存在任何约束条件
    啡鸟集:掌握咖啡6大撩人技法,提升个人魅力值
    【安卓13-Framework】SystemUI定制之屏蔽下拉状态栏部分快捷按钮
  • 原文地址:https://blog.csdn.net/weixin_45445598/article/details/125909500