Files
netease-modsdk-wiki/docs/mcguide/28-电脑网络游戏/课程3:编写Bukkit插件/4-底层内容/1-认识NMS与OBC.md
boybook 760c2dd9ad 2.6
2025-12-01 20:59:16 +08:00

146 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
front:
hard: 高级
time: 20分钟
---
# 认识 NMS 和 OBC
nms即 Java 包 `net.minecraft.server`,存放的是 Minecraft 服务端游戏逻辑代码,这篇教程将会采用 `Spigot` 作为 Bukkit API就你们日常开发用的API 体系的例子CraftBukkit 也是 SpigotMC 维护,且基本所有 Bukkit 分支都是基于 Spigot 开发)
BukkitAPI 真的涵盖了不少东西,但是 BukkitAPI 并不是十全十美的,有时候我们确实需要使用 NMS。
~~Forge其实也有个net.minecraft.server但他和Spigot的没半点关系~~
## 使用 NMS 之前
md_5*Wait!* 你真的需要使用 NMS 吗?
> NMS不是API。当你遇到什么想做的事情的时候你不应该第一时间去考虑 NMS 或者 发包
令人费解的是,我们几乎每天都看到人们专门使用 NMS 做一些简单的事情,如 ScoreBoard、BossBar 或粒子。但是实际上自从Mojang添加了这些东西之后Spigot/Bukkit-API 早就有这些功能了。
每当你考虑使用 NMS 时,请思考以下问题:
1. 我是否需要NMS来做这个
2. 是否有一个API来实现这个功能
3. 我可以为这个贡献/创建/ ***提议*** 一个API吗
对NMS的滥用造成的后果非常严重。
- 1. 插件将失去版本迁移的能力(针对单个版本而言)
- 2. 阻碍了 API 的发展并且树立了一个坏榜样。
如果你确实想清楚确实没有现有的 API 能帮到你,那么来...
# 怎么使用 NMS
NMS 里的内容太多,故本教程**不会**教授NMS有什么东西但是可以教你怎么玩。
## 开发环境准备
由于DMCA的原因Bukkit不会直接提供NMS您需要将构建好的服务端代码引入项目中
如果没有且你正在使用 `Gradle` , `Maven` 这样的依赖管理器,考虑如下方法(图文):
这边以Gradle为例
1. 在项目根目录创建libs文件夹用于放第三方库
![](../images/0_12.png)
2. 将构建好的服务端直接拖入libs文件夹中
![](../images/0_13.png)
3. 配置Gradle
![](../images/0_14.png)
4. 同步Gradle配置
![](../images/0_15.png)
5. 你已经成功引入NMS
## 获取到一个来自 API 背后的对象
*当你从控制台直接输出一个 `Player` 对象时,会发生什么?*
你会得到一个 `CraftPlayer{name=玩家名}`而不是NMS里面的`EntityPlayer`。这是因为在Bukkit-API背后还有一层他叫`OBC`,也就是`org.bukkit.craftbukkit`
OBC 是 Bukkit API 的实现其本质是NMS的封装因此我们并不需要太关心它。
最简单的一个说法就是实际上我们操作NMS封装API也是OBC所做的事情。
就上文问题,怎么拿到一个 NMS 里面的 `EntityPlayer`?
首先我们需要使用反编译工具当然了你也可以通过IDEA直接查看。
来看 `CraftPlayer``EntityPlayer` 做了什么..
![](../images/0_16.png)
CraftPlayer 将 EntityPlayer 传给了他的父类构造器,我们接着追踪..
![](../images/0_17.png)
而 CraftPlayer 的父类 CraftHumanEntity 依然将 EntityPlayer 传入父类 CraftLivingEntity我们继续翻阅到 CraftLivingEntity 中
![](../images/0_18.png)
经过一条不 是 很 长的继承链后,我们找到了 `CraftEntity`,看来 `EntityPlayer` 最后是被传道这里了!
> 注意OBC里面的类 `implements` 的都是BukkitAPI的实现不要搞混了!
然后往上翻,看看 entity 是什么情况。
![](../images/0_19.png)
他的修饰符是 `protected`这意味着只有继承树内或者同一个包里面才能访问到它而这在NMS/OBC中是常有的事情。
## 反射!
我们很容易就可以写出这样的代码来获取到这个 entity 对象。
而这就需要用到Java的特性 —— 反射
```java
public static final String serverVersion = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
try{
Player player = ....; // Who cares ?
Class<?> clazz = Class.forName("org.bukkit.craftbukkit."+serverVersion+".entity.CraftEntity");
Field f = clazz.getDeclaredField("entity");
f.setAccessible(true);
Object result = f.get(player);
}catch(Throwable t){
t.printStackTrace();
}
```
现在 `result` 里面存的就是我们需要的 EntityPlayer然后我们可以转换它...做点事情。
为什么要这么麻烦?其实下面的代码一样可以做到这个效果:
```java
try{
Player player = ....; // Who cares ?
Field f = CraftEntity.class.getDeclaredField("entity");
f.setAccessible(true);
Object result = f.get(player);
}catch(Throwable t){
t.printStackTrace();
}
```
假设BukkitAPI没有封装修改经验值的方法我们要修改玩家实体的经验值
就可以通过刚刚获取到的result的变量判断是否为 NMS 的 EntityPlayer 对象
```java
try{
Player player = event.getPlayer();
getLogger().info("test");
Class<?> clazz = Class.forName("org.bukkit.craftbukkit."+serverVersion+".entity.CraftEntity");
Field f = clazz.getDeclaredField("entity");
f.setAccessible(true);
Object result = f.get(player);
if (result instanceof EntityPlayer){
((EntityPlayer) result).d(1000);
}
}catch(Throwable t){
t.printStackTrace();
}
```
> 那为什么result的方法是 `d`
> 这是因为 Minecraft源码本身就是混淆的OBC则是Bukkit反编译探索出来这个大概是什么方法然后进行封装的
> 所以我们在和OBC做同样的事情时也要大概去猜测这些方法名是做什么的
> 大家可以尝试翻阅一下 EntityPlayer 源码,其中成员变量 newExp 被 方法 d() 所修改
> 所以大概猜测这个就是修改经验值的方法,调用后证实确实是这个方法
## 版本兼容性
实际上,如果你直接采用了 `CraftEntity.class` 的方法都会对这个 class 建立符号引用。
每个版本的 Spigot无论是 OBC 或者 NMS他们的包名都会变化——也就是说你的插件会爆炸
所以,如果你不想为了一个版本再把逻辑写一次,最好还是用反射的写法。对于公开的方法,也可以使用更高效的 `MethodHandle`
如果你有注意到的话, md_5 说过 `NMS 并不是`一个API(其实也包括OBC)。
什么意思呢?也就是`使用NMS没有任何安全性保障`**你反射的字段/方法或许下一个版本就会被删改。**
实例惨案: Minecraft 1.17 Spigot大改使用 Mojang 官方混淆表,以往 NMS 插件**全都**报废。(除了一些自带兼容的服务端)
# 本章小结
- NMS是`net.minecraft.server`一个包名放MC逻辑
- Spigot的NMS没有安全保障md_5 都不推荐用
- 使用NMS之前要先找是否已经有了对应的 API
- 在 Bukkit-API 和 NMS 之间还有一个实现,它叫 OBC。