So I have wanted to learn Java and since my favourite game of the moment, Minecraft is written in Java, I thought, “What better way to learn Java than to start with having a play with writing Minecraft plugins?”. I have a bit of experience with Java and I have looked over the source code for a few Bukkit plugins that I have used and to achieve some fairly simple things it isn’t that difficult and so I am here to share my experiences.

What is Bukkit?

I suppose I should start with explaining a little about Bukkit itself. Bukkit is an unofficial extension on the Minecraft Server providing an API allowing developers to write extensions to the server-side. Unfortunately you are unable to do anything that would require any changes on the client side however this still provides a lot of When playing multi-player, it is down the server to tell the client what blocks are where, which players are where, handle the chat and many more things that are required to link the user experiences of the many players together. Because of this, Bukkit is able to emulate a whole bunch of things – where I have found the main limitations would be, are with visual changes on the client, such as things that would require new textures or 3D models, or even additional configuration GUI but what you can do is use commands. The way Bukkit actually works is by decompiling the official Minecraft releases and editing this where required such that the methods of the API can be implemented. Minecraft (at the moment) do not provide their own API, and a lot of the code is subject to change.

The purpose of the API is to provide a fixed, unchanging interface into the underlying functionality of the server. The Bukkit API is essentially just a set of Java interface files, and the implementation of these interfaces is known as CraftBukkit. Although you can directly access methods of the CraftBukkit objects, this is discouraged, because if it is not part of the interface, then there is nothing saying it won’t change at the next release, and in turn potentially break your code. CraftBukkit is the bridge between what is often referred to as NMS (an abbreviation of the package name for the official Minecraft Server: net.minecraft.server) and hence is updated with every release of Minecraft. The idea is that it abstracts away from the actual code running behind Minecraft, just providing the hooks you need.

What is VoidWarp?

When deciding what plugin I wanted to do, I wanted to pick something simple and achievable as a starting point. After looking through the Bukkit API, playing with a few tutorial projects to get my head round things, I decided on making the VoidWarp plugin. VoidWarp is a simple plugin that teleports the player to a new location when they fall through the void. This would be a simple case of checking the players current location, determining if they are in the void (using their Y coördinate) and then teleporting them to new coördinates. Although this was a fairly simple thing to do there were a few concepts and a few problems I had to get my head around.

The core of a Bukkit Plugin

Making a Bukkit plugin is actually surprisingly simple – or at least to make the shell of a Bukkit Plugin. To get started with the most simple of plugins, you will need:

  • A plugin class
  • A plugin.yml

That’s it!

Plugin Class

Using VoidWarp as my example, the bare minimum for the main class:

package me.flungo.bukkit.VoidWarp;

import org.bukkit.plugin.java.JavaPlugin;

public class VoidWarp extends JavaPlugin {

}

Yep, that’s it! it really is THAT simple! This plugin won’t do much… well it won’t do anything and could be considered rather useless but it just shows how easy it is to actually get started with a Bukkit Plugin. Since that is fairly useless we will make it a bit more useful with the following alterations:

  • Implement a Java Logger for sending output to the console. You can use the System.out.println() but it is strongly discouraged. The logger is simple to use and requires just one line of code to set up:

    public final Logger logger = Logger.getLogger("MineCraft");
    

    If you haven’t used a Java logger before, it will not take long to get your head around the basics. The advantages of the Logger is that with each message a Logger.Level can be set. This makes it easy to have log messages that only show when a finer level is chosen. The true extent of a Logger is a whole subject matter in itself, but as with anything, the best place to start is with the documentation

  • Add onEnable and onDisable methods so that the plugin can be initialised. One thing you can’t (or at least absolutely shouldn’t do) with a Bukkit plugin is use the constructor on your main plugin class. It is for CraftBukkit to instantiate your plugin and only when onEnable is called should you do any work loading classes, initialising variables and such. This then abstracts away any concept of plugin management to CraftBukkit and means that if your plugin is disabled then it will not be consuming memory or other resources with objects that you have instantiated (which, may also lead to your plugin’s functionality not actually being disabled). This also means that the control flow is much more predictable and means you are less likely to break the whole server as calls to your classes, should be wrapped and isolated in try { } catch ( ) { } blocks.

    @Override
    public void onEnable() {
        PluginDescriptionFile pdffile = this.getDescription();
        this.logger.info(pdffile.getName() + " version " + pdffile.getVersion() + " is enabled.");
    }
    
    @Override
    public void onDisable() {
        PluginDescriptionFile pdffile = this.getDescription();
        this.logger.info(pdffile.getName() + " is now disabled");
    }
    

    In these methods we are just implementing a message that confirms that the plugin has been enabled. We use a PluginDescriptionFile object that represents the plugin.yml (see next section) of the plugin. This allows us to get the name and versions of the plugin. Although these strings could be hard coded this is poor practice as it makes the code difficult to maintain.

plugin.yml

The plugin.yml is a file that should be stored in the root of your generated jar (not inside the package) that contains all the metadata Bukkit needs to be able to load your class. The most important ones are name, version and main:

name: VoidWarp
main: me.flungo.bukkit.VoidWarp.VoidWarp
version: 0.2.2

The syntax of this file is YAML (Yet Another Markup Language) which is actually a great little markup language for configurations and settings. It’s simple to learn and the basics needed for now are the idea of key: value for simple mappings. Full details about what can be put into a plugin.yml is available from the BukkitWiki. There are a lot of different settings that can be set including dependencies (to ensure all dependencies are loaded before your plugin), command registration and various other pieces of metadata.

In the example, the most important attribute is main. This defines the fully qualified name of your main class (your plugin class). Without this Bukkit will not know which class to load for your plugin!

Bukkit Events

The first thing is the Bukkit events system. One of the functions provided by the Bukkit API is an events system which provides an efficient way to respond to in game actions. For detailed usage information on how to use this, the best place to look is the BukkitWiki but I hope to explain the basic of using this and also explain a bit about the actual implementation under the hood.

To implement the events, one of the things that CraftBukkit does is add method calls within the original NMS code to trigger the events. In some cases the event is constructed from known information about the event provided from the Minecraft code which can be actually modified by your event. A common modification to an event is to set a boolean flag so that the event is cancelled.

Listeners

There was a recent update to the usage of the events system, but if anything it has made it even easier to use! What you will need is a Listener. A listener is a class that represents a single or a set of EventHandlers. These are usually grouped in a logical way depending on how concise you want each class to be. For example, you may make a PlayerListener and handle all events related to players, or you might make a BlockListener for block events. I went for something a little more specific which was a PlayerLocationListener which would have the one EventHandler. Producing a Listener is as simple as implementing the Listener interface from org.bukkit.event.Listener; as an example, here is my stub class that needs to be populated with EventHandlers:

package me.flungo.bukkit.VoidWarp;

import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerMoveEvent;

public class PlayerLocationListener implements Listener {
    public static VoidWarp plugin;

    public PlayerLocationListener(VoidWarp instance) {
        plugin = instance;
    }
}

I have also implemented a basic constructor that just takes my implementation of JavaPlugin, VoidWarp for later use.

Event handler

Event handlers are very clever in terms of their implementation and make creating new handlers incredibly easy. Using the power of Java annotations, all you need to do is create a method within the Listener of arbitrary name and annotate it with @EventHandler. The way Bukkit then knows what kind of events it handles is through type inference, specifically the type of the first argument of the event handler. The signature for the method must be of the form public void arbitraryMethodName(T event) where T extends Event.

There are a whole bunch of events and the best and most up to date way to find the ones you need is through the Bukkit API Documentation.

In our case, we want the PlayerMoveEvent to check the players position, whenever it changes. We are looking to detect a player in the void so really we are only interested in their Y coördinate. To make sure they are in the void and add a bit more suspense to the teleportation, we will wait until they are at y = -50 (well infact we will check y < -50 as this gives a larger window in case of lag).

To then teleport the player we will construct a Location object that is 2 blocks above the spawn location so that they drop onto the spawn. To then teleport the player to this location is simple, using the Player.teleport() method of the Bukkit API.

All of the information and objects we need can be gotten from the event and can be implemented as so:

@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
    Player p = event.getPlayer();
    World w = p.getWorld();
    Location to = event.getTo();
    int y = to.getBlockY();
    if (y < -50) {
        Location spawn = new Location(w, w.getSpawnLocation().getX(), w.getHighestBlockYAt(w.getSpawnLocation())+2, w.getSpawnLocation().getZ());
        p.teleport(spawn);
    }
}

Registering Events

So that’s it isn’t it? We have our plugin, we have out listener and event handler, what else do we need to do? Well, we need to tell Bukkit about our listener class! This is easy as pie in the grand scheme of things. All we need to do is instantiate our PlayerLocationListener and register it with Bukkit. We will instantiate the class as an instance variable of our VoidWarp class. To then register this event we will add the following to our onEnable method:

PluginManager pm = getServer().getPluginManager();
pm.registerEvents(this.playerListener, this)

Summary

And that’s all that was needed to make a simple, first Bukkit plugin. It’s not perfect and there are many features to add and things to improve in the code but I thought I would just give you a rough guide of what I have been up to recently.

VoidWarp.java

package me.flungo.bukkit.VoidWarp;

import java.util.logging.Logger;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;

public class VoidWarp extends JavaPlugin {

    public static VoidWarp plugin;

    public final Logger logger = Logger.getLogger("MineCraft");

    public final PlayerLocationListener playerListener = new PlayerLocationListener(this);

    @Override
    public void onDisable() {
        PluginDescriptionFile pdffile = this.getDescription();
        this.logger.info(pdffile.getName() + " is now disabled");
    }

    @Override
    public void onEnable() {
        PluginManager pm = getServer().getPluginManager();
        pm.registerEvents(this.playerListener, this);
        PluginDescriptionFile pdffile = this.getDescription();
        this.logger.info(pdffile.getName() + " version " + pdffile.getVersion() + " is enabled.");
    }
}

PlayerLocationListener.java

package me.flungo.bukkit.VoidWarp;

import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerMoveEvent;

public class PlayerLocationListener implements Listener {
    public static VoidWarp plugin;

    public PlayerLocationListener(VoidWarp instance) {
        plugin = instance;
    }

    @EventHandler
    public void onPlayerMove(PlayerMoveEvent event) {
        Player p = event.getPlayer();
        World w = p.getWorld();
        Location to = event.getTo();
        int y = to.getBlockY();
        if (y < -50) {
            Location spawn = new Location(w, w.getSpawnLocation().getX(), w.getHighestBlockYAt(w.getSpawnLocation())+2, w.getSpawnLocation().getZ());
            p.teleport(spawn);
        }
    }
}

Source code

The full source can be found on my GitHub repository for VoidWarp. This was produced with the commit e167cf3