Skip to content

Instantly share code, notes, and snippets.

@I-Al-Istannen
Created August 24, 2016 19:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save I-Al-Istannen/815af277ade97261812a1c1d2fb169f0 to your computer and use it in GitHub Desktop.
Save I-Al-Istannen/815af277ade97261812a1c1d2fb169f0 to your computer and use it in GitHub Desktop.
A small utility to help with NBT
package me.ialistannen.bukkittoyaround;
import org.bukkit.inventory.ItemStack;
/**
* A Util to save NBT data to ItemStacks
*/
@SuppressWarnings({"unused", "WeakerAccess"})
public class ItemNBTUtil {
private static final Class<?> CRAFT_ITEM_STACK_CLASS = ReflectionUtil
.getCraftbukkitClass("CraftItemStack", "inventory");
/**
* @param itemStack The {@link ItemStack} to convert
*
* @return The NMS Item stack
*/
private static Object asNMSCopy(ItemStack itemStack) {
return ReflectionUtil.invokeMethod(CRAFT_ITEM_STACK_CLASS, null, "asNMSCopy",
new Class[]{ItemStack.class}, itemStack);
}
/**
* Only pass a NMS Itemstack!
*
* @param nmsItem The NMS item to convert
*
* @return The converted Item
*/
private static ItemStack asBukkitCopy(Object nmsItem) {
return (ItemStack) ReflectionUtil.invokeMethod(CRAFT_ITEM_STACK_CLASS, null, "asBukkitCopy",
new Class[]{ReflectionUtil.getNMSClass("ItemStack")}, nmsItem);
}
/**
* Sets the NBT tag of an item
*
* @param tag The new tag
* @param itemStack The ItemStack
*
* @return The modified itemStack
*/
public static ItemStack setNBTTag(NBTWrappers.NBTTagCompound tag, ItemStack itemStack) {
Object nbtTag = tag.toNBT();
Object nmsItem = asNMSCopy(itemStack);
ReflectionUtil.invokeMethod(nmsItem, "setTag",
new Class[]{ReflectionUtil.getNMSClass("NBTTagCompound")}, nbtTag);
ItemStack item = asBukkitCopy(nmsItem);
return item;
}
/**
* Gets the NBTTag of an item. In case of any error it returns a blank one.
*
* @param itemStack The ItemStack to get the tag for
*
* @return The NBTTagCompound of the ItemStack or a new one if it had none or an error occurred
*/
public static NBTWrappers.NBTTagCompound getTag(ItemStack itemStack) {
Object nmsItem = asNMSCopy(itemStack);
Object tag = ReflectionUtil.invokeMethod(nmsItem, "getTag", new Class[0]);
if (tag == null) {
return new NBTWrappers.NBTTagCompound();
}
NBTWrappers.INBTBase base = NBTWrappers.INBTBase.fromNBT(tag);
if (base == null || base.getClass() != NBTWrappers.NBTTagCompound.class) {
return new NBTWrappers.NBTTagCompound();
}
return (NBTWrappers.NBTTagCompound) base;
}
}
package me.ialistannen.bukkittoyaround;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Provides wrapper objects to abstract the NBT versions. Probably way too complicated...
*/
@SuppressWarnings({"unused", "WeakerAccess"})
public class NBTWrappers {
/**
* A base class for the essential methods
*/
public static abstract class INBTBase {
public INBTBase() {
}
abstract Object toNBT();
/**
* @param nbtObject The NBT object
*
* @return The correct {@link INBTBase} or null if the tag is not supported
*/
public static INBTBase fromNBT(Object nbtObject) {
switch (nbtObject.getClass().getSimpleName()) {
case "NBTTagByte": {
return NBTTagByte.fromNBT(nbtObject);
}
case "NBTTagShort": {
return NBTTagShort.fromNBT(nbtObject);
}
case "NBTTagInt": {
return NBTTagInt.fromNBT(nbtObject);
}
case "NBTTagLong": {
return NBTTagLong.fromNBT(nbtObject);
}
case "NBTTagFloat": {
return NBTTagFloat.fromNBT(nbtObject);
}
case "NBTTagDouble": {
return NBTTagDouble.fromNBT(nbtObject);
}
case "NBTTagByteArray": {
return NBTTagByteArray.fromNBT(nbtObject);
}
case "NBTTagIntArray": {
return NBTTagIntArray.fromNBT(nbtObject);
}
case "NBTTagString": {
return NBTTagString.fromNBT(nbtObject);
}
case "NBTTagCompound": {
return NBTTagCompound.fromNBT(nbtObject);
}
case "NBTTagList": {
return NBTTagList.fromNBT(nbtObject);
}
}
return null;
}
}
/**
* A NBTTagString
*/
public static class NBTTagString extends INBTBase {
private static final Constructor<?> NBT_TAG_STRING_CONSTRUCTOR = ReflectionUtil
.getConstructor(ReflectionUtil.getNMSClass("NBTTagString"), String.class);
private String string;
/**
* @param string The String value
*/
public NBTTagString(String string) {
this.string = string;
}
/**
* @param string The new value
*/
public void setString(String string) {
this.string = string;
}
/**
* @return The String value
*/
public String getString() {
return string;
}
@Override
public Object toNBT() {
return ReflectionUtil.instantiate(NBT_TAG_STRING_CONSTRUCTOR, getString());
}
public static INBTBase fromNBT(Object nbtObject) {
return new NBTTagString((String) ReflectionUtil.getInstanceField(nbtObject, "data"));
}
@Override
public String toString() {
return "NBTTagString{" +
"string='" + string + '\'' +
'}';
}
}
/**
* A NBTTagCompound
*/
public static class NBTTagCompound extends INBTBase {
private static final Constructor<?> NBT_TAG_COMPOUND_CONSTRUCTOR = ReflectionUtil.getConstructor(
ReflectionUtil.getNMSClass("NBTTagCompound")
);
private Map<String, INBTBase> map = new HashMap<>();
public void set(String key, INBTBase value) {
map.put(key, value);
}
public void setByte(String key, byte value) {
map.put(key, new NBTTagByte(value));
}
public void setShort(String key, short value) {
map.put(key, new NBTTagShort(value));
}
public void setInt(String key, int value) {
map.put(key, new NBTTagInt(value));
}
public void setLong(String key, long value) {
map.put(key, new NBTTagLong(value));
}
public void setFloat(String key, float value) {
map.put(key, new NBTTagFloat(value));
}
public void setDouble(String key, double value) {
map.put(key, new NBTTagDouble(value));
}
public void setString(String key, String value) {
map.put(key, new NBTTagString(value));
}
public void setByteArray(String key, byte[] value) {
map.put(key, new NBTTagByteArray(value));
}
public void setIntArray(String key, int[] value) {
map.put(key, new NBTTagIntArray(value));
}
public void setBoolean(String key, boolean value) {
setByte(key, (byte) (value ? 1 : 0));
}
public boolean hasKey(String key) {
return map.containsKey(key);
}
public boolean hasKeyOfType(String key, Class<? extends INBTBase> type) {
return map.containsKey(key) && map.get(key).getClass() == type;
}
/**
* @param key The key
*
* @return The assigned {@link INBTBase}
*/
public INBTBase get(String key) {
return map.get(key);
}
/**
* @param key The key
*
* @return The number or 0 if not found.
*/
public byte getByte(String key) {
if (!hasKey(key) || !hasKeyOfType(key, NBTTagByte.class)) {
return 0;
}
return ((NBTTagByte) get(key)).getAsByte();
}
/**
* @param key The key
*
* @return The number or 0 if not found.
*/
public short getShort(String key) {
if (!hasKey(key) || !hasKeyOfType(key, NBTTagShort.class)) {
return 0;
}
return ((NBTTagShort) get(key)).getAsShort();
}
/**
* @param key The key
*
* @return The number or 0 if not found.
*/
public int getInt(String key) {
if (!hasKey(key) || !hasKeyOfType(key, NBTTagInt.class)) {
return 0;
}
return ((NBTTagInt) get(key)).getAsInt();
}
/**
* @param key The key
*
* @return The number or 0 if not found.
*/
public long getLong(String key) {
if (!hasKey(key) || !hasKeyOfType(key, NBTTagLong.class)) {
return 0;
}
return ((NBTTagLong) get(key)).getAsLong();
}
/**
* @param key The key
*
* @return The number or 0 if not found.
*/
public float getFloat(String key) {
if (!hasKey(key) || !hasKeyOfType(key, NBTTagFloat.class)) {
return 0;
}
return ((NBTTagFloat) get(key)).getAsFloat();
}
/**
* @param key The key
*
* @return The number or 0 if not found.
*/
public double getDouble(String key) {
if (!hasKey(key) || !hasKeyOfType(key, NBTTagDouble.class)) {
return 0;
}
return ((NBTTagDouble) get(key)).getAsDouble();
}
/**
* @param key The key
*
* @return The String or null if not found.
*/
public String getString(String key) {
if (!hasKey(key) || !hasKeyOfType(key, NBTTagString.class)) {
return null;
}
return ((NBTTagString) get(key)).getString();
}
/**
* @param key The key
*
* @return The byte array or null if not found or wrong type.
*/
public byte[] getByteArray(String key) {
if (!hasKey(key) || !hasKeyOfType(key, NBTTagByteArray.class)) {
return null;
}
return ((NBTTagByteArray) get(key)).getValue();
}
/**
* @param key The key
*
* @return The byte int or null if not found or wrong type.
*/
public int[] getIntArray(String key) {
if (!hasKey(key) || !hasKeyOfType(key, NBTTagIntArray.class)) {
return null;
}
return ((NBTTagIntArray) get(key)).getValue();
}
/**
* @param key The key
*
* @return The boolean
*/
public boolean getBoolean(String key) {
return getByte(key) != 0;
}
@Override
public Object toNBT() {
Object compound = ReflectionUtil.instantiate(NBT_TAG_COMPOUND_CONSTRUCTOR);
for (Map.Entry<String, INBTBase> entry : map.entrySet()) {
ReflectionUtil.invokeMethod(compound, "set", new Class[]{String.class,
ReflectionUtil.getNMSClass("NBTBase")}, entry.getKey(), entry.getValue().toNBT());
}
return compound;
}
public static INBTBase fromNBT(Object nbtObject) {
Collection<String> keys = new HashSet<>();
for (Method method : nbtObject.getClass().getMethods()) {
if (Modifier.isPublic(method.getModifiers()) && Set.class.isAssignableFrom(method.getReturnType())) {
@SuppressWarnings("unchecked")
Collection<? extends String> collection = (Collection<? extends String>)
ReflectionUtil.invokeMethod(method, nbtObject);
if (collection != null) {
keys.addAll(collection);
}
}
}
NBTTagCompound compound = new NBTTagCompound();
for (String key : keys) {
Object value = ReflectionUtil.invokeMethod(nbtObject, "get", new Class[]{String.class}, key);
INBTBase base = INBTBase.fromNBT(value);
if (base != null) {
compound.set(key, base);
}
}
return compound;
}
@Override
public String toString() {
return "NBTTagCompound{" +
"map=" + map +
'}';
}
}
/**
* A NBTTagList.
*/
public static class NBTTagList extends INBTBase {
private static final Constructor<?> NBT_TAG_LIST_CONSTRUCTOR = ReflectionUtil.getConstructor(
ReflectionUtil.getNMSClass("NBTTagList")
);
private List<INBTBase> list = new ArrayList<>();
/**
* Adds the {@link INBTBase}, if the type of the list is correct or the list is empty
*
* @param base The {@link INBTBase} to add
*
* @return True if it was added.
*/
public boolean add(INBTBase base) {
return isType(base.getClass()) && list.add(base);
}
/**
* @param type The type to check for
*
* @return True if the list is empty or this type
*/
public boolean isType(Class<? extends INBTBase> type) {
return list.isEmpty() || list.get(0).getClass() == type;
}
@Override
public Object toNBT() {
Object nbtList = ReflectionUtil.instantiate(NBT_TAG_LIST_CONSTRUCTOR);
for (INBTBase inbtBase : list) {
ReflectionUtil.invokeMethod(nbtList, "add",
new Class[]{ReflectionUtil.getNMSClass("NBTBase")}, inbtBase.toNBT());
}
return nbtList;
}
public static INBTBase fromNBT(Object nbtObject) {
NBTTagList list = new NBTTagList();
List<?> savedList = (List<?>) ReflectionUtil.getInstanceField(nbtObject, "list");
if(savedList == null) {
return list;
}
for (Object entry : savedList) {
list.add(INBTBase.fromNBT(entry));
}
return list;
}
@Override
public String toString() {
return "NBTTagList{" +
"list=" + list +
'}';
}
}
/**
* A number.
*/
public static abstract class INBTNumber extends INBTBase {
/**
* @return The number as an int
*/
public int getAsInt() {
return (int) Math.round(getAsDouble());
}
/**
* @return The number as a long.
*/
public long getAsLong() {
return Math.round(getAsDouble());
}
/**
* @return The number as a double.
*/
public abstract double getAsDouble();
/**
* @return The number as a float
*/
public float getAsFloat() {
return (float) getAsDouble();
}
/**
* @return The number as a byte
*/
public byte getAsByte() {
return (byte) getAsInt();
}
/**
* @return The number as a short
*/
public short getAsShort() {
return (short) getAsInt();
}
/**
* Sets the value
*
* @param number The new value
*/
public abstract void set(Number number);
}
/**
* A NBTTagDouble
*/
public static class NBTTagDouble extends INBTNumber {
private static final Constructor<?> NBT_TAG_DOUBLE_CONSTRUCTOR = ReflectionUtil.getConstructor(
ReflectionUtil.getNMSClass("NBTTagDouble"), double.class
);
private double value;
/**
* @param value The Double value
*/
public NBTTagDouble(double value) {
this.value = value;
}
/**
* @param value The new value
*/
@Override
public void set(Number value) {
this.value = value.doubleValue();
}
/**
* @return The Double value
*/
@Override
public double getAsDouble() {
return value;
}
@Override
public Object toNBT() {
return ReflectionUtil.instantiate(NBT_TAG_DOUBLE_CONSTRUCTOR, getAsDouble());
}
public static INBTBase fromNBT(Object nbtObject) {
Double value = (Double) ReflectionUtil.invokeMethod(
findNBTNumberGetMethod(ReflectionUtil.getNMSClass("NBTTagDouble"), double.class), nbtObject
);
return value == null ? new NBTTagDouble(-1) : new NBTTagDouble(value);
}
@Override
public String toString() {
return "NBTTagDouble{" +
"value=" + value +
'}';
}
}
/**
* A NBTTagInt
*/
public static class NBTTagInt extends INBTNumber {
private static final Constructor<?> NBT_TAG_INT_CONSTRUCTOR = ReflectionUtil.getConstructor(
ReflectionUtil.getNMSClass("NBTTagInt"), int.class
);
private int value;
/**
* @param value The Int value
*/
public NBTTagInt(int value) {
this.value = value;
}
/**
* @param value The new value
*/
@Override
public void set(Number value) {
this.value = value.intValue();
}
/**
* @return The double value
*/
@Override
public double getAsDouble() {
return value;
}
@Override
public Object toNBT() {
return ReflectionUtil.instantiate(NBT_TAG_INT_CONSTRUCTOR, getAsInt());
}
public static INBTBase fromNBT(Object nbtObject) {
Integer value = (Integer) ReflectionUtil.invokeMethod(
findNBTNumberGetMethod(ReflectionUtil.getNMSClass("NBTTagInt"), int.class), nbtObject
);
return new NBTTagInt(value == null ? 0 : value);
}
@Override
public String toString() {
return "NBTTagInt{" +
"value=" + value +
'}';
}
}
/**
* A NBTTagIntArray
*/
public static class NBTTagIntArray extends INBTBase {
private static final Constructor<?> NBT_TAG_INT_ARRAY_CONSTRUCTOR = ReflectionUtil.getConstructor(
ReflectionUtil.getNMSClass("NBTTagIntArray"), int[].class
);
private int[] value;
public NBTTagIntArray() {
}
/**
* @param value The Int value
*/
public NBTTagIntArray(int[] value) {
this.value = value;
}
/**
* @return The saved integer array
*/
public int[] getValue() {
return value;
}
@Override
public Object toNBT() {
return ReflectionUtil.instantiate(NBT_TAG_INT_ARRAY_CONSTRUCTOR, (Object) getValue());
}
public static INBTBase fromNBT(Object nbtObject) {
int[] data = null;
for (Method method : nbtObject.getClass().getMethods()) {
if (method.getReturnType() == int[].class) {
data = (int[]) ReflectionUtil.invokeMethod(method, nbtObject);
}
}
return new NBTTagIntArray(data);
}
@Override
public String toString() {
return "NBTTagIntArray{" +
"value=" + Arrays.toString(value) +
'}';
}
}
/**
* A NBTTagByte
*/
public static class NBTTagByte extends INBTNumber {
private static final Constructor<?> NBT_TAG_BYTE_CONSTRUCTOR = ReflectionUtil.getConstructor(
ReflectionUtil.getNMSClass("NBTTagByte"), byte.class
);
private byte value;
/**
* @param value The Byte value
*/
public NBTTagByte(byte value) {
this.value = value;
}
/**
* @param value The new value
*/
@Override
public void set(Number value) {
this.value = value.byteValue();
}
/**
* @return The double value
*/
@Override
public double getAsDouble() {
return value;
}
@Override
public Object toNBT() {
return ReflectionUtil.instantiate(NBT_TAG_BYTE_CONSTRUCTOR, getAsByte());
}
public static INBTBase fromNBT(Object nbtObject) {
Byte value = (Byte) ReflectionUtil.invokeMethod(
findNBTNumberGetMethod(ReflectionUtil.getNMSClass("NBTTagByte"), byte.class), nbtObject
);
return new NBTTagByte(value == null ? 0 : value);
}
@Override
public String toString() {
return "NBTTagByte{" +
"value=" + value +
'}';
}
}
/**
* A NBTTagByteArray
*/
public static class NBTTagByteArray extends INBTBase {
private static final Constructor<?> NBT_TAG_BYTE_ARRAY_CONSTRUCTOR = ReflectionUtil.getConstructor(
ReflectionUtil.getNMSClass("NBTTagByteArray"), byte[].class
);
private byte[] value;
public NBTTagByteArray() {
}
/**
* @param value The Byte value
*/
public NBTTagByteArray(byte[] value) {
this.value = value;
}
/**
* @return The saved bytes
*/
public byte[] getValue() {
return value;
}
@Override
public Object toNBT() {
return ReflectionUtil.instantiate(NBT_TAG_BYTE_ARRAY_CONSTRUCTOR, (Object) getValue());
}
public static INBTBase fromNBT(Object nbtObject) {
byte[] data = null;
for (Method method : nbtObject.getClass().getMethods()) {
if (method.getReturnType() == byte[].class) {
data = (byte[]) ReflectionUtil.invokeMethod(method, nbtObject);
}
}
return new NBTTagByteArray(data);
}
@Override
public String toString() {
return "NBTTagByteArray{" +
"value=" + Arrays.toString(value) +
'}';
}
}
/**
* A NBTTagShort
*/
public static class NBTTagShort extends INBTNumber {
private static final Constructor<?> NBT_TAG_SHORT_CONSTRUCTOR = ReflectionUtil.getConstructor(
ReflectionUtil.getNMSClass("NBTTagShort"), short.class
);
private short value;
/**
* @param value The Short value
*/
public NBTTagShort(short value) {
this.value = value;
}
/**
* @param value The new value
*/
@Override
public void set(Number value) {
this.value = value.shortValue();
}
/**
* @return The double value
*/
@Override
public double getAsDouble() {
return value;
}
@Override
public Object toNBT() {
return ReflectionUtil.instantiate(NBT_TAG_SHORT_CONSTRUCTOR, getAsShort());
}
public static INBTBase fromNBT(Object nbtObject) {
Short value = (Short) ReflectionUtil.invokeMethod(
findNBTNumberGetMethod(ReflectionUtil.getNMSClass("NBTTagShort"), short.class), nbtObject
);
return new NBTTagShort(value == null ? 0 : value);
}
@Override
public String toString() {
return "NBTTagShort{" +
"value=" + value +
'}';
}
}
/**
* A NBTTagLong
*/
public static class NBTTagLong extends INBTNumber {
private static final Constructor<?> NBT_TAG_LONG_CONSTRUCTOR = ReflectionUtil.getConstructor(
ReflectionUtil.getNMSClass("NBTTagLong"), long.class
);
private long value;
/**
* @param value The Long value
*/
public NBTTagLong(long value) {
this.value = value;
}
/**
* @param value The new value
*/
@Override
public void set(Number value) {
this.value = value.longValue();
}
/**
* @return The double value
*/
@Override
public double getAsDouble() {
return value;
}
@Override
public Object toNBT() {
return ReflectionUtil.instantiate(NBT_TAG_LONG_CONSTRUCTOR, getAsLong());
}
public static INBTBase fromNBT(Object nbtObject) {
Long value = (Long) ReflectionUtil.invokeMethod(
findNBTNumberGetMethod(ReflectionUtil.getNMSClass("NBTTagLong"), long.class), nbtObject
);
return new NBTTagLong(value == null ? 0 : value);
}
@Override
public String toString() {
return "NBTTagLong{" +
"value=" + value +
'}';
}
}
/**
* A NBTTagFloat
*/
public static class NBTTagFloat extends INBTNumber {
private static final Constructor<?> NBT_TAG_LONG_CONSTRUCTOR = ReflectionUtil.getConstructor(
ReflectionUtil.getNMSClass("NBTTagFloat"), float.class
);
private float value;
/**
* @param value The Float value
*/
public NBTTagFloat(float value) {
this.value = value;
}
/**
* @param value The new value
*/
@Override
public void set(Number value) {
this.value = value.floatValue();
}
/**
* @return The double value
*/
@Override
public double getAsDouble() {
return value;
}
@Override
public Object toNBT() {
return ReflectionUtil.instantiate(NBT_TAG_LONG_CONSTRUCTOR, getAsFloat());
}
public static INBTBase fromNBT(Object nbtObject) {
Float value = (Float) ReflectionUtil.invokeMethod(
findNBTNumberGetMethod(ReflectionUtil.getNMSClass("NBTTagFloat"), float.class), nbtObject
);
return new NBTTagFloat(value == null ? 0 : value);
}
@Override
public String toString() {
return "NBTTagFloat{" +
"value=" + value +
'}';
}
}
/**
* Returns the method also existing in the Superclass
*
* @param clazz The Class to invoke it on
* @param returnClass The Return class it should have
*
* @return The found Method
*/
private static Method findNBTNumberGetMethod(Class<?> clazz, Class<?> returnClass) {
for (Method method : clazz.getMethods()) {
if (!method.getReturnType().equals(returnClass)) {
continue;
}
if (method.getName().equals("hashCode")) {
continue;
}
try {
//noinspection ConfusingArgumentToVarargsMethod // The array is desired.
clazz.getSuperclass().getMethod(method.getName(), method.getParameterTypes());
return method;
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
return null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment