Wockstar

A custom Bukkit ItemStack builder to create ItemStacks with ease.

Wockhardt Wizard (github) April 10th 2024 • 23 minute read • Java (Advanced)

Resources

ItemBuilder.java class
RegularItemBuilder.java class
SkullItemBuilder.java class

Introduction

In the world of Minecraft server development, creating custom items can be a challenging task. This is especially true when working with Bukkit. One of the most common tasks is creating an ItemStack, which represent the data of a particular item. However, the process can be complex and error-prone.

Beginning

To simplify this task, this post will show you how to develop a custom Bukkit ItemStack builder. Whether you're a seasoned Minecraft developer or a beginner just dipping your toes into the world of plugin development, this class will make your life easier.

This post will just overview coding the class, if you are not sure on how to even set up a project for plugin development, you will need to start somewhere else.

So how this works; the ItemBuilder class is going to be an abstract class which extends ItemMeta, take a look at this example:

ItemBuilder.java

public abstract class ItemBuilder<T extends ItemBuilder<?, ?>, M extends ItemMeta> {}
    
This is a declaration of an abstract Java class named ItemBuilder. It uses generics and is part of a builder pattern. T extends ItemBuilder with wildcard arguments and M extends ItemMeta.

Constructor

We must now create a constructor for this class, we will have a few variables which are populated when our constructor is called, we will store the current class, the ItemMeta class, and a boolean to check if the ItemStack has ItemMeta, we will also have 2 predicates that store booleans. We must also check what kind of ItemMeta it is, regular ItemMeta or SkullMeta, this is important because obviously this is just an abstract class.

ItemBuilder.java

public abstract class ItemBuilder<T extends ItemBuilder<?, ?>, M extends ItemMeta> {

    private final Class<T> thisClass;
    private final Class<M> metaClass;
    private final ItemStack item;
    boolean hasItemMeta;
        
    private static final Predicate<ItemBuilder<?, ?>> CONST_TRUE_PRED = builder -> true;
    private static final Predicate<ItemBuilder<?, ?>> CONST_FALSE_PRED = builder -> false;
        
    public ItemBuilder(Class<T> thisClass, Class<M> metaClass, ItemStack item) {
        
        // Null Checks
        if (thisClass == null) {
            throw new NullPointerException("The provided class is null");
        }

        if (metaClass == null) {
            throw new NullPointerException("The provided class is null");
        }

        if (item == null) {
            throw new NullPointerException("The provided item is null");
        }

        // ItemMeta Check
        if (item.hasItemMeta()) {
            this.hasItemMeta = false;
        } else {
            if (!metaClass.isInstance(item.getItemMeta())) {
                throw new IllegalArgumentException(String.format("The provided item has an invalid
                ItemMeta, %s is not an instance of %s",
                Objects.requireNonNull(item.getItemMeta()).getClass().getName(),
                metaClass.getName()));
            }
        this.hasItemMeta = true;
        }

        // Assigning Variables
        this.thisClass = thisClass;
        this.metaClass = metaClass;
        this.item = item;
    }
}

Adding Methods

So obviously our ItemBuilder can now be extended but you wont really be able to do anything with it, so lets add some methods that allow us to set various attributes. I won't add everything for the sake of simplicity but as stated before, you can find a copy of the class at the top of the post, or click here. Each of these methods will return the ItemBuilder instance, allowing us to chain these methods in a fluent interface style.

ItemBuilder.java

public abstract class ItemBuilder<T extends ItemBuilder<?, ?>, M extends ItemMeta> {
    
    // Variables from before

    // Constructor from before

    // Set an amount on the ItemStack
    public T withAmount(int amount) {
        this.item.setAmount(amount);
        return thisClass.cast(this);
    }

    // Add a custom display name
    public T withDisplayName(String displayName) {
        if (!this.hasItemMeta) {
            throw new IllegalStateException("This method requires items that have an ItemMeta");
    }

    return this.withProperties(meta -> meta.setDisplayName(ChatColor.translateAlternateColorCodes(
        '&', displayName == null ? " " : displayName)));
    }

    // Add a list of lore
    public T withLore(List<String> lore) {
        if (!this.hasItemMeta) {
        throw new IllegalStateException("This method requires items that have an
        ItemMeta");
    }

    if (lore != null) {
        lore = lore.stream().map(line -> ChatColor.translateAlternateColorCodes('&', line)).collect(Collectors.toList());
    }

    List<String> finalLore = lore;
        return this.withProperties(meta -> meta.setLore(
        finalLore == null ? Collections.emptyList() : finalLore));
    }

    // Another way to add the lore (Array)
    public T withLore(String... lines) {
        return this.withLore(lines == null ? null : Arrays.asList(lines));
    }

    // Another way to add the lore (String)
    public T withLore(String firstLine) {
        return this.withLore(
        firstLine == null ? null : Collections.singletonList(firstLine));
    }
}
    

This is an example of the basic methods you need to hit the road running, but you may remember from earlier that we defined some predicates, lets say you wanted to apply an attribute in some cases and then not in other, we will create a method to do just that.

ItemBuilder.java

public abstract class ItemBuilder<T extends ItemBuilder<?, ?>, M extends ItemMeta> {
    
    // Variables from before

    // Constructor from before

    // Other methods from before

    // Apply an attribute if a predicate is true
    public T applyIf(Predicate<ItemBuilder<?, ?>> test, Consumer<ItemBuilder<?, ?>> application) {
        if (test.test(this)) {
            application.accept(this);
        }
        return (T) this;
    }

    // Apply an attribute if a boolean is true
    public T applyIf(boolean condition, Consumer<ItemBuilder<?, ?>> application) {
        return applyIf(condition ? CONST_TRUE_PRED : CONST_FALSE_PRED, application);
    }
}

And of course we need a method which actually returns an ItemStack with the given attributes.

ItemBuilder.java

public abstract class ItemBuilder<T extends ItemBuilder<?, ?>, M extends ItemMeta> {
    
    // Variables from before

    // Constructor from before

    // Other methods from before

    public ItemStack result() {
        return this.item.clone();
    }
}
        

And just like that you can call the applyIf() method if you need to apply a attribute according to a boolean.

Extending ItemBuilder

As we are pretty much finished with the ItemBuilder class, feel free to add methods to implement the addition of Enchantments or ItemFlags or PDC etc.. Now we will create the classes that extend our ItemBuilder. We will start of with the simple RegularItemBuilder.

RegularItemBuilder.java

public final class RegularItemBuilder extends ItemBuilder<RegularItemBuilder, ItemMeta> {

    // Create an instance from an ItemStack
    public RegularItemBuilder(ItemStack item) {
        super(RegularItemBuilder.class, ItemMeta.class, item);
    }
    
    // Create an instance from a Material
    public RegularItemBuilder(Material material) {
        this(new ItemStack(material));
    }
}

And just like that you should have a working ItemBuilder, but we wouldn't make the ItemBuilder an abstract class just to have one other class extending it.

SkullItemBuilder

Now we will create the SkullItemBuilder class, this class will extend our ItemBuilder class and will have a few methods that allow us to set the SkullMeta and because it extends ItemBuilder it can also use all of the methods in there.

We need to start by getting a MethodHandle to SkullMeta using reflection to retrieve the setProfile() method to use later.

SkullItemBuilder.java

public final class SkullItemBuilder extends ItemBuilder<SkullItemBuilder, SkullMeta> {
    
    private static final MethodHandle CRAFT_META_SKULL_SET_PROFILE_METHOD_HANDLE;
            
    static {
        MethodType type = MethodType.methodType(void.class, GameProfile.class);
        MethodHandles.Lookup lookup = MethodHandles.lookup();

        try {
            CRAFT_META_SKULL_SET_PROFILE_METHOD_HANDLE = lookup.findVirtual(Objects.requireNonNull(Bukkit.getItemFactory().getItemMeta(Material.PLAYER_HEAD)).getClass(), "setProfile", type);
        } catch (Throwable throwable) {
            throw new RuntimeException(throwable);
        }
    }
}

The code above finds the method and sets it to the CRAFT_META_SKULL_SET_PROFILE_METHOD_HANDLE MethodHandle which we will need to invoke later. Now we can add our constructor:

SkullItemBuilder.java

public final class SkullItemBuilder extends ItemBuilder<SkullItemBuilder, SkullMeta> {
 
    // Variable from before
            
    // MethodHandle from before

    public SkullItemBuilder(ItemStack item) {
        super(SkullItemBuilder.class, SkullMeta.class, item);
    }
        
    public SkullItemBuilder() {
        this(new ItemStack(Material.PLAYER_HEAD));
    }
}

And now we can add the methods to copy the texture from a player or using a base64 encoded string, which means you can add custom heads from head databases. Note: This does use NMS.

SkullItemBuilder.java

public final class SkullItemBuilder extends ItemBuilder<SkullItemBuilder, SkullMeta> {

    // Variable from before

    // MethodHandle from before

    // Constructors from before

    // Copy the texture from a player wearing a skin using OfflinePlayer
    public SkullItemBuilder copyTextures(OfflinePlayer player) {
        return this.withProperties(meta -> meta.setOwningPlayer(player));
    }

    // Copy the texture from a player wearing a skin using their UUID
    public SkullItemBuilder copyTextures(UUID playerUUID) {
        return this.copyTextures(Bukkit.getOfflinePlayer(playerUUID));
    }

    // Copy the texture from a base64 string
    public SkullItemBuilder setTextures(String base64Textures) {
        return this.withProperties(meta -> {
            // Get the GameProfile and PropertyMap
            GameProfile textureHolderProfile = new GameProfile(UUID.randomUUID(), String.valueOf(new Random().nextInt()));
            PropertyMap textureHolderMap = textureHolderProfile.getProperties();

            Property texture = new Property("textures", base64Textures);

            // Add our custom texture from the base64 string
            textureHolderMap.removeAll("textures");
            textureHolderMap.put("textures", texture);

            // Get the SkullMeta class
            Class<?> metaClass = meta.getClass();

            // Try invoke the method in the SkullMeta class to set the
            texture try { CRAFT_META_SKULL_SET_PROFILE_METHOD_HANDLE.invoke(meta, textureHolderProfile); } catch (Throwable throwable) {
                throw new RuntimeException(String.format("Exception encountered while invoking %s.setProfile(GameProfile) ",
                      metaClass.getName()));
            }
        });
    }
}

Outcome

Well it looks like our classes are finally complete, so lets see an example of how you can use them:

SkullItemBuilder.java

public final class SkullItemBuilder extends ItemBuilder<SkullItemBuilder, SkullMeta> {
 
    // Variable from before
            
    // MethodHandle from before

    public SkullItemBuilder(ItemStack item) {
        super(SkullItemBuilder.class, SkullMeta.class, item);
    }
        
    public SkullItemBuilder() {
        this(new ItemStack(Material.PLAYER_HEAD));
    }
}

public class CustomItem() {

    public ItemStack builderExamples() {
        return new SkullItemBuilder()
                .withDisplayName("Custom Item")
                .withLore("This is a custom item")
                .copyTextures(UUID.fromString("f5c6d4b4-3e1c-4f6e-8c7d-7e3f3e3e3e3e"))
                .result();

        return new RegularItemBuilder(Material.DIAMOND_SWORD)
                .withAmount(1)
                .withDisplayName(name)
                .withLore(lore)
                .result();
    }
}

Ending

And that's it! You have now created a custom Bukkit ItemStack builder that allows you to create ItemStacks with ease. This class can be used in any of your plugins.

If you found this post helpful or learned something new, consider supporting me. Your donations help me continue to create and share content like this. Every contribution, no matter how big or small, is appreciated. Thank you for your support!

Bitcoin: 1NULLXYYLMy77Np8DMfCqUcNPDhVP3K9QJ
Litecoin: LWihbiZd7F2cRcSAbC8ptJJJu3V2ZD844Q
Etherium: 0x65bB26e9Ea91A4D958e47F9dd86999aeCD65815D
Monero: 46rgFADhcTbUrgG9FGq2RyTj1M69mq8qq9QC6EnSoNoN34vGcPqDxUYQpwZdkT3kxCMJRoeVTCyohUAhVq8auut7HDR2GNt
Dogecoin: DEMcjLL3idCygT9bp4ebfqFnu3dah6gpfD
Tether USDT: 0x65bB26e9Ea91A4D958e47F9dd86999aeCD65815D
Solana: 139iYdXNNm8gotaKE5kvUo2nEfZYBr2SZVGhzPMkV6Nf