An implementation of the Bukkit plugin API for Minecraft servers, currently maintained by SpigotMC.
The development team is very open to both bug and feature requests / suggestions. You can submit these on the JIRA Issue Tracker.
CraftBukkit is a Java program which uses Maven 3 for compilation. To compile fresh from Git, simply perform the following steps:
Some IDEs such as NetBeans can perform these steps for you. Any Maven capable Java IDE can be used to develop with CraftBukkit, however the current team's personal preference is to use NetBeans.
Contributions of all sorts are welcome. To manage community contributions, we use the pull request functionality of Stash. In to gain access to Stash and create a pull request, you will first need to perform the following steps:
Once you have performed these steps you can create a fork, push your code changes, and then submit it for review.
If you submit a PR involving both Bukkit and CraftBukkit, each PR should link the other.
Although the minimum requirement for compilation & usage is Java 8, we prefer all contributions to be written in Java 7 style code unless there is a compelling reason otherwise.
Bukkit/CraftBukkit employs JUnit 4 for testing. Pull Requests(PR) should attempt to integrate within that framework as appropriate. Bukkit is a large project and what seems simple to a PR author at the time of writing may easily be overlooked by other authors and updates. Including unit tests with your PR will help to ensure the PR can be easily maintained over time and encourage the Spigot team to pull the PR.
Any questions about these requirements can be asked in #spigot-dev in IRC.
Any time new patches are created and/or updated in CraftBukkit, you need to apply them to your development environment.
applyPatches.sh
script in the CraftBukkit directory.
Importing new NMS classes to CraftBukkit is actually very simple.
work/decompile-XXXXXX
folder in your BuildTools folder.net/minecraft/server
folder and copy it.src/main/java/net/minecraft/server
folder in CraftBukkit.makePatches.sh
to create a new patch for the new class.
Done! You have added a new patch for CraftBukkit!
Making Changes to NMS Classes
Bukkit/CB employs a Minimal Diff policy to help guide when changes should be changed to Minecraft and what those changes should be.
This is to ensure that any changes have the smallest impact possible on the update process whenever a new Minecraft version is released.
As well as the Minimal Diff Policy, every change made to a Minecraft class must be marked with the appropriate CraftBukkit comment.
At no point should you rename an existing/obfuscated field or method. All references to existing/obfusacted fields/methods should be marked with the // PAIL rename
comment.
Mapping of new fields/methods are done when there are enough changes to warrant the work. (Any questions can be asked in #spigot-dev in IRC)
Key Points:
PAIL rename
commentThe Minimal Diff Policy is key to any changes made within Minecraft classes. When people think of the phrase "minimal diffs", they often take it to the extreme - they go completely out of their way to abstract the changes they are trying to make away from editing Minecraft's classes as much as possible. However, this is not what is meant by "minimal diffs". Instead, when trying to understand this policy, it helps to keep in mind its goal: to reduce the impact of changes we make to Minecraft's internals have on our update process.
To put it simply, the Minimal Diffs Policy simply means to make the smallest change in a Minecraft class possible without duplicating logic.
Here are a few tips you should keep in mind, or common areas you should focus on:
For example, to short circuit this:
if (!this.world.isClientSide && !this.isDead && (d0*d0) + d1 + (d2*d2) > 0.0D) {
this.die();
this.h();
}
You would do this:
if (false && !this.world.isClientSide && !this.isDead && (d0*d0) + d1 + (d2*d2) > 0.0D) {
this.die();
this.h();
}
For example, you should use:
Validate.notNull(sender, "Sender cannot be null");
Instead of:
if (sender == null) {
throw new IllegalArgumentException("Sender cannot be null");
}
For example:
// CraftBukkit start - special case dropping so we can get info from the tile entity
public void dropNaturally(World world, int i, int j, int k, int l, float f, int i1) {
if (world.random.nextFloat() < f) {
ItemStack itemstack = new ItemStack(Item.SKULL, 1, this.getDropData(world, i, j, k));
TileEntitySkull tileentityskull = (TileEntitySkull) world.getTileEntity(i, j, k);
if (tileentityskull.getSkullType() == 3 && tileentityskull.getExtraType() != null && tileentityskull.getExtraType().length() > 0) {
itemstack.setTag(new NBTTagCompound());
itemstack.getTag().setString("SkullOwner", tileentityskull.getExtraType());
}
this.b(world, i, j, k, itemstack);
}
}
// CraftBukkit end
public void remove(World world, int i, int j, int k, int l, int i1) {
if (!world.isStatic) {
/* CraftBukkit start - drop item in code above, not here
if ((i1 & 8) == 0) {
ItemStack itemstack = new ItemStack(Item.SKULL, 1, this.getDropData(world, i, j, k));
TileEntitySkull tileentityskull = (TileEntitySkull) world.getTileEntity(i, j, k);
if (tileentityskull.getSkullType() == 3 && tileentityskull.getExtraType() != null && tileentityskull.getExtraType().length() > 0) {
itemstack.setTag(new NBTTagCompound());
itemstack.getTag().setString("SkullOwner", tileentityskull.getExtraType());
}
this.b(world, i, j, k, itemstack);
}
// CraftBukkit end */
super.remove(world, i, j, k, l, i1);
}
}
Changes to a Minecraft class should be clearly marked using CraftBukkit comments.
Examples:
If the change is obvious, then you need a simple end of line comment.
if (true || minecraftserver.getAllowNether()) { // CraftBukkit
Every reference to an obfuscated field/method in NMS should be marked with:
// PAIL rename newName
All PAIL rename comments must include a new name.
If, however, the change is something important to note or difficult to discern, you should include a reason at the end of the comment
public int fireTicks; // PAIL private -> public
Changing access levels must include a PAIL comment indicating the previous access level and the new access level.
If adding the CraftBukkit comment negatively affects the readability of the code, then you should place the comment on a new line above the change you made.
// CraftBukkit
if (!isEffect && !world.isStatic && world.difficulty >= 2 && world.areChunksLoaded(MathHelper.floor(d0), MathHelper.floor(d1), MathHelper.floor(d2), 10)) {
Example:
The majority of the time, multi-line changes should be accompanied by a reason since they're usually much more complicated than a single line change. If the change is something important to note or difficult to discern, you should include a reason at the end of line CraftBukkit comment.
// CraftBukkit start - special case dropping so we can get info from the tile entity
public void dropNaturally(World world, int i, int j, int k, int l, float f, int i1) {
if (world.random.nextFloat() < f) {
ItemStack itemstack = new ItemStack(Item.SKULL, 1, this.getDropData(world, i, j, k));
TileEntitySkull tileentityskull = (TileEntitySkull) world.getTileEntity(i, j, k);
if (tileentityskull.getSkullType() == 3 && tileentityskull.getExtraType() != null && tileentityskull.getExtraType().length() > 0) {
itemstack.setTag(new NBTTagCompound());
itemstack.getTag().setString("SkullOwner", tileentityskull.getExtraType());
}
this.b(world, i, j, k, itemstack);
}
}
// CraftBukkit end
Otherwise, if the change is obvious, such as firing an event, then you can simply use a multi-line comment.
// CraftBukkit start
BlockIgniteEvent event = new BlockIgniteEvent(this.cworld.getBlockAt(i, j, k), BlockIgniteEvent.IgniteCause.LIGHTNING, null);
world.getServer().getPluginManager().callEvent(event);
if (!event.isCancelled()) {
world.setTypeIdUpdate(i, j, k, Block.FIRE);
}
// CraftBukkit end
Imports in Minecraft Classes
To learn what Spigot expects of a Pull Request please view the Contributing guidelines