A Guide to Minecraft Plugin Development with Bukkit | Part 1

Recently, I decided to hold a series of workshops about Java development at the CoderDojo Linz. As lots of kids and teens there enjoy playing Minecraft, it was a straightforward decision to choose Minecraft plugin development as the topic for these workshops. As I never played Minecraft before, I wanted to get to know the game first. My plan to play for 2 or 3 hours to learn the basics escalated rather quickly and I ended up in not being productive for a week. Well, probably I could have anticipated that. Anyway, now I’m back and in the following lines I summarized some basics about Minecraft plugin development with Bukkit.

Prerequisites

Please note that this guide assumes, that you are already familiar with Java. If you are new to Java, I’d recommend getting to know Java first. Here you can find some great resources to learn it.

Running a local Spigot server

To install Bukkit plugins on your server, you need to run a CraftBukkit server or a fork of it, like Spigot or Paper. You can spin up and connect to a Spigot server with the below described steps.

  1. Execute the file BuildTools.jar with the command java -jar BuildTools.jar in your terminal.
  2. Copy or move the file spigot-<VERSION>.jar to an empty folder and execute it with java -jar spigot-<VERSION>.jar.
  3. This will generate some files and folders, but won’t spin up the server, as you need to accept Minecraft’s end-user license agreement (EULA) first. To accept it, open the file eula.txt and change the line eula=false to eula=true. Save and close the file.
  4. Execute the spigot-<VERSION>.jar again to spin up the server. Once the line [09:08:39 INFO]: Done (XXXs)! For help, type "help" appears, you are ready to go.
  5. Open Minecraft and select “Multiplayer”. There you can connect to your new server, which is running on localhost.

Creating a basic plugin

Now, that your server is up and running, it’s time to create a basic plugin. To get started, create a new Java project with Gradle or Maven. I went with Gradle and Java 11, but also Maven is described in this section. Further, I used IntelliJ IDEA Community as IDE.

implementation group: 'org.bukkit', name: 'bukkit', version: '[VERSION]'
maven { 
url "https://hub.spigotmc.org/nexus/content/repositories/public/"
}
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>[VERSION]</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/public/</url>
</repository>
  • version: The version of your plugin.
  • main: The main class of your plugin.
name: MinecraftPluginGuide_DemoPlugin
main: BukkitPlugin
version: 0.1
api-version: 1.16

Deploying a plugin

To deploy your plugin, you first need to generate a JAR file containing all your code and dependencies (if you have some). Next, make sure that your server is not running and copy the JAR to the plugins directory of your server. Now you can run your server (see section 1) and your plugin will be loaded. You can check if it worked by running the command pl in the game or directly in the server window. Your plugin should be contained in the resulting list. Besides that, you'll see a log message when your plugin is loaded.

Gradle

If you are only using Bukkit’s dependency, you can simply package your project by executing the following command in the terminal. Please note, that you need to be in your project’s root directory to execute it.

./gradlew jar
jar { 
manifest {
attributes "Main-Class": "BukkitPlugin"
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
id 'com.github.johnrengelman.shadow' version '[VERSION]'
plugins { 
id 'java'
id 'com.github.johnrengelman.shadow' version '[VERSION]'
}
./gradlew shadowJar

Maven

If you are only using Bukkit’s dependency, you can simply package your project by executing the corresponding Maven task. If you want to execute it in the console, you need to install Maven on your computer. Therefore I’ll just use IntelliJs built in functionality to execute Maven tasks. To create a JAR open the Maven toolbar in the top right of IntelliJ (orange square in the picture below), then open the Lifecycle dropdown, if it is closed, and click package (green square).

IntelliJ IDEA Community Maven Tasks

Logging

Bukkit has a built-in logging mechanism, which works quite good. The following subsections describe how to use it.

Getting a Logger

You can get an instance of the primary logger of a server by calling Bukkit.getLogger() or (if you are inside a JavaPlugin class) getServer().getLogger(). You can use this logger to send messages to the logstream of your server, but I would recommend using the logger of your plugin instead. This way, it is clear that log messages are coming from your plugin because they are prefixed. To get this logger, you need to be inside your JavaPlugin class. There you can simply call getLogger(). I'll use this approach in the following examples.

Log Levels

Bukkit provides 7 predefined log levels, which you should use to differentiate your log messages. Those levels (in descending order) are:

  • WARNING: indicates a potential problem
  • INFO: informational messages
  • CONFIG: static configuration messages
  • FINE: tracing information
  • FINER: fairly detailed tracing information
  • FINEST: highly detailed tracing information

Logging messages

To log a message you can simply execute the following line. Of course the first argument can be replaced with any of the above described log levels. Also the second argument, the message, can be chosen by you.

logger.log(Level.INFO, "Info log message");
logger.info("Info log message");

Setting a custom log prefix

By default, the name you provide in plugin.yml is used as a prefix for log messages, that are sent via the logger of the plugin class. If you prefer using a different prefix, you can set the attribute prefix in the plugin.yml like this:

prefix: custom-log-prefix

Changing the log level and logging to a file

Unfortunately it’s not possible to log anything with a level below INFO to the server's console or standard log files. A moderator of the Bukkit Forum, stated that in this thread. Therefore setting the log level programmatically only makes sense, if you want to set the level to INFO (which is default), WARNING or SEVERE. Setting a log level means, that all messages logged with this level and the levels above this level will be logged. This means, if the log level is INFO all INFO, WARNING and SEVERE messages will be logged. Changing the log level to one of those levels is quite easy with the following command. Of course INFO can be replaced with any other level, but (as explained above) only WARNING or SEVERE make a difference.

logger.setLevel(Level.INFO);

Understanding Minecraft’s coordinate system

A players coordinates represent his position in a dimension. A dimension is an accessible realm inside a world. The center of the coordinate system is the origin point. The spawn points of all players are located close to the origin point.

Minecraft’s coordinate system
  • Z axis: represents how far the player has moved to the south (positive value) or north (negative value) of the origin point.
  • Y axis: represents how high (positive value) or low (negative value) the player is compared to the origin point.

Adding custom commands

To add custom commands to your server, you need to complete two steps: Registering your command in the plugin.yml of your plugin and adding the command handler to your code. Both steps are described in this section. As an example we'll add two very simple commands. The first commands logs a certain message, while the second command logs the message, the user passes as an argument.

Registering commands

To register your command, you need to add it to the plugin.yml of your plugin. There you can add a list of commands with the key commands. The key elements of the list items are the command names. One command can contain the following attributes:

  • aliases: Alternative names for the command.
  • permission: The permission that is required to use the command (more details on that will follow in the second part of this guide).
  • permission-message: The message that is displayed, if somebody without permissions tries to trigger the command.
  • usage: A short description of how to use this command.
commands:
log-anything:
description: Logs a message to the console
aliases: [loganything, log_anything]
usage: /log-anything
log:
description: Logs the given message to the console
usage: /log [message]

Handling commands

There are two ways to handle commands:

  • Adding a custom CommandExecutor and overriding the onCommand(...) method there.

Overriding onCommand(...) in the main class of your plugin

Probably the simplest possibility to react to custom commands, is overriding the onCommand(...) method in the main class of your plugin. This method is executed every time the user invokes a command, that is registered in the plugin.yml. This method contains the following arguments:

  • Command command: The command that was executed.
  • String label: The alias that was used.
  • String[] args: The arguments that were passed to the command.

Adding a custom CommandExecutor

As you can imagine, the main class of your plugin can get quite big if you have lots of commands and use the previously described way. Fortunately, an alternative exists. You can add as many classes that implement CommandExecutor as you want. These classes can handle your commands then. To do so, you need to create a new class and let it implement the CommandExecutor interface. You'll be forced to override the onCommand(...) method and after doing so, you can use it in the exact same way as in the main class of your plugin. If you register a CommandExecutor for just one command, you can skip checking the command name and assume that the command you registered the executor for was triggered.

Command origin

Commands may be sent either from a player or from the server console. In some cases it may be important to check if the command was sent by a player. You can implement the following check, to make sure the command was sent by a player.

if (sender instanceof Player) { 
// Command was sent from a player
}

Command names

Think carefully about the command names you choose. Choosing command names that are already available in Minecraft or other plugins, may make your plugin incompatible with those plugins.

Communicating with the player

There are many possibilities to communicate with the player. The following subsections describe some ways, you can send information to the player. For all three methods you need an instance of the player you’d like to communicate with. As this is explained in other sections, it’s a prerequisite here that you have a player instance.

Sending a chat message

Sending a chat message works with a single command. You just need to execute the sendMessage(String message) method of the player.

player.sendMessage("This is a chat message");

Displaying a message in the game

Sending a message that is displayed directly in the game, works with sendTitle(...), which expects the following arguments:

  • subtitle: The subtitle to display.
  • fadeIn: The time in ticks for the titles to fade in (defaults to 10).
  • stay: The time in ticks for the titles to stay (defaults to 70).
  • fadeOut: The time in ticks for the titles to fade out (defaults to 20).
player.sendTitle("This is a title", "And a subtitle", 10, 70, 20);

Playing sounds

Also playing a sound works with a simple method call. The playSound(...) method expects the following arguments:

  • sound: The sound to play. Unfortunately only the sounds that are already in the game can be played via a plugin. You can access them via the Sound enumeration.
  • volume: The volume of the sound.
  • pitch: The pitch of the sound.
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1, 1);

What’s next?

This guide covered the basics of Minecraft plugin development with Bukkit. I’ve got a few more points on my roadmap, that I’d like to show you. So stay tuned for the continuations of this guide. They’ll follow within the next few weeks and will cover the following topics:

  • Adding custom events
  • Adding custom recipes
  • Adding configuration files
  • Handling permissions
  • Persisting data
  • Scheduling tasks
  • Localizing your plugin
  • Debugging your plugin
  • More details on plugin deployment

Resources

The full source code can be found on GitHub.

Version information

Creative and detail-oriented software developer. Advanced from mobile to backend development and now getting into full stack solutions.