How can I correctly set a Dye into an ItemStack?

José Roberto Araújo Júnior picture José Roberto Araújo Júnior · Jul 13, 2014 · Viewed 8.3k times · Source

I'm trying to apply a Dye color into an existing ItemStack, how can I do this without using deprecated methods and creating an new stack?

I tried the following code but it's resulting in a normal ink sack.

ps: I'm creating an stack in the first line only as example.

final ItemStack stack = new ItemStack(Material.INK_SACK);
Dye dye = new Dye();
dye.setColor(DyeColor.LIME);
stack.setData(dye);

edit: Added final to stack variable to show that it cannot be replaced by a new stack.

Answer

CrypticStorm picture CrypticStorm · Jul 21, 2014

I've found a way to do this, but it is nowhere near as efficient as using a deprecated method. Here it is with my own personal thought process along the way.

The problem with your current attempt is the interior of the setData(MaterialData) method.

public void setData(MaterialData data) {
    Material mat = getType();

    if (data == null || mat == null || mat.getData() == null) {
        this.data = data;
    } else {
        if ((data.getClass() == mat.getData()) || (data.getClass() == MaterialData.class)) {
            this.data = data;
        } else {
            throw new IllegalArgumentException("Provided data is not of type " + mat.getData().getName() + ", found " + data.getClass().getName());
        }
    }
}

This method works as a simple setter that handles null values and prevents incorrect data types. The problem here should be quite visible though. Wools and dyes don't store their info in MaterialData, they store it in their durability. The durability of a Colorable object determines its color. Being that this is the case, you will need to modify the durability in order to do anything to the color. The only place you can set the durability outside of the constructor is the setDurability method. This doesn't take a MaterialData, only a short. Thus our options are the following: Construct a new ItemStack, or gain non-deprecated access to a usable short value. By your criteria, we move to the latter.

First point of entry, the DyeColor class. If we can't find the short value here, it will at least be supplied from here to another class. Looking at the source gives us a grim reminder of just how rooted in deprecation the problem is.

There is one hacky solution using DyeColor alone, but it's not version proof incase of changes. The Enum.ordinal() method will return the ordinal value of an enum. This is the numerical value of the order it was defined. Starting from index zero, it will match the wool data values, but needs to be inverted for dyes, something you are trying to make universal. The option is there though.

So DyeColor is out of the question, but it got me thinking. The Dye class as a MaterialData works perfectly fine when using the toItemStack() method. Maybe we could use that. Turns out you can! It will require creating a new Itemstack, but we will use the new ItemStack to access data for the original one. Firstly, we create the MaterialData like you did above.

Dye dye = new Dye();
dye.setColor(DyeColor.LIME);

Next, convert the Dye into an Itemstack using the toItemStack() method.

ItemStack tempStack = dye.toItemStack();

Now hold on, why does this work? Well you see, the Dye.setColor(DyeColor) method has an internal implementation of the following:

public void setColor(DyeColor color) {
    setData(color.getDyeData());
}

It uses a deprecated method, but because it's wrapped in a DyeColor call, it's not deprecated to the plugin user. Please note: THIS IS PERFECTLY ACCEPTABLE! Many of Bukkit's calls using Material as a parameter actually just call the deprecated method with the type id associated to it. It's perfectly valid to call those methods though. Also note that the setData(byte) method is just a simple setter.

Next up, the toItemStack() method.

public ItemStack toItemStack(int amount) {
    return new ItemStack(type, amount, data);
}

We can see here that the DyeColor that was converted to a byte is now sent to the ItemStack constructor ItemStack(Material, int, short) as the durability (it automatically casts to a short). This means, we now have the short we require! It's stored in the durability of tempStack. To top it all off, we do the following.

stack.setDurability(tempStack.getDurability());
stack.setData(dye);

It's over. You have a modified ItemStack with no exposed deprecated methods. You may be asking though why I still call ItemStack.setData(MaterialData). This just ensures that if someone tries to access the DyeColor from the ItemStack's MaterialData it will match up with the durability.

I hope you are satisfied with the fact that it is possible, but I still just recommend using the deprecated methods until they are listed as broken (which usually doesn't happen with Bukkit).