之前我发现,很多人制作插件时需要使用一些当前版本的特性,比如发送一个title或一个ActionBar信息。很多人认为想实现这些功能就只能使用NMS(net.minecraft.server)。但由于每个版本的NMS都会有一些改动,所以有些插件的版本兼容性比较差。当版本更新时,它们往往会无法继续正常使用
一段时间后,我发现的最好方式是使用反射兼容我需要兼容的所有版本。
虽然我认为这是一件好事(因为我的插件现在能够向后兼容),但DarkSeraphim告诉我,在一个插件中如果使用了反射,那么应该写一个接口,并在兼容不同版本的功能类中分别实现不同的代码。
如果使用接口的话,我可以将NMS代码放到一个单独的类中,并只加载和使用特定版本的NMS类。这就避免了使用反射!
因为这个教程不可能让你避免有时候使用反射的需要,你可能会遇到类似于发送Title或使用ActionBar这样的需求
在这个示例插件中我们会在玩家进入服务器时发送一个ActionBar信息。同时,这个插件也会支持1.8的所有子版本。
那么让我们开始吧:
首先,我们需要创建一个包来放置接口和NMS类。
首先,让我们创建一个叫做Actionbar的接口类。
这个接口类有一个抽象方法,我们的NMS类可以实现这个方法并通过这个方法发送actionbar信息。
任何一个实现了我们的Actionbar接口的类都必须实现这个抽象类。
那么让我们创建我们的抽象类吧。
代码:
- package me.clip.actionbarplugin.actionbar;
- import org.bukkit.entity.Player;
- public interface Actionbar {
- public void sendActionbar(Player p, String message);
- }
复制代码
|
这个类里面只有一行代码。很简单,对吧?接下来,实现这个接口的类将会以自己的"版本特性"来实现这个抽象方法。
如果你使用这个教程中介绍的方法(也就是创建一个接口类,然后分开实现这个接口)来实现一些功能的话,请务必记住,你需要在每个NMS类中实现这个抽象类中的所有方法。
现在我们拥有我们自己的接口了,让我们创建实现它的类,并用NMS向玩家发送actionbar信息吧!
下面是用来支持1.8.1版本的类的代码,这个类实现了我们的Actionbar接口。
- package me.clip.actionbarplugin.actionbar;
- import net.minecraft.server.v1_8_R1.ChatSerializer;
- import net.minecraft.server.v1_8_R1.IChatBaseComponent;
- import net.minecraft.server.v1_8_R1.PacketPlayOutChat;
- import org.bukkit.craftbukkit.v1_8_R1.entity.CraftPlayer;
- import org.bukkit.entity.Player;
- public class Actionbar_1_8_R1 implements Actionbar {
- @Override
- public void sendActionbar(Player p, String message) {
- IChatBaseComponent icbc = ChatSerializer.a("{"text": "" + message + ""}");
- PacketPlayOutChat bar = new PacketPlayOutChat(icbc, (byte) 2);
- ((CraftPlayer) p).getHandle().playerConnection.sendPacket(bar);
- }
- }
复制代码 |
下面是用来支持1.8.3版本的类的代码:
- package me.clip.actionbarplugin.actionbar;
- import net.minecraft.server.v1_8_R2.IChatBaseComponent;
- import net.minecraft.server.v1_8_R2.PacketPlayOutChat;
- import net.minecraft.server.v1_8_R2.IChatBaseComponent.ChatSerializer;
- import org.bukkit.craftbukkit.v1_8_R2.entity.CraftPlayer;
- import org.bukkit.entity.Player;
- public class Actionbar_1_8_R2 implements Actionbar {
- @Override
- public void sendActionbar(Player p, String message) {
- IChatBaseComponent icbc = ChatSerializer.a("{"text": "" + message + ""}");
- PacketPlayOutChat bar = new PacketPlayOutChat(icbc, (byte) 2);
- ((CraftPlayer) p).getHandle().playerConnection.sendPacket(bar);
- }
- }
复制代码
|
现在,我们拥有分别用不同版本发送ActionBar信息的代码了!
接下来,我们需要做的就只剩下创建我们的主类,然后在插件加载时检测版本并调用合适的方法了。
主类代码:
- package me.clip.actionbarplugin;
- import me.clip.actionbarplugin.actionbar.Actionbar;
- import me.clip.actionbarplugin.actionbar.Actionbar_1_8_R1;
- import me.clip.actionbarplugin.actionbar.Actionbar_1_8_R2;
- import org.bukkit.Bukkit;
- import org.bukkit.event.EventHandler;
- import org.bukkit.event.Listener;
- import org.bukkit.event.player.PlayerJoinEvent;
- import org.bukkit.plugin.java.JavaPlugin;
- public class ActionbarPlugin extends JavaPlugin implements Listener {
- // our interface reference! Any class that implements Actionbar can be assigned to this reference!
- // when we need to send an actionbar, all we need to do is call actionbar.sendActionbar(player, message);
- // since the proper NMS class was assigned onEnable, we are now backwards compatible!
- private Actionbar actionbar;
- @Override
- public void onEnable() {
- if (setupActionbar()) {
- Bukkit.getPluginManager().registerEvents(this, this);
- getLogger().info("Actionbar setup was successful!");
- getLogger().info("The plugin setup process is complete!");
- } else {
- getLogger().severe("Failed to setup Actionbar!");
- getLogger().severe("Your server version is not compatible with this plugin!");
- Bukkit.getPluginManager().disablePlugin(this);
- }
- }
- // this method will setup our actionbar class and return true if the server is running a
- // version compatible with our NMS classes.
- // If the server is not compatible, it will return false!
- private boolean setupActionbar() {
- String version;
- try {
- version = Bukkit.getServer().getClass().getPackage().getName().replace(".", ",").split(",")[3];
- } catch (ArrayIndexOutOfBoundsException whatVersionAreYouUsingException) {
- return false;
- }
- getLogger().info("Your server is running version " + version);
- if (version.equals("v1_8_R1")) {
- //server is running 1.8-1.8.1 so we need to use the 1.8 R1 NMS class
- actionbar = new Actionbar_1_8_R1();
- } else if (version.equals("v1_8_R2")) {
- //server is running 1.8.3 so we need to use the 1.8 R2 NMS class
- actionbar = new Actionbar_1_8_R2();
- }
- // This will return true if the server version was compatible with one of our NMS classes
- // because if it is, our actionbar would not be null
- return actionbar != null;
- }
- @EventHandler
- public void onJoin(PlayerJoinEvent event) {
- actionbar.sendActionbar(event.getPlayer(), "Welcome to the server!");
- }
- }
复制代码
|
在这个教程中的示例中,我们在主类中就完成了所有操作。
但是,如果你要制作一个大型插件的话,可能会有很多类(比如你的监听器类等)。
这时候,你需要在你的主类中创建一个用来获取Actionbar的方法,并让你的其他类可以使用它。
这个方法是很简单的,如果你一直在认真看这个教程的话,想必就已经知道如何做了。
代码:
- public Actionbar getActionbar() {
- return actionbar;
- }
复制代码
|
现在,你的所有类都能用这个方法发送actionbar信息了!
你可以选择很多支持多版本的方法,在这里我使用的方法是得到Bukkit的Server类,然后获取它的包名。
当然,你也可以用Bukkit.getBukkitVersion()方法来查看你的NMS类需要支持的版本。
看到这里,你应该明白了一件事:这个让NMS代码向后兼容的方法是非常简单的。
另外,注意:如果你要实例化你的一个NMS类时,没有支持合适版本的类的话,你的插件将会抛出一个 ClassNotFoundException 异常。因此,建议先确定好服务器版本再去实例化NMS类。
在此此后,当spigot更新了一个新的NMS包名时,我们只需要创建一个新的NMS类并让它实现Actionbar接口,最后在插件加载时检测就可以了!
作者最后的话:
| Thanks for reading. I am not really the greatest developer and I am always learning new things every day just like you! I hope this tutorial helps someone get an understanding on how you can use an interface to do different things such as use NMS on different server versions without using reflecton! |
我自己的一些废话:
| 由于这是我第一个翻译作品,所以可能有很多问题。。如果你发现了什么问题的话,希望能私信告诉我或在本帖留言,我会尽快改正的QAQ |
[groupid=1181]Unknown Domain[/groupid]