在插件开发中,我们虽然希望尽可能自己完成开发,但有的时候还是得依赖其它的插件。
要使用其它插件的 API,我们需要在 plugin.yml
中做一些修改。
depend:
- Vault
- RarityCommons
softdepend:
- HoofPower
加上的就是这两个字段,depend
中是必需依赖,如果没有安装,Bukkit 会拒绝加载本插件。
softdepend
是非必需依赖。
另外,如果你想检查一个插件有没有被加载,可以使用:
Bukkit.getPluginManager().getPlugin("插件的名字");
// 如果加载了,返回值就是一个有效的 JavaPlugin 对象,否则是 null,插件名区分大小写
下面我们以使用 Vault 经济插件的 API 为例,介绍插件 API 的使用方法。
一些高级的插件将 API 从插件本身中分离出来,一些没有。如果有 API,我们就应该找到这个 API,如果没有,那就使用插件 Jar 文件。
?> 为什么要使用 API?
这里我们用到的 API 实际上就是接口,它没有被最终包含在我们的插件中,只是在编写和编译时用于提供类型。它就是一个「空壳」,只有外表没有内在,这使得 API 变得很小,易于使用。
开发时我们也不需要关注 API 的实现,只需要知道「有这个 API」就可以了。不懂?看图看图啦~
左边是开发时的场景,右边是插件打包后的场景。
API 做的事,就是图中粉红色的部分。最终打包时,实现和 API 都会被丢掉。
上面所说的「空壳」,就是去掉了紫色部分后的结果。紫色部分无论是在开发还是在成果中都是不必要的。
也就是说,我们对着粉色部分(API)盖房子,完全不需要紫色部分(实现),最后把粉色部分拿走,就是产物了。
Vault 是有 API 的,所以首先我们要找到该插件的 API,如果这个插件有 API,该 API 应该在它的「开发人员指南」部分有所提供。
Vault 的 API 位于 VaultAPI 仓库中。
咦?这里怎么没有下载链接?
Vault 使用了一种叫做 Maven 的工具发布它的 API,因此我们的项目也需要升级到 Maven。
原先 Maven 是需要单独进行安装的,但现在它已经被集成到 IDEA 中了。
打开「Project Structure」,转到「Modules」,单击左上角的「+」、「New module」。
在新的窗口中选择「Maven」,单击「Next」。
在新的窗口中输入项目的名字,项目的存储地点和 「GroupId」,「ArtifactId」。
「GroupId」 是这个项目组的名字,可以是 net.mcbbs.xxx
,可以是 rarityeg.plugins
,也可以是什么别的。
「ArtifactId」 是该模块的名字,在插件开发中一般以插件名小写组成。
这里「Location」比较坑,Maven 并不会为你选择一个合适的地点,需要将路径设置到之前的文件夹中(在我这里是 RarityPlugins
)。
最终的配置如下:
单击「Finish」完成。
我不想在这里引入许多复杂的概念,因此我们以最简单的方式介绍 pom.xml
。
pom.xml
是 Maven 模块中的一个文件,Maven 看着它来构建项目。
展开刚刚创建的「ExampleMavenPlugin」,你就能看到 pom.xml
,它看上去像这样:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>rarityeg</groupId>
<artifactId>examplemavenplugin</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
</project>
现在我们回到 VaultAPI 的介绍页面,其中写着这样的内容:
How to include the API with Maven:
<repositories> <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> </repositories> <dependencies> <dependency> <groupId>com.github.MilkBowl</groupId> <artifactId>VaultAPI</artifactId> <version>1.7</version> <scope>provided</scope> </dependency> </dependencies>
复制其中的内容,粘贴到 pom.xml
的 <project>
和 </project>
中间的任意位置:
IDEA 将某些部分标注为了红色,表示「有错」,这是什么意思呢?
这表示「没有同步」,Maven 的作用之一是「依赖管理」。刚刚我们粘贴进来的那部分中包括了 VaultAPI 的地址信息,Maven 用它来寻找 VaultAPI 的文件。
现在这个文件还在网络上,IDEA 在本地找不到,就会发出警告。
单击一下图中的按钮,这表示「同步」,IDEA 就会开始同步 Maven 项目,稍等一会,等到下面的进度条完成,并且 VaultAPI
字样变为黑色,同步就完成了:
在 Maven 这里,我们不是在 src
下创建包,而要在 src/main/java
中创建包。为什么呢?
Maven 不仅能够用于 Java 的构建,还能够用于 C#、Ruby 等项目,因此该文件夹被放到了 src/main/java
下,换句话说,你就把 src/main/java
当作之前的 src
就行啦。
右键 java
,创建包,包名自己写。
现在就可以在你的包中创建类啦!
在 Maven 中,我们无法通过之前的方式添加「spigot-1.16.5」作为依赖,这会与 Maven 的依赖系统冲突。
幸运的是,Paper 提供了它的 API 的 Maven 版本,既然 Paper 这么好,我们就抛弃 Spigot,改用 Paper 吧。
查询 Paper 的仓库介绍,里面有这样的内容:
- Maven Repo (for paper-api):
<repository> <id>papermc</id> <url>https://papermc.io/repo/repository/maven-public/</url> </repository>
- Artifact Information:
<dependency> <groupId>com.destroystokyo.paper</groupId> <artifactId>paper-api</artifactId> <version>1.16.5-R0.1-SNAPSHOT</version> <scope>provided</scope> </dependency>
我们将 <repository>
部分插入到 pom.xml
中的 <repositories></repositories>
之间,将 <dependency>
部分插入到 <dependencies></dependencies>
之间,就像这样:
你现在是不是对 Maven 的配置有一点点感觉了?看到 <repository>
,就插入 <repositories>
中,看到 <dependency>
,就插入 <dependencies>
中。
修改之后,右上角的按钮又出现了,我们再按一下「同步」,稍等一会,com.destroystokyo
字样就会变成黑色,即代表导入完成。
怎么样?Maven 使用起来是不是很方便?既然这样,我们就抛弃之前的导入方法,全部改用 Maven 吧,耶!晋升成功~
?> 到底怎么回事?
Maven 的 pom.xml
中记载着项目的依赖信息,<repositories>
表示「仓库」,也就是在哪些地方查找依赖,<dependencies>
则表示要使用的依赖。
IDEA 和 Maven 会按照配置在网络上寻找对应的 Jar 包并下载下来以供开发使用。只需要配置 pom.xml
,再按一下「同步」,这明显比「Project Structure」方便多了嘛!
只要有了像上面这样的配置信息(如 Paper 提供的),使用 Maven 就能够获取全世界任何开发者发布的项目文件以供开发,这就是 Maven 的强大之处——一套统一的标准,一个简便的方法。
哦,对了,我们 plugin.yml
的位置也要发生变化,位于 src/main/resources
下而不是 src
下了。
在 onEnable
方法中即可检查:
boolean isVaultEnabled = !(Bukkit.getPluginManager().getPlugin("Vault") == null);
如果 Vault 启用了,我们就可以调用它的 API,如果没有,那我们需要进行相应的善后措施。
接下来就可以参照 Vault 的文档进行开发了,那些提供了 API 的插件一般都会提供开发文档。
例如,Vault 的快速上手代码:
!> 这是示例代码!
这部分代码是 Vault 提供的,仅提示了使用方法,请不要尝试运行它!
package com.example.plugin;
import java.util.logging.Logger;
import net.milkbowl.vault.chat.Chat;
import net.milkbowl.vault.economy.Economy;
import net.milkbowl.vault.economy.EconomyResponse;
import net.milkbowl.vault.permission.Permission;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
public class ExamplePlugin extends JavaPlugin {
private static final Logger log = Logger.getLogger("Minecraft");
private static Economy econ = null;
private static Permission perms = null;
private static Chat chat = null;
@Override
public void onDisable() {
log.info(String.format("[%s] Disabled Version %s", getDescription().getName(), getDescription().getVersion()));
}
@Override
public void onEnable() {
if (!setupEconomy() ) {
log.severe(String.format("[%s] - Disabled due to no Vault dependency found!", getDescription().getName()));
getServer().getPluginManager().disablePlugin(this);
return;
}
setupPermissions();
setupChat();
}
private boolean setupEconomy() {
if (getServer().getPluginManager().getPlugin("Vault") == null) {
return false;
}
RegisteredServiceProvider<Economy> rsp = getServer().getServicesManager().getRegistration(Economy.class);
if (rsp == null) {
return false;
}
econ = rsp.getProvider();
return econ != null;
}
private boolean setupChat() {
RegisteredServiceProvider<Chat> rsp = getServer().getServicesManager().getRegistration(Chat.class);
chat = rsp.getProvider();
return chat != null;
}
private boolean setupPermissions() {
RegisteredServiceProvider<Permission> rsp = getServer().getServicesManager().getRegistration(Permission.class);
perms = rsp.getProvider();
return perms != null;
}
public boolean onCommand(CommandSender sender, Command command, String commandLabel, String[] args) {
if(!(sender instanceof Player)) {
log.info("Only players are supported for this Example Plugin, but you should not do this!!!");
return true;
}
Player player = (Player) sender;
if(command.getLabel().equals("test-economy")) {
// Lets give the player 1.05 currency (note that SOME economic plugins require rounding!)
sender.sendMessage(String.format("You have %s", econ.format(econ.getBalance(player.getName()))));
EconomyResponse r = econ.depositPlayer(player, 1.05);
if(r.transactionSuccess()) {
sender.sendMessage(String.format("You were given %s and now have %s", econ.format(r.amount), econ.format(r.balance)));
} else {
sender.sendMessage(String.format("An error occured: %s", r.errorMessage));
}
return true;
} else if(command.getLabel().equals("test-permission")) {
// Lets test if user has the node "example.plugin.awesome" to determine if they are awesome or just suck
if(perms.has(player, "example.plugin.awesome")) {
sender.sendMessage("You are awesome!");
} else {
sender.sendMessage("You suck!");
}
return true;
} else {
return false;
}
}
public static Economy getEconomy() {
return econ;
}
public static Permission getPermissions() {
return perms;
}
public static Chat getChat() {
return chat;
}
}
RegisteredServiceProvider
是「中间商」。由于我们只能访问到 Vault 的 API,这个中间商就负责将一个接口与一个实现它的类「绑」起来。也就是说,通过它的 getProvider
方法,我们获得了符合这个接口的一个类实例。至于那个实例具体怎么样……不在考虑范围之内。
还不明白?图!
打比方说就是,你提供需要的服务(接口的 class
)给 Bukkit,Bukkit 为你去找符合条件的酒店(提供方),你得到酒店地址后也不用了解它的底细(知道具体的类),走进去,里面就有你需要的服务(实现了接口)。
得到了接口的实现后,就可以开始进行开发了。关于究竟要如何利用一个插件为你提供的类、方法等内容,这是各个插件作者决定的,笔者也帮不上忙。你需要阅读他们编写的文档。
对了!最后打包时,记得把对应的 API 一起打包进去,可以参考 AC-1-3 中打包 JDBC 的过程(右键,「Extract Into Output Root」)。
- 创建 Maven 项目,添加依赖
- 检查插件是否可用
- 使用「中间商」获得接口对应的对象
- 像使用 Bukkit API 一样使用 Vault API 吧!
就是这样。