PaperMC/paper-server
CraftBukkit/Spigot 0076297721 Safer JSON Loading
By: Suddenly <suddenly@suddenly.coffee>
2014-07-08 09:44:18 +10:00
..
patches/sources Safer JSON Loading 2014-07-08 09:44:18 +10:00
src Further Seed Customisation 2014-07-04 13:28:45 +10:00
.gitignore Add eclipse .factorypath file to .gitignore 2023-11-09 10:20:31 +01:00
applyPatches.sh Repackage NMS 2021-03-16 09:00:00 +11:00
checkstyle.xml POM Changes 2013-07-02 13:07:39 +10:00
CONTRIBUTING.md Add note to CONTRIBUTING.md to suggest keeping commit messages / titles the same 2020-08-27 11:46:34 +10:00
LGPL.txt
LICENCE.txt
makePatches.sh Increase diff stability 2023-09-22 02:57:13 +10:00
pom.xml POM Changes 2013-07-02 13:07:39 +10:00
README.md #1473: Change tests to use suites, to run tests in different environments and feature flags 2024-09-26 20:01:56 +10:00

CraftBukkit

An implementation of the Bukkit plugin API for Minecraft servers, currently maintained by SpigotMC.

Index

Bug Reporting

The development team is very open to both bug and feature requests / suggestions. You can submit these on the JIRA Issue Tracker.

Compilation

CraftBukkit is a Java program which uses Maven 3 for compilation. To compile fresh from Git, simply perform the following steps:

  • Install Git using your preferred installation methods.
  • Download and run BuildTools

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.

Contributing

Contributions of all sorts are welcome. To manage community contributions, we use the pull request functionality of Stash. In order to gain access to Stash and create a pull request, you will first need to perform the following steps:

  • Create an account on JIRA.
  • Fill in the SpigotMC CLA and wait up to 24 hours for your Stash account to be activated. Please ensure that your username and email addresses match.
  • Log into Stash using your JIRA credentials.

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.

The minimum requirement for style, compilation & usage is Java 16 unless there is a compelling reason.

Code Requirements

  • For the most part, CraftBukkit and Bukkit use the Sun/Oracle coding standards.
  • No tabs; use 4 spaces instead.
    • Empty lines should contain no spaces.
  • No trailing whitespaces.
  • No 80 character column limit, or 'weird' mid-statement newlines unless absolutely necessary.
    • The 80 character column limit still applies to documentation.
  • No one-line methods.
  • All major additions should have documentation.
  • Try to follow test driven development where available.
  • All code should be free of magic values. If this is not possible, it should be marked with a TODO comment indicating it should be addressed in the future.
    • If magic values are absolutely necessary for your change, what those values represent should be documented in the code as well as an explanation in the Pull Request description on why those values are necessary.
  • No unnecessary code changes. Look through all your changes before you submit it.
  • Do not attempt to fix multiple problems with a single patch or pull request.
  • Do not submit your personal changes to configuration files.
  • Avoid moving or renaming classes.

Bukkit/CraftBukkit employs JUnit 5 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. CraftBukkit provides multiple environments for testing, which init the server to various states. Each test needs to have an environment, either by setting it to the whole class or the individual test methods. A test can have multiple environments. You can see the available test environments in src/test/java/org.bukkit.support.environment.

  • There needs to be a new line at the end of every file.
  • Imports should be organised in a logical manner.
    • Do not group packages
    • All new imports should be within existing CraftBukkit comments if any are present. If not, make them.
    • Absolutely no wildcard imports outside of tests.
    • If you only use an import once, don't import it. Use the fully qualified name.

Any questions about these requirements can be asked in #help-development in Discord.

Applying Patches

Any time new patches are created and/or updated in CraftBukkit, you need to apply them to your development environment.

  1. Pull changes from CraftBukkit repo.
  2. Run the applyPatches.sh script in the CraftBukkit directory.
    • This script requires a decompile directory from BuildTools. (e.g. /work/decompile-XXXXXX)
  3. Your development environment is now up to date with the latest CraftBukkit patches!

Making Changes to Minecraft

Importing new NMS classes to CraftBukkit is actually very simple.

  1. Find the work/decompile-XXXXXX folder in your BuildTools folder.
  2. Find the class you want to add in the net/minecraft/server folder and copy it.
  3. Copy the selected file to the src/main/java/net/minecraft/server folder in CraftBukkit.
  4. Implement changes.
  5. Run makePatches.sh to create a new patch for the new class.
    • Be sure that Git recognizes the new file. This might require manually adding the file.
  6. Commit new patch.

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 our Discord)

Key Points:

  • All additions to patches must be accompanied by an appropriate comment.
  • To avoid large patches, avoid adding methods where possible. We prefer making fields public over adding methods in patches.
    • If you have to add a method to a patch, please explain why in the Pull Request description.
  • Never rename an existing field or method. If you want something renamed, include a PAIL rename comment
  • Converting a method/class from one access level to another (i.e. private to public) is fine as long as that method is not overridden in subclasses.
    • If a method is overridden in its' subclasses, create a new method that calls that method instead (along with appropriate CraftBukkit comments).
  • If you can use a field to accomplish something, use that over creating a new method.

Minimal Diff Policy

The 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:

  • Try to avoid duplicating logic or code when making changes.
  • Try to keep your changes easily discernible - don't nest or group several unrelated changes together.
  • If you only use an import once within a class, don't import it and use a fully qualified name instead.
  • Try to employ "short-circuiting" of logic if at all possible. This means you should force a conditional to be the value needed to side step the code block to achieve your desired effect.

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 checks related to API where an exception needs to be thrown in a specific case we recommend use the package Preconditions
    • For checking arguments we recommend using Preconditions#checkArgument where a failing check should throw a IllegalArgumentException
      • We recommend using this to ensure the behaviour of @NotNull in the Bukkit API
    • For checking arguments we recommend using Preconditions#checkState where a failing check should throw a IllegalStateException

For example, you should use:

private Object messenger;

public void sendMessage(Sender sender, String message) {
    Preconditions.checkArgument(sender != null, "sender cannot be null");
    Preconditions.checkState(this.messenger != null, "The messenger instance cannot be used")
}

Instead of:

private Object messenger;

public void sendMessage(Sender sender, String message) {
    if (sender == null) {
        throw new IllegalArgumentException("Sender cannot be null");
    }

    if (this.messenger == null) {
        throw new IllegalStateException("The messenger instance cannot be used");
    }
}
  • When the change you are trying to make involves removing code, or delegating it somewhere else, instead of removing it, you should comment it out.

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);
    }
}

CraftBukkit Comments

Changes to a Minecraft class should be clearly marked using CraftBukkit comments.

  • All CraftBukkit comments should be capitalised appropriately. (i.e. CraftBukkit, not CB/craftBukkit, etc.)
  • If the change only affects one line of code, use an end of line CraftBukkit comment

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)) {
  • If the change affects more than one line, you should use a multi-line CraftBukkit comment.

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
  • All CraftBukkit comments should be on the same indentation level the code block it is in.

Imports in Minecraft Classes

  • Do not remove unused imports if they are not marked by CraftBukkit comments.

Creating Pull Requests

To learn what Spigot expects of a Pull Request please view the Contributing guidelines

Useful Resources