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 , which represent the data of a
particular item. However, the process can be complex and error-prone.
To simplify this task, this post will show you how to develop a custom Bukkit
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
class is going to be an abstract class which extends , take a look at this example:
public abstract class ItemBuilder<T extends ItemBuilder<?, ?>, M extends ItemMeta> {}
This is a declaration of an abstract Java class named . It uses generics and is part of a builder pattern.
with wildcard arguments and .
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 has , we will also have 2 predicates that store booleans.
We must also check what kind of it is, regular or , this is important because obviously this is just an
abstract class.
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;
}
}
So obviously our 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 . Each of
these methods
will return the instance,
allowing us to chain these methods in a fluent interface style.
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.
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.
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.
As we are pretty much finished with
the
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 .
We
will start of with the simple .
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 , but we
wouldn't make the an
abstract class just to have one other class extending it.
Now we will create the
class, this class will extend our ItemBuilder class and will
have a few methods that allow us to set the and
because it extends it can
also use all of the methods in there.
We need to start by getting a to
using
reflection to retrieve the setProfile() method to use later.
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 which we will need to invoke later.
Now we can add our constructor:
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 .
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()));
}
});
}
}
Well it looks like our classes are
finally complete, so lets see an example of how you can use
them:
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();
}
}
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