commit d9b73ab727c6cea038af818175b62b85378f0332 Author: davidontop Date: Fri Feb 9 10:35:21 2024 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ca21acb --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target/ +testserver/ +deleteme/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1a1aebe --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# WIP \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a2f8300 --- /dev/null +++ b/pom.xml @@ -0,0 +1,95 @@ + + + 4.0.0 + + the.david.replace + replace + 1.0 + jar + + RePlace + + allowes you to place blocks facing the other way + + 1.8 + UTF-8 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + shade + package + + shade + + + false + + + + + + + + + src/main/resources + true + + + + + + + papermc-repo + https://repo.papermc.io/repository/maven-public/ + + + sonatype + https://oss.sonatype.org/content/groups/public/ + + + + codemc-repo + https://repo.codemc.org/repository/maven-public/ + default + + + + + + io.papermc.paper + paper-api + 1.19-R0.1-SNAPSHOT + provided + + + + de.tr7zw + functional-annotations + 0.1-SNAPSHOT + compile + + + diff --git a/replace.iml b/replace.iml new file mode 100644 index 0000000..1ba5e54 --- /dev/null +++ b/replace.iml @@ -0,0 +1,57 @@ + + + + + + + PAPER + ADVENTURE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTBlock.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTBlock.java new file mode 100644 index 0000000..c7b7740 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTBlock.java @@ -0,0 +1,26 @@ +package ed.tr7zw.changeme.nbtapi; + +import org.bukkit.block.Block; + +import ed.tr7zw.changeme.nbtapi.utils.MinecraftVersion; + +public class NBTBlock { + + private final Block block; + private final NBTChunk nbtChunk; + + public NBTBlock(Block block) { + this.block = block; + if(!MinecraftVersion.isAtLeastVersion(MinecraftVersion.MC1_16_R3)) { + throw new NbtApiException("NBTBlock is only working for 1.16.4+!"); + } + nbtChunk = new NBTChunk(block.getChunk()); + } + + + public NBTCompound getData() { + return nbtChunk.getPersistentDataContainer().getOrCreateCompound("blocks").getOrCreateCompound(block.getX() + "_" + block.getY() + "_" + block.getZ()); + } + + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTChunk.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTChunk.java new file mode 100644 index 0000000..d79a704 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTChunk.java @@ -0,0 +1,30 @@ +package ed.tr7zw.changeme.nbtapi; + +import org.bukkit.Chunk; + +import de.tr7zw.annotations.FAUtil; +import ed.tr7zw.changeme.nbtapi.utils.MinecraftVersion; +import ed.tr7zw.changeme.nbtapi.utils.annotations.AvailableSince; +import ed.tr7zw.changeme.nbtapi.utils.annotations.CheckUtil; + +public class NBTChunk { + + private final Chunk chunk; + + public NBTChunk(Chunk chunk) { + this.chunk = chunk; + } + + /** + * Gets the NBTCompound used by spigots PersistentDataAPI. This method is only + * available for 1.16.4+! + * + * @return NBTCompound containing the data of the PersistentDataAPI + */ + @AvailableSince(version = MinecraftVersion.MC1_16_R3) + public NBTCompound getPersistentDataContainer() { + FAUtil.check(this::getPersistentDataContainer, CheckUtil::isAvaliable); + return new NBTPersistentDataContainer(chunk.getPersistentDataContainer()); + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTCompound.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTCompound.java new file mode 100644 index 0000000..717c47f --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTCompound.java @@ -0,0 +1,921 @@ +package ed.tr7zw.changeme.nbtapi; + +import java.io.OutputStream; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.Forge1710Mappings; +import org.bukkit.inventory.ItemStack; + +import ed.tr7zw.changeme.nbtapi.utils.MinecraftVersion; +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ReflectionMethod; + +/** + * Base class representing NMS Compounds. For a standalone implementation check + * {@link NBTContainer} + * + * @author tr7zw + * + */ +public class NBTCompound { + + private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + private final Lock readLock = readWriteLock.readLock(); + private final Lock writeLock = readWriteLock.writeLock(); + + private String compundName; + private NBTCompound parent; + + protected NBTCompound(NBTCompound owner, String name) { + this.compundName = name; + this.parent = owner; + } + + protected Lock getReadLock() { + return readLock; + } + + protected Lock getWriteLock() { + return writeLock; + } + + protected void saveCompound() { + if (parent != null) + parent.saveCompound(); + } + + /** + * @return The Compound name + */ + public String getName() { + return compundName; + } + + /** + * @return The NMS Compound behind this Object + */ + public Object getCompound() { + return parent.getCompound(); + } + + protected void setCompound(Object compound) { + parent.setCompound(compound); + } + + /** + * @return The parent Compound + */ + public NBTCompound getParent() { + return parent; + } + + /** + * Merges all data from comp into this compound. This is done in one action, so + * it also works with Tiles/Entities + * + * @param comp + */ + public void mergeCompound(NBTCompound comp) { + try { + writeLock.lock(); + NBTReflectionUtil.mergeOtherNBTCompound(this, comp); + saveCompound(); + } finally { + writeLock.unlock(); + } + } + + /** + * Setter + * + * @param key + * @param value + */ + public void setString(String key, String value) { + try { + writeLock.lock(); + NBTReflectionUtil.setData(this, ReflectionMethod.COMPOUND_SET_STRING, key, value); + saveCompound(); + } finally { + writeLock.unlock(); + } + } + + /** + * Getter + * + * @param key + * @return The stored value or NMS fallback + */ + public String getString(String key) { + try { + readLock.lock(); + return (String) NBTReflectionUtil.getData(this, ReflectionMethod.COMPOUND_GET_STRING, key); + } finally { + readLock.unlock(); + } + } + + protected String getContent(String key) { + return NBTReflectionUtil.getContent(this, key); + } + + /** + * Setter + * + * @param key + * @param value + */ + public void setInteger(String key, Integer value) { + try { + writeLock.lock(); + NBTReflectionUtil.setData(this, ReflectionMethod.COMPOUND_SET_INT, key, value); + saveCompound(); + } finally { + writeLock.unlock(); + } + } + + /** + * Getter + * + * @param key + * @return The stored value or NMS fallback + */ + public Integer getInteger(String key) { + try { + readLock.lock(); + return (Integer) NBTReflectionUtil.getData(this, ReflectionMethod.COMPOUND_GET_INT, key); + } finally { + readLock.unlock(); + } + } + + /** + * Setter + * + * @param key + * @param value + */ + public void setDouble(String key, Double value) { + try { + writeLock.lock(); + NBTReflectionUtil.setData(this, ReflectionMethod.COMPOUND_SET_DOUBLE, key, value); + saveCompound(); + } finally { + writeLock.unlock(); + } + } + + /** + * Getter + * + * @param key + * @return The stored value or NMS fallback + */ + public Double getDouble(String key) { + try { + readLock.lock(); + return (Double) NBTReflectionUtil.getData(this, ReflectionMethod.COMPOUND_GET_DOUBLE, key); + } finally { + readLock.unlock(); + } + } + + /** + * Setter + * + * @param key + * @param value + */ + public void setByte(String key, Byte value) { + try { + writeLock.lock(); + NBTReflectionUtil.setData(this, ReflectionMethod.COMPOUND_SET_BYTE, key, value); + saveCompound(); + } finally { + writeLock.unlock(); + } + } + + /** + * Getter + * + * @param key + * @return The stored value or NMS fallback + */ + public Byte getByte(String key) { + try { + readLock.lock(); + return (Byte) NBTReflectionUtil.getData(this, ReflectionMethod.COMPOUND_GET_BYTE, key); + } finally { + readLock.unlock(); + } + } + + /** + * Setter + * + * @param key + * @param value + */ + public void setShort(String key, Short value) { + try { + writeLock.lock(); + NBTReflectionUtil.setData(this, ReflectionMethod.COMPOUND_SET_SHORT, key, value); + saveCompound(); + } finally { + writeLock.unlock(); + } + } + + /** + * Getter + * + * @param key + * @return The stored value or NMS fallback + */ + public Short getShort(String key) { + try { + readLock.lock(); + return (Short) NBTReflectionUtil.getData(this, ReflectionMethod.COMPOUND_GET_SHORT, key); + } finally { + readLock.unlock(); + } + } + + /** + * Setter + * + * @param key + * @param value + */ + public void setLong(String key, Long value) { + try { + writeLock.lock(); + NBTReflectionUtil.setData(this, ReflectionMethod.COMPOUND_SET_LONG, key, value); + saveCompound(); + } finally { + writeLock.unlock(); + } + } + + /** + * Getter + * + * @param key + * @return The stored value or NMS fallback + */ + public Long getLong(String key) { + try { + readLock.lock(); + return (Long) NBTReflectionUtil.getData(this, ReflectionMethod.COMPOUND_GET_LONG, key); + } finally { + readLock.unlock(); + } + } + + /** + * Setter + * + * @param key + * @param value + */ + public void setFloat(String key, Float value) { + try { + writeLock.lock(); + NBTReflectionUtil.setData(this, ReflectionMethod.COMPOUND_SET_FLOAT, key, value); + saveCompound(); + } finally { + writeLock.unlock(); + } + } + + /** + * Getter + * + * @param key + * @return The stored value or NMS fallback + */ + public Float getFloat(String key) { + try { + readLock.lock(); + return (Float) NBTReflectionUtil.getData(this, ReflectionMethod.COMPOUND_GET_FLOAT, key); + } finally { + readLock.unlock(); + } + } + + /** + * Setter + * + * @param key + * @param value + */ + public void setByteArray(String key, byte[] value) { + try { + writeLock.lock(); + NBTReflectionUtil.setData(this, ReflectionMethod.COMPOUND_SET_BYTEARRAY, key, value); + saveCompound(); + } finally { + writeLock.unlock(); + } + } + + /** + * Getter + * + * @param key + * @return The stored value or NMS fallback + */ + public byte[] getByteArray(String key) { + try { + readLock.lock(); + return (byte[]) NBTReflectionUtil.getData(this, ReflectionMethod.COMPOUND_GET_BYTEARRAY, key); + } finally { + readLock.unlock(); + } + } + + /** + * Setter + * + * @param key + * @param value + */ + public void setIntArray(String key, int[] value) { + try { + writeLock.lock(); + NBTReflectionUtil.setData(this, ReflectionMethod.COMPOUND_SET_INTARRAY, key, value); + saveCompound(); + } finally { + writeLock.unlock(); + } + } + + /** + * Getter + * + * @param key + * @return The stored value or NMS fallback + */ + public int[] getIntArray(String key) { + try { + readLock.lock(); + return (int[]) NBTReflectionUtil.getData(this, ReflectionMethod.COMPOUND_GET_INTARRAY, key); + } finally { + readLock.unlock(); + } + } + + /** + * Setter + * + * @param key + * @param value + */ + public void setBoolean(String key, Boolean value) { + try { + writeLock.lock(); + NBTReflectionUtil.setData(this, ReflectionMethod.COMPOUND_SET_BOOLEAN, key, value); + saveCompound(); + } finally { + writeLock.unlock(); + } + } + + protected void set(String key, Object val) { + NBTReflectionUtil.set(this, key, val); + saveCompound(); + } + + /** + * Getter + * + * @param key + * @return The stored value or NMS fallback + */ + public Boolean getBoolean(String key) { + try { + readLock.lock(); + return (Boolean) NBTReflectionUtil.getData(this, ReflectionMethod.COMPOUND_GET_BOOLEAN, key); + } finally { + readLock.unlock(); + } + } + + /** + * Uses Gson to store an {@link Serializable} Object + * + * @param key + * @param value + */ + public void setObject(String key, Object value) { + try { + writeLock.lock(); + NBTReflectionUtil.setObject(this, key, value); + saveCompound(); + } finally { + writeLock.unlock(); + } + } + + /** + * Uses Gson to retrieve a stored Object + * + * @param key + * @param type Class of the Object + * @return The created Object or null if empty + */ + public T getObject(String key, Class type) { + try { + readLock.lock(); + return NBTReflectionUtil.getObject(this, key, type); + } finally { + readLock.unlock(); + } + } + + /** + * Save an ItemStack as a compound under a given key + * + * @param key + * @param item + */ + public void setItemStack(String key, ItemStack item) { + try { + writeLock.lock(); + removeKey(key); + addCompound(key).mergeCompound(NBTItem.convertItemtoNBT(item)); + } finally { + writeLock.unlock(); + } + } + + /** + * Get an ItemStack that was saved at the given key + * + * @param key + * @return + */ + public ItemStack getItemStack(String key) { + try { + readLock.lock(); + NBTCompound comp = getCompound(key); + return NBTItem.convertNBTtoItem(comp); + } finally { + readLock.unlock(); + } + } + + /** + * Setter + * + * @param key + * @param value + */ + public void setUUID(String key, UUID value) { + try { + writeLock.lock(); + NBTReflectionUtil.setData(this, ReflectionMethod.COMPOUND_SET_UUID, key, value); + saveCompound(); + } finally { + writeLock.unlock(); + } + } + + /** + * Getter + * + * @param key + * @return The stored value or NMS fallback + */ + public UUID getUUID(String key) { + try { + readLock.lock(); + return (UUID) NBTReflectionUtil.getData(this, ReflectionMethod.COMPOUND_GET_UUID, key); + } finally { + readLock.unlock(); + } + } + + /** + * @param key String key + * @return True if the key is set + * @deprecated Use {@link #hasTag(String)} instead + */ + @Deprecated + public Boolean hasKey(String key) { + return hasTag(key); + } + + /** + * @param key String key + * @return true, if the key is set + */ + public boolean hasTag(String key) { + try { + readLock.lock(); + Boolean b = (Boolean) NBTReflectionUtil.getData(this, ReflectionMethod.COMPOUND_HAS_KEY, key); + if (b == null) + return false; + return b; + } finally { + readLock.unlock(); + } + } + + /** + * @param key Deletes the given Key + */ + public void removeKey(String key) { + try { + writeLock.lock(); + NBTReflectionUtil.remove(this, key); + saveCompound(); + } finally { + writeLock.unlock(); + } + } + + /** + * @return Set of all stored Keys + */ + public Set getKeys() { + try { + readLock.lock(); + return NBTReflectionUtil.getKeys(this); + } finally { + readLock.unlock(); + } + } + + /** + * Creates a subCompound, or returns it if already provided + * + * @param name Key to use + * @return The subCompound Object + */ + public NBTCompound addCompound(String name) { + try { + writeLock.lock(); + if (getType(name) == NBTType.NBTTagCompound) + return getCompound(name); + NBTReflectionUtil.addNBTTagCompound(this, name); + NBTCompound comp = getCompound(name); + if (comp == null) + throw new NbtApiException("Error while adding Compound, got null!"); + saveCompound(); + return comp; + } finally { + writeLock.unlock(); + } + } + + /** + * @param name + * @return The Compound instance or null + */ + public NBTCompound getCompound(String name) { + try { + readLock.lock(); + if (getType(name) != NBTType.NBTTagCompound) + return null; + NBTCompound next = new NBTCompound(this, name); + if (NBTReflectionUtil.valideCompound(next)) + return next; + return null; + } finally { + readLock.unlock(); + } + } + + /** + * The same as addCompound, just with a name that better reflects what it does + * + * @param name + * @return + */ + public NBTCompound getOrCreateCompound(String name) { + return addCompound(name); + } + + /** + * @param name + * @return The retrieved String List + */ + public NBTList getStringList(String name) { + try { + writeLock.lock(); + NBTList list = NBTReflectionUtil.getList(this, name, NBTType.NBTTagString, String.class); + saveCompound(); + return list; + } finally { + writeLock.unlock(); + } + } + + /** + * @param name + * @return The retrieved Integer List + */ + public NBTList getIntegerList(String name) { + try { + writeLock.lock(); + NBTList list = NBTReflectionUtil.getList(this, name, NBTType.NBTTagInt, Integer.class); + saveCompound(); + return list; + } finally { + writeLock.unlock(); + } + } + + /** + * @param name + * @return The retrieved Integer List + */ + public NBTList getIntArrayList(String name) { + try { + writeLock.lock(); + NBTList list = NBTReflectionUtil.getList(this, name, NBTType.NBTTagIntArray, int[].class); + saveCompound(); + return list; + } finally { + writeLock.unlock(); + } + } + + /** + * @param name + * @return The retrieved Integer List + */ + public NBTList getUUIDList(String name) { + try { + writeLock.lock(); + NBTList list = NBTReflectionUtil.getList(this, name, NBTType.NBTTagIntArray, UUID.class); + saveCompound(); + return list; + } finally { + writeLock.unlock(); + } + } + + /** + * @param name + * @return The retrieved Float List + */ + public NBTList getFloatList(String name) { + try { + writeLock.lock(); + NBTList list = NBTReflectionUtil.getList(this, name, NBTType.NBTTagFloat, Float.class); + saveCompound(); + return list; + } finally { + writeLock.unlock(); + } + } + + /** + * @param name + * @return The retrieved Double List + */ + public NBTList getDoubleList(String name) { + try { + writeLock.lock(); + NBTList list = NBTReflectionUtil.getList(this, name, NBTType.NBTTagDouble, Double.class); + saveCompound(); + return list; + } finally { + writeLock.unlock(); + } + } + + /** + * @param name + * @return The retrieved Long List + */ + public NBTList getLongList(String name) { + try { + writeLock.lock(); + NBTList list = NBTReflectionUtil.getList(this, name, NBTType.NBTTagLong, Long.class); + saveCompound(); + return list; + } finally { + writeLock.unlock(); + } + } + + /** + * Returns the type of the list, null if not a list + * + * @param name + * @return + */ + public NBTType getListType(String name) { + try { + readLock.lock(); + if (getType(name) != NBTType.NBTTagList) + return null; + return NBTReflectionUtil.getListType(this, name); + } finally { + readLock.unlock(); + } + } + + /** + * @param name + * @return The retrieved Compound List + */ + public NBTCompoundList getCompoundList(String name) { + try { + writeLock.lock(); + NBTCompoundList list = (NBTCompoundList) NBTReflectionUtil.getList(this, name, NBTType.NBTTagCompound, + NBTListCompound.class); + saveCompound(); + return list; + } finally { + writeLock.unlock(); + } + } + + /** + * Returns the stored value if exists, or provided value otherwise. + *

Supported types: {@code byte/Byte, short/Short, int/Integer, long/Long, float/Float, double/Double, byte[], int[]}, {@link String}, {@link UUID} + * + * @param key key + * @param defaultValue default non-null value + * @param value type + * @return Stored or provided value + */ + @SuppressWarnings("unchecked") + public T getOrDefault(String key, T defaultValue) { + if (defaultValue == null) throw new NullPointerException("Default type in getOrDefault can't be null!"); + if (!hasTag(key)) return defaultValue; + + Class clazz = defaultValue.getClass(); + if (clazz == Byte.class) return (T) getByte(key); + if (clazz == Short.class) return (T) getShort(key); + if (clazz == Integer.class) return (T) getInteger(key); + if (clazz == Long.class) return (T) getLong(key); + if (clazz == Float.class) return (T) getFloat(key); + if (clazz == Double.class) return (T) getDouble(key); + if (clazz == byte[].class) return (T) getByteArray(key); + if (clazz == int[].class) return (T) getIntArray(key); + if (clazz == String.class) return (T) getString(key); + if (clazz == UUID.class) return (T) getUUID(key); + + throw new NbtApiException("Unsupported type for getOrDefault: " + clazz.getName()); + } + + /** + * @param name + * @return The type of the given stored key or null + */ + public NBTType getType(String name) { + try { + readLock.lock(); + if (MinecraftVersion.getVersion() == MinecraftVersion.MC1_7_R4) { + Object nbtbase = NBTReflectionUtil.getData(this, ReflectionMethod.COMPOUND_GET, name); + if(nbtbase == null) + return null; + return NBTType.valueOf((byte)ReflectionMethod.COMPOUND_OWN_TYPE.run(nbtbase)); + } + Object o = NBTReflectionUtil.getData(this, ReflectionMethod.COMPOUND_GET_TYPE, name); + if (o == null) + return null; + return NBTType.valueOf((byte) o); + } finally { + readLock.unlock(); + } + } + + public void writeCompound(OutputStream stream) { + try { + writeLock.lock(); + NBTReflectionUtil.writeApiNBT(this, stream); + } finally { + writeLock.unlock(); + } + } + + @Override + public String toString() { + /* + * StringBuilder result = new StringBuilder(); for (String key : getKeys()) { + * result.append(toString(key)); } return result.toString(); + */ + return asNBTString(); + } + + /** + * @deprecated Just use toString() + * @param key + * @return A string representation of the given key + */ + @Deprecated + public String toString(String key) { + /* + * StringBuilder result = new StringBuilder(); NBTCompound compound = this; + * while (compound.getParent() != null) { result.append(" "); compound = + * compound.getParent(); } if (this.getType(key) == NBTType.NBTTagCompound) { + * return this.getCompound(key).toString(); } else { return result + "-" + key + + * ": " + getContent(key) + System.lineSeparator(); } + */ + return asNBTString(); + } + + /** + * Remove all keys from this compound + */ + public void clearNBT(){ + for (String key : getKeys()) { + removeKey(key); + } + } + + /** + * @deprecated Just use toString() + * @return A {@link String} representation of the NBT in Mojang JSON. This is different from normal JSON! + */ + @Deprecated + public String asNBTString() { + try { + readLock.lock(); + Object comp = NBTReflectionUtil.gettoCompount(getCompound(), this); + if (comp == null) + return "{}"; + if (MinecraftVersion.isForgePresent() && MinecraftVersion.getVersion() == MinecraftVersion.MC1_7_R4){ + return Forge1710Mappings.toString(comp); + }else { + return comp.toString(); + } + } finally { + readLock.unlock(); + } + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + /** + * Does a deep compare to check if everything is the same + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if(obj instanceof NBTCompound) { + NBTCompound other = (NBTCompound) obj; + if(getKeys().equals(other.getKeys())) { + for(String key : getKeys()) { + if(!isEqual(this, other, key)) { + return false; + } + } + return true; + } + } + return false; + } + + protected static boolean isEqual(NBTCompound compA, NBTCompound compB, String key) { + if(compA.getType(key) != compB.getType(key))return false; + switch(compA.getType(key)) { + case NBTTagByte: + return compA.getByte(key).equals(compB.getByte(key)); + case NBTTagByteArray: + return Arrays.equals(compA.getByteArray(key), compB.getByteArray(key)); + case NBTTagCompound: + return compA.getCompound(key).equals(compB.getCompound(key)); + case NBTTagDouble: + return compA.getDouble(key).equals(compB.getDouble(key)); + case NBTTagEnd: + return true; //?? + case NBTTagFloat: + return compA.getFloat(key).equals(compB.getFloat(key)); + case NBTTagInt: + return compA.getInteger(key).equals(compB.getInteger(key)); + case NBTTagIntArray: + return Arrays.equals(compA.getIntArray(key), compB.getIntArray(key)); + case NBTTagList: + return NBTReflectionUtil.getEntry(compA, key).toString().equals(NBTReflectionUtil.getEntry(compB, key).toString()); // Just string compare the 2 lists + case NBTTagLong: + return compA.getLong(key).equals(compB.getLong(key)); + case NBTTagShort: + return compA.getShort(key).equals(compB.getShort(key)); + case NBTTagString: + return compA.getString(key).equals(compB.getString(key)); + } + return false; + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTCompoundList.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTCompoundList.java new file mode 100644 index 0000000..796f6ab --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTCompoundList.java @@ -0,0 +1,106 @@ +package ed.tr7zw.changeme.nbtapi; + +import ed.tr7zw.changeme.nbtapi.utils.MinecraftVersion; +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ClassWrapper; +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ReflectionMethod; + +/** + * {@link NBTListCompound} implementation for NBTLists + * + * @author tr7zw + * + */ +public class NBTCompoundList extends NBTList { + + protected NBTCompoundList(NBTCompound owner, String name, NBTType type, Object list) { + super(owner, name, type, list); + } + + /** + * Adds a new Compound to the end of the List and returns it. + * + * @return The added {@link NBTListCompound} + */ + public NBTListCompound addCompound() { + return (NBTListCompound) addCompound(null); + } + + /** + * Adds a copy of the Compound to the end of the List and returns it. + * When null is given, a new Compound will be created + * + * @param comp + * @return + */ + public NBTCompound addCompound(NBTCompound comp) { + try { + Object compound = ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz().newInstance(); + if (MinecraftVersion.getVersion().getVersionId() >= MinecraftVersion.MC1_14_R1.getVersionId()) { + ReflectionMethod.LIST_ADD.run(listObject, size(), compound); + } else { + ReflectionMethod.LEGACY_LIST_ADD.run(listObject, compound); + } + getParent().saveCompound(); + NBTListCompound listcomp = new NBTListCompound(this, compound); + if(comp != null){ + listcomp.mergeCompound(comp); + } + return listcomp; + } catch (Exception ex) { + throw new NbtApiException(ex); + } + } + + /** + * Adds a new Compound to the end of the List. + * + * + * @deprecated Please use addCompound! + * @param empty + * @return True, if compound was added + */ + @Override + @Deprecated + public boolean add(NBTListCompound empty) { + return addCompound(empty) != null; + } + + @Override + public void add(int index, NBTListCompound element) { + if (element != null) { + throw new NbtApiException("You need to pass null! ListCompounds from other lists won't work."); + } + try { + Object compound = ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz().newInstance(); + if (MinecraftVersion.getVersion().getVersionId() >= MinecraftVersion.MC1_14_R1.getVersionId()) { + ReflectionMethod.LIST_ADD.run(listObject, index, compound); + } else { + ReflectionMethod.LEGACY_LIST_ADD.run(listObject, compound); + } + super.getParent().saveCompound(); + } catch (Exception ex) { + throw new NbtApiException(ex); + } + } + + @Override + public NBTListCompound get(int index) { + try { + Object compound = ReflectionMethod.LIST_GET_COMPOUND.run(listObject, index); + return new NBTListCompound(this, compound); + } catch (Exception ex) { + throw new NbtApiException(ex); + } + } + + @Override + public NBTListCompound set(int index, NBTListCompound element) { + throw new NbtApiException("This method doesn't work in the ListCompound context."); + } + + @Override + protected Object asTag(NBTListCompound object) { + return null; + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTContainer.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTContainer.java new file mode 100644 index 0000000..f4c157d --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTContainer.java @@ -0,0 +1,82 @@ +package ed.tr7zw.changeme.nbtapi; + +import java.io.InputStream; + +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ClassWrapper; +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ObjectCreator; +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ReflectionMethod; + +/** + * A Standalone {@link NBTCompound} implementation. All data is just kept inside + * this Object. + * + * @author tr7zw + * + */ +public class NBTContainer extends NBTCompound { + + private Object nbt; + + /** + * Creates an empty, standalone NBTCompound + */ + public NBTContainer() { + super(null, null); + nbt = ObjectCreator.NMS_NBTTAGCOMPOUND.getInstance(); + } + + /** + * Takes in any NMS Compound to wrap it + * + * @param nbt + */ + public NBTContainer(Object nbt) { + super(null, null); + if (nbt == null) { + throw new NullPointerException("The NBT-Object can't be null!"); + } + if (!ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz().isAssignableFrom(nbt.getClass())) { + throw new NbtApiException("The object '" + nbt.getClass() + "' is not a valid NBT-Object!"); + } + this.nbt = nbt; + } + + /** + * Reads in a NBT InputStream + * + * @param inputsteam + */ + public NBTContainer(InputStream inputsteam) { + super(null, null); + this.nbt = NBTReflectionUtil.readNBT(inputsteam); + } + + /** + * Parses in a NBT String to a standalone {@link NBTCompound}. Can throw a + * {@link NbtApiException} in case something goes wrong. + * + * @param nbtString + */ + public NBTContainer(String nbtString) { + super(null, null); + if (nbtString == null) { + throw new NullPointerException("The String can't be null!"); + } + try { + nbt = ReflectionMethod.PARSE_NBT.run(null, nbtString); + } catch (Exception ex) { + throw new NbtApiException("Unable to parse Malformed Json!", ex); + } + } + + @Override + public Object getCompound() { + return nbt; + } + + @Override + public void setCompound(Object tag) { + nbt = tag; + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTDoubleList.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTDoubleList.java new file mode 100644 index 0000000..90bfcd7 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTDoubleList.java @@ -0,0 +1,45 @@ +package ed.tr7zw.changeme.nbtapi; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ClassWrapper; +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ReflectionMethod; + +/** + * Double implementation for NBTLists + * + * @author tr7zw + * + */ +public class NBTDoubleList extends NBTList { + + protected NBTDoubleList(NBTCompound owner, String name, NBTType type, Object list) { + super(owner, name, type, list); + } + + @Override + protected Object asTag(Double object) { + try { + Constructor con = ClassWrapper.NMS_NBTTAGDOUBLE.getClazz().getDeclaredConstructor(double.class); + con.setAccessible(true); + return con.newInstance(object); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) { + throw new NbtApiException("Error while wrapping the Object " + object + " to it's NMS object!", e); + } + } + + @Override + public Double get(int index) { + try { + Object obj = ReflectionMethod.LIST_GET.run(listObject, index); + return Double.valueOf(obj.toString()); + } catch (NumberFormatException nf) { + return 0d; + } catch (Exception ex) { + throw new NbtApiException(ex); + } + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTEntity.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTEntity.java new file mode 100644 index 0000000..9c335c7 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTEntity.java @@ -0,0 +1,58 @@ +package ed.tr7zw.changeme.nbtapi; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Entity; + +import de.tr7zw.annotations.FAUtil; +import ed.tr7zw.changeme.nbtapi.utils.MinecraftVersion; +import ed.tr7zw.changeme.nbtapi.utils.annotations.AvailableSince; +import ed.tr7zw.changeme.nbtapi.utils.annotations.CheckUtil; + +/** + * NBT class to access vanilla tags from Entities. Entities don't support custom + * tags. Use the NBTInjector for custom tags. Changes will be instantly applied + * to the Entity, use the merge method to do many things at once. + * + * @author tr7zw + * + */ +public class NBTEntity extends NBTCompound { + + private final Entity ent; + + /** + * @param entity Any valid Bukkit Entity + */ + public NBTEntity(Entity entity) { + super(null, null); + if (entity == null) { + throw new NullPointerException("Entity can't be null!"); + } + ent = entity; + } + + @Override + public Object getCompound() { + if(!Bukkit.isPrimaryThread())throw new NbtApiException("Entity NBT needs to be accessed sync!"); + return NBTReflectionUtil.getEntityNBTTagCompound(NBTReflectionUtil.getNMSEntity(ent)); + } + + @Override + protected void setCompound(Object compound) { + if(!Bukkit.isPrimaryThread())throw new NbtApiException("Entity NBT needs to be accessed sync!"); + NBTReflectionUtil.setEntityNBTTag(compound, NBTReflectionUtil.getNMSEntity(ent)); + } + + /** + * Gets the NBTCompound used by spigots PersistentDataAPI. This method is only + * available for 1.14+! + * + * @return NBTCompound containing the data of the PersistentDataAPI + */ + @AvailableSince(version = MinecraftVersion.MC1_14_R1) + public NBTCompound getPersistentDataContainer() { + FAUtil.check(this::getPersistentDataContainer, CheckUtil::isAvaliable); + return new NBTPersistentDataContainer(ent.getPersistentDataContainer()); + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTFile.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTFile.java new file mode 100644 index 0000000..f7a81d1 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTFile.java @@ -0,0 +1,102 @@ +package ed.tr7zw.changeme.nbtapi; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ObjectCreator; + +/** + * {@link NBTCompound} implementation backed by a {@link File} + * + * @author tr7zw + * + */ +public class NBTFile extends NBTCompound { + + private final File file; + private Object nbt; + + /** + * Creates a NBTFile that uses @param file to store it's data. If this file + * exists, the data will be loaded. + * + * @param file + * @throws IOException + */ + public NBTFile(File file) throws IOException { + super(null, null); + if (file == null) { + throw new NullPointerException("File can't be null!"); + } + this.file = file; + if (file.exists()) { + nbt = NBTReflectionUtil.readNBT(Files.newInputStream(file.toPath())); + } else { + nbt = ObjectCreator.NMS_NBTTAGCOMPOUND.getInstance(); + save(); + } + } + + /** + * Saves the data to the file + * + * @throws IOException + */ + public void save() throws IOException { + try { + getWriteLock().lock(); + saveTo(file, this); + } finally { + getWriteLock().unlock(); + } + } + + /** + * @return The File used to store the data + */ + public File getFile() { + return file; + } + + @Override + public Object getCompound() { + return nbt; + } + + @Override + protected void setCompound(Object compound) { + nbt = compound; + } + + /** + * Reads NBT data from the provided file. + *

Returns empty NBTContainer if file does not exist. + * + * @param file file to read + * @return NBTCompound holding file's nbt data + * @throws IOException exception + */ + public static NBTCompound readFrom(File file) throws IOException { + if (!file.exists()) return new NBTContainer(); + return new NBTContainer(NBTReflectionUtil.readNBT(Files.newInputStream(file.toPath()))); + } + + /** + * Saves NBT data to the provided file. + *

Will fully override the file if it already exists. + * + * @param file file + * @param nbt NBT data + * @throws IOException exception + */ + public static void saveTo(File file, NBTCompound nbt) throws IOException { + if (!file.exists()) { + file.getParentFile().mkdirs(); + if (!file.createNewFile()) + throw new IOException("Unable to create file at " + file.getAbsolutePath()); + } + NBTReflectionUtil.writeNBT(nbt.getCompound(), Files.newOutputStream(file.toPath())); + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTFloatList.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTFloatList.java new file mode 100644 index 0000000..df03969 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTFloatList.java @@ -0,0 +1,45 @@ +package ed.tr7zw.changeme.nbtapi; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ClassWrapper; +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ReflectionMethod; + +/** + * Float implementation for NBTLists + * + * @author tr7zw + * + */ +public class NBTFloatList extends NBTList { + + protected NBTFloatList(NBTCompound owner, String name, NBTType type, Object list) { + super(owner, name, type, list); + } + + @Override + protected Object asTag(Float object) { + try { + Constructor con = ClassWrapper.NMS_NBTTAGFLOAT.getClazz().getDeclaredConstructor(float.class); + con.setAccessible(true); + return con.newInstance(object); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) { + throw new NbtApiException("Error while wrapping the Object " + object + " to it's NMS object!", e); + } + } + + @Override + public Float get(int index) { + try { + Object obj = ReflectionMethod.LIST_GET.run(listObject, index); + return Float.valueOf(obj.toString()); + } catch (NumberFormatException nf) { + return 0f; + } catch (Exception ex) { + throw new NbtApiException(ex); + } + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTGameProfile.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTGameProfile.java new file mode 100644 index 0000000..b0a80ba --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTGameProfile.java @@ -0,0 +1,30 @@ +//package ed.tr7zw.changeme.nbtapi; +// +//import com.mojang.authlib.GameProfile; +// +//import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ObjectCreator; +//import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ReflectionMethod; +// +//public class NBTGameProfile { +// +// /** +// * Convert a GameProfile to NBT. The NBT then can be modified or be stored +// * +// * @param profile +// * @return A NBTContainer with all the GameProfile data +// */ +// public static NBTCompound toNBT(GameProfile profile) { +// return new NBTContainer(ReflectionMethod.GAMEPROFILE_SERIALIZE.run(null, ObjectCreator.NMS_NBTTAGCOMPOUND.getInstance(), profile)); +// } +// +// /** +// * Reconstructs a GameProfile from a NBTCompound +// * +// * @param compound Has to contain GameProfile data +// * @return The reconstructed GameProfile +// */ +// public static GameProfile fromNBT(NBTCompound compound) { +// return (GameProfile) ReflectionMethod.GAMEPROFILE_DESERIALIZE.run(null, NBTReflectionUtil.gettoCompount(compound.getCompound(), compound)); +// } +// +//} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTIntArrayList.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTIntArrayList.java new file mode 100644 index 0000000..689b345 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTIntArrayList.java @@ -0,0 +1,51 @@ +package ed.tr7zw.changeme.nbtapi; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ClassWrapper; +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ReflectionMethod; + +/** + * Integer implementation for NBTLists + * + * @author tr7zw + * + */ +public class NBTIntArrayList extends NBTList { + + private final NBTContainer tmpContainer; + + protected NBTIntArrayList(NBTCompound owner, String name, NBTType type, Object list) { + super(owner, name, type, list); + this.tmpContainer = new NBTContainer(); + } + + @Override + protected Object asTag(int[] object) { + try { + Constructor con = ClassWrapper.NMS_NBTTAGINTARRAY.getClazz().getDeclaredConstructor(int[].class); + con.setAccessible(true); + return con.newInstance(object); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) { + throw new NbtApiException("Error while wrapping the Object " + object + " to it's NMS object!", e); + } + } + + @Override + public int[] get(int index) { + try { + Object obj = ReflectionMethod.LIST_GET.run(listObject, index); + ReflectionMethod.COMPOUND_SET.run(tmpContainer.getCompound(), "tmp", obj); + int[] val = tmpContainer.getIntArray("tmp"); + tmpContainer.removeKey("tmp"); + return val; + } catch (NumberFormatException nf) { + return null; + } catch (Exception ex) { + throw new NbtApiException(ex); + } + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTIntegerList.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTIntegerList.java new file mode 100644 index 0000000..70be7b3 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTIntegerList.java @@ -0,0 +1,45 @@ +package ed.tr7zw.changeme.nbtapi; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ClassWrapper; +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ReflectionMethod; + +/** + * Integer implementation for NBTLists + * + * @author tr7zw + * + */ +public class NBTIntegerList extends NBTList { + + protected NBTIntegerList(NBTCompound owner, String name, NBTType type, Object list) { + super(owner, name, type, list); + } + + @Override + protected Object asTag(Integer object) { + try { + Constructor con = ClassWrapper.NMS_NBTTAGINT.getClazz().getDeclaredConstructor(int.class); + con.setAccessible(true); + return con.newInstance(object); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) { + throw new NbtApiException("Error while wrapping the Object " + object + " to it's NMS object!", e); + } + } + + @Override + public Integer get(int index) { + try { + Object obj = ReflectionMethod.LIST_GET.run(listObject, index); + return Integer.valueOf(obj.toString()); + } catch (NumberFormatException nf) { + return 0; + } catch (Exception ex) { + throw new NbtApiException(ex); + } + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTItem.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTItem.java new file mode 100644 index 0000000..34a5178 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTItem.java @@ -0,0 +1,175 @@ +package ed.tr7zw.changeme.nbtapi; + +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ReflectionMethod; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +/** + * NBT class to access vanilla/custom tags on ItemStacks. This class doesn't + * autosave to the Itemstack, use getItem to get the changed ItemStack + * + * @author tr7zw + * + */ +public class NBTItem extends NBTCompound { + + private ItemStack bukkitItem; + private boolean directApply; + private ItemStack originalSrcStack = null; + + /** + * Constructor for NBTItems. The ItemStack will be cloned! + * + * @param item + */ + public NBTItem(ItemStack item) { + this(item, false); + } + + /** + * Constructor for NBTItems. The ItemStack will be cloned! If directApply is true, + * all changed will be mapped to the original item. Changes to the NBTItem will overwrite changes done + * to the original item in that case. + * + * @param item + * @param directApply + */ + public NBTItem(ItemStack item, boolean directApply) { + super(null, null); + if (item == null || item.getType() == Material.AIR) { + throw new NullPointerException("ItemStack can't be null/Air! This is not a NBTAPI bug!"); + } + this.directApply = directApply; + bukkitItem = item.clone(); + if(directApply) { + this.originalSrcStack = item; + } + } + + @Override + public Object getCompound() { + return NBTReflectionUtil.getItemRootNBTTagCompound(ReflectionMethod.ITEMSTACK_NMSCOPY.run(null, bukkitItem)); + } + + @Override + protected void setCompound(Object compound) { + Object stack = ReflectionMethod.ITEMSTACK_NMSCOPY.run(null, bukkitItem); + ReflectionMethod.ITEMSTACK_SET_TAG.run(stack, compound); + bukkitItem = (ItemStack) ReflectionMethod.ITEMSTACK_BUKKITMIRROR.run(null, stack); + } + + /** + * Apply stored NBT tags to the provided ItemStack. + *

+ * Note: This will completely override current item's {@link ItemMeta}. + * If you still want to keep the original item's NBT tags, see + * {@link #mergeNBT(ItemStack)} and {@link #mergeCustomNBT(ItemStack)}. + * + * @param item ItemStack that should get the new NBT data + */ + public void applyNBT(ItemStack item) { + if (item == null || item.getType() == Material.AIR) { + throw new NullPointerException("ItemStack can't be null/Air! This is not a NBTAPI bug!"); + } + NBTItem nbti = new NBTItem(new ItemStack(item.getType())); + nbti.mergeCompound(this); + item.setItemMeta(nbti.getItem().getItemMeta()); + } + + /** + * Merge all NBT tags to the provided ItemStack. + * + * @param item ItemStack that should get the new NBT data + */ + public void mergeNBT(ItemStack item) { + NBTItem nbti = new NBTItem(item); + nbti.mergeCompound(this); + item.setItemMeta(nbti.getItem().getItemMeta()); + } + + /** + * Merge only custom (non-vanilla) NBT tags to the provided ItemStack. + * + * @param item ItemStack that should get the new NBT data + */ + public void mergeCustomNBT(ItemStack item) { + if (item == null || item.getType() == Material.AIR) { + throw new NullPointerException("ItemStack can't be null/Air!"); + } + ItemMeta meta = item.getItemMeta(); + NBTReflectionUtil.getUnhandledNBTTags(meta).putAll(NBTReflectionUtil.getUnhandledNBTTags(bukkitItem.getItemMeta())); + item.setItemMeta(meta); + } + + + /** + * True, if the item has any tags now known for this item type. + * + * @return true when custom tags are present + */ + public boolean hasCustomNbtData() { + ItemMeta meta = bukkitItem.getItemMeta(); + return !NBTReflectionUtil.getUnhandledNBTTags(meta).isEmpty(); + } + + /** + * Remove all custom (non-vanilla) NBT tags from the NBTItem. + */ + public void clearCustomNBT() { + ItemMeta meta = bukkitItem.getItemMeta(); + NBTReflectionUtil.getUnhandledNBTTags(meta).clear(); + bukkitItem.setItemMeta(meta); + } + + /** + * @return The modified ItemStack + */ + public ItemStack getItem() { + return bukkitItem; + } + + protected void setItem(ItemStack item) { + bukkitItem = item; + } + + /** + * This may return true even when the NBT is empty. + * + * @return Does the ItemStack have a NBTCompound. + */ + public boolean hasNBTData() { + return getCompound() != null; + } + + /** + * Helper method that converts {@link ItemStack} to {@link NBTContainer} with + * all it's data like Material, Damage, Amount and Tags. + * + * @param item + * @return Standalone {@link NBTContainer} with the Item's data + */ + public static NBTContainer convertItemtoNBT(ItemStack item) { + return NBTReflectionUtil.convertNMSItemtoNBTCompound(ReflectionMethod.ITEMSTACK_NMSCOPY.run(null, item)); + } + + /** + * Helper method to do the inverse to "convertItemtoNBT". Creates an + * {@link ItemStack} using the {@link NBTCompound} + * + * @param comp + * @return ItemStack using the {@link NBTCompound}'s data + */ + public static ItemStack convertNBTtoItem(NBTCompound comp) { + return (ItemStack) ReflectionMethod.ITEMSTACK_BUKKITMIRROR.run(null, + NBTReflectionUtil.convertNBTCompoundtoNMSItem(comp)); + } + + @Override + protected void saveCompound() { + if(directApply) { + applyNBT(originalSrcStack); + } + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTList.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTList.java new file mode 100644 index 0000000..d1a631d --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTList.java @@ -0,0 +1,429 @@ +package ed.tr7zw.changeme.nbtapi; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; + +import ed.tr7zw.changeme.nbtapi.utils.MinecraftVersion; +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ReflectionMethod; + +/** + * Abstract List implementation for ListCompounds + * + * @author tr7zw + * + * @param + */ +public abstract class NBTList implements List { + + private String listName; + private NBTCompound parent; + private NBTType type; + protected Object listObject; + + protected NBTList(NBTCompound owner, String name, NBTType type, Object list) { + parent = owner; + listName = name; + this.type = type; + this.listObject = list; + } + + /** + * @return Name of this list-compound + */ + public String getName() { + return listName; + } + + /** + * @return The Compound's parent Object + */ + public NBTCompound getParent() { + return parent; + } + + protected void save() { + parent.set(listName, listObject); + } + + protected abstract Object asTag(T object); + + @Override + public boolean add(T element) { + try { + parent.getWriteLock().lock(); + if (MinecraftVersion.getVersion().getVersionId() >= MinecraftVersion.MC1_14_R1.getVersionId()) { + ReflectionMethod.LIST_ADD.run(listObject, size(), asTag(element)); + } else { + ReflectionMethod.LEGACY_LIST_ADD.run(listObject, asTag(element)); + } + save(); + return true; + } catch (Exception ex) { + throw new NbtApiException(ex); + } finally { + parent.getWriteLock().unlock(); + } + } + + @Override + public void add(int index, T element) { + try { + parent.getWriteLock().lock(); + if (MinecraftVersion.getVersion().getVersionId() >= MinecraftVersion.MC1_14_R1.getVersionId()) { + ReflectionMethod.LIST_ADD.run(listObject, index, asTag(element)); + } else { + ReflectionMethod.LEGACY_LIST_ADD.run(listObject, asTag(element)); + } + save(); + } catch (Exception ex) { + throw new NbtApiException(ex); + } finally { + parent.getWriteLock().unlock(); + } + } + + @Override + public T set(int index, T element) { + try { + parent.getWriteLock().lock(); + T prev = get(index); + ReflectionMethod.LIST_SET.run(listObject, index, asTag(element)); + save(); + return prev; + } catch (Exception ex) { + throw new NbtApiException(ex); + } finally { + parent.getWriteLock().unlock(); + } + } + + public T remove(int i) { + try { + parent.getWriteLock().lock(); + T old = get(i); + ReflectionMethod.LIST_REMOVE_KEY.run(listObject, i); + save(); + return old; + } catch (Exception ex) { + throw new NbtApiException(ex); + } finally { + parent.getWriteLock().unlock(); + } + } + + public int size() { + try { + parent.getReadLock().lock(); + return (int) ReflectionMethod.LIST_SIZE.run(listObject); + } catch (Exception ex) { + throw new NbtApiException(ex); + } finally { + parent.getReadLock().unlock(); + } + } + + /** + * @return The type that this list contains + */ + public NBTType getType() { + return type; + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public void clear() { + while (!isEmpty()) { + remove(0); + } + } + + @Override + public boolean contains(Object o) { + try { + parent.getReadLock().lock(); + for (int i = 0; i < size(); i++) { + if (o.equals(get(i))) + return true; + } + return false; + } finally { + parent.getReadLock().unlock(); + } + } + + @Override + public int indexOf(Object o) { + try { + parent.getReadLock().lock(); + for (int i = 0; i < size(); i++) { + if (o.equals(get(i))) + return i; + } + return -1; + } finally { + parent.getReadLock().unlock(); + } + } + + @Override + public boolean addAll(Collection c) { + try { + parent.getWriteLock().lock(); + int size = size(); + for (T ele : c) { + add(ele); + } + return size != size(); + } finally { + parent.getWriteLock().unlock(); + } + } + + @Override + public boolean addAll(int index, Collection c) { + try { + parent.getWriteLock().lock(); + int size = size(); + for (T ele : c) { + add(index++, ele); + } + return size != size(); + } finally { + parent.getWriteLock().unlock(); + } + } + + @Override + public boolean containsAll(Collection c) { + try { + parent.getReadLock().lock(); + for (Object ele : c) { + if (!contains(ele)) + return false; + } + return true; + } finally { + parent.getReadLock().unlock(); + } + } + + @Override + public int lastIndexOf(Object o) { + try { + parent.getReadLock().lock(); + int index = -1; + for (int i = 0; i < size(); i++) { + if (o.equals(get(i))) + index = i; + } + return index; + } finally { + parent.getReadLock().unlock(); + } + } + + @Override + public boolean removeAll(Collection c) { + try { + parent.getWriteLock().lock(); + int size = size(); + for (Object obj : c) { + remove(obj); + } + return size != size(); + } finally { + parent.getWriteLock().unlock(); + } + } + + @Override + public boolean retainAll(Collection c) { + try { + parent.getWriteLock().lock(); + int size = size(); + for (Object obj : c) { + for (int i = 0; i < size(); i++) { + if (!obj.equals(get(i))) { + remove(i--); + } + } + } + return size != size(); + } finally { + parent.getWriteLock().unlock(); + } + } + + @Override + public boolean remove(Object o) { + try { + parent.getWriteLock().lock(); + int size = size(); + int id = -1; + while ((id = indexOf(o)) != -1) { + remove(id); + } + return size != size(); + } finally { + parent.getWriteLock().unlock(); + } + } + + @Override + public Iterator iterator() { + return new Iterator() { + + private int index = -1; + + @Override + public boolean hasNext() { + return size() > index + 1; + } + + @Override + public T next() { + if (!hasNext()) + throw new NoSuchElementException(); + return get(++index); + } + + @Override + public void remove() { + NBTList.this.remove(index); + index--; + } + }; + } + + @Override + public ListIterator listIterator() { + return listIterator(0); + } + + @Override + public ListIterator listIterator(int startIndex) { + final NBTList list = this; + return new ListIterator() { + + int index = startIndex - 1; + + @Override + public void add(T e) { + list.add(index, e); + } + + @Override + public boolean hasNext() { + return size() > index + 1; + } + + @Override + public boolean hasPrevious() { + return index >= 0 && index <= size(); + } + + @Override + public T next() { + if (!hasNext()) + throw new NoSuchElementException(); + return get(++index); + } + + @Override + public int nextIndex() { + return index + 1; + } + + @Override + public T previous() { + if (!hasPrevious()) + throw new NoSuchElementException("Id: " + (index - 1)); + return get(index--); + } + + @Override + public int previousIndex() { + return index - 1; + } + + @Override + public void remove() { + list.remove(index); + index--; + } + + @Override + public void set(T e) { + list.set(index, e); + } + }; + } + + @Override + public Object[] toArray() { + try { + parent.getReadLock().lock(); + Object[] ar = new Object[size()]; + for (int i = 0; i < size(); i++) + ar[i] = get(i); + return ar; + } finally { + parent.getReadLock().unlock(); + } + } + + @SuppressWarnings("unchecked") + @Override + public E[] toArray(E[] a) { + try { + parent.getReadLock().lock(); + E[] ar = Arrays.copyOf(a, size()); + Arrays.fill(ar, null); + Class arrayclass = a.getClass().getComponentType(); + for (int i = 0; i < size(); i++) { + T obj = get(i); + if (arrayclass.isInstance(obj)) { + ar[i] = (E) get(i); + } else { + throw new ArrayStoreException("The array does not match the objects stored in the List."); + } + } + return ar; + } finally { + parent.getReadLock().unlock(); + } + } + + @Override + public List subList(int fromIndex, int toIndex) { + try { + parent.getReadLock().lock(); + ArrayList list = new ArrayList<>(); + for (int i = fromIndex; i < toIndex; i++) + list.add(get(i)); + return list; + } finally { + parent.getReadLock().unlock(); + } + } + + @Override + public String toString() { + try { + parent.getReadLock().lock(); + return listObject.toString(); + } finally { + parent.getReadLock().unlock(); + } + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTListCompound.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTListCompound.java new file mode 100644 index 0000000..ca2491f --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTListCompound.java @@ -0,0 +1,42 @@ +package ed.tr7zw.changeme.nbtapi; + +/** + * Cut down version of the {@link NBTCompound} for inside + * {@link NBTCompoundList} This Compound implementation is missing the ability + * for further subCompounds and Lists. This class probably will change in the + * future + * + * @author tr7zw + * + */ +public class NBTListCompound extends NBTCompound { + + private NBTList owner; + private Object compound; + + protected NBTListCompound(NBTList parent, Object obj) { + super(null, null); + owner = parent; + compound = obj; + } + + public NBTList getListParent() { + return owner; + } + + @Override + public Object getCompound() { + return compound; + } + + @Override + protected void setCompound(Object compound) { + this.compound = compound; + } + + @Override + protected void saveCompound() { + owner.save(); + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTLongList.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTLongList.java new file mode 100644 index 0000000..3e51636 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTLongList.java @@ -0,0 +1,45 @@ +package ed.tr7zw.changeme.nbtapi; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ClassWrapper; +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ReflectionMethod; + +/** + * Long implementation for NBTLists + * + * @author tr7zw + * + */ +public class NBTLongList extends NBTList { + + protected NBTLongList(NBTCompound owner, String name, NBTType type, Object list) { + super(owner, name, type, list); + } + + @Override + protected Object asTag(Long object) { + try { + Constructor con = ClassWrapper.NMS_NBTTAGLONG.getClazz().getDeclaredConstructor(long.class); + con.setAccessible(true); + return con.newInstance(object); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) { + throw new NbtApiException("Error while wrapping the Object " + object + " to it's NMS object!", e); + } + } + + @Override + public Long get(int index) { + try { + Object obj = ReflectionMethod.LIST_GET.run(listObject, index); + return Long.valueOf(obj.toString().replace("L", "")); + } catch (NumberFormatException nf) { + return 0l; + } catch (Exception ex) { + throw new NbtApiException(ex); + } + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTPersistentDataContainer.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTPersistentDataContainer.java new file mode 100644 index 0000000..5ae9895 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTPersistentDataContainer.java @@ -0,0 +1,30 @@ +package ed.tr7zw.changeme.nbtapi; + +import java.util.Map; + +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ReflectionMethod; +import org.bukkit.persistence.PersistentDataContainer; + +public class NBTPersistentDataContainer extends NBTCompound { + + private final PersistentDataContainer container; + + public NBTPersistentDataContainer(PersistentDataContainer container) { + super(null, null); + this.container = container; + } + + @Override + public Object getCompound() { + return ReflectionMethod.CRAFT_PERSISTENT_DATA_CONTAINER_TO_TAG.run(container); + } + + @Override + protected void setCompound(Object compound) { + @SuppressWarnings("unchecked") + Map map = (Map) ReflectionMethod.CRAFT_PERSISTENT_DATA_CONTAINER_GET_MAP.run(container); + map.clear(); + ReflectionMethod.CRAFT_PERSISTENT_DATA_CONTAINER_PUT_ALL.run(container, compound); + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTReflectionUtil.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTReflectionUtil.java new file mode 100644 index 0000000..931f671 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTReflectionUtil.java @@ -0,0 +1,633 @@ +package ed.tr7zw.changeme.nbtapi; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.lang.reflect.Field; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import ed.tr7zw.changeme.nbtapi.utils.GsonWrapper; +import ed.tr7zw.changeme.nbtapi.utils.MinecraftVersion; +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ClassWrapper; +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ObjectCreator; +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ReflectionMethod; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Entity; +import org.bukkit.inventory.meta.ItemMeta; + +/** + * Utility class for translating NBTApi calls to reflections into NMS code All + * methods are allowed to throw {@link NbtApiException} + * + * @author tr7zw + * + */ +public class NBTReflectionUtil { + + private static Field field_unhandledTags = null; + + static { + try { + field_unhandledTags = ClassWrapper.CRAFT_METAITEM.getClazz().getDeclaredField("unhandledTags"); + field_unhandledTags.setAccessible(true); + } catch (NoSuchFieldException e) { + + } + } + + /** + * Hidden constructor + */ + private NBTReflectionUtil() { + + } + + /** + * Gets the NMS Entity for a given Bukkit Entity + * + * @param entity Bukkit Entity + * @return NMS Entity + */ + public static Object getNMSEntity(Entity entity) { + try { + return ReflectionMethod.CRAFT_ENTITY_GET_HANDLE.run(ClassWrapper.CRAFT_ENTITY.getClazz().cast(entity)); + } catch (Exception e) { + throw new NbtApiException("Exception while getting the NMS Entity from a Bukkit Entity!", e); + } + } + + /** + * Reads in a InputStream as NMS Compound + * + * @param stream InputStream of any NBT file + * @return NMS Compound + */ + public static Object readNBT(InputStream stream) { + try { + return ReflectionMethod.NBTFILE_READ.run(null, stream); + } catch (Exception e) { + try { + stream.close(); + }catch(IOException ignore) {} + throw new NbtApiException("Exception while reading a NBT File!", e); + } + } + + /** + * Writes a NMS Compound to an OutputStream + * + * @param nbt NMS Compound + * @param stream Stream to write to + * @return ??? + */ + public static Object writeNBT(Object nbt, OutputStream stream) { + try { + return ReflectionMethod.NBTFILE_WRITE.run(null, nbt, stream); + } catch (Exception e) { + throw new NbtApiException("Exception while writing NBT!", e); + } + } + + /** + * Writes a Compound to an OutputStream + * + * @param comp Compound + * @param stream Stream to write to + */ + public static void writeApiNBT(NBTCompound comp, OutputStream stream) { + try { + Object nbttag = comp.getCompound(); + if (nbttag == null) { + nbttag = ObjectCreator.NMS_NBTTAGCOMPOUND.getInstance(); + } + if (!valideCompound(comp)) + return; + Object workingtag = gettoCompount(nbttag, comp); + ReflectionMethod.NBTFILE_WRITE.run(null, workingtag, stream); + } catch (Exception e) { + throw new NbtApiException("Exception while writing NBT!", e); + } + } + + /** + * Simulates getOrCreateTag. If an Item doesn't yet have a Tag, it will return a + * new empty tag. + * + * @param nmsitem + * @return NMS Compound + */ + public static Object getItemRootNBTTagCompound(Object nmsitem) { + try { + Object answer = ReflectionMethod.NMSITEM_GETTAG.run(nmsitem); + return answer != null ? answer : ObjectCreator.NMS_NBTTAGCOMPOUND.getInstance(); + } catch (Exception e) { + throw new NbtApiException("Exception while getting an Itemstack's NBTCompound!", e); + } + } + + /** + * Converts {@link NBTCompound} to NMS ItemStacks + * + * @param nbtcompound Any valid {@link NBTCompound} + * @return NMS ItemStack + */ + public static Object convertNBTCompoundtoNMSItem(NBTCompound nbtcompound) { + try { + Object nmsComp = gettoCompount(nbtcompound.getCompound(), nbtcompound); + if (MinecraftVersion.getVersion().getVersionId() >= MinecraftVersion.MC1_11_R1.getVersionId()) { + return ObjectCreator.NMS_COMPOUNDFROMITEM.getInstance(nmsComp); + } else { + return ReflectionMethod.NMSITEM_CREATESTACK.run(null, nmsComp); + } + } catch (Exception e) { + throw new NbtApiException("Exception while converting NBTCompound to NMS ItemStack!", e); + } + } + + /** + * Converts NMS ItemStacks to {@link NBTContainer} + * + * @param nmsitem NMS ItemStack + * @return {@link NBTContainer} with all the data + */ + public static NBTContainer convertNMSItemtoNBTCompound(Object nmsitem) { + try { + Object answer = ReflectionMethod.NMSITEM_SAVE.run(nmsitem, ObjectCreator.NMS_NBTTAGCOMPOUND.getInstance()); + return new NBTContainer(answer); + } catch (Exception e) { + throw new NbtApiException("Exception while converting NMS ItemStack to NBTCompound!", e); + } + } + + /** + * Gets a live copy of non-vanilla NBT tags. + * + * @param meta ItemMeta from which tags should be retrieved + * @return Map containing unhandled (custom) NBT tags + */ + @SuppressWarnings("unchecked") + public static Map getUnhandledNBTTags(ItemMeta meta) { + try { + return (Map) field_unhandledTags.get(meta); + } catch (Exception e) { + throw new NbtApiException("Exception while getting unhandled tags from ItemMeta!", e); + } + } + + /** + * Gets the Vanilla NBT Compound from a given NMS Entity + * + * @param nmsEntity + * @return NMS NBT Compound + */ + public static Object getEntityNBTTagCompound(Object nmsEntity) { + try { + Object nbt = ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz().newInstance(); + Object answer = ReflectionMethod.NMS_ENTITY_GET_NBT.run(nmsEntity, nbt); + if (answer == null) + answer = nbt; + return answer; + } catch (Exception e) { + throw new NbtApiException("Exception while getting NBTCompound from NMS Entity!", e); + } + } + + /** + * Loads all Vanilla tags from a NMS Compound into a NMS Entity + * + * @param nbtTag + * @param nmsEntity + * @return The NMS Entity + */ + public static Object setEntityNBTTag(Object nbtTag, Object nmsEntity) { + try { + ReflectionMethod.NMS_ENTITY_SET_NBT.run(nmsEntity, nbtTag); + return nmsEntity; + } catch (Exception ex) { + throw new NbtApiException("Exception while setting the NBTCompound of an Entity", ex); + } + } + + /** + * Gets the NMS Compound from a given TileEntity + * + * @param tile + * @return NMS Compound with the Vanilla data + */ + public static Object getTileEntityNBTTagCompound(BlockState tile) { + try { + Object cworld = ClassWrapper.CRAFT_WORLD.getClazz().cast(tile.getWorld()); + Object nmsworld = ReflectionMethod.CRAFT_WORLD_GET_HANDLE.run(cworld); + Object o = null; + if(MinecraftVersion.getVersion() == MinecraftVersion.MC1_7_R4) { + o = ReflectionMethod.NMS_WORLD_GET_TILEENTITY_1_7_10.run(nmsworld, tile.getX(), tile.getY(), tile.getZ()); + }else { + Object pos = ObjectCreator.NMS_BLOCKPOSITION.getInstance(tile.getX(), tile.getY(), tile.getZ()); + o = ReflectionMethod.NMS_WORLD_GET_TILEENTITY.run(nmsworld, pos); + } + + Object answer = null; + if(MinecraftVersion.isAtLeastVersion(MinecraftVersion.MC1_18_R1)) { + answer = ReflectionMethod.TILEENTITY_GET_NBT_1181.run(o); + } else { + answer = ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz().newInstance(); + ReflectionMethod.TILEENTITY_GET_NBT.run(o, answer); + } + if (answer == null) { + throw new NbtApiException("Unable to get NBTCompound from TileEntity! " + tile + " " + o); + } + return answer; + } catch (Exception e) { + throw new NbtApiException("Exception while getting NBTCompound from TileEntity!", e); + } + } + + /** + * Sets Vanilla tags from a NMS Compound to a TileEntity + * + * @param tile + * @param comp + */ + public static void setTileEntityNBTTagCompound(BlockState tile, Object comp) { + try { + Object cworld = ClassWrapper.CRAFT_WORLD.getClazz().cast(tile.getWorld()); + Object nmsworld = ReflectionMethod.CRAFT_WORLD_GET_HANDLE.run(cworld); + Object o = null; + if(MinecraftVersion.getVersion() == MinecraftVersion.MC1_7_R4) { + o = ReflectionMethod.NMS_WORLD_GET_TILEENTITY_1_7_10.run(nmsworld, tile.getX(), tile.getY(), tile.getZ()); + }else { + Object pos = ObjectCreator.NMS_BLOCKPOSITION.getInstance(tile.getX(), tile.getY(), tile.getZ()); + o = ReflectionMethod.NMS_WORLD_GET_TILEENTITY.run(nmsworld, pos); + } + if(MinecraftVersion.isAtLeastVersion(MinecraftVersion.MC1_17_R1)) { + ReflectionMethod.TILEENTITY_SET_NBT.run(o, comp); + }else if(MinecraftVersion.isAtLeastVersion(MinecraftVersion.MC1_16_R1)) { + Object blockData = ReflectionMethod.TILEENTITY_GET_BLOCKDATA.run(o); + ReflectionMethod.TILEENTITY_SET_NBT_LEGACY1161.run(o, blockData, comp); + }else { + ReflectionMethod.TILEENTITY_SET_NBT_LEGACY1151.run(o, comp); + } + } catch (Exception e) { + throw new NbtApiException("Exception while setting NBTData for a TileEntity!", e); + } + } + + /** + * Gets the subCompound with a given name from a NMS Compound + * + * @param compound + * @param name + * @return NMS Compound or null + */ + public static Object getSubNBTTagCompound(Object compound, String name) { + try { + if ((boolean) ReflectionMethod.COMPOUND_HAS_KEY.run(compound, name)) { + return ReflectionMethod.COMPOUND_GET_COMPOUND.run(compound, name); + } else { + throw new NbtApiException("Tried getting invalide compound '" + name + "' from '" + compound + "'!"); + } + } catch (Exception e) { + throw new NbtApiException("Exception while getting NBT subcompounds!", e); + } + } + + /** + * Creates a subCompound with a given name in the given NMS Compound + * + * @param comp + * @param name + */ + public static void addNBTTagCompound(NBTCompound comp, String name) { + if (name == null) { + remove(comp, name); + return; + } + Object nbttag = comp.getCompound(); + if (nbttag == null) { + nbttag = ObjectCreator.NMS_NBTTAGCOMPOUND.getInstance(); + } + if (!valideCompound(comp)) { + return; + } + Object workingtag = gettoCompount(nbttag, comp); + try { + ReflectionMethod.COMPOUND_SET.run(workingtag, name, + ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz().newInstance()); + comp.setCompound(nbttag); + } catch (Exception e) { + throw new NbtApiException("Exception while adding a Compound!", e); + } + } + + /** + * Checks if the Compound is correctly linked to it's roots + * + * @param comp + * @return true if this is a valide Compound, else false + */ + public static Boolean valideCompound(NBTCompound comp) { + Object root = comp.getCompound(); + if (root == null) { + root = ObjectCreator.NMS_NBTTAGCOMPOUND.getInstance(); + } + return (gettoCompount(root, comp)) != null; + } + + protected static Object gettoCompount(Object nbttag, NBTCompound comp) { + Deque structure = new ArrayDeque<>(); + while (comp.getParent() != null) { + structure.add(comp.getName()); + comp = comp.getParent(); + } + while (!structure.isEmpty()) { + String target = structure.pollLast(); + nbttag = getSubNBTTagCompound(nbttag, target); + if (nbttag == null) { + throw new NbtApiException("Unable to find tag '" + target + "' in " + nbttag); + } + } + return nbttag; + } + + /** + * Merges the second {@link NBTCompound} into the first one + * + * @param comp Target for the merge + * @param nbtcompoundSrc Data to merge + */ + public static void mergeOtherNBTCompound(NBTCompound comp, NBTCompound nbtcompoundSrc) { + Object rootnbttag = comp.getCompound(); + if (rootnbttag == null) { + rootnbttag = ObjectCreator.NMS_NBTTAGCOMPOUND.getInstance(); + } + if (!valideCompound(comp)) + throw new NbtApiException("The Compound wasn't able to be linked back to the root!"); + Object workingtag = gettoCompount(rootnbttag, comp); + Object rootnbttagSrc = nbtcompoundSrc.getCompound(); + if (rootnbttagSrc == null) { + rootnbttagSrc = ObjectCreator.NMS_NBTTAGCOMPOUND.getInstance(); + } + if (!valideCompound(nbtcompoundSrc)) + throw new NbtApiException("The Compound wasn't able to be linked back to the root!"); + Object workingtagSrc = gettoCompount(rootnbttagSrc, nbtcompoundSrc); + try { + ReflectionMethod.COMPOUND_MERGE.run(workingtag, workingtagSrc); + comp.setCompound(rootnbttag); + } catch (Exception e) { + throw new NbtApiException("Exception while merging two NBTCompounds!", e); + } + } + + /** + * Returns the content for a given key inside a Compound + * + * @param comp + * @param key + * @return Content saved under this key + */ + public static String getContent(NBTCompound comp, String key) { + Object rootnbttag = comp.getCompound(); + if (rootnbttag == null) { + rootnbttag = ObjectCreator.NMS_NBTTAGCOMPOUND.getInstance(); + } + if (!valideCompound(comp)) + throw new NbtApiException("The Compound wasn't able to be linked back to the root!"); + Object workingtag = gettoCompount(rootnbttag, comp); + try { + return ReflectionMethod.COMPOUND_GET.run(workingtag, key).toString(); + } catch (Exception e) { + throw new NbtApiException("Exception while getting the Content for key '" + key + "'!", e); + } + } + + /** + * Sets a key in a {@link NBTCompound} to a given value + * + * @param comp + * @param key + * @param val + */ + public static void set(NBTCompound comp, String key, Object val) { + if (val == null) { + remove(comp, key); + return; + } + Object rootnbttag = comp.getCompound(); + if (rootnbttag == null) { + rootnbttag = ObjectCreator.NMS_NBTTAGCOMPOUND.getInstance(); + } + if (!valideCompound(comp)) { + throw new NbtApiException("The Compound wasn't able to be linked back to the root!"); + } + Object workingtag = gettoCompount(rootnbttag, comp); + try { + ReflectionMethod.COMPOUND_SET.run(workingtag, key, val); + comp.setCompound(rootnbttag); + } catch (Exception e) { + throw new NbtApiException("Exception while setting key '" + key + "' to '" + val + "'!", e); + } + } + + /** + * Returns the List saved with a given key. + * + * @param comp + * @param key + * @param type + * @param clazz + * @return The list at that key. Null if it's an invalide type + */ + @SuppressWarnings("unchecked") + public static NBTList getList(NBTCompound comp, String key, NBTType type, Class clazz) { + Object rootnbttag = comp.getCompound(); + if (rootnbttag == null) { + rootnbttag = ObjectCreator.NMS_NBTTAGCOMPOUND.getInstance(); + } + if (!valideCompound(comp)) + return null; + Object workingtag = gettoCompount(rootnbttag, comp); + try { + Object nbt = ReflectionMethod.COMPOUND_GET_LIST.run(workingtag, key, type.getId()); + if (clazz == String.class) { + return (NBTList) new NBTStringList(comp, key, type, nbt); + } else if (clazz == NBTListCompound.class) { + return (NBTList) new NBTCompoundList(comp, key, type, nbt); + } else if (clazz == Integer.class) { + return (NBTList) new NBTIntegerList(comp, key, type, nbt); + } else if (clazz == Float.class) { + return (NBTList) new NBTFloatList(comp, key, type, nbt); + } else if (clazz == Double.class) { + return (NBTList) new NBTDoubleList(comp, key, type, nbt); + } else if (clazz == Long.class) { + return (NBTList) new NBTLongList(comp, key, type, nbt); + } else if (clazz == int[].class) { + return (NBTList) new NBTIntArrayList(comp, key, type, nbt); + } else if (clazz == UUID.class) { + return (NBTList) new NBTUUIDList(comp, key, type, nbt); + } else { + return null; + } + } catch (Exception ex) { + throw new NbtApiException("Exception while getting a list with the type '" + type + "'!", ex); + } + } + + public static NBTType getListType(NBTCompound comp, String key) { + Object rootnbttag = comp.getCompound(); + if (rootnbttag == null) { + rootnbttag = ObjectCreator.NMS_NBTTAGCOMPOUND.getInstance(); + } + if (!valideCompound(comp)) + return null; + Object workingtag = gettoCompount(rootnbttag, comp); + try { + Object nbt = ReflectionMethod.COMPOUND_GET.run(workingtag, key); + String fieldname = "type"; + if(MinecraftVersion.isAtLeastVersion(MinecraftVersion.MC1_17_R1)) { + fieldname = "w"; + } + Field f = nbt.getClass().getDeclaredField(fieldname); + f.setAccessible(true); + return NBTType.valueOf(f.getByte(nbt)); + } catch (Exception ex) { + throw new NbtApiException("Exception while getting the list type!", ex); + } + } + + public static Object getEntry(NBTCompound comp, String key) { + Object rootnbttag = comp.getCompound(); + if (rootnbttag == null) { + rootnbttag = ObjectCreator.NMS_NBTTAGCOMPOUND.getInstance(); + } + if (!valideCompound(comp)) + return null; + Object workingtag = gettoCompount(rootnbttag, comp); + try { + Object nbt = ReflectionMethod.COMPOUND_GET.run(workingtag, key); + return nbt; + } catch (Exception ex) { + throw new NbtApiException("Exception while getting an Entry!", ex); + } + } + + /** + * Uses Gson to set a {@link Serializable} value in a Compound + * + * @param comp + * @param key + * @param value + */ + public static void setObject(NBTCompound comp, String key, Object value) { + if (!MinecraftVersion.hasGsonSupport()) + return; + try { + String json = GsonWrapper.getString(value); + setData(comp, ReflectionMethod.COMPOUND_SET_STRING, key, json); + } catch (Exception e) { + throw new NbtApiException("Exception while setting the Object '" + value + "'!", e); + } + } + + /** + * Uses Gson to load back a {@link Serializable} object from the Compound + * + * @param comp + * @param key + * @param type + * @return The loaded Object or null, if not found + */ + public static T getObject(NBTCompound comp, String key, Class type) { + if (!MinecraftVersion.hasGsonSupport()) + return null; + String json = (String) getData(comp, ReflectionMethod.COMPOUND_GET_STRING, key); + if (json == null) { + return null; + } + return GsonWrapper.deserializeJson(json, type); + } + + /** + * Deletes the given key + * + * @param comp + * @param key + */ + public static void remove(NBTCompound comp, String key) { + Object rootnbttag = comp.getCompound(); + if (rootnbttag == null) { + rootnbttag = ObjectCreator.NMS_NBTTAGCOMPOUND.getInstance(); + } + if (!valideCompound(comp)) + return; + Object workingtag = gettoCompount(rootnbttag, comp); + ReflectionMethod.COMPOUND_REMOVE_KEY.run(workingtag, key); + comp.setCompound(rootnbttag); + } + + /** + * Gets the Keyset inside this Compound + * + * @param comp + * @return Set of all keys + */ + @SuppressWarnings("unchecked") + public static Set getKeys(NBTCompound comp) { + Object rootnbttag = comp.getCompound(); + if (rootnbttag == null) { + rootnbttag = ObjectCreator.NMS_NBTTAGCOMPOUND.getInstance(); + } + if (!valideCompound(comp)) + throw new NbtApiException("The Compound wasn't able to be linked back to the root!"); + Object workingtag = gettoCompount(rootnbttag, comp); + return (Set) ReflectionMethod.COMPOUND_GET_KEYS.run(workingtag); + } + + /** + * Sets data inside the Compound + * + * @param comp + * @param type + * @param key + * @param data + */ + public static void setData(NBTCompound comp, ReflectionMethod type, String key, Object data) { + if (data == null) { + remove(comp, key); + return; + } + Object rootnbttag = comp.getCompound(); + if (rootnbttag == null) { + rootnbttag = ObjectCreator.NMS_NBTTAGCOMPOUND.getInstance(); + } + if (!valideCompound(comp)) + throw new NbtApiException("The Compound wasn't able to be linked back to the root!"); + Object workingtag = gettoCompount(rootnbttag, comp); + type.run(workingtag, key, data); + comp.setCompound(rootnbttag); + } + + /** + * Gets data from the Compound + * + * @param comp + * @param type + * @param key + * @return The value or default fallback from NMS + */ + public static Object getData(NBTCompound comp, ReflectionMethod type, String key) { + Object rootnbttag = comp.getCompound(); + if (rootnbttag == null) { + return null; + } + if (!valideCompound(comp)) + throw new NbtApiException("The Compound wasn't able to be linked back to the root!"); + Object workingtag = gettoCompount(rootnbttag, comp); + return type.run(workingtag, key); + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTStringList.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTStringList.java new file mode 100644 index 0000000..df06d78 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTStringList.java @@ -0,0 +1,42 @@ +package ed.tr7zw.changeme.nbtapi; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ClassWrapper; +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ReflectionMethod; + +/** + * String implementation for NBTLists + * + * @author tr7zw + * + */ +public class NBTStringList extends NBTList { + + protected NBTStringList(NBTCompound owner, String name, NBTType type, Object list) { + super(owner, name, type, list); + } + + @Override + public String get(int index) { + try { + return (String) ReflectionMethod.LIST_GET_STRING.run(listObject, index); + } catch (Exception ex) { + throw new NbtApiException(ex); + } + } + + @Override + protected Object asTag(String object) { + try { + Constructor con = ClassWrapper.NMS_NBTTAGSTRING.getClazz().getDeclaredConstructor(String.class); + con.setAccessible(true); + return con.newInstance(object); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) { + throw new NbtApiException("Error while wrapping the Object " + object + " to it's NMS object!", e); + } + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTTileEntity.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTTileEntity.java new file mode 100644 index 0000000..c78a9bb --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTTileEntity.java @@ -0,0 +1,67 @@ +package ed.tr7zw.changeme.nbtapi; + +import ed.tr7zw.changeme.nbtapi.utils.MinecraftVersion; +import ed.tr7zw.changeme.nbtapi.utils.annotations.AvailableSince; +import ed.tr7zw.changeme.nbtapi.utils.annotations.CheckUtil; +import org.bukkit.Bukkit; +import org.bukkit.block.BlockState; + +import de.tr7zw.annotations.FAUtil; + +/** + * NBT class to access vanilla tags from TileEntities. TileEntities don't + * support custom tags. Use the NBTInjector for custom tags. Changes will be + * instantly applied to the Tile, use the merge method to do many things at + * once. + * + * @author tr7zw + * + */ +public class NBTTileEntity extends NBTCompound { + + private final BlockState tile; + + /** + * @param tile BlockState from any TileEntity + */ + public NBTTileEntity(BlockState tile) { + super(null, null); + if (tile == null || (MinecraftVersion.isAtLeastVersion(MinecraftVersion.MC1_8_R3) && !tile.isPlaced())) { + throw new NullPointerException("Tile can't be null/not placed!"); + } + this.tile = tile; + } + + @Override + public Object getCompound() { + if(!Bukkit.isPrimaryThread())throw new NbtApiException("BlockEntity NBT needs to be accessed sync!"); + return NBTReflectionUtil.getTileEntityNBTTagCompound(tile); + } + + @Override + protected void setCompound(Object compound) { + if(!Bukkit.isPrimaryThread())throw new NbtApiException("BlockEntity NBT needs to be accessed sync!"); + NBTReflectionUtil.setTileEntityNBTTagCompound(tile, compound); + } + + /** + * Gets the NBTCompound used by spigots PersistentDataAPI. This method is only + * available for 1.14+! + * + * @return NBTCompound containing the data of the PersistentDataAPI + */ + @AvailableSince(version = MinecraftVersion.MC1_14_R1) + public NBTCompound getPersistentDataContainer() { + FAUtil.check(this::getPersistentDataContainer, CheckUtil::isAvaliable); + if (hasTag("PublicBukkitValues")) { + return getCompound("PublicBukkitValues"); + } else { + NBTContainer container = new NBTContainer(); + container.addCompound("PublicBukkitValues").setString("__nbtapi", + "Marker to make the PersistentDataContainer have content"); + mergeCompound(container); + return getCompound("PublicBukkitValues"); + } + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTType.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTType.java new file mode 100644 index 0000000..b41e3f3 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTType.java @@ -0,0 +1,48 @@ +package ed.tr7zw.changeme.nbtapi; + +/** + * Enum of all NBT Types Minecraft contains + * + * @author tr7zw + * + */ +@SuppressWarnings("javadoc") +public enum NBTType { + NBTTagEnd(0), + NBTTagByte(1), + NBTTagShort(2), + NBTTagInt(3), + NBTTagLong(4), + NBTTagFloat(5), + NBTTagDouble(6), + NBTTagByteArray(7), + NBTTagIntArray(11), + NBTTagString(8), + NBTTagList(9), + NBTTagCompound(10); + + NBTType(int i) { + id = i; + } + + private final int id; + + /** + * @return Id used by Minecraft internally + */ + public int getId() { + return id; + } + + /** + * @param id Internal Minecraft id + * @return Enum representing the id, NBTTagEnd for invalide ids + */ + public static NBTType valueOf(int id) { + for (NBTType t : values()) + if (t.getId() == id) + return t; + return NBTType.NBTTagEnd; + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NBTUUIDList.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTUUIDList.java new file mode 100644 index 0000000..a40ac31 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NBTUUIDList.java @@ -0,0 +1,53 @@ +package ed.tr7zw.changeme.nbtapi; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.UUID; + +import ed.tr7zw.changeme.nbtapi.utils.UUIDUtil; +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ClassWrapper; +import ed.tr7zw.changeme.nbtapi.utils.nmsmappings.ReflectionMethod; + +/** + * Integer implementation for NBTLists + * + * @author tr7zw + * + */ +public class NBTUUIDList extends NBTList { + + private final NBTContainer tmpContainer; + + protected NBTUUIDList(NBTCompound owner, String name, NBTType type, Object list) { + super(owner, name, type, list); + this.tmpContainer = new NBTContainer(); + } + + @Override + protected Object asTag(UUID object) { + try { + Constructor con = ClassWrapper.NMS_NBTTAGINTARRAY.getClazz().getDeclaredConstructor(int[].class); + con.setAccessible(true); + return con.newInstance(UUIDUtil.uuidToIntArray(object)); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) { + throw new NbtApiException("Error while wrapping the Object " + object + " to it's NMS object!", e); + } + } + + @Override + public UUID get(int index) { + try { + Object obj = ReflectionMethod.LIST_GET.run(listObject, index); + ReflectionMethod.COMPOUND_SET.run(tmpContainer.getCompound(), "tmp", obj); + int[] val = tmpContainer.getIntArray("tmp"); + tmpContainer.removeKey("tmp"); + return UUIDUtil.uuidFromIntArray(val); + } catch (NumberFormatException nf) { + return null; + } catch (Exception ex) { + throw new NbtApiException(ex); + } + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/NbtApiException.java b/src/main/java/ed/tr7zw/changeme/nbtapi/NbtApiException.java new file mode 100644 index 0000000..aad8f1b --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/NbtApiException.java @@ -0,0 +1,76 @@ +package ed.tr7zw.changeme.nbtapi; + +import ed.tr7zw.changeme.nbtapi.utils.MinecraftVersion; + +/** + * A generic {@link RuntimeException} that can be thrown by most methods in the + * NBTAPI. + * + * @author tr7zw + * + */ +public class NbtApiException extends RuntimeException { + + /** + * + */ + private static final long serialVersionUID = -993309714559452334L; + /** + * Keep track of the plugin selfcheck. + * Null = not checked(silentquickstart/shaded) + * true = selfcheck failed + * false = everything should be fine, but apparently wasn't? + */ + public static Boolean confirmedBroken = null; + + /** + * + */ + public NbtApiException() { + super(); + } + + /** + * @param message + * @param cause + * @param enableSuppression + * @param writableStackTrace + */ + public NbtApiException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(generateMessage(message), cause, enableSuppression, writableStackTrace); + } + + /** + * @param message + * @param cause + */ + public NbtApiException(String message, Throwable cause) { + super(generateMessage(message), cause); + } + + /** + * @param message + */ + public NbtApiException(String message) { + super(generateMessage(message)); + } + + /** + * @param cause + */ + public NbtApiException(Throwable cause) { + super(generateMessage(cause==null ? null : cause.toString()), cause); + } + + private static String generateMessage(String message) { + if(message == null)return null; + if(confirmedBroken == null) { + return "[?]"+message; + }else if(confirmedBroken == false) { + return "[Selfchecked]"+message; + } + + return "[" + MinecraftVersion.getVersion() + "]There were errors detected during the server self-check! Please, make sure that NBT-API is up to date. Error message: " + message; + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/utils/ApiMetricsLite.java b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/ApiMetricsLite.java new file mode 100644 index 0000000..c1f013e --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/ApiMetricsLite.java @@ -0,0 +1,386 @@ +package ed.tr7zw.changeme.nbtapi.utils; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.bukkit.plugin.ServicePriority; + +import javax.net.ssl.HttpsURLConnection; +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; +import java.util.logging.Level; +import java.util.zip.GZIPOutputStream; + +/** + * bStats collects some data for plugin authors. + *

+ * Check out https://bStats.org/ to learn more about bStats! + * + * This class is modified by tr7zw to work when the api is shaded into other peoples plugins. + */ +public class ApiMetricsLite { + + private static final String PLUGINNAME = "ItemNBTAPI"; // DO NOT CHANGE THE NAME! else it won't link the data on bStats + + // The version of this bStats class + public static final int B_STATS_VERSION = 1; + + // The version of the NBT-Api bStats + public static final int NBT_BSTATS_VERSION = 1; + + // The url to which the data is sent + private static final String URL = "https://bStats.org/submitData/bukkit"; + + // Is bStats enabled on this server? + private boolean enabled; + + // Should failed requests be logged? + private static boolean logFailedRequests; + + // Should the sent data be logged? + private static boolean logSentData; + + // Should the response text be logged? + private static boolean logResponseStatusText; + + // The uuid of the server + private static String serverUUID; + + // The plugin + private Plugin plugin; + + /** + * Class constructor. + * + */ + public ApiMetricsLite() { + + // The register method just uses any enabled plugin it can find to register. This *shouldn't* cause any problems, since the plugin isn't used any other way. + // Register our service + for(Plugin plug : Bukkit.getPluginManager().getPlugins()) { + plugin = plug; + if(plugin != null) + break; + } + if(plugin == null) { + return;// Didn't find any plugin that could work + } + + // Get the config file + File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); + File configFile = new File(bStatsFolder, "config.yml"); + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + + // Check if the config file exists + if (!config.isSet("serverUuid")) { + + // Add default values + config.addDefault("enabled", true); + // Every server gets it's unique random id. + config.addDefault("serverUuid", UUID.randomUUID().toString()); + // Should failed request be logged? + config.addDefault("logFailedRequests", false); + // Should the sent data be logged? + config.addDefault("logSentData", false); + // Should the response text be logged? + config.addDefault("logResponseStatusText", false); + + // Inform the server owners about bStats + config.options().header( + "bStats collects some data for plugin authors like how many servers are using their plugins.\n" + + "To honor their work, you should not disable it.\n" + + "This has nearly no effect on the server performance!\n" + + "Check out https://bStats.org/ to learn more :)" + ).copyDefaults(true); + try { + config.save(configFile); + } catch (IOException ignored) { } + } + + // Load the data + serverUUID = config.getString("serverUuid"); + logFailedRequests = config.getBoolean("logFailedRequests", false); + enabled = config.getBoolean("enabled", true); + logSentData = config.getBoolean("logSentData", false); + logResponseStatusText = config.getBoolean("logResponseStatusText", false); + if (enabled) { + boolean found = false; + // Search for all other bStats Metrics classes to see if we are the first one + for (Class service : Bukkit.getServicesManager().getKnownServices()) { + try { + service.getField("NBT_BSTATS_VERSION"); // Create only one instance of the nbt-api bstats. + return; + } catch (NoSuchFieldException ignored) { } + try { + service.getField("B_STATS_VERSION"); // Our identifier :) + found = true; // We aren't the first + break; + } catch (NoSuchFieldException ignored) { } + } + boolean fFound = found; + // Register our service + if(Bukkit.isPrimaryThread()){ + Bukkit.getServicesManager().register(ApiMetricsLite.class, this, plugin, ServicePriority.Normal); + if (!fFound) { + MinecraftVersion.getLogger().info("[NBTAPI] Using the plugin '" + plugin.getName() + "' to create a bStats instance!"); + // We are the first! + startSubmitting(); + } + }else{ + Bukkit.getScheduler().runTask(plugin, () -> { + Bukkit.getServicesManager().register(ApiMetricsLite.class, this, plugin, ServicePriority.Normal); + if (!fFound) { + MinecraftVersion.getLogger().info("[NBTAPI] Using the plugin '" + plugin.getName() + "' to create a bStats instance!"); + // We are the first! + startSubmitting(); + } + }); + } + } + } + + /** + * Checks if bStats is enabled. + * + * @return Whether bStats is enabled or not. + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Starts the Scheduler which submits our data every 30 minutes. + */ + private void startSubmitting() { + final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + if (!plugin.isEnabled()) { // Plugin was disabled + timer.cancel(); + return; + } + // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler + // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) + Bukkit.getScheduler().runTask(plugin, () -> submitData()); + } + }, 1000l * 60l * 5l, 1000l * 60l * 30l); + // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start + // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! + // WARNING: Just don't do it! + } + + /** + * Gets the plugin specific data. + * This method is called using Reflection. + * + * @return The plugin specific data. + */ + public JsonObject getPluginData() { + JsonObject data = new JsonObject(); + + data.addProperty("pluginName", PLUGINNAME); // Append the name of the plugin + data.addProperty("pluginVersion", MinecraftVersion.VERSION); // Append the version of the plugin + data.add("customCharts", new JsonArray()); + + return data; + } + + /** + * Gets the server specific data. + * + * @return The server specific data. + */ + private JsonObject getServerData() { + // Minecraft specific data + int playerAmount; + try { + // Around MC 1.8 the return type was changed to a collection from an array, + // This fixes java.lang.NoSuchMethodError: org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; + Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); + playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class) + ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() + : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; + } catch (Exception e) { + playerAmount = Bukkit.getOnlinePlayers().size(); // Just use the new method if the Reflection failed + } + int onlineMode = Bukkit.getOnlineMode() ? 1 : 0; + String bukkitVersion = Bukkit.getVersion(); + String bukkitName = Bukkit.getName(); + + // OS/Java specific data + String javaVersion = System.getProperty("java.version"); + String osName = System.getProperty("os.name"); + String osArch = System.getProperty("os.arch"); + String osVersion = System.getProperty("os.version"); + int coreCount = Runtime.getRuntime().availableProcessors(); + + JsonObject data = new JsonObject(); + + data.addProperty("serverUUID", serverUUID); + + data.addProperty("playerAmount", playerAmount); + data.addProperty("onlineMode", onlineMode); + data.addProperty("bukkitVersion", bukkitVersion); + data.addProperty("bukkitName", bukkitName); + + data.addProperty("javaVersion", javaVersion); + data.addProperty("osName", osName); + data.addProperty("osArch", osArch); + data.addProperty("osVersion", osVersion); + data.addProperty("coreCount", coreCount); + + return data; + } + + /** + * Collects the data and sends it afterwards. + */ + private void submitData() { + final JsonObject data = getServerData(); + + JsonArray pluginData = new JsonArray(); + // Search for all other bStats Metrics classes to get their plugin data + for (Class service : Bukkit.getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) + + for (RegisteredServiceProvider provider : Bukkit.getServicesManager().getRegistrations(service)) { + try { + Object plugin = provider.getService().getMethod("getPluginData").invoke(provider.getProvider()); + if (plugin instanceof JsonObject) { + pluginData.add((JsonObject) plugin); + } else { // old bstats version compatibility + try { + Class jsonObjectJsonSimple = Class.forName("org.json.simple.JSONObject"); + if (plugin.getClass().isAssignableFrom(jsonObjectJsonSimple)) { + Method jsonStringGetter = jsonObjectJsonSimple.getDeclaredMethod("toJSONString"); + jsonStringGetter.setAccessible(true); + String jsonString = (String) jsonStringGetter.invoke(plugin); + JsonObject object = new JsonParser().parse(jsonString).getAsJsonObject(); + pluginData.add(object); + } + } catch (ClassNotFoundException e) { + // minecraft version 1.14+ + if (logFailedRequests) { + MinecraftVersion.getLogger().log(Level.WARNING, "[NBTAPI][BSTATS] Encountered exception while posting request!", e); + // Not using the plugins logger since the plugin isn't the plugin containing the NBT-Api most of the time + //this.plugin.getLogger().log(Level.SEVERE, "Encountered unexpected exception ", e); + } + continue; // continue looping since we cannot do any other thing. + } + } + } catch (NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { + } + } + } catch (NoSuchFieldException ignored) { } + } + + data.add("plugins", pluginData); + + // Create a new thread for the connection to the bStats server + new Thread(new Runnable() { + @Override + public void run() { + try { + // Send the data + sendData(plugin, data); + } catch (Exception e) { + // Something went wrong! :( + if (logFailedRequests) { + MinecraftVersion.getLogger().log(Level.WARNING, "[NBTAPI][BSTATS] Could not submit plugin stats of " + plugin.getName(), e); + // Not using the plugins logger since the plugin isn't the plugin containing the NBT-Api most of the time + //plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); + } + } + } + }).start(); + } + + /** + * Sends the data to the bStats server. + * + * @param plugin Any plugin. It's just used to get a logger instance. + * @param data The data to send. + * @throws Exception If the request failed. + */ + private static void sendData(Plugin plugin, JsonObject data) throws Exception { + if (data == null) { + throw new IllegalArgumentException("Data cannot be null!"); + } + if (Bukkit.isPrimaryThread()) { + throw new IllegalAccessException("This method must not be called from the main thread!"); + } + if (logSentData) { + MinecraftVersion.getLogger().info("[NBTAPI][BSTATS] Sending data to bStats: " + data.toString()); + // Not using the plugins logger since the plugin isn't the plugin containing the NBT-Api most of the time + //plugin.getLogger().info("Sending data to bStats: " + data.toString()); + } + HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); + + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + + // Add headers + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format + connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); + + // Send data + connection.setDoOutput(true); + DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); + outputStream.write(compressedData); + outputStream.flush(); + outputStream.close(); + + InputStream inputStream = connection.getInputStream(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + + StringBuilder builder = new StringBuilder(); + String line; + while ((line = bufferedReader.readLine()) != null) { + builder.append(line); + } + bufferedReader.close(); + if (logResponseStatusText) { + MinecraftVersion.getLogger().info("[NBTAPI][BSTATS] Sent data to bStats and received response: " + builder.toString()); + // Not using the plugins logger since the plugin isn't the plugin containing the NBT-Api most of the time + //plugin.getLogger().info("Sent data to bStats and received response: " + builder.toString()); + } + } + + /** + * Gzips the given String. + * + * @param str The string to gzip. + * @return The gzipped String. + * @throws IOException If the compression failed. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return new byte[0]; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(outputStream); + gzip.write(str.getBytes(StandardCharsets.UTF_8)); + gzip.close(); + return outputStream.toByteArray(); + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/utils/GsonWrapper.java b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/GsonWrapper.java new file mode 100644 index 0000000..134728e --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/GsonWrapper.java @@ -0,0 +1,54 @@ +package ed.tr7zw.changeme.nbtapi.utils; + +import com.google.gson.Gson; + +import ed.tr7zw.changeme.nbtapi.NbtApiException; + +/** + * Helper class for 1.7 servers without Gson + * + * @author tr7zw + * + */ +public class GsonWrapper { + + /** + * Private constructor + */ + private GsonWrapper() { + + } + + private static final Gson gson = new Gson(); + + /** + * Turns Objects into Json Strings + * + * @param obj + * @return Json, representing the Object + */ + public static String getString(Object obj) { + return gson.toJson(obj); + } + + /** + * Creates an Object of the given type using the Json String + * + * @param json + * @param type + * @return Object that got created, or null if the json is null + */ + public static T deserializeJson(String json, Class type) { + try { + if (json == null) { + return null; + } + + T obj = gson.fromJson(json, type); + return type.cast(obj); + } catch (Exception ex) { + throw new NbtApiException("Error while converting json to " + type.getName(), ex); + } + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/utils/MinecraftVersion.java b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/MinecraftVersion.java new file mode 100644 index 0000000..cb5e088 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/MinecraftVersion.java @@ -0,0 +1,233 @@ +package ed.tr7zw.changeme.nbtapi.utils; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bukkit.Bukkit; + +/** + * This class acts as the "Brain" of the NBTApi. It contains the main logger for + * other classes,registers bStats and checks rather Maven shading was done + * correctly. + * + * @author tr7zw + * + */ +@SuppressWarnings("javadoc") +public enum MinecraftVersion { + UNKNOWN(Integer.MAX_VALUE), // Use the newest known mappings + MC1_7_R4(174), MC1_8_R3(183), MC1_9_R1(191), MC1_9_R2(192), MC1_10_R1(1101), MC1_11_R1(1111), MC1_12_R1(1121), + MC1_13_R1(1131), MC1_13_R2(1132), MC1_14_R1(1141), MC1_15_R1(1151), MC1_16_R1(1161), MC1_16_R2(1162), MC1_16_R3(1163), MC1_17_R1(1171), MC1_18_R1(1181, true), MC1_18_R2(1182, true), MC1_19_R1(1191, true); + + private static MinecraftVersion version; + private static Boolean hasGsonSupport; + private static Boolean isForgePresent; + private static boolean bStatsDisabled = false; + private static boolean disablePackageWarning = false; + private static boolean updateCheckDisabled = false; + /** + * Logger used by the api + */ + private static Logger logger = Logger.getLogger("NBTAPI"); + + // NBT-API Version + protected static final String VERSION = "2.11.0-SNAPSHOT"; + + private final int versionId; + private final boolean mojangMapping; + + MinecraftVersion(int versionId) { + this(versionId, false); + } + + MinecraftVersion(int versionId, boolean mojangMapping) { + this.versionId = versionId; + this.mojangMapping = mojangMapping; + } + + /** + * @return A simple comparable Integer, representing the version. + */ + public int getVersionId() { + return versionId; + } + + /** + * @return True if method names are in Mojang format and need to be remapped internally + */ + public boolean isMojangMapping() { + return mojangMapping; + } + + /** + * This method is required to hot-wire the plugin during mappings generation for newer mc versions thanks to md_5 not used mojmap. + * + * @return + */ + public String getPackageName() { + if(this == UNKNOWN) { + return Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; + } + return this.name().replace("MC", "v"); + } + + /** + * Returns true if the current versions is at least the given Version + * + * @param version The minimum version + * @return + */ + public static boolean isAtLeastVersion(MinecraftVersion version) { + return getVersion().getVersionId() >= version.getVersionId(); + } + + /** + * Returns true if the current versions newer (not equal) than the given version + * + * @param version The minimum version + * @return + */ + public static boolean isNewerThan(MinecraftVersion version) { + return getVersion().getVersionId() > version.getVersionId(); + } + + /** + * Getter for this servers MinecraftVersion. Also init's bStats and checks the + * shading. + * + * @return The enum for the MinecraftVersion this server is running + */ + public static MinecraftVersion getVersion() { + if (version != null) { + return version; + } + final String ver = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; + logger.info("[NBTAPI] Found Spigot: " + ver + "! Trying to find NMS support"); + try { + version = MinecraftVersion.valueOf(ver.replace("v", "MC")); + } catch (IllegalArgumentException ex) { + version = MinecraftVersion.UNKNOWN; + } + if (version != UNKNOWN) { + logger.info("[NBTAPI] NMS support '" + version.name() + "' loaded!"); + } else { + logger.warning("[NBTAPI] This Server-Version(" + ver + ") is not supported by this NBT-API Version(" + VERSION + ") located at " + MinecraftVersion.class.getName() + ". The NBT-API will try to work as good as it can! Some functions may not work!"); + } + init(); + return version; + } + + private static void init() { + try { + if (hasGsonSupport() && !bStatsDisabled) + new ApiMetricsLite(); + } catch (Exception ex) { + logger.log(Level.WARNING, "[NBTAPI] Error enabling Metrics!", ex); + } + + if (hasGsonSupport() && !updateCheckDisabled) + new Thread(() -> { + try { + VersionChecker.checkForUpdates(); + } catch (Exception ex) { + logger.log(Level.WARNING, "[NBTAPI] Error while checking for updates! Error: " + ex.getMessage()); + } + }).start(); + // Maven's Relocate is clever and changes strings, too. So we have to use this + // little "trick" ... :D (from bStats) + final String defaultPackage = new String(new byte[] { 'd', 'e', '.', 't', 'r', '7', 'z', 'w', '.', 'c', 'h', + 'a', 'n', 'g', 'e', 'm', 'e', '.', 'n', 'b', 't', 'a', 'p', 'i', '.', 'u', 't', 'i', 'l', 's' }); + if (!disablePackageWarning && MinecraftVersion.class.getPackage().getName().equals(defaultPackage)) { + logger.warning( + "#########################################- NBTAPI -#########################################"); + logger.warning( + "The NBT-API package has not been moved! This *will* cause problems with other plugins containing"); + logger.warning( + "a different version of the api! Please read the guide on the plugin page on how to get the"); + logger.warning( + "Maven Shade plugin to relocate the api to your personal location! If you are not the developer,"); + logger.warning("please check your plugins and contact their developer, so he can fix this issue."); + logger.warning( + "#########################################- NBTAPI -#########################################"); + } + } + + /** + * @return True, if Gson is usable + */ + public static boolean hasGsonSupport() { + if (hasGsonSupport != null) { + return hasGsonSupport; + } + try { + logger.info("[NBTAPI] Found Gson: " + Class.forName("com.google.gson.Gson")); + hasGsonSupport = true; + } catch (Exception ex) { + logger.info("[NBTAPI] Gson not found! This will not allow the usage of some methods!"); + hasGsonSupport = false; + } + return hasGsonSupport; + } + + /** + * @return True, if Forge is present + */ + public static boolean isForgePresent() { + if (isForgePresent != null) { + return isForgePresent; + } + try { + logger.info("[NBTAPI] Found Forge: " + + (getVersion() == MinecraftVersion.MC1_7_R4 ? Class.forName("cpw.mods.fml.common.Loader") : Class.forName("net.minecraftforge.fml.common.Loader"))); + isForgePresent = true; + } catch (Exception ex) { + isForgePresent = false; + } + return isForgePresent; + } + + /** + * Calling this function before the NBT-Api is used will disable bStats stats + * collection. Please consider not to do that, since it won't affect your plugin + * and helps the NBT-Api developer to see api's demand. + */ + public static void disableBStats() { + bStatsDisabled = true; + } + + /** + * Disables the update check. Uses Spiget to get the current version and prints + * a warning when outdated. + */ + public static void disableUpdateCheck() { + updateCheckDisabled = true; + } + + /** + * Forcefully disables the log message for plugins not shading the API to + * another location. This may be helpful for networks or development + * environments, but please don't use it for plugins that are uploaded to + * Spigotmc. + */ + public static void disablePackageWarning() { + disablePackageWarning = true; + } + + /** + * @return Logger used by the NBT-API + */ + public static Logger getLogger() { + return logger; + } + + /** + * Replaces the NBT-API logger with a custom implementation. + * + * @param logger The new logger(can not be null!) + */ + public static void replaceLogger(Logger logger) { + if(logger == null)throw new NullPointerException("Logger can not be null!"); + MinecraftVersion.logger = logger; + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/utils/ReflectionUtil.java b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/ReflectionUtil.java new file mode 100644 index 0000000..cb0b4ab --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/ReflectionUtil.java @@ -0,0 +1,53 @@ +package ed.tr7zw.changeme.nbtapi.utils; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import ed.tr7zw.changeme.nbtapi.NbtApiException; + +public final class ReflectionUtil { + + private static Field field_modifiers; + + static { + try { + field_modifiers = Field.class.getDeclaredField("modifiers"); + field_modifiers.setAccessible(true); + } catch (NoSuchFieldException ex) { + try { + // This hacky workaround is for newer jdk versions 11+? + Method fieldGetter = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); + fieldGetter.setAccessible(true); + Field[] fields = (Field[]) fieldGetter.invoke(Field.class, false); + for (Field f : fields) + if (f.getName().equals("modifiers")) { + field_modifiers = f; + field_modifiers.setAccessible(true); + break; + } + } catch (Exception e) { + throw new NbtApiException(e); + } + } + if (field_modifiers == null) { + throw new NbtApiException("Unable to init the modifiers Field."); + } + } + + public static Field makeNonFinal(Field field) throws IllegalArgumentException, IllegalAccessException { + int mods = field.getModifiers(); + if (Modifier.isFinal(mods)) { + field_modifiers.set(field, mods & ~Modifier.FINAL); + } + return field; + } + + public static void setFinal(Object obj, Field field, Object newValue) + throws IllegalArgumentException, IllegalAccessException { + field.setAccessible(true); + field = makeNonFinal(field); + field.set(obj, newValue); + } + +} \ No newline at end of file diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/utils/UUIDUtil.java b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/UUIDUtil.java new file mode 100644 index 0000000..5f38e38 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/UUIDUtil.java @@ -0,0 +1,22 @@ +package ed.tr7zw.changeme.nbtapi.utils; + +import java.util.UUID; + +public class UUIDUtil { + + public static UUID uuidFromIntArray(int[] is) { + return new UUID((long) is[0] << 32 | (long) is[1] & 4294967295L, + (long) is[2] << 32 | (long) is[3] & 4294967295L); + } + + public static int[] uuidToIntArray(UUID uUID) { + long l = uUID.getMostSignificantBits(); + long m = uUID.getLeastSignificantBits(); + return leastMostToIntArray(l, m); + } + + private static int[] leastMostToIntArray(long l, long m) { + return new int[]{(int) (l >> 32), (int) l, (int) (m >> 32), (int) m}; + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/utils/VersionChecker.java b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/VersionChecker.java new file mode 100644 index 0000000..a1f1cbc --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/VersionChecker.java @@ -0,0 +1,108 @@ +package ed.tr7zw.changeme.nbtapi.utils; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.logging.Level; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import ed.tr7zw.changeme.nbtapi.NBTItem; + +/** + * This class uses the Spiget API to check for updates + * + */ +public class VersionChecker { + + private static final String USER_AGENT = "nbt-api Version check"; + private static final String REQUEST_URL = "https://api.spiget.org/v2/resources/7939/versions?size=100"; + public static boolean hideOk = false; + + protected static void checkForUpdates() throws Exception { + URL url = new URL(REQUEST_URL); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.addRequestProperty("User-Agent", USER_AGENT);// Set + // User-Agent + + // If you're not sure if the request will be successful, + // you need to check the response code and use #getErrorStream if it + // returned an error code + InputStream inputStream = connection.getInputStream(); + InputStreamReader reader = new InputStreamReader(inputStream); + + // This could be either a JsonArray or JsonObject + JsonElement element = new JsonParser().parse(reader); + if (element.isJsonArray()) { + // Is JsonArray + JsonArray updates = (JsonArray) element; + JsonObject latest = (JsonObject) updates.get(updates.size() - 1); + int versionDifference = getVersionDifference(latest.get("name").getAsString()); + if (versionDifference == -1) { // Outdated + MinecraftVersion.getLogger().log(Level.WARNING, + "[NBTAPI] The NBT-API located at '" + NBTItem.class.getPackage() + "' seems to be outdated!"); + MinecraftVersion.getLogger().log(Level.WARNING, "[NBTAPI] Current Version: '" + MinecraftVersion.VERSION + + "' Newest Version: " + latest.get("name").getAsString() + "'"); + MinecraftVersion.getLogger().log(Level.WARNING, + "[NBTAPI] Please update the NBTAPI or the plugin that contains the api(nag the mod author when the newest release has an old version, not the NBTAPI dev)!"); + + } else if (versionDifference == 0) { + if(!hideOk) + MinecraftVersion.getLogger().log(Level.INFO, "[NBTAPI] The NBT-API seems to be up-to-date!"); + } else if (versionDifference == 1) { + MinecraftVersion.getLogger().log(Level.INFO, "[NBTAPI] The NBT-API at '" + NBTItem.class.getPackage() + + "' seems to be a future Version, not yet released on Spigot/CurseForge! This is not an error!"); + MinecraftVersion.getLogger().log(Level.INFO, "[NBTAPI] Current Version: '" + MinecraftVersion.VERSION + + "' Newest Version: " + latest.get("name").getAsString() + "'"); + } + } else { + // wut?! + MinecraftVersion.getLogger().log(Level.WARNING, + "[NBTAPI] Error when looking for Updates! Got non Json Array: '" + element.toString() + "'"); + } + } + + // -1 = we are outdated + // 0 = up to date + // 1 = using a future version + // This method is only able to compare the Format 0.0.0(-SNAPSHOT) + private static int getVersionDifference(String version) { + String current = MinecraftVersion.VERSION; + if (current.equals(version)) + return 0; + String pattern = "\\."; + if (current.split(pattern).length != 3 || version.split(pattern).length != 3) + return -1; + int curMaj = Integer.parseInt(current.split(pattern)[0]); + int curMin = Integer.parseInt(current.split(pattern)[1]); + String curPatch = current.split(pattern)[2]; + int relMaj = Integer.parseInt(version.split(pattern)[0]); + int relMin = Integer.parseInt(version.split(pattern)[1]); + String relPatch = version.split(pattern)[2]; + if (curMaj < relMaj) + return -1; + if (curMaj > relMaj) + return 1; + if (curMin < relMin) + return -1; + if (curMin > relMin) + return 1; + int curPatchN = Integer.parseInt(curPatch.split("-")[0]); + int relPatchN = Integer.parseInt(relPatch.split("-")[0]); + if (curPatchN < relPatchN) + return -1; + if (curPatchN > relPatchN) + return 1; + if (!relPatch.contains("-") && curPatch.contains("-")) + return -1; // Release has no - but we do = We use a Snapshot of the + // release + if (relPatch.contains("-") && curPatch.contains("-")) + return 0; // Release and cur are Snapshots/alpha/beta + return 1; + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/utils/annotations/AvailableSince.java b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/annotations/AvailableSince.java new file mode 100644 index 0000000..60e90d8 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/annotations/AvailableSince.java @@ -0,0 +1,17 @@ +package ed.tr7zw.changeme.nbtapi.utils.annotations; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import ed.tr7zw.changeme.nbtapi.utils.MinecraftVersion; + +@Retention(RUNTIME) +@Target({ METHOD }) +public @interface AvailableSince { + + MinecraftVersion version(); + +} \ No newline at end of file diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/utils/annotations/CheckUtil.java b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/annotations/CheckUtil.java new file mode 100644 index 0000000..bbfd951 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/annotations/CheckUtil.java @@ -0,0 +1,16 @@ +package ed.tr7zw.changeme.nbtapi.utils.annotations; + +import java.lang.reflect.Method; + +import ed.tr7zw.changeme.nbtapi.NbtApiException; +import ed.tr7zw.changeme.nbtapi.utils.MinecraftVersion; + +public class CheckUtil { + + public static boolean isAvaliable(Method method) { + if(MinecraftVersion.getVersion().getVersionId() < method.getAnnotation(AvailableSince.class).version().getVersionId()) + throw new NbtApiException("The Method '" + method.getName() + "' is only avaliable for the Versions " + method.getAnnotation(AvailableSince.class).version() + "+, but still got called!"); + return true; + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/utils/nmsmappings/ClassWrapper.java b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/nmsmappings/ClassWrapper.java new file mode 100644 index 0000000..86d4a08 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/nmsmappings/ClassWrapper.java @@ -0,0 +1,106 @@ +package ed.tr7zw.changeme.nbtapi.utils.nmsmappings; + +import static ed.tr7zw.changeme.nbtapi.utils.MinecraftVersion.getLogger; + +import java.util.logging.Level; + +import ed.tr7zw.changeme.nbtapi.utils.MinecraftVersion; + +/** + * Wraps NMS and CRAFT classes + * + * @author tr7zw + * + */ +@SuppressWarnings("javadoc") +public enum ClassWrapper { + CRAFT_ITEMSTACK(PackageWrapper.CRAFTBUKKIT, "inventory.CraftItemStack", null, null), + CRAFT_METAITEM(PackageWrapper.CRAFTBUKKIT, "inventory.CraftMetaItem", null, null), + CRAFT_ENTITY(PackageWrapper.CRAFTBUKKIT, "entity.CraftEntity", null, null), + CRAFT_WORLD(PackageWrapper.CRAFTBUKKIT, "CraftWorld", null, null), + CRAFT_PERSISTENTDATACONTAINER(PackageWrapper.CRAFTBUKKIT, "persistence.CraftPersistentDataContainer", + MinecraftVersion.MC1_14_R1, null), + NMS_NBTBASE(PackageWrapper.NMS, "NBTBase", null, null, "net.minecraft.nbt", "net.minecraft.nbt.Tag"), + NMS_NBTTAGSTRING(PackageWrapper.NMS, "NBTTagString", null, null, "net.minecraft.nbt", "net.minecraft.nbt.StringTag"), + NMS_NBTTAGINT(PackageWrapper.NMS, "NBTTagInt", null, null, "net.minecraft.nbt", "net.minecraft.nbt.IntTag"), + NMS_NBTTAGINTARRAY(PackageWrapper.NMS, "NBTTagIntArray", null, null, "net.minecraft.nbt", "net.minecraft.nbt.IntArrayTag"), + NMS_NBTTAGFLOAT(PackageWrapper.NMS, "NBTTagFloat", null, null, "net.minecraft.nbt", "net.minecraft.nbt.FloatTag"), + NMS_NBTTAGDOUBLE(PackageWrapper.NMS, "NBTTagDouble", null, null, "net.minecraft.nbt", "net.minecraft.nbt.DoubleTag"), + NMS_NBTTAGLONG(PackageWrapper.NMS, "NBTTagLong", null, null, "net.minecraft.nbt", "net.minecraft.nbt.LongTag"), + NMS_ITEMSTACK(PackageWrapper.NMS, "ItemStack", null, null, "net.minecraft.world.item", "net.minecraft.world.item.ItemStack"), + NMS_NBTTAGCOMPOUND(PackageWrapper.NMS, "NBTTagCompound", null, null, "net.minecraft.nbt", "net.minecraft.nbt.CompoundTag"), + NMS_NBTTAGLIST(PackageWrapper.NMS, "NBTTagList", null, null, "net.minecraft.nbt", "net.minecraft.nbt.ListTag"), + NMS_NBTCOMPRESSEDSTREAMTOOLS(PackageWrapper.NMS, "NBTCompressedStreamTools", null, null, "net.minecraft.nbt", "net.minecraft.nbt.NbtIo"), + NMS_MOJANGSONPARSER(PackageWrapper.NMS, "MojangsonParser", null, null, "net.minecraft.nbt", "net.minecraft.nbt.TagParser"), + NMS_TILEENTITY(PackageWrapper.NMS, "TileEntity", null, null, "net.minecraft.world.level.block.entity", "net.minecraft.world.level.block.entity.BlockEntity"), + NMS_BLOCKPOSITION(PackageWrapper.NMS, "BlockPosition", MinecraftVersion.MC1_8_R3, null, "net.minecraft.core", "net.minecraft.core.BlockPos"), + NMS_WORLDSERVER(PackageWrapper.NMS, "WorldServer", null, null, "net.minecraft.server.level", "net.minecraft.server.level.ServerLevel"), + NMS_MINECRAFTSERVER(PackageWrapper.NMS, "MinecraftServer", null, null, "net.minecraft.server", "net.minecraft.server.MinecraftServer"), + NMS_WORLD(PackageWrapper.NMS, "World", null, null, "net.minecraft.world.level", "net.minecraft.world.level.Level"), + NMS_ENTITY(PackageWrapper.NMS, "Entity", null, null, "net.minecraft.world.entity", "net.minecraft.world.entity.Entity"), + NMS_ENTITYTYPES(PackageWrapper.NMS, "EntityTypes", null, null, "net.minecraft.world.entity", "net.minecraft.world.entity.EntityType"), + NMS_REGISTRYSIMPLE(PackageWrapper.NMS, "RegistrySimple", MinecraftVersion.MC1_11_R1, MinecraftVersion.MC1_12_R1), + NMS_REGISTRYMATERIALS(PackageWrapper.NMS, "RegistryMaterials", null, null, "net.minecraft.core", "net.minecraft.core.MappedRegistry"), + NMS_IREGISTRY(PackageWrapper.NMS, "IRegistry", null, null, "net.minecraft.core", "net.minecraft.core.Registry"), + NMS_MINECRAFTKEY(PackageWrapper.NMS, "MinecraftKey", MinecraftVersion.MC1_8_R3, null, "net.minecraft.resources", "net.minecraft.resources.ResourceKey"), + NMS_GAMEPROFILESERIALIZER(PackageWrapper.NMS, "GameProfileSerializer", null, null, "net.minecraft.nbt", "net.minecraft.nbt.NbtUtils"), + NMS_IBLOCKDATA(PackageWrapper.NMS, "IBlockData", MinecraftVersion.MC1_8_R3, null, + "net.minecraft.world.level.block.state", "net.minecraft.world.level.block.state.BlockState"), + GAMEPROFILE(PackageWrapper.NONE, "com.mojang.authlib.GameProfile", MinecraftVersion.MC1_8_R3, null); + + private Class clazz; + private boolean enabled = false; + private final String mojangName; + + ClassWrapper(PackageWrapper packageId, String clazzName, MinecraftVersion from, MinecraftVersion to) { + this(packageId, clazzName, from, to, null, null); + } + + ClassWrapper(PackageWrapper packageId, String clazzName, MinecraftVersion from, MinecraftVersion to, + String mojangMap, String mojangName) { + this.mojangName = mojangName; + if (from != null && MinecraftVersion.getVersion().getVersionId() < from.getVersionId()) { + return; + } + if (to != null && MinecraftVersion.getVersion().getVersionId() > to.getVersionId()) { + return; + } + enabled = true; + try { + if (MinecraftVersion.isAtLeastVersion(MinecraftVersion.MC1_17_R1) && mojangMap != null) { + clazz = Class.forName(mojangMap + "." + clazzName); + } else if (packageId == PackageWrapper.NONE) { + clazz = Class.forName(clazzName); + } else if (MinecraftVersion.isForgePresent() && MinecraftVersion.getVersion() == MinecraftVersion.MC1_7_R4 && Forge1710Mappings.getClassMappings().get(this.name()) != null){ + clazz = Class.forName(clazzName = Forge1710Mappings.getClassMappings().get(this.name())); + }else { + String version = MinecraftVersion.getVersion().getPackageName(); + clazz = Class.forName(packageId.getUri() + "." + version + "." + clazzName); + } + } catch (Throwable ex) { + getLogger().log(Level.WARNING, "[NBTAPI] Error while trying to resolve the class '" + clazzName + "'!", ex); + } + } + + /** + * @return The wrapped class + */ + public Class getClazz() { + return clazz; + } + + /** + * @return Is this class available in this Version + */ + public boolean isEnabled() { + return enabled; + } + + /** + * @return Package+Class name used by Mojang + */ + public String getMojangName() { + return mojangName; + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/utils/nmsmappings/Forge1710Mappings.java b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/nmsmappings/Forge1710Mappings.java new file mode 100644 index 0000000..68c3eff --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/nmsmappings/Forge1710Mappings.java @@ -0,0 +1,122 @@ +package ed.tr7zw.changeme.nbtapi.utils.nmsmappings; + +import ed.tr7zw.changeme.nbtapi.NbtApiException; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +/** + * Temporary solution to hold Forge1710 mappings. + * + * @author EverNife + * + */ +public class Forge1710Mappings { + + private static Map classMap = new HashMap<>(); + private static Map methodMap = new HashMap<>(); + private static Method crucible_toString; + + static { + //Fields + classMap.put("NMS_NBTBASE","net.minecraft.nbt.NBTBase"); + classMap.put("NMS_NBTTAGSTRING","net.minecraft.nbt.NBTTagString"); + classMap.put("NMS_NBTTAGINT","net.minecraft.nbt.NBTTagInt"); + classMap.put("NMS_NBTTAGINTARRAY","net.minecraft.nbt.NBTTagIntArray"); + classMap.put("NMS_NBTTAGFLOAT","net.minecraft.nbt.NBTTagFloat"); + classMap.put("NMS_NBTTAGDOUBLE","net.minecraft.nbt.NBTTagDouble"); + classMap.put("NMS_NBTTAGLONG","net.minecraft.nbt.NBTTagLong"); + classMap.put("NMS_ITEMSTACK","net.minecraft.item.ItemStack"); + classMap.put("NMS_NBTTAGCOMPOUND","net.minecraft.nbt.NBTTagCompound"); + classMap.put("NMS_NBTTAGLIST","net.minecraft.nbt.NBTTagList"); + classMap.put("NMS_NBTCOMPRESSEDSTREAMTOOLS","net.minecraft.nbt.CompressedStreamTools"); + classMap.put("NMS_MOJANGSONPARSER","io.github.crucible.nbt.Crucible_JsonToNBT"); + classMap.put("NMS_TILEENTITY","net.minecraft.tileentity.TileEntity"); + classMap.put("NMS_WORLDSERVER","net.minecraft.world.WorldServer"); + classMap.put("NMS_MINECRAFTSERVER","net.minecraft.server.MinecraftServer"); + classMap.put("NMS_WORLD","net.minecraft.world.World"); + classMap.put("NMS_ENTITY","net.minecraft.entity.Entity"); + classMap.put("NMS_ENTITYTYPES","net.minecraft.entity.EntityList"); + classMap.put("NMS_REGISTRYMATERIALS","net.minecraft.util.RegistryNamespaced"); + classMap.put("NMS_GAMEPROFILESERIALIZER","net.minecraft.nbt.NBTUtil"); + classMap.put("NMS_IREGISTRY","net.minecraft.util.IRegistry"); + + //Methods + methodMap.put("COMPOUND_SET_FLOAT","func_74776_a"); + methodMap.put("COMPOUND_SET_STRING","func_74778_a"); + methodMap.put("COMPOUND_SET_INT","func_74768_a"); + methodMap.put("COMPOUND_SET_BYTEARRAY","func_74773_a"); + methodMap.put("COMPOUND_SET_INTARRAY","func_74783_a"); + methodMap.put("COMPOUND_SET_LONG","func_74772_a"); + methodMap.put("COMPOUND_SET_SHORT","func_74777_a"); + methodMap.put("COMPOUND_SET_BYTE","func_74774_a"); + methodMap.put("COMPOUND_SET_DOUBLE","func_74780_a"); + methodMap.put("COMPOUND_SET_BOOLEAN","func_74757_a"); + methodMap.put("COMPOUND_MERGE","merge");//Only present on Crucible + methodMap.put("COMPOUND_SET","func_74782_a"); + methodMap.put("COMPOUND_GET","func_74781_a"); + methodMap.put("COMPOUND_GET_LIST","func_150295_c"); + methodMap.put("COMPOUND_OWN_TYPE","func_74732_a"); + methodMap.put("COMPOUND_GET_FLOAT","func_74760_g"); + methodMap.put("COMPOUND_GET_STRING","func_74779_i"); + methodMap.put("COMPOUND_GET_INT","func_74762_e"); + methodMap.put("COMPOUND_GET_BYTEARRAY","func_74770_j"); + methodMap.put("COMPOUND_GET_INTARRAY","func_74759_k"); + methodMap.put("COMPOUND_GET_LONG","func_74763_f"); + methodMap.put("COMPOUND_GET_SHORT","func_74765_d"); + methodMap.put("COMPOUND_GET_BYTE","func_74771_c"); + methodMap.put("COMPOUND_GET_DOUBLE","func_74769_h"); + methodMap.put("COMPOUND_GET_BOOLEAN","func_74767_n"); + methodMap.put("COMPOUND_GET_COMPOUND","func_74775_l"); + methodMap.put("COMPOUND_REMOVE_KEY","func_82580_o"); + methodMap.put("COMPOUND_HAS_KEY","func_74764_b"); + methodMap.put("COMPOUND_GET_KEYS","func_150296_c"); + methodMap.put("LISTCOMPOUND_GET_KEYS","func_150296_c"); + methodMap.put("NMSITEM_GETTAG","func_77978_p"); + methodMap.put("NMSITEM_SAVE","func_77955_b"); + methodMap.put("NMSITEM_CREATESTACK","func_77949_a"); + methodMap.put("ITEMSTACK_SET_TAG","func_77982_d"); + methodMap.put("LIST_SIZE","func_74745_c"); + methodMap.put("LEGACY_LIST_ADD","func_74742_a"); + methodMap.put("LIST_GET_STRING","func_150307_f"); + methodMap.put("LIST_GET_COMPOUND","func_150305_b"); + methodMap.put("LIST_GET","func_150305_b"); + methodMap.put("NMS_WORLD_GET_TILEENTITY_1_7_10","func_147438_o"); + methodMap.put("TILEENTITY_GET_NBT","func_145841_b"); + methodMap.put("TILEENTITY_SET_NBT","func_145839_a"); + methodMap.put("TILEENTITY_SET_NBT_LEGACY1151","func_145839_a"); + methodMap.put("NMS_ENTITY_GET_NBT","func_70109_d"); + methodMap.put("NMS_ENTITY_SET_NBT","func_70020_e"); + methodMap.put("NBTFILE_READ","func_74796_a"); + methodMap.put("NBTFILE_WRITE","func_74799_a"); + methodMap.put("PARSE_NBT","getTagFromJson"); + methodMap.put("GAMEPROFILE_DESERIALIZE","func_152459_a"); + + //Crucible + try { + crucible_toString = Class.forName("net.minecraft.nbt.NBTTagCompound").getDeclaredMethod("crucible_toString"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static Map getClassMappings(){ + return classMap; + } + + public static Map getMethodMapping(){ + return methodMap; + } + + public static String toString(Object nbtTagCompound) { + if(crucible_toString == null) + throw new NbtApiException("Method not loaded! 'Forge1710Mappings.crucible_toString' "); + try{ + return (String) crucible_toString.invoke(nbtTagCompound); + }catch(Exception ex){ + throw new NbtApiException("Error while calling the method 'crucible_toString', from Forge1710Mappings. Passed Class: " + Forge1710Mappings.class, ex); + } + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/utils/nmsmappings/MojangToMapping.java b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/nmsmappings/MojangToMapping.java new file mode 100644 index 0000000..e117814 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/nmsmappings/MojangToMapping.java @@ -0,0 +1,109 @@ +package ed.tr7zw.changeme.nbtapi.utils.nmsmappings; + +import java.util.HashMap; +import java.util.Map; + +import ed.tr7zw.changeme.nbtapi.utils.MinecraftVersion; + +/** + * Temporary solution to hold Mojang to unmapped Spigot mappings. + * + * @author tr7zw + * + */ +public class MojangToMapping { + + @SuppressWarnings("serial") + private static Map MC1_18R1 = new HashMap() { + + { + put("net.minecraft.nbt.CompoundTag#contains(java.lang.String)", "e"); + put("net.minecraft.nbt.CompoundTag#getCompound(java.lang.String)", "p"); + put("net.minecraft.nbt.CompoundTag#getList(java.lang.String,int)", "c"); + put("net.minecraft.nbt.CompoundTag#putByteArray(java.lang.String,byte[])", "a"); + put("net.minecraft.nbt.CompoundTag#getDouble(java.lang.String)", "k"); + put("net.minecraft.nbt.CompoundTag#putDouble(java.lang.String,double)", "a"); + put("net.minecraft.nbt.CompoundTag#getByteArray(java.lang.String)", "m"); + put("net.minecraft.nbt.CompoundTag#putInt(java.lang.String,int)", "a"); + put("net.minecraft.nbt.CompoundTag#getIntArray(java.lang.String)", "n"); + put("net.minecraft.nbt.CompoundTag#remove(java.lang.String)", "r"); + put("net.minecraft.nbt.CompoundTag#get(java.lang.String)", "c"); + put("net.minecraft.nbt.CompoundTag#put(java.lang.String,net.minecraft.nbt.Tag)", "a"); + put("net.minecraft.nbt.CompoundTag#putBoolean(java.lang.String,boolean)", "a"); + put("net.minecraft.nbt.CompoundTag#getTagType(java.lang.String)", "d"); + put("net.minecraft.nbt.CompoundTag#putLong(java.lang.String,long)", "a"); + put("net.minecraft.nbt.CompoundTag#getString(java.lang.String)", "l"); + put("net.minecraft.nbt.CompoundTag#getInt(java.lang.String)", "h"); + put("net.minecraft.nbt.CompoundTag#putString(java.lang.String,java.lang.String)", "a"); + put("net.minecraft.nbt.CompoundTag#put(java.lang.String,net.minecraft.nbt.Tag)", "a"); + put("net.minecraft.nbt.CompoundTag#getByte(java.lang.String)", "f"); + put("net.minecraft.nbt.CompoundTag#putIntArray(java.lang.String,int[])", "a"); + put("net.minecraft.nbt.CompoundTag#getShort(java.lang.String)", "g"); + put("net.minecraft.nbt.CompoundTag#putByte(java.lang.String,byte)", "a"); + put("net.minecraft.nbt.CompoundTag#getAllKeys()", "d"); + put("net.minecraft.nbt.CompoundTag#getAllKeys()", "d"); + put("net.minecraft.nbt.CompoundTag#putUUID(java.lang.String,java.util.UUID)", "a"); + put("net.minecraft.nbt.CompoundTag#putShort(java.lang.String,short)", "a"); + put("net.minecraft.nbt.CompoundTag#getLong(java.lang.String)", "i"); + put("net.minecraft.nbt.CompoundTag#putFloat(java.lang.String,float)", "a"); + put("net.minecraft.nbt.CompoundTag#getBoolean(java.lang.String)", "q"); + put("net.minecraft.nbt.CompoundTag#getUUID(java.lang.String)", "a"); + put("net.minecraft.nbt.CompoundTag#getFloat(java.lang.String)", "j"); + put("net.minecraft.nbt.ListTag#addTag(int,net.minecraft.nbt.Tag)", "b"); + put("net.minecraft.nbt.ListTag#setTag(int,net.minecraft.nbt.Tag)", "a"); + put("net.minecraft.nbt.ListTag#getString(int)", "j"); + put("net.minecraft.nbt.ListTag#remove(int)", "remove"); + put("net.minecraft.nbt.ListTag#getCompound(int)", "a"); + put("net.minecraft.nbt.ListTag#size()", "size"); + put("net.minecraft.nbt.ListTag#get(int)", "get"); + put("net.minecraft.nbt.NbtIo#readCompressed(java.io.InputStream)", "a"); + put("net.minecraft.nbt.NbtIo#writeCompressed(net.minecraft.nbt.CompoundTag,java.io.OutputStream)", "a"); + put("net.minecraft.nbt.NbtUtils#readGameProfile(net.minecraft.nbt.CompoundTag)", "a"); + put("net.minecraft.nbt.NbtUtils#writeGameProfile(net.minecraft.nbt.CompoundTag,com.mojang.authlib.GameProfile)", "a"); + put("net.minecraft.nbt.TagParser#parseTag(java.lang.String)", "a"); + put("net.minecraft.world.entity.Entity#getEncodeId()", "bk"); + put("net.minecraft.world.entity.Entity#load(net.minecraft.nbt.CompoundTag)", "g"); + put("net.minecraft.world.entity.Entity#saveWithoutId(net.minecraft.nbt.CompoundTag)", "f"); + put("net.minecraft.world.item.ItemStack#setTag(net.minecraft.nbt.CompoundTag)", "c"); + put("net.minecraft.world.item.ItemStack#getTag()", "s"); + put("net.minecraft.world.item.ItemStack#save(net.minecraft.nbt.CompoundTag)", "b"); + put("net.minecraft.world.level.block.entity.BlockEntity#saveWithId()", "n"); + put("net.minecraft.world.level.block.entity.BlockEntity#getBlockState()", "q"); + put("net.minecraft.world.level.block.entity.BlockEntity#load(net.minecraft.nbt.CompoundTag)", "a"); + put("net.minecraft.server.level.ServerLevel#getBlockEntity(net.minecraft.core.BlockPos)", "c_"); + } + + }; + + @SuppressWarnings("serial") + private static Map MC1_18R2 = new HashMap() { + + { + putAll(MC1_18R1); + + put("net.minecraft.world.item.ItemStack#getTag()", "t"); + } + }; + + @SuppressWarnings("serial") + private static Map MC1_19R1 = new HashMap() { + + { + putAll(MC1_18R2); + + put("net.minecraft.world.item.ItemStack#getTag()", "u"); + } + + }; + + + public static Map getMapping(){ + switch(MinecraftVersion.getVersion()) { + case MC1_19_R1: return MC1_19R1; + case MC1_18_R2: return MC1_18R2; + case MC1_18_R1: return MC1_18R1; + default: return MC1_19R1;//throw new NbtApiException("This version of the NBTAPI is not compatible with this server version!"); + } + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/utils/nmsmappings/ObjectCreator.java b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/nmsmappings/ObjectCreator.java new file mode 100644 index 0000000..481cc32 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/nmsmappings/ObjectCreator.java @@ -0,0 +1,56 @@ +package ed.tr7zw.changeme.nbtapi.utils.nmsmappings; + +import java.lang.reflect.Constructor; +import java.util.logging.Level; + +import ed.tr7zw.changeme.nbtapi.NbtApiException; +import ed.tr7zw.changeme.nbtapi.utils.MinecraftVersion; + +import static ed.tr7zw.changeme.nbtapi.utils.MinecraftVersion.getLogger; + +/** + * This Enum wraps Constructors for NMS classes + * + * @author tr7zw + * + */ +@SuppressWarnings("javadoc") +public enum ObjectCreator { + NMS_NBTTAGCOMPOUND(null, null, ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz()), + NMS_BLOCKPOSITION(null, null, ClassWrapper.NMS_BLOCKPOSITION.getClazz(), int.class, int.class, int.class), + NMS_COMPOUNDFROMITEM(MinecraftVersion.MC1_11_R1, null, ClassWrapper.NMS_ITEMSTACK.getClazz(), ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz()),; + + private Constructor construct; + private Class targetClass; + + ObjectCreator(MinecraftVersion from, MinecraftVersion to, Class clazz, Class... args) { + if (clazz == null) + return; + if (from != null && MinecraftVersion.getVersion().getVersionId() < from.getVersionId()) + return; + if (to != null && MinecraftVersion.getVersion().getVersionId() > to.getVersionId()) + return; + try { + this.targetClass = clazz; + construct = clazz.getDeclaredConstructor(args); + construct.setAccessible(true); + } catch (Exception ex) { + getLogger().log(Level.SEVERE, "Unable to find the constructor for the class '" + clazz.getName() + "'", ex); + } + } + + /** + * Creates an Object instance with given args + * + * @param args + * @return Object created + */ + public Object getInstance(Object... args) { + try { + return construct.newInstance(args); + } catch (Exception ex) { + throw new NbtApiException("Exception while creating a new instance of '" + targetClass + "'", ex); + } + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/utils/nmsmappings/PackageWrapper.java b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/nmsmappings/PackageWrapper.java new file mode 100644 index 0000000..cb403f6 --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/nmsmappings/PackageWrapper.java @@ -0,0 +1,29 @@ +package ed.tr7zw.changeme.nbtapi.utils.nmsmappings; + +/** + * Package enum + * + * @author tr7zw + * + */ +@SuppressWarnings("javadoc") +public enum PackageWrapper { + NMS(new String(new byte[] {'n', 'e', 't', '.', 'm', 'i', 'n', 'e', 'c', 'r', 'a', 'f', 't', '.', 's', 'e', 'r', 'v', 'e', 'r'})), + CRAFTBUKKIT(new String(new byte[] {'o', 'r', 'g', '.', 'b', 'u', 'k', 'k', 'i', 't', '.', 'c', 'r', 'a', 'f', 't', 'b', 'u', 'k', 'k', 'i', 't'})), + NONE("") + ; + + private final String uri; + + private PackageWrapper(String uri) { + this.uri = uri; + } + + /** + * @return The Uri for that package + */ + public String getUri() { + return uri; + } + +} diff --git a/src/main/java/ed/tr7zw/changeme/nbtapi/utils/nmsmappings/ReflectionMethod.java b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/nmsmappings/ReflectionMethod.java new file mode 100644 index 0000000..67cc51e --- /dev/null +++ b/src/main/java/ed/tr7zw/changeme/nbtapi/utils/nmsmappings/ReflectionMethod.java @@ -0,0 +1,230 @@ +package ed.tr7zw.changeme.nbtapi.utils.nmsmappings; + +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.UUID; + +import org.bukkit.inventory.ItemStack; + +import ed.tr7zw.changeme.nbtapi.NbtApiException; +import ed.tr7zw.changeme.nbtapi.utils.MinecraftVersion; + +/** + * This class caches method reflections, keeps track of method name changes between versions and allows early checking for problems + * + * @author tr7zw + * + */ +@SuppressWarnings("javadoc") +public enum ReflectionMethod { + + COMPOUND_SET_FLOAT(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class, float.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "setFloat"), new Since(MinecraftVersion.MC1_18_R1, "putFloat(java.lang.String,float)")), + COMPOUND_SET_STRING(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class, String.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "setString"), new Since(MinecraftVersion.MC1_18_R1, "putString(java.lang.String,java.lang.String)")), + COMPOUND_SET_INT(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class, int.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "setInt"), new Since(MinecraftVersion.MC1_18_R1, "putInt(java.lang.String,int)")), + COMPOUND_SET_BYTEARRAY(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class, byte[].class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "setByteArray"), new Since(MinecraftVersion.MC1_18_R1, "putByteArray(java.lang.String,byte[])")), + COMPOUND_SET_INTARRAY(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class, int[].class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "setIntArray"), new Since(MinecraftVersion.MC1_18_R1, "putIntArray(java.lang.String,int[])")), + COMPOUND_SET_LONG(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class, long.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "setLong"), new Since(MinecraftVersion.MC1_18_R1, "putLong(java.lang.String,long)")), + COMPOUND_SET_SHORT(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class, short.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "setShort"), new Since(MinecraftVersion.MC1_18_R1, "putShort(java.lang.String,short)")), + COMPOUND_SET_BYTE(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class, byte.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "setByte"), new Since(MinecraftVersion.MC1_18_R1, "putByte(java.lang.String,byte)")), + COMPOUND_SET_DOUBLE(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class, double.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "setDouble"), new Since(MinecraftVersion.MC1_18_R1, "putDouble(java.lang.String,double)")), + COMPOUND_SET_BOOLEAN(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class, boolean.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "setBoolean"), new Since(MinecraftVersion.MC1_18_R1, "putBoolean(java.lang.String,boolean)")), + COMPOUND_SET_UUID(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class, UUID.class}, MinecraftVersion.MC1_16_R1, new Since(MinecraftVersion.MC1_16_R1, "a"), new Since(MinecraftVersion.MC1_18_R1, "putUUID(java.lang.String,java.util.UUID)")), + COMPOUND_MERGE(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz()}, MinecraftVersion.MC1_8_R3, new Since(MinecraftVersion.MC1_8_R3, "a"), new Since(MinecraftVersion.MC1_18_R1, "put(java.lang.String,net.minecraft.nbt.Tag)")), + COMPOUND_SET(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class, ClassWrapper.NMS_NBTBASE.getClazz()}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "set"), new Since(MinecraftVersion.MC1_18_R1, "put(java.lang.String,net.minecraft.nbt.Tag)")), + COMPOUND_GET(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "get"), new Since(MinecraftVersion.MC1_18_R1, "get(java.lang.String)")), + COMPOUND_GET_LIST(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class, int.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "getList"), new Since(MinecraftVersion.MC1_18_R1, "getList(java.lang.String,int)")), + COMPOUND_OWN_TYPE(ClassWrapper.NMS_NBTBASE, new Class[]{}, MinecraftVersion.MC1_7_R4, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "getTypeId")), // Only needed for 1.7.10 getType + + COMPOUND_GET_FLOAT(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "getFloat"), new Since(MinecraftVersion.MC1_18_R1, "getFloat(java.lang.String)")), + COMPOUND_GET_STRING(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "getString"), new Since(MinecraftVersion.MC1_18_R1, "getString(java.lang.String)")), + COMPOUND_GET_INT(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "getInt"), new Since(MinecraftVersion.MC1_18_R1, "getInt(java.lang.String)")), + COMPOUND_GET_BYTEARRAY(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "getByteArray"), new Since(MinecraftVersion.MC1_18_R1, "getByteArray(java.lang.String)")), + COMPOUND_GET_INTARRAY(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "getIntArray"), new Since(MinecraftVersion.MC1_18_R1, "getIntArray(java.lang.String)")), + COMPOUND_GET_LONG(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "getLong"), new Since(MinecraftVersion.MC1_18_R1, "getLong(java.lang.String)")), + COMPOUND_GET_SHORT(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "getShort"), new Since(MinecraftVersion.MC1_18_R1, "getShort(java.lang.String)")), + COMPOUND_GET_BYTE(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "getByte"), new Since(MinecraftVersion.MC1_18_R1, "getByte(java.lang.String)")), + COMPOUND_GET_DOUBLE(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "getDouble"), new Since(MinecraftVersion.MC1_18_R1, "getDouble(java.lang.String)")), + COMPOUND_GET_BOOLEAN(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "getBoolean"), new Since(MinecraftVersion.MC1_18_R1, "getBoolean(java.lang.String)")), + COMPOUND_GET_UUID(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class}, MinecraftVersion.MC1_16_R1, new Since(MinecraftVersion.MC1_16_R1, "a"), new Since(MinecraftVersion.MC1_18_R1, "getUUID(java.lang.String)")), + COMPOUND_GET_COMPOUND(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "getCompound"), new Since(MinecraftVersion.MC1_18_R1, "getCompound(java.lang.String)")), + + NMSITEM_GETTAG(ClassWrapper.NMS_ITEMSTACK, new Class[] {}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "getTag"), new Since(MinecraftVersion.MC1_18_R1, "getTag()")), + NMSITEM_SAVE(ClassWrapper.NMS_ITEMSTACK, new Class[] {ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz()}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "save"), new Since(MinecraftVersion.MC1_18_R1, "save(net.minecraft.nbt.CompoundTag)")), + NMSITEM_CREATESTACK(ClassWrapper.NMS_ITEMSTACK, new Class[] {ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz()}, MinecraftVersion.MC1_7_R4, MinecraftVersion.MC1_10_R1, new Since(MinecraftVersion.MC1_7_R4, "createStack")), + + COMPOUND_REMOVE_KEY(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "remove"), new Since(MinecraftVersion.MC1_18_R1, "remove(java.lang.String)")), + COMPOUND_HAS_KEY(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "hasKey"), new Since(MinecraftVersion.MC1_18_R1, "contains(java.lang.String)")), + COMPOUND_GET_TYPE(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{String.class}, MinecraftVersion.MC1_8_R3, new Since(MinecraftVersion.MC1_8_R3, "b"), new Since(MinecraftVersion.MC1_9_R1, "d"), new Since(MinecraftVersion.MC1_15_R1, "e"), new Since(MinecraftVersion.MC1_16_R1, "d"), new Since(MinecraftVersion.MC1_18_R1, "getTagType(java.lang.String)")), + COMPOUND_GET_KEYS(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "c"), new Since(MinecraftVersion.MC1_13_R1, "getKeys"), new Since(MinecraftVersion.MC1_18_R1, "getAllKeys()")), + + LISTCOMPOUND_GET_KEYS(ClassWrapper.NMS_NBTTAGCOMPOUND, new Class[]{}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "c"), new Since(MinecraftVersion.MC1_13_R1, "getKeys"), new Since(MinecraftVersion.MC1_18_R1, "getAllKeys()")), // FIXME ?!? + LIST_REMOVE_KEY(ClassWrapper.NMS_NBTTAGLIST, new Class[]{int.class}, MinecraftVersion.MC1_8_R3, new Since(MinecraftVersion.MC1_8_R3, "a"), new Since(MinecraftVersion.MC1_9_R1, "remove"), new Since(MinecraftVersion.MC1_18_R1, "remove(int)")), + LIST_SIZE(ClassWrapper.NMS_NBTTAGLIST, new Class[]{}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "size"), new Since(MinecraftVersion.MC1_18_R1, "size()")), + LIST_SET(ClassWrapper.NMS_NBTTAGLIST, new Class[]{int.class, ClassWrapper.NMS_NBTBASE.getClazz()}, MinecraftVersion.MC1_8_R3, new Since(MinecraftVersion.MC1_8_R3, "a"), new Since(MinecraftVersion.MC1_13_R1, "set"), new Since(MinecraftVersion.MC1_18_R1, "setTag(int,net.minecraft.nbt.Tag)")), + LEGACY_LIST_ADD(ClassWrapper.NMS_NBTTAGLIST, new Class[]{ClassWrapper.NMS_NBTBASE.getClazz()}, MinecraftVersion.MC1_7_R4, MinecraftVersion.MC1_13_R2, new Since(MinecraftVersion.MC1_7_R4, "add")), + LIST_ADD(ClassWrapper.NMS_NBTTAGLIST, new Class[]{int.class, ClassWrapper.NMS_NBTBASE.getClazz()}, MinecraftVersion.MC1_14_R1, new Since(MinecraftVersion.MC1_14_R1, "add"), new Since(MinecraftVersion.MC1_18_R1, "addTag(int,net.minecraft.nbt.Tag)")), + LIST_GET_STRING(ClassWrapper.NMS_NBTTAGLIST, new Class[]{int.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "getString"), new Since(MinecraftVersion.MC1_18_R1, "getString(int)")), + LIST_GET_COMPOUND(ClassWrapper.NMS_NBTTAGLIST, new Class[]{int.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "get"), new Since(MinecraftVersion.MC1_18_R1, "getCompound(int)")), + LIST_GET(ClassWrapper.NMS_NBTTAGLIST, new Class[]{int.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "get"), new Since(MinecraftVersion.MC1_8_R3, "g"), new Since(MinecraftVersion.MC1_9_R1, "h"), new Since(MinecraftVersion.MC1_12_R1, "i"), new Since(MinecraftVersion.MC1_13_R1, "get"), new Since(MinecraftVersion.MC1_18_R1, "get(int)")), + + ITEMSTACK_SET_TAG(ClassWrapper.NMS_ITEMSTACK, new Class[]{ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz()}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "setTag"), new Since(MinecraftVersion.MC1_18_R1, "setTag(net.minecraft.nbt.CompoundTag)")), + ITEMSTACK_NMSCOPY(ClassWrapper.CRAFT_ITEMSTACK, new Class[]{ItemStack.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "asNMSCopy")), + ITEMSTACK_BUKKITMIRROR(ClassWrapper.CRAFT_ITEMSTACK, new Class[]{ClassWrapper.NMS_ITEMSTACK.getClazz()}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "asCraftMirror")), + + CRAFT_WORLD_GET_HANDLE(ClassWrapper.CRAFT_WORLD, new Class[]{}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "getHandle")), + NMS_WORLD_GET_TILEENTITY(ClassWrapper.NMS_WORLDSERVER, new Class[]{ClassWrapper.NMS_BLOCKPOSITION.getClazz()}, MinecraftVersion.MC1_8_R3, new Since(MinecraftVersion.MC1_8_R3, "getTileEntity"), new Since(MinecraftVersion.MC1_18_R1, "getBlockEntity(net.minecraft.core.BlockPos)")), + NMS_WORLD_SET_TILEENTITY(ClassWrapper.NMS_WORLDSERVER, new Class[]{ClassWrapper.NMS_BLOCKPOSITION.getClazz(), ClassWrapper.NMS_TILEENTITY.getClazz()}, MinecraftVersion.MC1_8_R3, MinecraftVersion.MC1_16_R3, new Since(MinecraftVersion.MC1_8_R3, "setTileEntity")), + NMS_WORLD_REMOVE_TILEENTITY(ClassWrapper.NMS_WORLDSERVER, new Class[]{ClassWrapper.NMS_BLOCKPOSITION.getClazz()}, MinecraftVersion.MC1_8_R3, MinecraftVersion.MC1_17_R1, new Since(MinecraftVersion.MC1_8_R3, "t"), new Since(MinecraftVersion.MC1_9_R1, "s"), new Since(MinecraftVersion.MC1_13_R1, "n"), new Since(MinecraftVersion.MC1_14_R1, "removeTileEntity")), + + NMS_WORLD_GET_TILEENTITY_1_7_10(ClassWrapper.NMS_WORLDSERVER, new Class[]{int.class, int.class, int.class}, MinecraftVersion.MC1_7_R4, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "getTileEntity")), + + TILEENTITY_LOAD_LEGACY191(ClassWrapper.NMS_TILEENTITY, new Class[]{ClassWrapper.NMS_MINECRAFTSERVER.getClazz(), ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz()}, MinecraftVersion.MC1_9_R1, MinecraftVersion.MC1_9_R1, new Since(MinecraftVersion.MC1_9_R1, "a")), //FIXME: No Spigot mapping! + TILEENTITY_LOAD_LEGACY183(ClassWrapper.NMS_TILEENTITY, new Class[]{ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz()}, MinecraftVersion.MC1_8_R3, MinecraftVersion.MC1_9_R2, new Since(MinecraftVersion.MC1_8_R3, "c"), new Since(MinecraftVersion.MC1_9_R1, "a"), new Since(MinecraftVersion.MC1_9_R2, "c")), //FIXME: No Spigot mapping! + TILEENTITY_LOAD_LEGACY1121(ClassWrapper.NMS_TILEENTITY, new Class[]{ClassWrapper.NMS_WORLD.getClazz(), ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz()}, MinecraftVersion.MC1_10_R1, MinecraftVersion.MC1_12_R1, new Since(MinecraftVersion.MC1_10_R1, "a"), new Since(MinecraftVersion.MC1_12_R1, "create")), + TILEENTITY_LOAD_LEGACY1151(ClassWrapper.NMS_TILEENTITY, new Class[]{ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz()}, MinecraftVersion.MC1_13_R1, MinecraftVersion.MC1_15_R1, new Since(MinecraftVersion.MC1_12_R1, "create")), + TILEENTITY_LOAD(ClassWrapper.NMS_TILEENTITY, new Class[]{ClassWrapper.NMS_IBLOCKDATA.getClazz(), ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz()}, MinecraftVersion.MC1_16_R1, MinecraftVersion.MC1_16_R3, new Since(MinecraftVersion.MC1_16_R1, "create")), + + TILEENTITY_GET_NBT(ClassWrapper.NMS_TILEENTITY, new Class[]{ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz()}, MinecraftVersion.MC1_7_R4, MinecraftVersion.MC1_17_R1, new Since(MinecraftVersion.MC1_7_R4, "b"), new Since(MinecraftVersion.MC1_9_R1, "save")), + TILEENTITY_GET_NBT_1181(ClassWrapper.NMS_TILEENTITY, new Class[]{}, MinecraftVersion.MC1_18_R1, new Since(MinecraftVersion.MC1_18_R1, "saveWithId()")), + TILEENTITY_SET_NBT_LEGACY1151(ClassWrapper.NMS_TILEENTITY, new Class[]{ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz()}, MinecraftVersion.MC1_7_R4, MinecraftVersion.MC1_15_R1, new Since(MinecraftVersion.MC1_7_R4, "a"), new Since(MinecraftVersion.MC1_12_R1, "load")), + TILEENTITY_SET_NBT_LEGACY1161(ClassWrapper.NMS_TILEENTITY, new Class[]{ClassWrapper.NMS_IBLOCKDATA.getClazz(), ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz()}, MinecraftVersion.MC1_16_R1, MinecraftVersion.MC1_16_R3, new Since(MinecraftVersion.MC1_16_R1, "load")), + TILEENTITY_SET_NBT(ClassWrapper.NMS_TILEENTITY, new Class[]{ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz()}, MinecraftVersion.MC1_17_R1, new Since(MinecraftVersion.MC1_16_R1, "load"), new Since(MinecraftVersion.MC1_18_R1, "load(net.minecraft.nbt.CompoundTag)")), + TILEENTITY_GET_BLOCKDATA(ClassWrapper.NMS_TILEENTITY, new Class[]{}, MinecraftVersion.MC1_16_R1, new Since(MinecraftVersion.MC1_16_R1, "getBlock"), new Since(MinecraftVersion.MC1_18_R1, "getBlockState()")), + + CRAFT_ENTITY_GET_HANDLE(ClassWrapper.CRAFT_ENTITY, new Class[]{}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "getHandle")), + NMS_ENTITY_SET_NBT(ClassWrapper.NMS_ENTITY, new Class[]{ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz()}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "f"), new Since(MinecraftVersion.MC1_16_R1, "load"), new Since(MinecraftVersion.MC1_18_R1, "load(net.minecraft.nbt.CompoundTag)")), + NMS_ENTITY_GET_NBT(ClassWrapper.NMS_ENTITY, new Class[]{ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz()}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "e"), new Since(MinecraftVersion.MC1_12_R1, "save"), new Since(MinecraftVersion.MC1_18_R1, "saveWithoutId(net.minecraft.nbt.CompoundTag)")), + NMS_ENTITY_GETSAVEID(ClassWrapper.NMS_ENTITY, new Class[]{}, MinecraftVersion.MC1_14_R1,new Since(MinecraftVersion.MC1_14_R1, "getSaveID"), new Since(MinecraftVersion.MC1_18_R1, "getEncodeId()")), + + NBTFILE_READ(ClassWrapper.NMS_NBTCOMPRESSEDSTREAMTOOLS, new Class[]{InputStream.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "a"), new Since(MinecraftVersion.MC1_18_R1, "readCompressed(java.io.InputStream)")), + NBTFILE_WRITE(ClassWrapper.NMS_NBTCOMPRESSEDSTREAMTOOLS, new Class[]{ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz(), OutputStream.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "a"), new Since(MinecraftVersion.MC1_18_R1, "writeCompressed(net.minecraft.nbt.CompoundTag,java.io.OutputStream)")), + + PARSE_NBT(ClassWrapper.NMS_MOJANGSONPARSER, new Class[]{String.class}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "parse"), new Since(MinecraftVersion.MC1_18_R1, "parseTag(java.lang.String)")), + REGISTRY_KEYSET (ClassWrapper.NMS_REGISTRYSIMPLE, new Class[]{}, MinecraftVersion.MC1_11_R1, MinecraftVersion.MC1_13_R1, new Since(MinecraftVersion.MC1_11_R1, "keySet")), + REGISTRY_GET (ClassWrapper.NMS_REGISTRYSIMPLE, new Class[]{Object.class}, MinecraftVersion.MC1_11_R1, MinecraftVersion.MC1_13_R1, new Since(MinecraftVersion.MC1_11_R1, "get")), + REGISTRY_SET (ClassWrapper.NMS_REGISTRYSIMPLE, new Class[]{Object.class, Object.class}, MinecraftVersion.MC1_11_R1, MinecraftVersion.MC1_13_R1, new Since(MinecraftVersion.MC1_11_R1, "a")), //FIXME: No Spigot mapping! + REGISTRY_GET_INVERSE (ClassWrapper.NMS_REGISTRYMATERIALS, new Class[]{Object.class}, MinecraftVersion.MC1_11_R1, MinecraftVersion.MC1_13_R1, new Since(MinecraftVersion.MC1_11_R1, "b")), //FIXME: No Spigot mapping! + REGISTRYMATERIALS_KEYSET (ClassWrapper.NMS_REGISTRYMATERIALS, new Class[]{}, MinecraftVersion.MC1_13_R1, MinecraftVersion.MC1_17_R1, new Since(MinecraftVersion.MC1_13_R1, "keySet")), + REGISTRYMATERIALS_GET (ClassWrapper.NMS_REGISTRYMATERIALS, new Class[]{ClassWrapper.NMS_MINECRAFTKEY.getClazz()}, MinecraftVersion.MC1_13_R1, MinecraftVersion.MC1_17_R1, new Since(MinecraftVersion.MC1_13_R1, "get")), + REGISTRYMATERIALS_GETKEY (ClassWrapper.NMS_REGISTRYMATERIALS, new Class[]{Object.class}, MinecraftVersion.MC1_13_R2, MinecraftVersion.MC1_17_R1, new Since(MinecraftVersion.MC1_13_R2, "getKey")), + + GAMEPROFILE_DESERIALIZE (ClassWrapper.NMS_GAMEPROFILESERIALIZER, new Class[]{ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz()}, MinecraftVersion.MC1_7_R4, new Since(MinecraftVersion.MC1_7_R4, "deserialize"), new Since(MinecraftVersion.MC1_18_R1, "readGameProfile(net.minecraft.nbt.CompoundTag)")), + GAMEPROFILE_SERIALIZE (ClassWrapper.NMS_GAMEPROFILESERIALIZER, new Class[]{ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz(), ClassWrapper.GAMEPROFILE.getClazz()}, MinecraftVersion.MC1_8_R3, new Since(MinecraftVersion.MC1_8_R3, "serialize"), new Since(MinecraftVersion.MC1_18_R1, "writeGameProfile(net.minecraft.nbt.CompoundTag,com.mojang.authlib.GameProfile)")), + + CRAFT_PERSISTENT_DATA_CONTAINER_TO_TAG (ClassWrapper.CRAFT_PERSISTENTDATACONTAINER, new Class[]{}, MinecraftVersion.MC1_14_R1, new Since(MinecraftVersion.MC1_14_R1, "toTagCompound")), + CRAFT_PERSISTENT_DATA_CONTAINER_GET_MAP (ClassWrapper.CRAFT_PERSISTENTDATACONTAINER, new Class[]{}, MinecraftVersion.MC1_14_R1, new Since(MinecraftVersion.MC1_14_R1, "getRaw")), + CRAFT_PERSISTENT_DATA_CONTAINER_PUT_ALL (ClassWrapper.CRAFT_PERSISTENTDATACONTAINER, new Class[]{ClassWrapper.NMS_NBTTAGCOMPOUND.getClazz()}, MinecraftVersion.MC1_14_R1, new Since(MinecraftVersion.MC1_14_R1, "putAll")), + ; + + private MinecraftVersion removedAfter; + private Since targetVersion; + private Method method; + private boolean loaded = false; + private boolean compatible = false; + private String methodName = null; + private ClassWrapper parentClassWrapper; + + ReflectionMethod(ClassWrapper targetClass, Class[] args, MinecraftVersion addedSince, MinecraftVersion removedAfter, Since... methodnames){ + this.removedAfter = removedAfter; + this.parentClassWrapper = targetClass; + //Special Case for Modded 1.7.10 + boolean specialCase = (MinecraftVersion.isForgePresent() && this.name().equals("COMPOUND_MERGE") && MinecraftVersion.getVersion() == MinecraftVersion.MC1_7_R4); //COMPOUND_MERGE is only present on Crucible, not on vanilla 1.7.10 + if(!specialCase && (!MinecraftVersion.isAtLeastVersion(addedSince) || (this.removedAfter != null && MinecraftVersion.isNewerThan(removedAfter))))return; + compatible = true; + MinecraftVersion server = MinecraftVersion.getVersion(); + Since target = methodnames[0]; + for(Since s : methodnames){ + if(s.version.getVersionId() <= server.getVersionId() && target.version.getVersionId() < s.version.getVersionId()) + target = s; + } + targetVersion = target; + String targetMethodName = targetVersion.name; + try{ + if (MinecraftVersion.isForgePresent() && MinecraftVersion.getVersion() == MinecraftVersion.MC1_7_R4){ + targetMethodName = Forge1710Mappings.getMethodMapping().getOrDefault(this.name(), targetMethodName); + } else if(targetVersion.version.isMojangMapping()){ + targetMethodName = MojangToMapping.getMapping().getOrDefault(targetClass.getMojangName() + "#" + targetVersion.name, "Unmapped" + targetVersion.name); + } + method = targetClass.getClazz().getDeclaredMethod(targetMethodName, args); + method.setAccessible(true); + loaded = true; + methodName = targetVersion.name; + }catch(NullPointerException | NoSuchMethodException | SecurityException ex){ + try{ + if(targetVersion.version.isMojangMapping()) + targetMethodName = MojangToMapping.getMapping().getOrDefault(targetClass.getMojangName() + "#" + targetVersion.name, "Unmapped" + targetVersion.name); + method = targetClass.getClazz().getMethod(targetMethodName, args); + method.setAccessible(true); + loaded = true; + methodName = targetVersion.name; + }catch(NullPointerException | NoSuchMethodException | SecurityException ex2){ + MinecraftVersion.getLogger().warning("[NBTAPI] Unable to find the method '" + targetMethodName + "' in '" + (targetClass.getClazz() == null ? targetClass.getMojangName() : targetClass.getClazz().getSimpleName()) + "' Args: " + Arrays.toString(args) + " Enum: " + this); //NOSONAR This gets loaded before the logger is loaded + } + } + } + + ReflectionMethod(ClassWrapper targetClass, Class[] args, MinecraftVersion addedSince, Since... methodnames){ + this(targetClass, args, addedSince, null, methodnames); + } + + /** + * Runs the method on a given target object using the given args. + * + * @param target + * @param args + * @return Value returned by the method + */ + public Object run(Object target, Object... args){ + if(method == null) + throw new NbtApiException("Method not loaded! '" + this + "'"); + try{ + return method.invoke(target, args); + }catch(Exception ex){ + throw new NbtApiException("Error while calling the method '" + methodName + "', loaded: " + loaded + ", Enum: " + this + " Passed Class: " + target.getClass(), ex); + } + } + + /** + * @return The MethodName, used in this Minecraft Version + */ + public String getMethodName() { + return methodName; + } + + /** + * @return Has this method been linked + */ + public boolean isLoaded() { + return loaded; + } + + /** + * @return Is this method available in this Minecraft Version + */ + public boolean isCompatible() { + return compatible; + } + + public Since getSelectedVersionInfo() { + return targetVersion; + } + + /** + * @return Get Wrapper of the parent class + */ + public ClassWrapper getParentClassWrapper() { + return parentClassWrapper; + } + + public static class Since{ + public final MinecraftVersion version; + public final String name; + public Since(MinecraftVersion version, String name) { + this.version = version; + this.name = name; + } + } + +} diff --git a/src/main/java/the/david/replace/RePlace.java b/src/main/java/the/david/replace/RePlace.java new file mode 100644 index 0000000..ee79b80 --- /dev/null +++ b/src/main/java/the/david/replace/RePlace.java @@ -0,0 +1,21 @@ +package the.david.replace; + +import org.bukkit.plugin.java.JavaPlugin; +import the.david.replace.commands.*; +import the.david.replace.events.PlayerPlace; + +public final class RePlace extends JavaPlugin { + + @Override + public void onEnable() { + this.getCommand("rp").setExecutor(new CommandRP()); + this.getCommand("rp").setTabCompleter(new ConstructTabCompleter()); + getServer().getPluginManager().registerEvents(new PlayerPlace(), this); + + } + + @Override + public void onDisable() { + // Plugin shutdown logic + } +} diff --git a/src/main/java/the/david/replace/commands/CommandRP.java b/src/main/java/the/david/replace/commands/CommandRP.java new file mode 100644 index 0000000..d95ee1d --- /dev/null +++ b/src/main/java/the/david/replace/commands/CommandRP.java @@ -0,0 +1,115 @@ +package the.david.replace.commands; + +import ed.tr7zw.changeme.nbtapi.NBTItem; +import org.bukkit.Material; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; + +public class CommandRP implements CommandExecutor { + public void replaceItem(ItemStack item, PlayerInventory inv, String replace) { + if (replace.equalsIgnoreCase("main")) { + inv.setItemInMainHand(item); + } else if (replace.equalsIgnoreCase("off")) { + inv.setItemInOffHand(item); + } + } + + @Override + public boolean onCommand( CommandSender sender, Command command, String label, String[] args) { + Player player = (Player) sender; + PlayerInventory inv = player.getInventory(); + ItemStack itemMain = inv.getItemInMainHand(); + NBTItem nbtItem; + ItemStack itemOff = inv.getItemInOffHand(); + String replace; + if (itemMain.getType() == Material.CARROT_ON_A_STICK) { + nbtItem = new NBTItem(itemMain); + replace = "main"; + } else if (itemOff.getType() == Material.CARROT_ON_A_STICK) { + nbtItem = new NBTItem(itemOff); + replace = "off"; + } else { + player.sendMessage("You need to be holding a carrot on a stick in main hand or ofhand"); + return false; + } + if (args.length < 1) { + return false; + } + switch (args[0]) { + case "create": + if (nbtItem.hasTag("RPWrench")) { + player.sendMessage("This item already is rotato thinkey"); + } else { + nbtItem.setString("RPWrench", "reverse"); + replaceItem(nbtItem.getItem(), inv, replace); + } + return true; + case "delete": + if (nbtItem.hasTag("RPWrench")) { + nbtItem.removeKey("RPWrench"); + replaceItem(nbtItem.getItem(), inv, replace); + } else { + player.sendMessage("Item that you are holding doesnt have rotato thinkey on it so cant remove it."); + } + return true; + case "mode": + if (!(args.length >= 2)) { + if (nbtItem.hasTag("RPWrench")) { + player.sendMessage("Mode: " + nbtItem.getString("RPWrench")); + return true; + } else { + player.sendMessage("Item you are holding doesnt have rotato thinkey on it, create one by holding carrot on a stick and running /rp create"); + return false; + } + } + if (!nbtItem.hasTag("RPWrench")) { + player.sendMessage("Item you are holding doesnt have rotato thinkey on it, create one by holding carrot on a stick and running /rp create"); + return false; + } + switch (args[1]) { + case "reverse": + nbtItem.setString("RPWrench", "reverse"); + replaceItem(nbtItem.getItem(), inv, replace); + return true; + case "down": + nbtItem.setString("RPWrench", "down"); + replaceItem(nbtItem.getItem(), inv, replace); + return true; + case "up": + nbtItem.setString("RPWrench", "up"); + replaceItem(nbtItem.getItem(), inv, replace); + return true; + case "north": + nbtItem.setString("RPWrench", "north"); + replaceItem(nbtItem.getItem(), inv, replace); + return true; + case "east": + nbtItem.setString("RPWrench", "east"); + replaceItem(nbtItem.getItem(), inv, replace); + return true; + case "south": + nbtItem.setString("RPWrench", "south"); + replaceItem(nbtItem.getItem(), inv, replace); + return true; + case "west": + nbtItem.setString("RPWrench", "west"); + replaceItem(nbtItem.getItem(), inv, replace); + return true; + case "off": + nbtItem.setString("RPWrench", "off"); + replaceItem(nbtItem.getItem(), inv, replace); + return true; + default: + player.sendMessage("Invalid argument."); + return false; + } + default: + player.sendMessage("Invalid argument."); + return false; + } + } +} diff --git a/src/main/java/the/david/replace/commands/ConstructTabCompleter.java b/src/main/java/the/david/replace/commands/ConstructTabCompleter.java new file mode 100644 index 0000000..0569184 --- /dev/null +++ b/src/main/java/the/david/replace/commands/ConstructTabCompleter.java @@ -0,0 +1,30 @@ +package the.david.replace.commands; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; + +import java.util.ArrayList; +import java.util.List; + +public class ConstructTabCompleter implements TabCompleter { + @Override + public List onTabComplete( CommandSender sender, Command command, String label, String[] args) { + List list = new ArrayList<>(); + if (args.length == 1) { + list.add("create"); + list.add("delete"); + list.add("mode"); + } else if (args.length == 2 && args[0].equalsIgnoreCase("mode")) { + list.add("reverse"); + list.add("down"); + list.add("up"); + list.add("north"); + list.add("east"); + list.add("south"); + list.add("west"); + list.add("off"); + } + return list; + } +} diff --git a/src/main/java/the/david/replace/events/PlayerPlace.java b/src/main/java/the/david/replace/events/PlayerPlace.java new file mode 100644 index 0000000..37705aa --- /dev/null +++ b/src/main/java/the/david/replace/events/PlayerPlace.java @@ -0,0 +1,68 @@ +package the.david.replace.events; + +import ed.tr7zw.changeme.nbtapi.NBTItem; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.Directional; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; + +public class PlayerPlace implements Listener { + public void setRotation (Block b, BlockFace face) { + BlockData data = b.getBlockData(); + if (data instanceof Directional) { + Directional dir = (Directional) data; + dir.setFacing(face); + b.setBlockData(dir); + } + } + + @EventHandler + public void onBlockPlace(BlockPlaceEvent e) { + Player player = e.getPlayer(); + PlayerInventory inv = player.getInventory(); + ItemStack offhand = inv.getItemInOffHand(); + if (offhand.getType() == Material.CARROT_ON_A_STICK) { + NBTItem nbt = new NBTItem(offhand); + if (nbt.hasTag("RPWrench")) { + Block block = e.getBlockPlaced(); + switch (nbt.getString("RPWrench")) { + case "reverse": + BlockData data = block.getBlockData(); + if (data instanceof Directional) { + Directional dir = (Directional) data; + dir.setFacing(dir.getFacing().getOppositeFace()); + block.setBlockData(dir); + } + break; + case "down": + setRotation(block, BlockFace.DOWN); + break; + case "up": + setRotation(block, BlockFace.UP); + break; + case "north": + setRotation(block, BlockFace.NORTH); + break; + case "east": + setRotation(block, BlockFace.EAST); + break; + case "south": + setRotation(block, BlockFace.SOUTH); + break; + case "west": + setRotation(block, BlockFace.WEST); + break; + case "off": + break; + } + } + } + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..d0c8c11 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,12 @@ +name: RePlace +version: '${project.version}' +main: the.david.replace.RePlace +api-version: 1.19 +prefix: RP +authors: [ 0David, itIsMeDavid ] +description: allowes you to place blocks facing the other way +commands: + rp: + description: Create change mode or delete function of this plugin + usage: Correct usage / create/remove/mode +# permission: replace.useRePlace