Writing Spigot Plugins with Dependencies
December 12, 2015
Scenario: you’re writing a Minecraft plugin that needs to communicate with another plugin. How do you achieve that?
Some plugins are written with this in mind, and expose their own API. A couple of prominent examples of this are WorldGuard and WorldEdit. Let’s write a plugin that can perform an operation on a player’s WorldEdit selection. Like…a /spongify
command that makes every non-air block a sponge.
Maven Remote Dependencies
First thing’s first: we need to add a Maven dependency to our plugin’s pom.xml
. Otherwise you won’t be able to access the APIs exposed by the other plugin. For your plugin to compile against the Bukkit API, you’d already have something like this in the file:
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>1.8.7-R0.1-SNAPSHOT</version>
</dependency>
</dependencies>
That tells Maven to grab the org.bukkit.bukkit
package from Spigot’s repository and include it as a dependency when building your plugin.
To add another dependency, you must locate a Maven repository that includes builds of the package you need. In the case of WorldEdit, the author maintains a repository with builds of his plugins. So we would update the pom.xml
to look like this:
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
</repository>
<repository>
<id>sk89q-repo</id>
<url>http://maven.sk89q.com/repo/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>1.8.7-R0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.sk89q</groupId>
<artifactId>worldedit</artifactId>
<version>6.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
Now when we build our plugin, it will include the com.sk89q.worldedit
package, which our plugin can now make use of.
Maven Local Dependencies
But what if a plugin doesn’t have a convenient repository you can just add and have the dependency be automatically resolved?
This is something that I just had to deal with before writing this post, actually. I was making an update to a plugin that depdends on WGCustomFlags, which used to have a remote Maven repository but has since ceased to be online.
The solution is actully very simple. All you have to do is download the plugin’s source, build it with Maven, and then install it to your system’s local repository. Maven caches every package it downloads in an internal repository on your computer, so it doesn’t have to download them every time. When you build a plugin, it quickly checks to see if dependencies on remote repositories are newer, and uses the local copies if the remotes haven’t updated.
So if you download and compile a plugin, compile it, and then run maven install
, it will copy the package into your local repository, where other builds can find it. This is exactly what Spigot’s BuildTools.jar does. (Spigot can’t legally distribute completed builds, due to the DMCA concerns, so they distribute source and an automated build tool.)
Basically, I did this:
git clone https://github.com/mewin/WGCustomFlags.git
cd WGCustomFlags
mvn clean package
mvn install
Et voilà. Now my IDE stopped complaining about not being able to resolve the dependency.
The YAML
Before we forget, let’s tell the server that our plugin depends on another one, and that it should be loaded after the other plugin. It’s just a simple addition to plugin.yml
, using the plugin’s name (as defined in its own plugin.yml
).
depend: [WorldEdit]
Getting the WorldEdit Instance
Now we’re down to the part that’s specific to the plugin in question: actually writing code. The Bukkit API allows us to get a plugin instance through the PluginManager
like this:
private WorldEditPlugin getWE() {
Plugin plugin = getServer().getPluginManager().getPlugin("WorldEdit");
if (plugin == null || !(plugin instanceof WorldEditPlugin)) {
return null;
}
return (WorldEditPlugin) plugin;
}
With a method like that in place, we can get a WorldEditPlugin instance by calling getWE()
.
Now if I want to get a WorldEdit Selection
object representing a selection the player has made, I can just do this (with player
being a standard Bukkit Player
object):
Selection sel = getWE().getWE().getSelection(player);
In the case of WorldEdit, sk89q makes thorough documentation of his plugins’ public APIs available in the form of JavaDocs. So you can riddle out how things work between them and the source on GitHub. Other plugins may only give you a GitHub repository and expect you to pick through it the hard way.
Spongify
So about that example use case…let’s make a command that loops through a WorldEdit selection and turns non-air blocks into sponge!
@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
if (cmd.getName().equalsIgnoreCase("spongify")) {
// Stop console from running the command
if (!(sender instanceof Player)) {
sender.sendMessage("Console cannot do that.");
return true;
}
// Get the player and their WE selection
Player player = (Player) sender;
Selection sel = getWE().getWE().getSelection(player);
// Get the min and max coordinates in the cuboid
Location max = sel.getMaximumPoint();
Location min = sel.getMinimumPoint();
// Loop through the blocks
for (int x = min.getBlockX(); x <= max.getBlockX(); x++) {
for (int y = min.getBlockY(); y <= max,getBlockY(); y++) {
for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) {
Block block = world.getBlockAt(x, y, z);
if (block.getType() != Material.AIR) {
block.setType(Material.SPONGE);
}
}
}
}
return true;
}
return false;
}