之前我发现,很多人制作插件时需要使用一些当前版本的特性,比如发送一个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 | 
来自群组: Unknown Domain