Latest HTML: http://htmlpreview.github.io/?https://raw.githubusercontent.com/arun-gupta/spigot-book/master/book.html
Your First Plugin
Minecraft is a 3D game about breaking and placing blocks to obtain materials. These materials can then be used to build or craft new items and tools. These, in turn, can be used to harvest more types of blocks. The game also contains entities, which are dynamic moving objects in Minecraft, for example, cow, pig, and horse. Some of the entities are hostile and called as monsters, for example, zombie and creeper. Bosses are monsters that are very hard to defeat.
Playing the game itself is a lot of fun, but what makes the game more interesting and engaging is how it allows modifications (known as "mods" or "plugins"). These modifications can change the game from what it was originally written. They can add content to the game to alter gameplay. For example, new blocks, mobs, and abilities of the player can be added.
An individual player uses a client, think of it as a program downloaded on your machine, to connect to a server, think of it as a program running on your machine or a different machine on the Internet. This is called as single player. Minecraft also allows multiple players to join a server. This is called as multiplayer. This book will explain how to run your own Minecraft server, write plugins and install them on that server.
Its very common to have a server run with multiple plugins. There are almost an infinite number of plugins someone could do to make Minecraft a more amusing game to play. There is no official way to create these plugins, but there are several third-party vendors that provide that capability; Bukkit is one such way, and the most popular way. The ability to write plugins and alter the gameplay gives players more control over the game, and makes them more excited.
Spigot introduction
Spigot is a free, open-source, software project that provides the means to extend Minecraft multiplayer servers. It is not the official way to add mods to a server, nor is it supported by Mojang yet. However, it is one of the most popular ways to build server-side plugins, which are plugins that only work on servers.
Spigot does NOT make mods. It makes plugins, which, contrary to popular belief, are not the same as mods.
The plugins you can make with Spigot don’t actually modify your server. Instead, they just add on to it. For example, a mod would change the size of a TNT explosion by modifying the source code of Minecraft. In order to remove that modification and go back to the original game play, the source code needs to be modified back to the original one. A plugin can also make a bigger TNT explosion. They are called plugins because that is how you install them; you ``plug them in'' to your server.
There are so many currently existing plugins for Bukkit (over 10,000) that it can do pretty much anything! You can find all of these plugins at http://plugins.bukkit.org/. This book will teach you how to build plugins like that.
Download and install tools
In order to create plugins, we need to use a few tools.
-
Java Development Kit
-
Spigot
-
NetBeans
Lets learn about these tools and see how to download and install them.
Java
Markus "Notch" Persson (@notch) wrote the game of Minecraft using the Java programming language. Java is one of the most popular programming languages. Game developers like Notch write computer programs as text files following the rules defined by Java. These text files are called as Java source files or class files, and end with a .java
extension. These files are then converted into binary JAR files using the tools provided by Java Development Kit (JDK).
Before you can run your server, and later build plugins, you need to install JDK. JDK is like a swiss army knife for Java that provides a variety of tools. Another important tool provided by JDK is how these JAR files can be run on any machine.
Download JDK from http://www.oracle.com/technetwork/java/javase/downloads/index.html and install on your machine by following the instructions at http://docs.oracle.com/javase/8/docs/technotes/guides/install/install_overview.html.
|
Tip
|
Is JDK already installed ?
If you are not sure that JDK is already installed on your machine then follow the installation instructions listed above. This will update to the latest version anyway and is the recommended version for this book. Either way, you’ll need at least JDK 6. |
Spigot
The vanilla, or un-modded server as downloaded from http://minecraft.net does not understand the plugins built using Bukkit. Spigot is a modified version of the vanilla server that does understand Bukkit plugins. It is available for download at https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar at no cost at all. That is one of the many reasons that modders like to use it to make plugins for their servers.
To use the plugins you will make later on, you will need a server to put them on. This can be done by downloading BuildTools.jar
and installing it. Detailed instructions to install Spigot are available at https://www.spigotmc.org/wiki/buildtools/#running-buildtools.
|
Note
|
This book will provide all the instructions assuming you are using a Mac OS X. |
-
Open a Terminal using Spotlight (Press
Cmd
andSpace
keys together and type “terminal” and hit Enter) -
Create a new directory ‘spigot’ by typing the command
mkdir spigot
. -
Change to this directory by giving the command
cd spigot
. -
Download
BuildTools.jar
by giving the commandcurl -o BuildTools.jar https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar
-
Setup properties to generate the Spigot project by giving the command
export MAVEN_OPTS="-Xmx2G"
. -
Generate Spigot project by giving the command
java -Xmx2G -jar BuildTools.jar
. This will show the output:spigot> java -Xmx2G -jar ~/Downloads/BuildTools.jar Loading BuildTools version: git-BuildTools-1847da7-45 (#45) arun-gupta<EMAIL_ADDRESS>Starting clone of https://hub.spigotmc.org/stash/scm/spigot/bukkit.git to Bukkit Cloned git repository https://hub.spigotmc.org/stash/scm/spigot/bukkit.git to /Users/arungupta/tools/spigot/Bukkit. Current HEAD: 31d4f76a1510fa4810a917af05eeceec03d4b98a . . . Success! Everything compiled successfully. Copying final .jar files now. Copying craftbukkit-1.8.8-R0.1-SNAPSHOT.jar to /Users/arungupta/tools/spigot/. - Saved as craftbukkit-1.8.8.jar Copying spigot-1.8.8-R0.1-SNAPSHOT.jar to /Users/arungupta/tools/spigot/. - Saved as spigot-1.8.8.jar
This generates the following directories:
BuildData Bukkit Spigot craftbukkit-1.8.8.jar work BuildTools.log.txt CraftBukkit apache-maven-3.2.5 spigot-1.8.8.jar
NetBeans
A Spigot plugin is a JAR file and requires a specific directory structure with Java and other files in it. These files have a lot of similarities but also contain some basic differences like plugin name, different commands they offer, and of course the functionality itself. These directories and files can be manually created every time by going into Explorer for Windows or Finder for Mac. That does help you understand the directory layout better, and might be interesting for the first couple of times. However, we plan to write quite a few plugins in this book. So we need to think of an efficient way to create these directories and files.
To create the plugin, we will be using a recipe to generate our project. The project contains the entire source code and other files to make the plugin. This recipe allows us to create a project to start with. We will call this recipe as the Spigot Recipe throughout this book. It simplifies the task of making plugins so you can focus on the more important and fun stuff, such as bigger TNT explosions.
So, how do you use this recipe?
This can be accomplished with an IDE, or an integrated development environment. An IDE is a tool that has a way to edit files, a way to package and run those files, and a debugger for finding issues with the files. There are many IDEs available for use, but this book will use NetBeans IDE for creating our plugins.
The two important parts of an IDE are the file editor and the packager. The file editor lets you edit separate code files in the project, while the packager puts them together into one file. In our case, that file is the plugin JAR file.
NetBeans is a free tool that runs on Macintosh, Windows, Linux, and other operating systems that has JDK installed already. This book will use it for creating plugins.
Download NetBeans from https://netbeans.org/downloads/. Make sure to click on ``Download’ button in ‘Java SE’ column as shown in NetBeans download.
Install NetBeans following the instructions at https://netbeans.org/community/releases/80/install.html.
If you do have a prior version of NetBeans, its recommended to install this new version anyway since it is much faster and easier to work with.
Start Minecraft server
Spigot JAR file (generated earlier) and JDK are required to start the modded Minecraft server.
-
Go to ‘spigot’ directory in a Command Prompt on a Windows machine or Terminal in Macintosh.
-
Create a new directory ‘server’ in this directory. This is required because starting the server later will generate several files and directories and this will keep all of them contained in a single directory.
-
Type in
java -jar ../spigot-1.8.8.jar
.java -jar <file name>
is the generic command for starting any JAR file. In this case, we are starting the server jar file, which starts the Minecraft server.Tipjava command not foundIf running the server shows an error as `java: Command not found' or `java: No such file or directory' then you need to make sure the tools bundled with JDK can be invoked from Command Prompt or Terminal. Follow the instructions from http://docs.oracle.com/javase/tutorial/essential/environment/paths.html to set up PATH envinronment variable.
-
Running this command will show the output:
Loading libraries, please wait... [21:11:27 INFO]: Starting minecraft server version 1.8.8 [21:11:27 INFO]: Loading properties [21:11:27 WARN]: server.properties does not exist [21:11:27 INFO]: Generating new properties file [21:11:27 WARN]: Failed to load eula.txt [21:11:27 INFO]: You need to agree to the EULA in order to run the server. Go to eula.txt for more info. [21:11:27 INFO]: Stopping server
-
Edit
eula.txt
by giving the commandvi eula.txt
and change the lineeula=false
toeula=true
. -
Give the command
java -jar ../spigot-1.8.8.jar
again in a Terminal.
Running this command will display a lot of messages in Terminal. It may look confusing at first but there is relevant for each and every line. However two messages in the output are important to understand, and are shown in Output from Minecraft server start.
[21:15:37 INFO]: Starting minecraft server version 1.8.8 (1)
...
[21:15:37 INFO]: Default game type: SURVIVAL (2)
-
Shows that Minecraft server version is 1.8.8. Note the exact version may differ if you have a more recent version of the JAR file.
-
Shows the default game mode is Survival. It is much easier to test these plugins in Creative, but for now, you can stay in Survival.
Starting a server for the first time in a new directory will create some new directories and files. These are all important to the server, but the only one that is important to us is the plugins
directory.
Join the Server
Now that the server has started, lets see how we can join this server. Without being able to log onto a server, you cannot test your plugins. To log onto the server:
-
Click on the ‘Multiplayer’ tab in the Minecraft laucnher
-
Click on ‘Direct Connect’
-
Enter ‘localhost’ in the ‘Server Adress’ box
-
Click ‘Join Server’
‘localhost’ is a special name reserved for everybody’s local machine. What you are doing is telling Minecraft to join the server running on your computer. Plugins will be installed on this server later.
Lets create our plugin and see how this directory is used.
Create a Plugin
This section will walk you through the steps of creating a plugin. Don’t panic if they seem unfamiliar at first. We’ll be using steps multiple times through out the book and you’ll be an expert at them pretty quickly.
The steps are outlined in Steps for creating a plugin:
-
Generate a project as a placeholder to contain different files
-
Edit Java and other files, create new images, etc. in the project
-
Package the project in a JAR file
-
Copy the JAR file to CraftBukkit’s
plugins
directory
NetBeans helps us simplify all of these steps. Lets create our first plugin.
Our first plugin will send a message to the server console every time a player joins or leaves the server. Minecraft already does that, but the message contains many elements that may not be useful to whoever is reading it. These elements can make the message confusing. The message looks something like Message shown on player login.
[17:31:18 INFO]: Adityagupta[/<IP_ADDRESS>:51180] logged in with entity id 462 at ([world] 19.27553977561729, 18.113231330555088, -68.8607335912105)
This plugin will make the console print another message, but it will be much simpler. Each plugin needs to have a name, this plugin will be called ``Player Entry/Exit''.
Generate project
NetBeans will be used to create the project.
|
Tip
|
How to start NetBeans
On Windows, NetBeans can be started by clicking on |
To create the project, start NetBeans, and create a new project by selecting File'',
New Project…'' as shown in Create new project in NetBeans.
|
Note
|
The images are captured from a Windows machine and so the menu will look different if you are using a different operating system. |
NetBeans allows you to create many different types of projects, but we want to create a project from Maven Archetype. This is what allows us to specify the recipe that has been precreated for you. You may wonder how and where this recipe is downloaded from. For curious readers, more details are available in Maven.
To do this, select Maven'' and
Project from Archetype'' as shown in Create new project in NetBeans from Maven archetype.
This will display a list of many recipes, but we want to select our Bukkit Recipe. In the Search:'' box, type
bukkit-template'', because that is the name of our recipe.
This recipe does not come with NetBeans. Instead, it is downloaded from Internet and installed on your machine. So make sure you are connected to the Internet when you try this step.
|
Important
|
Although NetBeans allows you to change other values, you must not change them, becuase if you do, the correct recipe will not be chosen. |
At the time this book was written, the latest version of Bukkit was version 1.7.9-R0.2. The corresponding recipe version is 1.5. As newer versions of Bukkit are released, the recipe’s version will change as well.
|
Warning
|
Bukkit Recipe version
If the version in the creation screen is incorrect, change it to 1.5. |
Click on ``Next'' to accept the values and continue with the creation of the plugin. You should see a screen like PlayerEntryExit plugin details.
There are many important values in this screen:
-
Change the
Project Name'' to
PlayerEntryExit'', or whatever name you like. The Project Name defines the name of the plugin. Normally, you would keep the first letter of all words capital so you can distinguish between words. Spaces and characters like;'',
.'', and ``)'' are not allowed in the name. -
Project Location'' is the location of a directory where you would like to store all of your plugins. It is recommended to define one directory where all the plugins will be stored. For convenience, create
plugins'' directoryC:\
on Windows or/Users/<name>
on Mac where <name> is your username.TipIts recommended to keep all the plugins in the same directory. This makes it easy to zip them all together and share with your minecraft buddies.
-
Group Id'' uniquely identifies your plugin amongst all the plugins that you create. For example, you can change this value to
org.yourname.bukkit.plugins''. Make sure to replace ``yourname'' with your name. This value will be same for all plugins created in this book. -
``Version'' defines the first version of your plugin. The default value of this field follows the standard version naming convention and typically does not need to be updated.
-
If the
Group Id'' value is changed, then the
Package'' value is accordingly updated. For simplicity, its better to accept this value. -
In the
Additional Creation Properties'', click in the text box under the
Value'' column and type the value of craftbukkit directory from your machine. This will be used to install the generated plugin JAR file to the plugins directory of your server. This directory name must match the directory where your server was started,C:\craftbukkit
or\Users\<username>\craftbukkit
in our case.The directory path shown is from a Windows machine. It will be different if your machine has a different operating system.
Clicking on ``Finish'' button creates your plugin project and shows the structure in Player Entry/Exit plugin directory structure. It will be located on the left-hand side of the IDE.
This structure contains the files that make your plugin work. Source Packages'' contains the Java source files.
Other Sources'' contains the configuration files. ``Project Files'' has some other files required for creating the JAR file.
If you click on the +'' signs next to
Source Packages'', Other Sources'', or
Project Files'', it will expand them to let you see what’s inside. An expanded directory structure with those three directories open looks like as shown in Player Entry/Exit plugin expanded directory structure.
|
Tip
|
You can also click on the ``-'' signs to close the structure. |
The key points in Player Entry/Exit plugin expanded directory structure are explained:
-
Source Packages'' shows the
org.devoxx4kids.Bukkits.plugins.playerentryexit'' Java package. Package is a Java concept that allows to keep similar Java files together. For example, Java files for entities can be in one package, all files for enchantments can be in a different package, and so on. This allows an easy organization of a large number of Java files easily. In our case, ``MyPlugin.java'' file is in the shown package.TipEach Java file is a text-based file, and ends with
.java
extension. -
Other Sources'' has
plugin.yml''. This file is used to provide basic information about the plugin such as plugin name, author, vesion, and the commands exposed by the plugin. -
Project Files'' has
pom.xml''. This file provides some information about how the project will be turned into a single JAR file and how it can be installed on your server. The most important information is the version of the CraftBukkit JAR file being used.
Click on ``MyPlugin.java'' to see the code shown in MyPlugin.java in Player Entry/Exit plugin. It is shown in the large middle section of the IDE.
package org.devoxx4kids.bukkit.plugins.playerentryexit;
import java.util.logging.Level;
import org.Bukkit.plugin.java.JavaPlugin;
public class MyPlugin extends JavaPlugin {
// This code is called after the server starts and after the /reload command
@Override
public void onEnable() {
getLogger().log(Level.INFO, "{0}.onEnable()",
this.getClass().getName());
}
// This code is called before the server stops and after the /reload command
@Override
public void onDisable() {
getLogger().log(Level.INFO, "{0}.onDisable()",
this.getClass().getName());
}
}
Most of the code in this file is a Java class, which in this case is named MyPlugin
(as you can see on the third line). This is our first Java class and follows the syntax defined by the Java programming langauge. There is no need to worry too much about different Java keywords, parentheses, and formatting in this code.
The key part to understand is that this class contains two methods, which is where you keep the code that actually does something. The two methods in this class are called onEnable()
and onDisable()
. (Don’t worry about the ``public void''"'' stuff; we’ll explain that later.) Most method names are verbs, which indicate what the code in the method does.
The two methods in this code are made to run when the plugin is enabled and disabled.
Register and create the listener
One of the ways Bukkit plugins are created is listening for different events in listeners. Events are something that happens in your world such as player breaks a block, an entity explodes, or a recipe completed in a crafting table. These events are ``handled'' using Event Handlers in Listener classes.
Event Handlers are methods written in a Java class bundled with the plugin.  The Java class itself is called the ``Listener'' because its methods are listening for the events. Lets create a Listener and add Event Handler to them.
We need to make sure that the listener is registered before the player can play the game. This will ensure that any events are correctly handled by the listener. We’ll look at what these events little later in this chapter. For now, the appropriate place to add the listener would be in the onEnable()
method, as this method is called when the plugin is enabled. Registering a listener requires us to add a little bit of Java code. Lets add that code first, and then we’ll understand it.
Add the line shown in Registering the Player Entry/Exit Listener to onEnable()
method of MyPlugin
class. Click after the {
right next to onEnable()
, then press Enter to make a new line. Copy and paste the code there.
getServer()
.getPluginManager()
.registerEvents(
new PlayerEntryExitListener(getLogger()),
this);
This code may look confusing at first but it has been formatted so that you can read it easily. Don’t worry about ()
, .
, this
, and new
for now, this is explained in [Getting_Started_with_Java]. This method obtains the CraftBukkit server where the plugin is installed, gets a manager to interact with different plugins, and register the event listener named PlayerEntryExitListener
. Simple, isn’t it ?
The new listener class needs to be created next. NetBeans simplifies the creation of this file. Next to the line you just created, there should be a stop sign.
|
Tip
|
The stop sign ( or ) is shown on the left side of your code to indicate a syntax error. That way, you can fix the error before building the plugin. The plugin cannot be built if it contains errors. Generally the error can be fixed by clicking on the stop sign to open a list of suggested fixes. |
The purpose of the stop sign is to inform that there is an error. Click on the stop sign to open a drop-down menu with three options in it as shown in Creating the PlayerEntryExitListener. The one you want to select is Create class 'PlayerEntryExitListener' with constructor "PlayerEntryExitListener(java.util.logging.Logger)" in package org.devoxx4kids.bukkit.plugins.playerentryexit
.
This will automatically create a new file PlayerEntryExitListener
and opens it in the IDE. It looks like as shown in PlayerEntryExitListener Java source.
package org.devoxx4kids.bukkit.plugins.playerentryexit; (1)
import java.util.logging.Logger; (2)
import org.bukkit.event.Listener;
class PlayerEntryExitListener implements Listener { (3)
public PlayerEntryExitListener(Logger logger) { (4)
}
}
Some key Java concepts to be understood in this code:
-
Each class belongs to a package. This is identified using
package
keyword in Java, a space, followed by the package name, and ended by;
. -
A Java class can use other Java classes from different packages. These classes need to be imported so that they can be referred to within the code. This class is importing two classes,
java.util.logging.Logger
is from Java andorg.bukkit.event.Listener
is from Bukkit. Any package name starting withjava.
comes from Java and any package name starting withorg.bukkit.
comes from Bukkit. -
A special method is created in the class that has the exact same name as of the class. This method is called as constructor of the class. This method also takes a parameter (we’ll explain this shortly), in this case
Logger
is a JDK class that allows to log statements.
When a file is created, it shows up in the middle part of the IDE. You can make changes to it there.
Lets change this code, or the constructor specifically, as shown in PlayerEntryExitListener updated method.
package org.devoxx4kids.bukkit.plugins.playerentryexit;
import java.util.logging.Logger; import org.Bukkit.event.Listener;
class PlayerEntryExitListener implements Listener {
Logger logger; (1)
public PlayerEntryExitListener(Logger logger) { this.logger = logger; (2) }
}
This will allow us to log the message in Minecraft server log using logger
.
Now, we will have to add some methods in the class so that it can listen for players joining and leaving the server.
Add event handlers
Finally, we will have to add the methods that `listen'' for players joining and leaving the server in Listener. The events are generated by the server and identified by predefined Java classes in the Bukkit API. For example, `PlayerJoinEvent
is an event called when a player joins the server and PlayerQuitEvent
is an event called when a player leaves the server. A complete list of these events is defined in Bukkit API but you don’t need to worry about it for now. These will be explained in Bukkit documentation.
For now, lets take a look at the methods that need to be added in Listener methods for Player Entry/Exit plugin.
@EventHandler (1)
public void playerJoin(PlayerJoinEvent event){ (2)
logger.log(Level.INFO,
"{0} has joined the server.", event.getPlayer().getName()); (3)
}
@EventHandler
public void playerLeave(PlayerQuitEvent event){ (4)
logger.log(Level.INFO,
"{0} has left the server.", event.getPlayer().getName());
}
There are some important parts in this code:
-
Any method of this class can be marked to
`listen'' for events. Such a method needs to be marked with `@EventHandler
. The@
at the beginning indicates that this is a Java annotation which allows Java to define special marks in the code. The exact behavior of the annotation is defined by Bukkit. -
This method is called when a player joins the server. This is identified by the
PlayerJoinEvent
parameter, which as explained earlier, is an event created by Minecraft server and captured as a Bukkit class. -
This line logs the message stating that a player has joined the server. The text is
playername has joined the server.
whereplayername
is value returned fromevent.getPlayer().getName()
. -
The second method does the same thing, but listens for a player leaving the server, as defined by the
PlayerQuitEvent
parameter. Of course, the message now saysplayername has left the server
.
You are now done adding functionality to your Listener. The complete code should look like as shown in Player Entry/Exit plugin complete Listener.
package org.devoxx4kids.bukkit.plugins.playerentryexit;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
public class PlayerEntryExitListener implements Listener {
Logger logger;
public PlayerEntryExitListener(Logger logger) {
this.logger = logger;
}
@EventHandler
public void playerJoin(PlayerJoinEvent event){
logger.log(Level.INFO,
"{0} has joined the server.", event.getPlayer().getName());
}
@EventHandler
public void playerLeave(PlayerQuitEvent event){
logger.log(Level.INFO,
"{0} has left the server.", event.getPlayer().getName());
}
}
Build and install the plugin
Now, we will have to install the plugin into your server.
We can’t just put the plugin source directory into it; that wouldn’t work. Computers undertand binary code. So we’ll have to use JDK to generate the JAR file. This will require a few steps in order to install the plugin:
-
Create a JAR file from your plugin Java source files and configuration files
-
Copy that JAR file into the ``plugins'' directory in your server directory
-
Restart your server and check that the plugin is correctly installed
Fortunately, this is not too hard, but you will have to pay attention!
First of all, you will have to turn the plugin source and configuration files into a JAR file. In NetBeans, if you right-click on your project in the Projects window, a drop-down menu will appear with many options on it as shown in Build the plugin.
The one you want is called ``Build'', so select that.
Clicking on Build'' will generate the JAR file and also copy it to the
plugins'' directory of your server.
When you click on ``Build'', a console window in NetBeans will pop up at the bottom of your screen as shown in NetBeans console window.
|
Tip
|
NetBeans output window
If NetBeans Output window does not show up or you accidentally closed it, then you can click on Window → Projects menu to bring this window back. |
Messages will start appearing in the log as the plugin JAR file is built and installed in the ``plugins'' directory. If the plugin is successfully built, you should see the output as shown in Clean and build log.
[antrun:run] Executing tasks [copy] Copying 1 file to C:\Users\Aditya\Desktop\craftbukkit\plugins (1) Executed tasks ------------------------------------------------------------------------ BUILD SUCCESS (2) ------------------------------------------------------------------------
There are two important things in this part of the log:
-
Tells you that the generated plugin is installed in this directory.
-
If this line says
BUILD SUCCESS
, then your plugin was built correctly. Otherwise, the plugin was not built correctly.
|
Warning
|
Any errors during building the plugin JAR file are highlighted in the console window as shown in A sample error. By default, the generated sample plugin should work as is. But these errors could occur if you mistakenly typed something in the source files before clicking on ``Build''. |
The best way to fix it is to delete the plugin directory and regenerate it again using the instructions in [Create_Sample_Plugin].
Verify that it works
Clicking that one simple Build'' menu item created the JAR file and deployed it in the
plugins'' directory of our server as well. We just need to restart our server now.
The deployed plugin can be reloaded by giving the reload
command in server console as shown in Reloading your server. This will print the following message in the server console.
>reload
[21:32:33 INFO]: [sample] Loading playerentryexit v1.0-SNAPSHOT (1)
[21:32:33 INFO]: [sample] Enabling playerentryexit v1.0-SNAPSHOT (2)
[21:32:33 INFO]: [sample] org.devoxx4kids.bukkit.plugins.playerentryexit.MyPlugin.onEnable() (3)
[21:32:33 INFO]: Server permissions file permissions.yml is empty, ignoring it
[21:32:33 INFO]: CONSOLE: Reload complete.
-
This message is printed by CraftBukkit to indicate that the plugin is now loaded.
-
This message is printed by CraftBukkit to indicate that the plugin is now enabled.
-
This message is printed from the
onEnable
method ofMyPlugin
class.
Now that the plugin has been successfully installed, lets make sure the plugin actually works. Join your server. You should see a message in the console saying that playername joined the server
where playername
should be your Minecraft account name.
Disconnect from the server and check for the message playername has left the server
in the console. Connect to the server again to see the message playername has joined the server
. Disconnect and connect a couple of times just to celebrate the creationf of your first plugin! Don’t forget to check for messages in the console.
Another way to confirm that the plugin is indeed installed on the server is by issuing the plugins
command in server as shown in Check the list of installed plugins on server.
>plugins
[15:23:06 INFO]: Plugins (1): playerentryexit
The list here shows that only ``playerentryexit'' plugin is installed. If you have moved ahead, and installed other plugins, then those will be shown here as well.
Summary
This chapter explained the key components to create a plugin. Your machine was setup with the tools required to create a plugin. A simple CraftBukkit server was started and we explained how you can use Minecraft launcher to connect to this server. Bukkit Recipe and how it simplifies the creation of a plugin was introduced. This recipe will be used in subsequent chapters to create other plugins. Finally we packaged, installed, and verified the plugin to our server.
Even though this was a simple plugin but it showed the most important pieces to create a plugin. It also taught some fundamental Bukkit concepts like Listeners and Event Handlers. Basic Java concepts like annotations, methods, and method parameters were introduced as well.
Now that we know how to make a simple plugin, we can move on to build more advanced plugins. The next few chapters will be based upon this chapter and make more plugins. They will start off simple like this one, and get more complicated along the way.
Lets keep reading and make some fun plugins!
Creating In-game Money
Now that you know how to create a plugin, lets make our first fun plugin.
In this chapter, we will be using the Bukkit Recipe introduced in last chapter to create a new plugin. We will then use a Listener to reward a player with in-game money whenever he/she breaks or places a block.
Create the plugin
Again, we will be using the Bukkit Recipe to create the plugin. To reiterate the steps for creating a plugin are:
-
Click on
File'' then
New Project…''. -
Select
Maven'' then
Project from Archetype''. -
Use the
Search'' box to find
bukkit-template'' and make sure the version is 1.5. -
Click ``Next'' to create the plugin.
The information that you enter in the plugin creation will be very similar to PlayerEntryExit plugin details. BlockMoney plugin updated values shows the values that needs to be updated.
| Field | Value |
|---|---|
Project Name |
BlockMoney |
Package |
org.devoxx4kids.bukkit.plugins.blockmoney |
Click on `Finish' button to complete the plugin creation and open the project. The expanded directory should look like as shown in Block Money expanded directory structure.
Note that the new values are reflected in first and fourth line of this image.
Just like Player Entry/Exit plugin directory structure explained in Player Entry/Exit plugin expanded directory structure, this directory has org.devoxx4kids.bukkit.plugins.blockmoney.MyPlugin
class which is the entry point of the plugin, plugin.yml
that defines plugin configuration, and pom.xml
that defines instructions on how to build and install the plugin.
Register and create the Listener
A new Listener, just like the one in Player Entry/Exit plugin, needs to be created. Open the generated MyPlugin.java
, add the line shown in Register listener in MyPlugin class of Block Money plugin in onEnable()
method. This can be done by placing your curson before the first occurrence of }
after onEnable
method and pressing Enter key couple of times to create empty lines to add this code. And then type or copy/paste this code in these newly added lines.
getServer()
.getPluginManager()
.registerEvents(new MoneyListener(),
this);
This line registers the Listener, just like in Registering the Player Entry/Exit Listener. Notice that the name of the Listener is different, to match the purpose of this plugin.
The updated method will look like as shown in onEnable method in BlockMoney plugin class.
@Override
public void onEnable() {
getLogger().log(Level.INFO, "{0}.onEnable()",
this.getClass().getName());
getServer().getPluginManager() (1)
.registerEvents(new MoneyListener(), this);
}
The newly added code is highlighted in bold.
Again, click on the stop sign to see the drop-down menu as shown in Create the BlockMoneyListener class and select the option that says Create class 'BlockMoneyListener' in package org.devoxx4kids.bukkit.plugins.blockmoney
This will create a new Java source file for you and opens it in NetBeans. Now the functionality can be added to the plugin. The generated class is shown in Generated MoneyListener class.
class MoneyListener implements Listener {
public MoneyListener() {
}
}
This is a Java class named MoneyListener
with a constructor method.
Add event handlers
Open up the Listener class. Unlike Player Entry/Exit plugin, there is no need to change the constructor. However we need to store some values such as how much money to reward and how much money is accumulated by each player. These values are stored in variables. A variable needs a name, has a type, and is assigned a value using =
. Java has lots of predefined types such as int
for storing integer values, boolean
for storing true or false values, and String
for storing text values.
Add the code shown in Variables in BlockMoney plugin in the BlockMoneyListener.java
.
float moneyToReward = 0.10F; (1)
HashMap<String, Float> money = new HashMap(); (2)
public MoneyListener() {
}
These lines add two important variables:
-
The first one is of the type float, another of the Java predefined types, and stores a decimal value. It is set to 0.10, and will be used to determine how much money each player will receive. By default, any decimal value in Java, 0.10 in our case, is stored such that it can accommodate really large decimal values. But since our decimal number is rather small,
f
orF
can be added after the number to indicate otherwise.Note no $ signs are stored as part of this value. Its our understanding that this variable will be used for storing money. And we’ll show that symbol when we display the money earned.
-
The second one is a
HashMap
, and will be used to store each player’s money value. HashMap is a predefined Java class that store multiple records. Each record has a key and a value of different types. The key uniquely idenitfies a record. In our case, the key is the player’s name, of the typeString
, and the corresponding value is the player’s money, of the typeFloat
. More details about HashMap are available in the javadocs at http://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html.An in-memory representation of a Java class needs to be created in order to run the program. This representation is called as an instance and can be created using the
new
keyword in Java. Constructor of the class needs to be speified in order to create a new instance. We will create a new instance of HashMap to store the money earned by each player.
Now, we need to add methods to earn money. The methods are shown in BlockMoney plugin methods to earn money.
@EventHandler
public void checkForNullMoney(PlayerJoinEvent event){ (1)
if (!money.containsKey(event.getPlayer().getName())) { (2)
money.put(event.getPlayer().getName(), 0F); (3)
}
}
@EventHandler
public void rewardBlockPlace(BlockPlaceEvent event){ (4)
money.put(event.getPlayer(), (5)
money.get(event.getPlayer().getName())
+ moneyToReward);
event.getPlayer().sendMessage(ChatColor.GOLD + "You now have " (6)
+ ChatColor.GREEN + "$"
+ String.format("%.2f", money.get(event.getPlayer())));
}
@EventHandler
public void rewardBlockBreak(BlockBreakEvent event){ (7)
money.put(event.getPlayer(),
money.get(event.getPlayer().getName())
+ moneyToReward);
event.getPlayer().sendMessage(ChatColor.GOLD + "You now have "
+ ChatColor.DARK_GREEN
+ "$" + String.format("%.2f", money.get(event.getPlayer())));
}
All these methods are event handlers but probably look confusing to you, so here is an explanation:
-
This method has
@EventHandler
above it, so it can listen for events. Minecraft generates an event when a player joins the server. This event is captured asPlayerJoinEvent
in Bukkit. Using this class as Java method parameter type ensures that this method is called whenever a player joins the server. -
money.containsKey
is invoking the methodcontainsKey
on themoney
HashMap and returns true if it contains a specific key. In this case the key is player’s name that is obtained byevent.getPlayer().getName()
. Note that the name returned will be of the player that caused the event. So ifmoney
HashMap contains the player’s name then a true value is returned, false otherwise. The true and false values are defined by another predefined Java type known as boolean. A boolean type can have only true or false value. -
If the player’s name does not exist in the
money
HashMap then a new one is created with a value of 0. -
This method also listens for events, but the event is
BlockPlaceEvent
in this case. This event is once again generated by the server and defined by the Bukkit API. This method will reward players with money when they place blocks. -
This line updates the player’s money value in the HashMap.
-
This line sends the player a message telling them how much money they now have. The money value is in the color GREEN to show that the player placed a block.
-
This method does the same thing as the previous one, but is called when a player breaks a block. The money value is in DARK_GREEN to show that the player has broken a block.
Install and verify
Now, build and install the plugin as explained in Reloading your server. To reiterate, right-click on your project and select Build
. This will create the JAR file of your plugin that you can put in your server’s ``plugins'' directory.
If there are no errors, NetBeans should show the message as shown in NetBeans output window for BlockMoney plugin. It will be the same as the message shown when you build the Player Entry/Exit plugin.
[antrun:run]
Executing tasks
[copy] Copying 1 file to C:\Users\Aditya\Desktop\craftbukkit\plugins (1)
Executed tasks
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Restart the CraftBukkit server as explained in Run a CraftBukkit server. Now when you play the game and try to place or break a block, the amount of money is shown in the game console as in Money earned for breaking and placing blocks.
Notice that the player placed two blocks and broke one block. There are two light green messages for placing blocks and one dark green message for breaking blocks.
Summary
This chapter used concepts from the previous chapter to build a plugin that rewards players with ingame money whenever they place or break blocks. The Bukkit Recipe was used to create the plugin, PlayerJoinEvent
, BlockPlaceEvent
, and BlockBreakEvent
event handlers were used in the listener.
Creating a new instance of a Java class, variables, data types, using HashMap to store key/value records, comparing two values of same type using if
statement, and boolean conditions were also explained using examples from the code.
In the next chapter, we will be exploring even more ways to use Listeners.
Zombies Dropping Bones
Normally, in Minecraft, zombies are the most common hostile mob, and they are also probably the most complicated. For example, they can wear armor and hold items/blocks. They also drop a lot of items. Normally, they drop rotten flesh, and sometimes they drop other items like carrots and potatoes. In this chapter, we will make a new plugin that makes them drop bones as well.
Create the plugin
As always, our first step is to create the plugin using Bukkit Recipe. The exact steps to use the recipe are explained in earlier chapters. The information that you enter in the plugin creation will be very similar to PlayerEntryExit plugin details. BlockMoney plugin updated values shows the values that needs to be updated.
| Field | Value |
|---|---|
Project Name |
ZombieBones |
Package |
org.devoxx4kids.bukkit.plugins.zombiebones |
Click on `Finish' button to complete the plugin creation and open the project. The expanded directory should look like as shown in ZombieBones expanded directory structure.
Notice the initial structure of the plugins created so far is quite similar to the plugins created in earlier chapters. This directory has org.devoxx4kids.bukkit.plugins.zombiebones.MyPlugin
class which is the entry point of the plugin, plugin.yml
that defines plugin configuration, and pom.xml
that defines instructions on how to build and install the plugin.
Register and create the Listener
Now, the plugin needs a Listener. Like Player Entry/Exit and Block Money, add a line in the onEnable()
method in MyPlugin.java
as shown in Registering the ZombieBones Listener. It is similar to the listener registration lines in previous plugins. Remember you need to create empty lines by placing your curson before }
and pressing Enter key and then add the code there.
getServer()
.getPluginManager()
.registerEvents(new BoneListener(),
this);
This line registers the Listener, just like in previous chapters. Note that the name of the Listener is different, to match the purpose of this plugin.
Click on the stop sign to show the drop-down menu in Creating the ZombieBones Listener. Select the option that says Create class 'BoneListener' in package org.devoxx4kids.bukkit.plugins.zombiebones
.
NetBeans create a new Java source file, calls it BoneListener.java
, opens it in the main window and it should look like as in ZombieBones Listener.
package org.devoxx4kids.bukkit.plugins.zombiebones;
import org.bukkit.event.Listener;
class BoneListener implements Listener {
public BoneListener() {
}
}
Note that the constructor does not take any parameters here.
Add event handlers
A listener needs to have an event handler. Since we are dropping bones when a zombie dies, so we will look for an event when the zombie dies. Instead of throwing a specific event for when zombie dies, Bukkit throws an event when an entity dies. This simplifies to catch one event for all entity deaths and then find out which of the entity has died. We’ll catch that event and then perform the needed action, i.e. drop bones instead of rotten flesh.
Only one method will have to be added and it looks like Listener method for ZombieBones.
@EventHandler (1)
public void dropBones(EntityDeathEvent e) {
if (e.getEntityType() != EntityType.ZOMBIE){ (2)
return;
}
Location location = e.getEntity().getLocation(); (3)
World world = e.getEntity().getWorld(); (4)
Random random = new Random(); (5)
int howManyBones = random.nextInt(3); (6)
world.dropItem(location, new ItemStack(Material.BONE, howManyBones)); (7)
}
This method has some important parts:
-
As in plugins created so far,
@EventHandler
marks the methoddropBones
as listener. The method is called when an entity dies, an event generated by Minecraft server, and is identified by a Bukkit-definedEntityDeathEvent
parameter to this method. The parametere
provides information about the died entity such as how much EXP should be dropped from this death, or you can even set this value by callingsetDroppedExp
method and giving an integer value. -
This class represents an entity death and it could be pig, sheep, horse, or any other entity. Our purpose is to call this method only when this event is thrown for a zombie. This can be verified by comparing if the entity that throws the event is indeed a zombie.
-
If the entity is indeed a zombie then the method does not stop and continues executing other statements after the
return
statement. The location, or the x, y, and z coordinates of the entity are stored inlocation
variable. Note that instead of storing the value of x, y, and z coordinates separately, they are stored inLocation
class. This is another class defined by the Bukkit API. -
The world in which the entity resides is stored in
world
variable and defined by Bukkit-definedWorld
class. -
Lets make this method somewhat more interesting by dropping a random number of bones. This can be achieved by using
Random
class from Java. A new instance of the class can be created usingnew Random();
. -
A random number is always generated higher than 0 but an upper limit of 3 can be specified by passing that value in the parentheses after
nextInt
. This number is used to determine how many bones should be dropped, and so the variable is aptly namedhowManyBones
. -
The bones are then dropped at zombie’s location by calling
dropItem
method and specifyig the previously stored locations and creating a stack of bones using Bukkit-definedItemStack
class.
|
Note
|
You can remove the part that says |
Lastly, zombies do rare drops of potato, carrot, and ingot iron. A rare drop usually drops about 2.5% of the time. We will update our method so that zombies drop diamonds 2.5% of the time.
To add the rare drop, the code in Rare drops Listener method for ZombieBones must be added to dropBones
event handler. Place your cursor at the end of the line world.dropItem(location, new ItemStack(Material.BONE, howManyBones));
and press Enter key to make some space to add code. Type or copy the code from Listener method for ZombieBones in this newly created space.
if(random.nextInt(200) < 5){ (1)
world.dropItem(location, new ItemStack(Material.DIAMOND, 1));
}
As earlier, random.nextInt(200)
will generate a random number between 0 and 200. <
is a predefined Java symbol to compare two integers and returns true if the number on the left hand side is less than the number on the right hand side. In our case, the ``if'' statement checks if the random number is less than 5. If so then a diamond is dropped. 5 out of 200 makes it 2.5% of the times the diamon is dropped.
Just like <
, >
returns true if the left hand side is greater than the right side. Together <
and >
are part of a group called equality operators.
This is all you need to add to make diamonds a rare drop of zombies. Feel free to change diamonn to some other material or change the percentage of time the rare drop is dropped.
The finished Listener should look like Finished ZombieBones Listener.
package org.devoxx4kids.bukkit.plugins.zombiebones;
import java.util.Random;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.EntityType;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.inventory.ItemStack;
class BoneListener implements Listener {
public BoneListener() {
}
@EventHandler
public void dropBones(EntityDeathEvent e) {
if(e.getEntityType() != EntityType.ZOMBIE){
return;
}
Location location = e.getEntity().getLocation();
World world = e.getEntity().getWorld();
Random random = new Random();
int i = random.nextInt(3);
world.dropItem(location, new ItemStack(Material.BONE, i));
if(random.nextInt(200) < 5){
world.dropItem(location, new ItemStack(Material.DIAMOND, 1));
}
}
}
Wow, that’s a lot of code! Feel free to take some time and reread the sections if any portion is not clear.
Install and verify
Like the other plugins, you will have to build the plugin by clicking on `Build' menu item before it can be used.  Zombie dropping a bone shows a red-colored zombie dead on the floor. The white thing in the middle of the image is a bone that was dropped by the zombie.
|
Note
|
Sometimes, the zombie will drop ``ghost items'', which are items that cannot be picked up. If all of the dropped items are ghost items, you will just have to spawn and kill another zombie. |
Zombie dropping a diamond shows a zombie dropping diamonds as its rare drop.
Summary
In this chapter, we explored example of another Event Handler. EntityDeathEvent
was used to make zombies drop bones instead of rotten flesh. We also learned some basic Java concepts such as stopping sequential execution of a method using return, random number generation, and comparing two values for less than or greater than using equality operators.
In the next chapter, you will see more examples of Listeners by making a plugin that enlarges TNT explosions.
Bigger TNT Explosions
TNT is a block in Minecraft and can be lit with a Flint and Steel item. There are multiple other ways to light it as well. However once lit, it will then explode after 4 seconds. Normally, TNT has a blast radius of 4 blocks, which isn’t that big. Bigger TNT explosions are more fun and so we will change that and make the explosions bigger. One cool thing you can do with TNT is to make cannons that shoot lit TNT for far distances.
As in previous chapters, we’ll be using Listeners and Event Handlers to achieve this functionality.
Create the plugin
After making a few plugins, you should now be an expert in how to get started with creating a sample plugin. So, as always, we start with the Bukkit Recipe. The information that you enter in the plugin creation, once again, will be very similar to PlayerEntryExit plugin details. BiggerTNT plugin updated values shows the values that needs to be updated.
| Field | Value |
|---|---|
Project Name |
BiggerTNTExplosions |
Package |
org.devoxx4kids.bukkit.plugins.biggertnt |
Click on `Finish' button to complete the plugin creation and open the project. The expanded directory should look like as shown in BiggetxrTNT expanded directory structure.
This directory has org.devoxx4kids.bukkit.plugins.biggertnt.MyPlugin
class which is the entry point of the plugin, plugin.yml
that defines plugin configuration, and pom.xml
that defines instructions on how to build and install the plugin.
Create the Listener
As in previous chapters, the plugin needs a listener where event handlers can be registered. Add the line from Registering the BiggerTNT Listener in the onEnable
method of MyPlugin.java
.
Remember you need to create space before this code can be added by placing your curson at the right place and pressing Enter keys.
getServer()
.getPluginManager()
.registerEvents(new TNTListener(),
this);
This code registers the TNTListener
class as the plugin’s Listener. Notice that the name of the Listener is different, to match the purpose of this plugin.
Click on the stop sign and select the option shown in Creating the TNTListener from the drop-down menu.
This will generate a new Java source file TNTListener.java and opens it in main window of NetBeans.
Add event handlers
The server generates an event when an entity explodes. This entity could be a creeper, a primed TNT, an ender crystal, or any other entity that can explode. Our event handler will listen for this event, check if the entity causing the event is a TNT, and then cause an explosion with the bigger radus.
Lets add the method shown in BiggerTNT Listener method to the Listener.
@EventHandler
public void makeExplosion(EntityExplodeEvent event) { (1)
World world = event.getEntity().getWorld(); (2)
Location location = event.getEntity().getLocation();
float power = 16.0F; (3)
if (event.getEntityType() == EntityType.PRIMED_TNT) { (4)
event.setCancelled(true);
world.createExplosion(location, power);
}
}
There are some important parts in this method:
-
Server generated event is represented by
EntityExplodeEvent
Bukkit class and specified as the method parameter here. The parameter name,event
, provides more information about the explosion such as list of blocks that would have been removed or were removed from the explosion, location where the explosion happened, and percentage of blocks to drop from this explosion. -
Entity’s world is retrieved by calling
event.getEntity().getWorld()
method and stored inworld
variable. Entity’s current location is stored inlocation
variable. -
A float variable with a value of 16.0 is created here. It is called
power
and will be used to determine how big the TNT explosion is. If you set it to a value over 16, it will make an explosion with a 16 block radius, because that is the limit defined in the game’s source code. -
Check if the entity that caused the explosion is a primed TNT.
EntityType.PRIMED_TNT
is a predefined Bukkit class and the values are compared using==
operator. If it is then its current explosion is cancelled by callingsetCancelled
method on theevent
variable. This is required so that a bigger TNT explosion of our desired choice can be made to happen. Finally a bigger explosion, with the power of the variablepower
, is created at the same location.
Install and verify
Now, the plugin must be built so that you can install it on your server. Right-click on the plugin and select ``Build''. The output that you get should look like BiggerTNT build output.
[antrun:run]
Executing tasks
[copy] Copying 1 file to C:\Users\Aditya\Desktop\craftbukkit\plugins
Executed tasks
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Lets try out this new plugin.
Place a TNT somewhere in the game, light it up with a Flint and Steel and move away from it. The explosion will now have a blast radius of 16 blocks. Comparison of TNT explosion sizes compares the size of a normal explosion to a enlarged one.
The areas that are not green have been blown up. The small explosion didn’t reach to stone, so the crater is brown from dirt. The big explosion’s crater is gray because it contains stone. The big crater also contains other blocks you can find under the stone, like ores and gravel.
Summary
This chapter explained another Event Handler to cause bigger TNT explosions. EntityExplodeEvent
was used to listen for an entity explosion, checked if the explosion is caused by a primed TNT, and then a bigger explosion was caused in place of the current smaller explosion.
Refer to Bukkit documentation on how to read Bukkit documentation. A complete list of all the classes that represent events generated by the server is listed there.
Flaming Pigs
Have you ever wanted to add new commands to Minecraft? With Bukkit, you can do that. In this chapter, we will learn how to add new commands to the game.
In Minecraft, pigs aren’t normally on fire. The only way to ignite them is to push them into a fire block or into lava. This plugin will add a command that lets you spawn whatever number of pigs you want and also sets them on fire. It isn’t really very useful, just funny to see a bunch of pigs on fire. And isnt’t that what the entire game of Minecraft is about ?
Create the plugin
If you’ve followed the previous chapters, you exactly know how to get started. Yep, we’ll use Bukkit Recipe to create this plugin. The information for plugin creation is quite similar to PlayerEntryExit plugin details. FlamingPigs plugin updated values shows the values that needs to be updated.
| Field | Value |
|---|---|
Project Name |
FlamingPigs |
Package |
org.devoxx4kids.bukkit.plugins.flamingpigs |
Click on `Finish' button to complete the plugin creation and open the project. The expanded directory should look like as shown in FlamingPigs expanded directory structure.
Like other plugins, the initial structure of the project is quite similar. This directory has org.devoxx4kids.bukkit.plugins.flamingpigs.MyPlugin
class which is the entry point of the plugin, plugin.yml
that defines plugin configuration and will be used in this plugin, and pom.xml
that defines instructions on how to build and install the plugin.
Register the command
New commands are not registered in a Listener. Instead, they need to be registered in plugin.yml
which ensures that Bukkit can tell Minecraft about the new command. The actual action of the command needs to be defined in MyPlugin.java
.
First, we will register the command in plugin.yml
. Click on the file and it opens up in the main window of NetBeans. The file should currently look like FlamingPigs plugin.yml.
name: FlamingPigs
version: "1.0-SNAPSHOT"
author: Aditya Gupta
main: org.devoxx4kids.bukkit.plugins.flamingpigs.MyPlugin
The code you will have to add is pretty simple. It looks like Register the flamingpigs command, and should be added at the end of the file. For that, place your cursor at the last line of the file and press Enter key to create a new line. Type the code shown below below there.
commands: (1)
flamingpigs: (2)
description: spawn a certain number of flaming pigs where you are, which are on fire for 30 seconds. (3)
usage: /flamingpigs <number of pigs>
aliases: fp
Make sure that the code is indented as shown.
|
Note
|
YAML is a simple, easy to understand language used commonly to define configuration files. It uses spaces and indents to categorize different items. |
There are a few important things in this file:
-
commands:
tells you that everything under it is a command. One or more commands can be registered here. -
flamingpigs:
registers a command under the name of/flamingpigs''. Each new command is indented 2 spaces to the right from
/'' is automatically added to the beginning of the command name.commands:
line. The -
A text description of what the command does, how it should be used, and aliases of the command are set here. Aliases registers other commands, typically short cuts, that will do the same thing as the original command. Think of it as just another name for this command. So the user can issue the command
/flamingpigs
or a short cut name/fp
to invoke this command.
Your plugin.yml
file should now look like Finished plugin.yml.
name: FlamingPigs
version: "1.0-SNAPSHOT"
author: Aditya Gupta
main: org.devoxx4kids.bukkit.plugins.flamingpigs.MyPlugin
commands: (1)
flamingpigs:
description: spawn a certain number of flaming pigs where you are, which are on fire for 30 seconds.
usage: /flamingpigs <number of pigs>
aliases: fp
The newly added code is highlighted in bold. This ensures that when your plugin is installed then your new command will be registered with Minecraft.
 This functionality needs be added in MyPlugin.java
so that the command functions properly.
Place your cursor after the first }
after onDisable
method in MyPlugin.java
. Press Enter key couple of times to create space for adding a new method and add the method in FlamingPigs onCommand method there.
@Override
public boolean onCommand(CommandSender sender, (1)
Command cmd,
String label,
String[] args) {
if (!cmd.getName().equals("flamingpigs")) { (2)
return false;
}
if (!(sender instanceof Player)) { (3)
return false;
}
if (args.length != 1) { (4)
sender.sendMessage(ChatColor.DARK_RED + cmd.getUsage());
return false;
}
Location location = ((Player) sender).getLocation(); (5)
int numberOfPigs;
try { (6)
numberOfPigs = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe){
sender.sendMessage(ChatColor.RED + args[0] +
ChatColor.DARK_RED + " is not a valid number!");
return false;
}
for(int i = 0; i < numberOfPigs; i++){ (7)
Entity pig = ((Player) sender).getWorld()
.spawnEntity(location, EntityType.PIG);
pig.setFireTicks(6000);
}
return true; (8)
}
This method uses many Java concepts, and there are many important things in it:
-
The method
onCommand
is called by Bukkit whenever a command is issued in Minecraft. Bukkit also provides information about the command such as which player issued this command, the actual command that is being issued, alias of the command, and if any arguments were passed. All of this information is available in four parameters of this method. -
equals()
is a predefined Java method that compares two String values. It returns true if the two values are equal, false otherwise. It is strongly recommended to useequals()
method instead of==
to compare string values. -
Commands may be issued at console. However this command is valid only if the command is issued by a player because we need the exact location where the pigs need to be spawned.
instanceof
is another Java keyword that returns true if the left hand side of this keyword is not of the type defined by right hand side. This method returns false again if the command is not issued by a player. And like previous statement, no further statements are executed. -
The arguments to the command are passed as an array of String. Arrays is a Java concept that stores multiple elements of the same type together. The
[]
after the type indicates this is an array, an array of String in this case. Each element of the array can be accessed using an index, starting from 0. So the first element is at 0th index, second element is at 1st index, and so on. The number of elements in the array can be found out by invokinglength
operator. -
Command sender is confirmed to be a player by the time this piece of the code is reached. Java has the concept of type casting that allows a class to be defined to be of a different type, if the rules permit. We will not explain the rules here but lets say the
CommandSender
can be casted toPlayer
in this case. Once that is done, we can invoke any methods from thePlayer
. In this case specifically we can get the location of the player by callinggetLocation()
method and store it in thelocation
variable. -
Its normal for errors to occur when a user input is required. For example the user may accidentally type alphabets or punctuation characters when numbers are required. Java provides a very sophisticated mechanism to deal with such errors and even recover gracefully. This process is called as exception handling. A piece of code that is likely to generate an error is put inside a try block. This code may generate an error, or throw an exception. The exception is then caught using a catch block. Name of the exception is specified in the catch block. So the try and catch blocks together provides error handling within Java.
-
Java provides a means to repeat a certain piece of code one or more times easily. The number of times the code needs to be repeated can be a predefined number or until a condition is met. This is achieved using loops. Each repetition of the code is called as an iteration.
-
Finally a
true
value is returned by the method to signify that everything worked.
Finished MyPlugin.java
should look like Finished MyPlugin.java
package org.devoxx4kids.bukkit.plugins.flamingpigs;
import java.util.logging.Level; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin;
public class MyPlugin extends JavaPlugin {
// This code is called after the server starts and after the /reload command @Override public void onEnable() { getLogger().log(Level.INFO, "{0}.onEnable()", this.getClass().getName()); }
// This code is called before the server stops and after the /reload command @Override public void onDisable() { getLogger().log(Level.INFO, "{0}.onDisable()", this.getClass().getName()); }
@Override public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { if (!(cmd.getName().equals("flamingpigs"))) { (1) return false; }
if (!(sender instanceof Player)) { (2) return false; }
Location location = ((Player) sender).getLocation(); (3)
if (args.length != 1) { (4) sender.sendMessage(ChatColor.DARK_RED + cmd.getUsage()); return false; }
int numberOfPigs; (5)
try{ (6) numberOfPigs = Integer.parseInt(args[0]); } catch (NumberFormatException nfe){ sender.sendMessage(ChatColor.RED + args[1] + ChatColor.DARK_RED + " is not a valid number!"); return false; }
for (int i = 0; i < numberOfPigs; i++) { (7) Entity pig = ((Player) sender).getWorld() (8) .spawnEntity(location, EntityType.PIG); pig.setFireTicks(6000); } return true; (9) } }
Install and verify
Like the other plugins, you will have to build the plugin before it can be used. If it successfully builds, it should output the message in Building the FlamingPigs plugin.
[antrun:run]
Executing tasks
[copy] Copying 1 file to C:\Users\Aditya\Desktop\craftbukkit\plugins
Executed tasks
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Restart your server and try out the new command! In your chat window, give the command as shown in FlamingPigs command
/flamingpigs 10
This should spawn 10 pigs at your current location and set them on fire.
Try with a larger number of pigs, say 100, and see how it works. Try the command alias. So instead of FlamingPigs command, use FlamingPigs command alias.
/fp 10
If you spawn pigs on the ground, they probably look something like Flaming pigs.
Also try giving a command that does not include a number as shown in FlamingPigs command with incorrect number.
/flamingpigs bob
This should generate a chat message in dark red color indicating that bob
is not a number.
One fun thing you can do with it is fly up into the air about 10 blocks and type in the command in FlamingPigs command.
200 pigs will drop down and they will take damage from falling. When they drop, they will spread out.  When they die, the particles created from their deaths will make a cloud''. FlamingPigs cloud shows a picture of the
cloud''.
Summary
This chapter taught you how to add new commands to the game using plugin.yml
and onCommand()
method of Plugin.java
. You added a plugin that spawned any number of flaming pigs at player’s current location.
Several Java concepts like comparing two string values, arrays, return values, error handling using try/catch blocks, and for loops were explained as well.
Throw Egg
This chapter will build upon the previous chapter and show how commands and Listeners can work together.
When you right-click with an egg item, it gets thrown, and on landing, it has a 12.5% chance of spawning a baby chicken. This plugin will add a command that controls what spawns when the egg lands. So instead of chickens, it could spawn zombies, pigs, or whatever you want it to. You can also configure how many of that mob will be spawned.
Create the plugin
First of all, like all other plugins, use the Bukkit Recipe to create a new plugin. The information for plugin creation is quite similar to PlayerEntryExit plugin details. ThrowEgg plugin updated values shows the values that needs to be updated.
| Field | Value |
|---|---|
Project Name |
ThrowEgg |
Package |
org.devoxx4kids.bukkit.plugins.throwegg |
Click on `Finish' button to complete the plugin creation and open the project. The expanded directory should look like as shown in ThrowEgg expanded directory structure.
Like other plugins created so far, there is org.devoxx4kids.bukkit.plugins.flamingpigs.MyPlugin
class, plugin.yml
that defines plugin configuration and used for command registration, and pom.xml
that defines instructions on how to build and install the plugin.
Register the command
Like the last plugin, you will need to register a new command in plugin.yml
. The updated plugin.yml
file should look like [ThrowEgg_Finished_plugin_yml].
name: ThrowEgg
version: "1.0-SNAPSHOT"
author: Aditya Gupta
main: org.devoxx4kids.bukkit.plugins.throwegg.MyPlugin
commands:
<emphasis role="strong">eggconfig:
description: control what hatches from an egg and how many of it.
usage: /eggconfig <entity to hatch> <number of entities to hatch>
aliases: ec
The file is very similar to the one shown in Finished plugin.yml. The updated sections are highlighted in bold.
A new command eggconfig
is added with an alias ec
. The command requires two arguments, first is the entity to hatch and second is the number of entities to hatch. Other properties are intuitively named and explained in Flaming Pigs.
Add command functionality
Now, like the last plugin, the command is registered, but doesn’t do anything. Functionality will have to be added in MyPlugin.java
.
First of all, a couple of variables are defined to store the entity type and the number of entities to hatch from the command. These variables are shown in Variables to add in ThrowEgg MyPlugin.java
public static EntityType entityType; (1)
public static int numberOfEntities; (2)
-
This variable stores an
EntityType
type, which is a type defined by Bukkit and is used to identify the entity type specified in the command. It is used in methods that spawn entities to determine what entity should be spawned. -
numberOfEntities
is an integer variable that will store the number of entities that should be spawned.
These variables do not have any use right now, so code will have to be added to use them. This code is shown in ThrowEgg command method and should be added as the last method in the file. If you recall, this can be easily done by placing your cursor after the first }
after onDisable
method in MyPlugin.java
. Press Enter key to create space add this method there.
@Override
public boolean onCommand(CommandSender sender,
Command cmd,
String label,
String[] args) {
if (!(cmd.getName().equals("eggconfig"))) { (1)
return false;
}
if (args.length != 2) { (2)
sender.sendMessage(ChatColor.DARK_RED + cmd.getUsage());
return false;
}
try{ (3)
entityType = EntityType.valueOf(args[0]
.toString()
.toUpperCase());
} catch (NullPointerException npe){
sender.sendMessage(ChatColor.RED +
entityType.toString() + ChatColor.DARK_RED +
" is not a valid entity!");
return false;
}
try{ (4)
numberOfEntities = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe){
sender.sendMessage(ChatColor.RED + args[1]
+ ChatColor.DARK_RED + " is not a valid number!");
return false;
}
sender.sendMessage(ChatColor.GREEN + "Eggs will now hatch " (5)
+ numberOfEntities + " " + entityType + "(s).");
return true; (6)
}
This code is quite similar to the one shown in Flaming Pigs but has a few differences:
-
If the command is not
eggconfig
or one of its aliases, this method will return false and stop running any further. -
The method will also stop running if the number of arguments is not two. The first argument is the entity type and second argument denotes the number of entities to hatch.
-
toUpperCase()
is a method on Java-defined String class that converts the first argument to all upper case letters. Note that the first argument is identified by 0th element of the array. This conversion is required asEntityType
stores all Minecraft defined entities in upper case letters. -
Second element of the
args
array, indexed by 1, is then converted to integer. As discussed in the previous chapter,NumberFormatException
is a Java defined class and is used to catch any exception if the string can not be parsed into an integer. If the conversion is sucessful then thenumberOfEntites
variable is set to the number of entities to spawn. If the value is invalid and the conversion is not successful, then the player is notified of that and the method stops running any further. -
If everything looks good then the player is sent a message indicating the entity and the number of entities to spawn.
-
Finally, the method will return a value of true to indicate that the command execution is successful.
Now, the command sets the variables to whatever the player inputs. The variables' values still are not used anywhere, so a Listener will have to be added. The Listener will check for a chicken being hatched from an egg and change the chicken to the entity in entityType
.
Register and create the Listener
In MyPlugin.java
, add a line in the onEnable()
method that looks like Register the ThrowEgg Listener to register the Listener.
Remember you need to create empty lines by placing your curson before }
and pressing Enter key and then add the code there.
getServer().getPluginManager()
.registerEvents(new EggListener(),
this);
Click on the stop sign next to this line and select the option shown in Create the ThrowEgg Listener.
This will generate a new class EggListener
and open it in NetBeans.
Add event handlers
First of all, delete the constructor in the Listener. The constructor looks like ThrowEgg Listener constructor.
public EggListener() {
}
The code in ThrowEgg Listener method will have to be added into the Listener for it to work.
@EventHandler
public void hatchEntity(CreatureSpawnEvent event) { (1)
Entity entity = event.getEntity(); (2)
Location location = entity.getLocation();
World world = entity.getWorld();
if (event.getSpawnReason() == SpawnReason.EGG (3)
&& event.getEntityType() == EntityType.CHICKEN) {
entity.remove(); (4)
for (int i = 0 ; i < MyPlugin.numberOfEntities ; i++) {
world.spawnEntity(location, MyPlugin.entityType);
}
}
}
This code should look quite familiar if you’ve worked through the previous chapters. However there are a few important things to explain in this code:
-
This method is run when a creature is spawned into the world and is identified by
CreatureSpawnEvent
Bukkit class. In this case, we will be using it to check for a chicken spwaning from a thrown eggs. -
The entity spawned, its location, and its world are stored in variables called
entity
,location
, andworld
, respectively. -
We learned about
==
operator for comparing values. So the spawn reason for an entity is checked to be an egg by comparing the values returned fromevent.getSpawnReason()
andSpawnReason.EGG
. Both of these values are obtained using Bukkit API. Similarly the entity type is checked to be of type chicken. This statement has a new&&
operator. It allows to combine two or more boolean statements together and provide a unified result.&&
is called as logical AND operator. Another similar operator is||
and is called logical OR. The results of these operators on two boolean operands is shown in Logical operator results. -
The body of
if
statement removes the entity from the world by callingentity.remove()
Bukkit method. Finally, thefor
loop is used to spawn the chosen entity, defined byMyPlugin.entityType
, at the location of the now dead chicken. The number of iterations is defined byMyPlugin.numberOfEntites
.
Install and verify
Now, you can build the plugin and restart your server to install it. Use the ``/reload'' command to reload the server, then check that it works. Use the command in ThrowEgg command to change what the egg spawns.
/eggconfig <entity type> <number of entities>
Try the full command eggconfig
, the alias ec
, different entities to hatch as defined at http://jd.bukkit.org/apidocs/org/bukkit/entity/EntityType.html, invalid text for specifying the number and validating that the error message shows correctly.
Some fun things you can do are:
-
Make the egg spawn 100 BAT and make a ``bat cloud''
-
Make the egg spawn 1 PRIMED_TNT and make explosive eggs
-
Make the egg spawn 100 ZOMBIE and make a zombie apocalypse
There are lots of fun things you can do with this plugin!
ThrowEgg command output shows the output that comes when you do the command.
The player set eggs to hatch 5 ZOMBIEs, then 50 BATs, then 17 WITCHes.
Summary
This chapter built upon the previous chapter by registering a new command in plugin.yml
. The command lets you configure what a thrown egg should hatch, and how many of it, when it hits the ground. It utilized both command registration in plugin.yml
and a Listener.
In the process, the chapter also explained how multiple try/catch blocks can exist in the same method. Two logical operators in Java, &&
and ||
were also explained.
Sponge Water
In this chapter, we will make a plugin that makes removing water easier. When you place a sponge, it will replace all water directly adjacent to it with glass. It may look like it is building a structure and indeed it builds a shell of glass around it.
New structures can be built either with Listeners or commands. The plugin in this chapter will use a Listener, while the next chapter will utilize commands.
In the very early versions of Minecraft, sponges actually did absorb water. The sponges made an area of air around them to ward away water. This functionality was removed in later versions, so a lot of work was required if a player wanted to remove water. The only ways they could achieve that were very hard to do and usually did not work. This plugin will make sponges do what they used to do - absorb water.
Create the plugin
Like all other plugins, this one starts with the Bukkit Recipe. The information for plugin creation is quite similar to PlayerEntryExit plugin details. ThrowEgg plugin updated values shows the values that needs to be updated.
| Field | Value |
|---|---|
Project Name |
SpongeWater |
Package |
org.devoxx4kids.bukkit.plugins.spongewater |
Click on `Finish' button to complete the plugin creation and open the project. The expanded directory should look like as shown in SpongeWater expanded directory structure.
Like other plugins created so far, there is org.devoxx4kids.bukkit.plugins.spongewater.MyPlugin
as the entry point to plugin, plugin.yml
that defines plugin configuration, and pom.xml
that defines instructions on how to build and install the plugin.
Register and create the Listener
First of all, a Listener will have to be registered for this plugin. The code for registering the Listener needs to be specified in onEnable()
method of MyPlugin.java
and can be found in Register the SpongeWater Listener. You know how to add code to onEnable()
method. If you’ve forgotten then refresh your memory by referring to similar section from previous chapters.
getServer()
.getPluginManager()
.registerEvents(new SpongeListener(this),
this);
Unlike the last plugins, instead of creating a default instance using new SpongeListener()
, we are passing this
as the parameter to the constructor. this
is a Java keyword and refers to the current object, which is an instance of the MyPlugin.java
class in our case. This allows the Listener to use the methods from MyPlugin.java
, especially the plugin’s logger.
Click on the stop sign next to the line in Register the SpongeWater Listener and choose the option Create class "SpongeListener" with constructor "SpongeListener(org.devoxx4kids.bukkit.plugins.spongewater.MyPlugin)" in pakage org.devoxx4kids.bukkit.plugins.spongewater
to create the Listener class. This is shown Creating the SpongeWater Listener.
A new Java source file is generated and opened in NetBeans main window.
Add event handlers
By default, the Listener doesn’t do anything, so lets add functionality for the sponges to work. The code from SpongeWater Listener methods will have to be added to the newly generated Listener class.
@EventHandler
public void absorbWater(BlockPlaceEvent event) { (1)
Block eventblock = event.getBlock(); (2)
World world = eventblock.getWorld();
if (eventblock.getType() != Material.SPONGE) {
return;
}
plugin.getLogger().info("Sponge place detected."); (3)
int radius = 1; (4)
int x = eventblock.getX();
int y = eventblock.getY();
int z = eventblock.getZ();
int waterBlocksFound = 0;
for (int xx = x - radius; xx <= x + radius; xx++) { (5)
for (int yy = y - radius; yy <= y + radius; yy++) {
for (int zz = z - radius; zz <= z + radius; zz++) {
Block block = world.getBlockAt(xx, yy, zz);
if (block.getType() == Material.WATER
|| block.getType() == Material.STATIONARY_WATER) {
block.setType(Material.GLASS);
waterBlocksFound++;
}
}
}
}
plugin.getLogger().log(Level.INFO,
"Replaced {0} water blocks with glass.", waterBlocksFound); (6)
}
As you can realize by now, a Listener class has one or more event handlers, each identified by a Java method marked with @EventHandler
. Lets understand this ode now.
-
Using
BlockPlaceEvent
Bukkit class as the Java method parameter ensures that this method is called whenever a block is placed by the player. As explained earlier, the name of the method does not matter, and just placing the@EventHandler
annotation above the method is sufficient. -
Two variables are created,
eventBlock
to store the event’s block andworld
to store the event’s world. If the block is not a Sponge, then the method returns without doing anything because this method is looking only for sponges. If the block is a Sponge, the method continues. -
A message is logged in the console saying that a sponge has been placed.
-
A few variables are created.
radius
is initialized with a value of 1 and indicates the radius of blocks that will be converted into glass. Three more variables,x
,y
, andz
are created to store the block’s position. One last variable,waterBlocksFound
, is created to log the number of water blocks that were converted into glass. -
We learned about for loops in earlier chapters. Java allows nesting of for loops, or using another for loop within a for loop. The first for loop is called as outside for loop and the second for loop is called as inner for loop. This allows the inner for loop to be executed for each iteration of the outside for loop. In this case, there are three nested for loop.
-
A message is logged in the server console that states the number of water blocks found and turned into Glass.
Install and verify
This plugin is now complete! Build it and install it to your server to use it. You can change the radius
variable to whatever you want the radius to be. It’s a good idea to keep it under 4 because otherwise you could become trapped in the glass. You have to be under five blocks away from a block to place something on it, so if you are too close, the glass could be placed where you are standing, trapping you.
SpongeWater sponges in water shows what happens if you put multiple sponges in a row. The yellow blocks are sponges, and they are surrounded by transparent glass. The glass is surrounded by blue water.
Summary
This chapter showed how you can use sponges to remove water from the world. When a sponge was placed, it turned all water in a one-block radius into glass. In the next chapter, you will use commands to make towers of any block and any height.
We also learned about this
keyword in Java and nested for loops.
Tower Builder
This chapter will show how to build a pre-set schematic of blocks, something like a tower of iron or a quartz mansion.
Now that you have an idea of how to build a structure, we will make a plugin that allows you to create towers wherever you want. This plugin will add a command that creates a tower with whatever material and height that you want.
Towers like the ones created by this plugin can be created normally, but if you want to make really high towers, it could take a while. You would have to place eight blocks for each layer of the tower, so for a 100 block tower, you would have to place 800 blocks! That would be very time-consuming and not very fun either. This plugin lets you make towers with just one command, so you can focus on other fun aspects of Minecraft instead of spending your time building towers.
Create the plugin
First of all, like all other plugins, use the Bukkit Recipe to create a new plugin. The information for plugin creation is quite similar to PlayerEntryExit plugin details. ThrowEgg plugin updated values shows the values that needs to be updated.
| Field | Value |
|---|---|
Project Name |
TowerBuilder |
Package |
org.devoxx4kids.bukkit.plugins.towerbuilder |
Click on `Finish' button to complete the plugin creation and open the project.
Now that you’ve built several plugins, you can click around the project and look for the expanded directory yourself.
Register the command
This plugin does not require a Listener, as it is command-only. First, the command will have to be registered. The updated plugin.yml
is shown in [TowerBuilder_updated_plugin_yml]. The code that needs to be added is shown in bold.
name: ThrowEgg
version: "1.0-SNAPSHOT"
author: Aditya Gupta
main: org.devoxx4kids.bukkit.plugins.throwegg.MyPlugin
commands:
commands:
tower:
description: make a tower of <material> that is <height> blocks tall.
usage: /tower <material> <height>
This will introduce a new command tower
that will take two arguments. The first argument is the material to be used for building the tower and the second argument is the height of the tower.
Note, no alias of the command is specified as that is optional.
Add command functionality
After the command is registered, functionality must be added to it with the onCommand()
method in MyPlugin.java
. The code for doing this is shown in Tower onCommand method and must be added to the end of MyPlugin.java
.
@Override
public boolean onCommand(CommandSender sender,
Command cmd,
String label,
String[] args) {
if (!(cmd.getName().equals("tower"))) { (1)
return false;
}
if (!(sender instanceof Player)) {
return false;
}
if (args.length != 2) {
sender.sendMessage("Usage: /tower <material> <height>");
return false;
}
Material material = Material.matchMaterial(args[0]); (2)
if(material == null){
sender.sendMessage(ChatColor.RED + args[0]
+ ChatColor.DARK_RED + " is not a valid material!");
return false;
}
int height; (3)
try{
height = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe){
sender.sendMessage(ChatColor.RED + args[1]
+ ChatColor.DARK_RED + " is not a valid number!");
return false;
}
int x = ((Player) sender).getLocation().getBlockX(); (4)
int y = ((Player) sender).getLocation().getBlockY();
int z = ((Player) sender).getLocation().getBlockZ();
int[] tower = {
x + 1, y, z,
x - 1, y, z,
x, y, z + 1,
x, y, z - 1,
x + 1, y, z + 1,
x - 1, y, z - 1,
x + 1, y, z - 1,
x - 1, y, z + 1
};
World world = ((Player) sender).getWorld(); (5)
int layer = 0;
for (int i = 0; i < height; i++) { (6)
for (int j = 0; j < tower.length; j += 3) {
world.getBlockAt(tower[j],
tower[j + 1] + layer,
tower[j + 2]).setType(material);
}
layer++;
}
sender.sendMessage(ChatColor.GREEN + "You made a tower of " (7)
+ material + " that is " + height + " blocks high.");
return true;
}
A good part of this code should look familiar as it has been explained in previous chapters. There are some things that need explanation.
-
If the command is
/tower
, a player is running it, and there are 2 arguments, the method continues. If not, it returns false and stops execution. All of these statements have been explained earlier. -
Material
is a Bukkit class that provides amatchMaterial
method that can take any string parameter and returns a matching material. The exact list of material is shown at http://jd.bukkit.org/apidocs/org/bukkit/Material.html. -
Another variable,
height
, is created to store the tower’s height. Second argument to the command, identified by index 1 inargs
parameter, provides the second user input. If it is not a number, the method returns false and further execution is stopped. try/catch block are used to do exception handling here. -
Three variables,
x
,y
, andz
, store the player’s current x, y, and z coordinates. A new array of integer is created based upon player’s current position and contain the positions for one layer of the tower. The array is calledtower
. -
Two variables are created, one to store the world and another called
layer
that will be used in the creation of the tower. -
A nested for loop makes tower layers on top of each other until the tower reaches the height specified by the player.
-
The player is sent a message telling them what their tower was built from and how tall it was.
Install and verify
Now, you can build this plugin and install it to your server. The towers from the plugin are useful for building cool structures and spawnpoints.
TowerBuilder towers shows some of the different kinds of towers that can be created with this plugin. The tower on the left is made of COAL_ORE and is 2 blocks tall. The middle tower is made of DIAMOND_BLOCK and is 10 blocks tall. The tower on the right is made of IRON_BLOCK and is 5 blocks tall.
The commands used to generate these towers are shown in
/tower COAL_ORE 2
/tower DIAMOND_BLOCK 10
/tower IRON_BLOCK 5
Note the tower is created at player’s current location. So the first tower is created by giving the first command, the player moves to the next location and issues the second command, and the player moves to the last location and creates the last tower.
You can make towers with any blocks and with any heights. Note that you cannot have blocks over the Minecraft height limit, which is 256 blocks above the bottom of the world.
One fun thing you can do is make ``flaming blocks''. Make a tower of Mob Spawners (MOB_SPAWNER), then make a tower of the same height and at the same place, but with another block.
Summary
In this chapter, you learned how to generate structures with commands. You built on the concepts from the last chapter to make a plugin that allows you to build towers with commands.
The Java concepts learned so far, such as exception handling using try/catch, nested for loops, arrays, instanceof
operator, were applied once again. In addition, the null
keyword was introduced.
Recipes and Enchantments
In this chapter, you will learn how to make three new plugins. The first two will show how to create a new crafting recipe and a smelting recipe. The last one is how to have higher enchantments. Since you already know how to create a plugin, and now can easily guess the value required during plugin creation, this chapter will provide only the relevant code for the plugins.
This chapter and the two after it are mostly to show you the capabilities of the Bukkit API.
Crafting Recipe
Minecraft already has several predefined crafting recipes such as TBD. In some recipes, the configuration of the items matters. For example, TBD. These are called shaped recipes. In shapeless recipes, the placement of the items does not matter.
The end result of each crafting recipe is creation of a one or multiple item(s) or block(s). This plugin shows how to add a new shaped crafting recipe to the existing set of recipes.
Crafting recipe requires placement of ingredients in a 3x3 crafting table. Creating a new shaped recipe will require you to define the placement of these ingredients in the grid, one row at a time. The output item(s) or block(s) also need to be defined. onEnable method for shaped crafting recipe shows how this can be specified in onEnable
method of MyPlugin.java
. Remember, you need to create the plugin following the steps explained in earlier chapters before making these edits.
@Override
public void onEnable() {
getLogger().log(Level.INFO, "{0}.onEnable()",
this.getClass().getName());
ShapedRecipe recipe = new ShapedRecipe( (1)
new ItemStack(Material.NETHER_STAR, 1));
recipe.shape("ded", "lnl", "ded"); (2)
recipe.setIngredient('d', Material.DIAMOND_BLOCK) (3)
recipe.setIngredient('e', Material.EMERALD)
recipe.setIngredient('l', Material.LAPIS_BLOCK)
recipe.setIngredient('n', Material.REDSTONE_BLOCK);
getServer().addRecipe(recipe); (4)
}
-
Creates a new
ShapedRecipe
Bukkit class where the output item will a single Nether Star. The exact list of materials is available at http://jd.bukkit.org/apidocs/org/bukkit/Material.html. -
Three rows of the crafting table are defined by three parameters in the
shape()
method. Each column in that row is defined by one character. And since a row has three columns, three characters are required to specify a row. -
The letters used in the recipe are mapped to the corresponding item using
setIngredient()
methods, one method call for each letter used in the recipe. For example, the letter ``e'' in the recipe signifies the item Emerald. -
The recipe is added to the server.
New crafting recipe shows the new crafting recipe in action.
The org.bukkit.inventory.ShapelessRecipe
Bukkit class can be used to create a shapeless crafting recipe. The only difference between the two recipes is that there is no shape()
method call. Only the ingredients and how many of them are required needs to be specified. This allows the specified number of items to be placed anywhere in the 3x3 grid to craft the output item.
@Override
public void onEnable() {
getLogger().log(Level.INFO, "{0}.onEnable()",
this.getClass().getName());
ShapelessRecipe recipe = new ShapelessRecipe( (1)
new ItemStack(Material.DIAMOND, 1));
recipe.setIngredient(5, Material.GRASS) (3)
recipe.setIngredient(3, Material.DIRT);
recipe.setIngredient(3, Material.PAPER);
getServer().addRecipe(recipe); (4)
}
onEnable method for shapeless crafting recipe shows how a diamond can be created using grass, paper, and dirt.
Smelting Recipe
Smelting is a method of producing refined goods. Smelting is done in furnace blocks which takes one input'' ingredient that will be heated, one field for the fuel, and one
output'' field for the final smelted product.
This plugin shows how to add a new smelting recipe. onEnable method for smelting recipe shows the onEnable
method for MyPlugin.java
for this plugin.
@Override
public void onEnable() {
getLogger().log(Level.INFO, "{0}.onEnable()",
this.getClass().getName());
FurnaceRecipe recipe = new FurnaceRecipe( (1)
new ItemStack(Material.DRAGON_EGG, 1), (2)
Material.EGG (3)
);
getServer().addRecipe(recipe); (4)
}
-
This recipe is a furnace recipe as identified by creating a new instance Bukkit-defined
FurnaceRecipe
class. The created recipe is stored in therecipe
variable. -
This is the
output'' item obtained from smelting. In this case, it is a Dragon Egg item. The second parameter defines the number of
output'' items that will be obtained. -
This is the ``input'' ingredient that will be heated. In this case, it an Egg item. So one Egg item will be smelted to one Dragon Egg item.
-
The recipe is added to the server.
New smelting recipe shows the new recipe in action. A stack of 16 eggs was put into the input slot, and four of them are already smelted. The four Dragon Eggs resulting from that are in the output slot at the right. The remaining 12 Eggs are still at the top and will be eventually smelted to Dragon Egg as well.
Higher Enchantments
Enchantments can be added to armor or weapons to make them more powerful. However the level of enchantment is restricted. This plugin shows how higher enchantments can be enabled for the item currently held by the player. It shows that only for a small subset of enchantments. But similar mechanism may be applied to enable higher levels for other enchantments.
The plugin will introduce a new command that enables higher enchantment level. Tower Builder provide more details about how to add new commands. The `plugin.yml
required for this plugin is shown in plugin.yml for higher enchantments.
name: Enchantments
version: "1.0-SNAPSHOT"
author: Aditya Gupta
main: org.devoxx4kids.bukkit.plugins.enchantments.MyPlugin
<emphasis role="strong">commands:
enchantment: (1)
description: allows a higher enchantment level.
usage: /enchantment <enchantment> <level> (2)
aliases: ench</emphasis> (3)
Updated code is shown in bold.
-
The command name is
enchantment''. Note that the command name could not be
enchant'' because that is a vanilla command that enchants items up to the normal limits. -
Usage requires to specify the enchantment and the level. The enchantment name should match the enchantment’s in-game name, with underscores for spaces. The exact list of enchantment is shown at http://jd.bukkit.org/rb/apidocs/org/bukkit/enchantments/Enchantment.html.
-
An alias for the command is ``ench''.
onEnable()
method of MyPlugin.java
needs to be updated and is shown in MyPlugin.java for higher enchantments. In addition, onCommand()
is added as well.
public class MyPlugin extends JavaPlugin {
HashMap<String, Enchantment> enchantments = new HashMap(); (1)
// This code is called after the server starts and after the /reload command
@Override
public void onEnable() {
getLogger().log(Level.INFO, "{0}.onEnable()",
this.getClass().getName());
enchantments.put("flame", Enchantment.ARROW_FIRE); (2)
enchantments.put("infinity", Enchantment.ARROW_INFINITE);
enchantments.put("knockback", Enchantment.KNOCKBACK);
enchantments.put("fortune", Enchantment.LUCK);
enchantments.put("blast_protection", Enchantment.PROTECTION_EXPLOSIONS);
enchantments.put("unbreaking", Enchantment.DURABILITY);
}
// This code is called before the server stops and after the /reload command
@Override
public void onDisable() {
getLogger().log(Level.INFO, "{0}.onDisable()", this.getClass().getName());
}
@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
if (!cmd.getName().equals("enchantment")) {
return false;
}
if (!(sender instanceof Player)) {
return false;
}
if (args.length != 2) { (3)
sender.sendMessage(ChatColor.DARK_RED + cmd.getUsage());
return false;
}
if (!enchantments.containsKey(args[0])) { (4)
sender.sendMessage(ChatColor.RED + args[0]
+ ChatColor.DARK_RED + " is not a supported enchantment!");
return false;
}
int level;
try{ (5)
level = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe){
sender.sendMessage(ChatColor.RED + args[1]
+ ChatColor.DARK_RED + " is not a valid number!");
return false;
}
ItemStack itemStack = ((Player) sender).getItemInHand(); (6)
itemStack.addUnsafeEnchantment( (7)
enchantments.get(args[0]), level);
((Player) sender).sendMessage(ChatColor.GOLD (8)
+ "You have enchanted your item with "
+ ChatColor.GREEN + args[0]
+ ChatColor.GOLD + " at level "
+ ChatColor.GREEN + level
+ ChatColor.GOLD + ".");
return true;
}
}
-
Create a
java.util.HashMap
class that will store the mapping between in-game and Bukkit name of enchantment -
onEnable
method adds several mappings for in-game enchantment name to the Bukkit name of enchantment. The mappings shown here don’t cover all of the enchantments, so all the other enchantments need to be added to make the command support them. The complete list of enchantments are liste at http://jd.bukkit.org/rb/apidocs/org/bukkit/enchantments/Enchantment.html. -
Checks that at least two parameters are specified, one for enchantment in-game name, and another one for the level. If not then a message is displayed to the player and further execution of the method is halted.
-
Ensures that the first parameter is a valid enchantment.
containsKey
method ensures that the first command argument, identified byargs[0]
is in theenchantments
map. If not, then a message is displayed to the user and further execution halted. -
Checks that the second argument, identified by args[1], is an integer. If it is not, a
NumberFormatException
is thrown, and the player is notified that their number is invalid. -
Gets the item player is holding by calling
getItemInHand()
Bukkit method on player. -
Sets the specified enchantment level. Note that
addUnsafeEnchantment
is used because it ignores level restrictions.addEnchantment
would not allow you to make enchantments above the normal limits. -
Send the player a message telling them what enchantment they did and at what level. HigherEnchantments chat output shows the output in the chat window.
The player in the above picture enchanted their item with Flame, Infinity, and Unbreaking, all at level 100.
Summary
In this chapter, you created three plugins. The first plugin added a new crafting recipe and we discussed how shaped and shapeless recipes can be created. The second plugin created a new smelting recipe. The last plugin added a new command to make higher enchantments.
The Java concepts learned in earlier chapters are applied and so the descriptions could be kept short. It shows how your overall Java knowledge is improving as well.
Explosions and Rocket Launchers
This chapter, like the previous one, is targeted at readers who have read through the previous ones and know how to make plugins. It will expand on your knowledge of the Bukkit API and show you more about what it can do.
These two plugins are more complicated than the previous ones, and so will require you to pay closer attention to details.
TNT Magnet
This next plugin is very explosive!
Whenever a TNT block is placed, it turns into a TNT magnet. After 5 seconds, it will detonate, and all mobs around it will be teleported to it just before. In this way, you can destroy all mobs in an 8 block radius around the TNT.
First some Java concepts.
Java allows a class to be derived from another class. A class that is derived from another class is called a subclass (also a derived class, extended class, or child class). The class from which the subclass is derived is called a superclass (also a base class or parent class). A child class inherits a parent class using extends
Java keyword. So a child class is also known to extend the parent class.
A class can be derived from a class that is derived from another class, and so on, and ultimately derived from the top most java.lang.Object
class. All the classes, up to java.lang.Object
are said to be in the parent hierachy of the child class.
This concept is commonly known as inheritance. A subclass inherits fields and methods of the classes in parent hierarchy. So the fields from all the parent classes can be directly manipulated in the child class. Similarly, methods from all the parent classes can be invoked in the child class.
Lets create our plugin now. First of all, like every other plugin, use the Bukkit Recipe to create a new plugin. Choose the plugin name as TNTMagnet
and the package name as org.devoxx4kids.bukkit.plugins.tntmagnet
.
In order to display a countdown timer in the chat console that shows TNT will explode in 5 seconds'',
TNT will explode in 4 seconds'' and so on, we’ll use BukkitRunnable
Bukkit class. This class allows us to defer execution of code to a later time, blow up TNT after 5 seconds in our case. This is called as defining work in Bukkit. This can be easily achieved by making any class extend from BukkitRunnable
Bukkit class. This code, or `work'', may be scheduled to be executed repeatedly at a fixed interval, show a message every 1 second in our case. This is called as scheduling work in Bukkit. This can be achieved by using any of the `run
methods in BukkitRunnable
.
This plugin needs to register a Listener in onEnable()
method of MyPlugin.java
and can be found in Register the SpongeWater Listener. You know how to add code to onEnable()
method. If you’ve forgotten then refresh your memory by referring to similar section from previous chapters.
getServer()
.getPluginManager()
.registerEvents(new TNTListener(this),
this);
TNTListener
can be created by clicking on the stop sign in NetBeans and picking the right option. The Listener itself, TNTListener.java
is pretty straight forward and the complete class is shown in TNTListener class.
class TNTListener implements Listener {
private final MyPlugin plugin;
public TNTListener(MyPlugin plugin) { this.plugin = plugin; }
<emphasis role="strong">@EventHandler public void explodeTNT(BlockPlaceEvent event) throws InterruptedException { <co id="tntmag-co-1" linkends="tntmag-co-11" /> if (event.getBlock().getType() != Material.TNT) { <co id="tntmag-co-2" linkends="tntmag-co-22" /> return; }
p.sendMessage(ChatColor.GOLD + "You have placed a " + <co id="tntmag-co-3" linkends="tntmag-co-33" /> ChatColor.RED + "TNT Magnet" + ChatColor.GOLD + "! It will detonate in " + ChatColor.DARK_PURPLE + "5 " + ChatColor.GOLD + "seconds.");
new TNTTask(this.plugin, 4, event.getPlayer(), event.getBlock()) <co id="tntmag-co-4" linkends="tntmag-co-44" /> .runTaskTimer(this.plugin, 20, 20); <co id="tntmag-co-5" linkends="tntmag-co-55" /> }</emphasis> } <simpara>Event handler is highligted in bold.</simpara> <calloutlist> <callout arearefs="tntmag-co-1" id="tntmag-co-11"> <para> The method is run when a block is placed by a player in the game. The game generates an event and this is captured as <literal>BlockPlaceEvent</literal> Bukkit class. </para> </callout> <callout arearefs="tntmag-co-2" id="tntmag-co-22"> <para> If the block is not TNT, the method returns. Optionally, you may display a message here. </para> </callout> <callout arearefs="tntmag-co-3" id="tntmag-co-33"> <para> First message is displayed using a mix of purple and gold colors. </para> </callout> <callout arearefs="tntmag-co-4" id="tntmag-co-44"> <para> <literal>TNTTask</literal> is a new class that defines the “work”, showing a message with countdown timer and blowing up TNT in our case. The first parameter indicates how many times the task needs to be executed. Second parameter is the player and third parameter is the block where TNT is placed. This class will be explained more in <xref linkend="TNTMagnet_Runnable"/>. </para> </callout> <callout arearefs="tntmag-co-5" id="tntmag-co-55"> <para> The <literal>runTaskTimer()</literal> method from <literal>BukkitRunnable</literal> class schedules a task repeatedly until cancelled. It takes three parameters. The first parameter is the plugin that is scheduling the task. The second parameter is the ticks to wait before running the task. 20 ticks is 1 second and so wait 1 second before the first execution of the task. The third parameter is the ticks to wait between runs, and this is also 1 second between each subsequent run of the task. </para> </callout> </calloutlist>
One last file will have to be created for this plugin. Like other files, this can be easily created by clicking on the stop sign and selecting the option Create class "TNTTask" with constructor "TNTTask(org.devoxx4kids.bukkit.plugins.tntmagnet.MyPlugin, int, org.bukkit.entity.Player, org.bukkit.block.Block)" in package org.devoxx4kids.bukkit.plugins.tntmagnet
.
NetBeans will create this file and open it in the main window. The updated file, with newly added code highlighted in bold, is shown in TNTMagnet Runnable class.
public class TNTTask extends BukkitRunnable { (1)
<emphasis role="strong>private int counter; (2)
Player player;
Block block;</emphasis>
public TNTTask(int counter, Player p, Block b) { (3)
this.player = p;
this.block = b;
this.counter = counter;
}
<emphasis role="strong>@Override
public void run() { (4)
if (counter > 0) { (5)
player.sendMessage(ChatColor.GOLD + "Detonating in "
+ ChatColor.DARK_PURPLE + counter-- + ChatColor.GOLD
+ " seconds!");
return;
}
player.sendMessage(ChatColor.GOLD + "Your " (6)
+ ChatColor.RED + "TNT Magnet "
+ ChatColor.GOLD + "has detonated!");
Entity bat = block.getWorld().spawnEntity(new Location( (7)
block.getWorld(),
block.getX() + 0.5,
block.getY() + 0.5,
block.getZ() + 0.5),
EntityType.BAT);
List<Entity> entitylist = ((LivingEntity) bat)
.getNearbyEntities(4.0, 4.0, 4.0);
bat.remove();
for (int i = 0; i < entitylist.toArray().length; i++) { (8)
entitylist.get(i).teleport(
new Location(block.getWorld(),
block.getX() + 0.5,
block.getY() + 0.5,
block.getZ() + 0.5));
}
block.setType(Material.AIR);
TNTPrimed tnt = (TNTPrimed) block.getWorld().spawnEntity(
new Location(block.getWorld(),
block.getX() + 0.5,
block.getY() + 0.5,
block.getZ()),
EntityType.PRIMED_TNT);
tnt.setFuseTicks(0);
this.cancel(); (9)
}</emphasis>
}
-
This class extends from
BukkitRunnable
. In this case,TNTTask
is the child class andBukkitRunnable
is the parent class. -
Three variables are created. The first one will be used to determine how many seconds are left until the TNT explodes. The other two will store the player who placed the TNT and the TNT itself.
-
The player, block, and counter variables are set to the ones given in the constructor. This will allow us to access these values in other methods of the class.
-
The method
run()
in anyBukkitRunnable
class is executed when the class is instantiated in the Listener. Note the return type of this method isvoid
. This is a Java keyword indicating that the method does not return any value. However the method can still return after any statement by placing an`empty'' `return
statement. We’ll look at this usage in this method next. -
If the counter is greater than 0, the player is sent a message telling them how much time is remaining before their TNT Magnet explodes. The method also returns using
return
statement. -
If the method has not returned, the counter is equal to 0, and it is time for the TNT Magnet to explode. The player is sent a message telling them that their TNT Magnet has detonated.
-
A bat is spawned at the location of the TNT, and its nearby entities are stored in a list. A bat is a good mob to spawn because it is so small you cannot see it outside the TNT. The bat is then removed.
-
All the entities in the list are teleported to the TNT. This is done using a ``for'' loop that runs through the entire list. The TNT is then removed by setting it to AIR, and primed TNT is spawned there with no fuse, so it blows up automatically.
-
The task has executed the exepcted number of times until this time and so
this.cancel()
cancels the task so that it does not keep repeating forever.
To use this plugin, find a mob that you would like to blow up and place a TNT block next to it. After 5 seconds, if the mob hasn’t moven out of range, it will be dead from the force of the explosion, especially if you have the BiggerTNTExplosions plugin installed.
More details about Bukkit Schedulers can be found at http://wiki.bukkit.org/Scheduler_Programming.
Rocket Launcher
This is yet another fun plugin that will add a rocket launcher'' which throws explosive fireballs with 2.5 times the power of TNT. The rocket launcher will be a Diamond Horse Armor item by default, but you can change it to whatever you want. The plugin will also add a
safety'' feature that lets you turn the rocket launcher on or off, so it doesn’t shoot fireballs when you don’t want it to.
First of all, like every other plugin, use the Bukkit Recipe to create a new plugin.
This plugin doesn’t need anything in MyPlugin.java
except for a line that registers a Listener. This line can be found in Register the SpongeWater Listener. Change the TNTListener
to RocketLauncher
and create the new class by clicking on the stop sign.
In the Listener, the code from RocketLauncher Listener methods will have to be added. This code does all the work of firing fireballs and turning safety on/off.
Map<String, Boolean> safety = new HashMap(); (1)
@EventHandler
public void checkForNewPlayer(PlayerJoinEvent e){ (2)
if (!(safety.containsKey(e.getPlayer().getName()))) {
safety.put(e.getPlayer().getName(), true);
}
}
@EventHandler
public void fireGun(PlayerInteractEvent e) { (3)
Player p = e.getPlayer();
if(e.getAction() == Action.LEFT_CLICK_AIR ||
e.getAction() == Action.LEFT_CLICK_BLOCK){ (3)
if(e.getPlayer().getItemInHand().getType()
!= Material.DIAMOND_BARDING){
return;
}
if(safety.get(p.getName())){
safety.put(p.getName(), false);
p.sendMessage(ChatColor.AQUA + "Safety is now OFF.");
} else {
safety.put(p.getName(), true);
p.sendMessage(ChatColor.AQUA + "Safety is now ON.");
}
return;
}
if (e.getAction() != Action.RIGHT_CLICK_AIR
&& e.getAction() != Action.RIGHT_CLICK_BLOCK) { (4)
return;
}
if (p.getItemInHand().getType() != Material.DIAMOND_BARDING ||
safety.get(p.getName())) {
return;
}
Fireball f = e.getPlayer().launchProjectile(Fireball.class);
f.setIsIncendiary(false);
}
@EventHandler
public void makeBigExplosion(EntityExplodeEvent event) { (5)
Entity e = event.getEntity();
if (!(e instanceof Fireball)) {
return;
}
Fireball f = (Fireball) e;
if (f.isIncendiary()) {
return;
}
World w = f.getWorld();
w.createExplosion(f.getLocation(), 10.0F);
event.setCancelled(true);
}
There are some important parts in this code:
-
A
HashMap
variable is created to store the safety state of all the players. -
This method is called when a player joins the server. The server then generates the event and is captured as
PlayerJoinEvent
Bukkit class. If a player without a safety value joins, they get a default value of true, meaning that rocket launchers won’t fire for them unless they turn off the safety. -
This method is called when a player interacts with an object or air. The server then generates an event and is captured as
PlayerInteractEvent
Bukkit class. -
Specific interactions of a player can be identified by calling
getAction()
method on the event. If the player left clicks in air or on a block then we check the item in hand. If the item in hand with is diamond horse armor (DIAMOND_BARDING), then their safety state is switched from off to on or vice versa. The player is notified about their safety state.get
andput
are methods onHashMap
to retrieve the stored values and save the values respectively. -
If the player right clicks with a diamond horse armor, a fireball is created. It is not incendiary, meaning it won’t make fire. This is to identify it as a rocket launcher fireball, and not a different fireball.
-
This method is called when an entity explode event happens, as identified by
EntityExplodeEvent
Bukkit class. If the entity is a fireball and it is not incendiary, it will stop the explosion and make a much bigger one, because the fireball is from a rocket launcher.
Now, build the plugin and install it into your server. Get a Diamond Horse Armor from your inventory, turn the safety off by left-clicking with it, and blow up whatever you want! RocketLauncher explosion shows the size of an explosion made by a rocket launcher.
Summary
This chapter showed you some more examples of fun plugins that can be made using the Bukkit API. You added TNT magnets for blowing up stuff and rocket launchers to make even more explosions.
A void return type from Java method and an advanced Java concept of inheritance was also explained. A new Bukkit event handler that shows how to handle player interaction was explained as well.
How to handle when player interacts with an object or air
Warp Board
This is the last plugin that we will make in this book. The plugin will show you how to add a custom inventory with three clickable items. Each click will teleport you to a different place. It will be called a Warp Board.
Warp Board
This plugin is very useful for large servers. When a player joins the game, they are given a compass in their inventory that is renamed ``Warp Board'' in gold letters. When they right-click with it, it opens up an inventory with the same size as a small chest. The inventory contains three items:
-
Emerald Block
-
Diamond Block
-
Redstone Block
When a player clicks on one of the icons, he/she gets teleported to a specific place. The player can also do the command /warpboard
or /wb
to open up the Warp Board. The Warp Board looks like as shown in Warp Board.
The bottom inventory is the player’s inventory, and the top inventory is the Warp Board. The compass is shown in the bottom left corner as part of player’s inventory and the three items are shown in the Warp Board’s inventory.
Registering the command
First thing is to use the Bukkit Recipe and create a new plugin. Choose the plugin name as WarpBoard
and the package name as org.devoxx4kids.bukkit.plugins.warpboard
.
In plugin.yml
, the /warpboard
command will have to be registered. Complete plugin.yml
is shown in Registering the /warpboard command and the updated portions are in bold.
name: WarpBoard
version: "1.0-SNAPSHOT"
author: Your Name
main: org.aditya.bukkit.plugins.warpboard.MyPlugin
commands:
warpboard:
description: Opens up a Warp Board from which players can teleport to different places.
usage: /warpboard
aliases: wb
It registers the /warpboard
command with an alias: /wb
. No arguments are needed to open the board.
Adding command functionality
Now, the command handler in MyPlugin.java
needs to be updated. For that, you will first have to make a variable at the top of the class file, like WarpBoard inventory variable.
public Inventory inv;
This variable will store the inventory that will be shown to the player.
Next, two new methods will have to be added to the bottom of the file, before the last }
. They are shown in WarpBoard MyPlugin.java methods.
@Override
public boolean onCommand(CommandSender sender,
Command cmd,
String label,
String[] args) { (1)
if (!(cmd.getName().equals("warpboard"))) {
return false;
}
if(!(sender instanceof Player)){
return false;
}
openGUI(((Player) sender));
return true;
}
public void openGUI(Player p) { (2)
ItemStack isEmerald = new ItemStack(Material.EMERALD_BLOCK); (3)
ItemMeta imEmerald = isEmerald.getItemMeta();
imEmerald.setDisplayName(ChatColor.GOLD + "Warp to " (4)
+ ChatColor.GREEN + "Emerald Land");
isEmerald.setItemMeta(imEmerald);
ItemStack isDiamond = new ItemStack(Material.DIAMOND_BLOCK);
ItemMeta imDiamond = isDiamond.getItemMeta();
imDiamond.setDisplayName(ChatColor.GOLD + "Warp to "
+ ChatColor.BLUE + "Diamond Land");
isDiamond.setItemMeta(imDiamond);
ItemStack isRedstone = new ItemStack(Material.REDSTONE_BLOCK);
ItemMeta imRedstone = isRedstone.getItemMeta();
imRedstone.setDisplayName(ChatColor.GOLD + "Warp to "
+ ChatColor.RED + "Redstone Land");
isRedstone.setItemMeta(imRedstone);
inv = Bukkit.createInventory(p, 27, ChatColor.DARK_PURPLE (5)
+ "Warp Board");
inv.setItem(12, isEmerald); (6)
inv.setItem(13, isDiamond);
inv.setItem(14, isRedstone);
p.openInventory(inv); (7)
}
-
This method is like all other
onCommand()
methods - it checks for the appropriate command name or one of its aliases. It also returns if the sender isn’t a player because a command block or the console cannot open an inventory. A check for arguments is not required because no arguments are needed for this command. If every thing looks good then it invokes another method and pass the player as a parameter. -
The second method,
openGUI(Player p)
, is where all the magic happens. -
Creates three new
ItemStack
variables, one each for emerald, diamond, and redstone block.ItemStack
is used to create a stack of items. Different metadata can be attached to each stack, such as display name and enchantment of different levels. -
Sets the name of the item to text like
Warp to Emerald Land'', one for each block. The text
Warp to'' is highlighted in gold color and the name of the element is highlighted in green, blue, and red color respectively. -
Create the inventory with the name ``Warp Board'' in purple letters. The first parameter identifies the holder of the inventory, the player in this case. The second parameter defines the size of the inventory, must be a multiple of 9. Third and the last parameter is the title of the inventory that is displayed when it is viewed.
-
Set the three middle slots, identified by 12, 13, and 14, in the inventory to the previously created `ItemStack`s.
-
Opens the inventory and show it to the player.
Create the Listener and add event handlers
Lastly, a Listener will be created to check for the items being clicked on. Create a Listener by clicking on the stop sign and selecting the appropriate option. Add event handler code from WarpBoard Listener methods to the Listener.
@EventHandler
public void giveWarpBoardCompass(PlayerJoinEvent e){ (1)
Player p = e.getPlayer();
ItemStack is = new ItemStack(Material.COMPASS);
ItemMeta im = is.getItemMeta();
im.setDisplayName(ChatColor.GOLD + "Warp Board");
is.setItemMeta(im);
p.getInventory().setItem(0, is);
}
@EventHandler
public void InventoryClick(InventoryClickEvent e) { (2)
Player p = (Player) e.getWhoClicked(); (3)
if (!e.getInventory().getTitle() (4)
.contains(ChatColor.DARK_PURPLE + "Warp Board")) {
return;
}
e.setCancelled(true);
if (e.getCurrentItem().getType() == Material.EMERALD_BLOCK) { (5)
p.sendMessage(ChatColor.GOLD + "Warping to "
+ ChatColor.GREEN + "Emerald Land");
p.teleport(new Location(p.getWorld(), 0.5, 100, 0.5));
} else if (e.getCurrentItem().getType() == Material.DIAMOND_BLOCK) {
p.sendMessage(ChatColor.GOLD + "Warping to "
+ ChatColor.BLUE + "Diamond Land");
p.teleport(new Location(p.getWorld(), 100.5, 100, 0.5));
} else if (e.getCurrentItem().getType() == Material.REDSTONE_BLOCK) {
p.sendMessage(ChatColor.GOLD + "Warping to "
+ ChatColor.RED + "Redstone Land");
p.teleport(new Location(p.getWorld(), 200.5, 100, 0.5));
}
}
@EventHandler
public void openCustomInventory(PlayerInteractEvent e){ (6)
if(e.getAction() != Action.RIGHT_CLICK_AIR &&
e.getAction() != Action.RIGHT_CLICK_BLOCK){
return;
}
if(!e.getItem().getItemMeta().getDisplayName()
.equals(ChatColor.GOLD + "Warp Board")){
return;
}
e.getPlayer().performCommand("warpboard");
}
-
Checks for a
PlayerJoinEvent
, which happens when a player joins the server. Gives the player a compass when they join. It is called ``Warp Board'' in gold letters, which is impossible to legitimately obtain in Minecraft - you need plugins to do it for you. That way, players cannot craft Warp Boards. The compass is placed in the first slot of the player’s hotbar. -
This event handler is called when the player clicks a slot in inventory. The server generates an event and this is captured as
InventoryClickEvent
in Bukkit. -
Player who clicked on the inventory is obtained in
p
variable. -
If the clicked inventory is not our
`Warp Board'' inventory then further execution of the method is halted by using a `return
statement. -
We learned about
if' statements in earlier chapters. They are useful if you want some statements to execute when a particular condition is met. Java extends the `if
statements and also provide if-else statements. Theelse
part is immediately after the code statements ofif
block, and is followed by another set of statements which are executed if the condition is not met. In addition, theelse
statement may be combined with an additionalif
statement. This allows to create a sequence of sophisticatedif
andelse-if
blocks and execute certain piece of code based upon different conditions. -
This event handler is called when the player interacts with an object and is captured as
PlayerInteractEvent
Bukkit class. Checks for a player right-clicking with a compass renamed to`Warp Board'' in gold letters. If that happens, it does the `/warpboard
command from the player who clicked with the compass. Note, a command is issued from the event handler here using theperformCommand()
method.
Verify that it works
Now, you can build the plugin, install it, and restart or reload your server for the changes to take place. You should start off with the compass mentioned in the beginning of this plugin’s explanation. Right-click with it to open the Warp Board. Alternatively you can use the /warpboard
command to open it. Warp to all the places and build things at each of those places. The items in the Warp Board are renamed to ``Warp to [Item Name] Land'', so you should build things out of their corresponding warp item. One example of a warp is shown in Emerald Land warpboard example and it is used with the Emerald Land warp.
Summary
This chapter showed you one last plugin that can be made with Bukkit. You added a Warp Board that lets you teleport to different places, and you also added a command and a compass to open it. In addition we also learned about if-else statements in Java.
This is the last plugin that will be explained in this book, but you can try making some plugins yourself! Bukkit Javadocs for different builds provide a complete summary of where to find complete Bukkit API for different builds.
Managing your CraftBukkit server
Part I of this book explained how to start your own Bukkit server and install plugins in it. The commands were simple and easy to understand. However you feel the need of taking control of your server and managing it more efficiently. This chapter will refresh the introduction to Bukkit and CraftBukkit and the difference between them. It will also explain how to run a Minecraft server using CraftBukkit and show basic commands for managing the server.
Bukkit API
Bukkit is a free, open-source, software project that provide the means to extend Minecraft multiplayer servers. It is neither the official modding Application Programming Interface (API) nor supported by Mojang, yet. However it is one of the most popular way to build server-side plugins, which are plugins that only work on servers. That means you cannot change any client-side functionality using Bukkit.
The vanilla'' or un-modded server as downloaded from http://minecraft.net does not understand the plugins built using Bukkit API. CraftBukkit is a
modified'' or ``modded'' server that does understand this API. It is available for download at https://dl.bukkit.org/downloads/craftbukkit/ at no cost at all. So plugin builders use Bukkit API to build plugins and install them on their CraftBukkit server.
Multiple plugins with different functionalities can be easily installed on CraftBukkit server. Multiple clients can then connect to this server and all use the same plugins as shown in Bukkit Plugins.
This book will teach you how to build Bukkit plugins and install them on CraftBukkit server.
Which CraftBukkit build to use ?
CraftBukkit can be downloaded from https://dl.bukkit.org/downloads/craftbukkit/. It is typically available in three different builds:
-
Recommended
-
Beta
-
Development
These builds differ in terms of what version of Minecraft is supported and what items, blocks, and entities can be changed. For example, the support at the time of this writing is shown in Bukkit Builds.
| Build | Version |
|---|---|
Recommended |
1.6.4-R2.0 |
Beta |
1.7.9-R0.2 |