diff --git a/out/artifacts/VillagerTradeLimiter_jar/VillagerTradeLimiter.jar b/out/artifacts/VillagerTradeLimiter_jar/VillagerTradeLimiter.jar new file mode 100644 index 0000000..d936cb1 Binary files /dev/null and b/out/artifacts/VillagerTradeLimiter_jar/VillagerTradeLimiter.jar differ diff --git a/pom.xml b/pom.xml index 0fc5339..62f3e7b 100644 --- a/pom.xml +++ b/pom.xml @@ -29,9 +29,19 @@ spigotmc-repo https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + codemc-repo + https://repo.codemc.org/repository/maven-public/ + default + + + org.spigotmc + spigot + 1.18-R0.1-SNAPSHOT + org.spigotmc spigot @@ -43,5 +53,11 @@ 1.16.5-R0.1-SNAPSHOT provided + + de.tr7zw + functional-annotations + 0.1-SNAPSHOT + compile + \ No newline at end of file diff --git a/src/com/pretzel/dev/villagertradelimiter/listeners/PlayerListener.java b/src/com/pretzel/dev/villagertradelimiter/listeners/PlayerListener.java index 68c357e..d60fb9a 100644 --- a/src/com/pretzel/dev/villagertradelimiter/listeners/PlayerListener.java +++ b/src/com/pretzel/dev/villagertradelimiter/listeners/PlayerListener.java @@ -3,7 +3,6 @@ package com.pretzel.dev.villagertradelimiter.listeners; import com.pretzel.dev.villagertradelimiter.VillagerTradeLimiter; import com.pretzel.dev.villagertradelimiter.lib.Util; import com.pretzel.dev.villagertradelimiter.nms.*; -import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.configuration.ConfigurationSection; @@ -14,7 +13,6 @@ import org.bukkit.entity.Villager; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerInteractEntityEvent; -import org.bukkit.inventory.MerchantRecipe; import org.bukkit.inventory.meta.EnchantmentStorageMeta; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; @@ -26,11 +24,9 @@ public class PlayerListener implements Listener { private static final Material[] MATERIALS = new Material[] { Material.IRON_HELMET, Material.IRON_CHESTPLATE, Material.IRON_LEGGINGS, Material.IRON_BOOTS, Material.BELL, Material.CHAINMAIL_HELMET, Material.CHAINMAIL_CHESTPLATE, Material.CHAINMAIL_LEGGINGS, Material.CHAINMAIL_BOOTS, Material.SHIELD, Material.DIAMOND_HELMET, Material.DIAMOND_CHESTPLATE, Material.DIAMOND_LEGGINGS, Material.DIAMOND_BOOTS, Material.FILLED_MAP, Material.FISHING_ROD, Material.LEATHER_HELMET, Material.LEATHER_CHESTPLATE, Material.LEATHER_LEGGINGS, Material.LEATHER_BOOTS, Material.LEATHER_HORSE_ARMOR, Material.SADDLE, Material.ENCHANTED_BOOK, Material.STONE_AXE, Material.STONE_SHOVEL, Material.STONE_PICKAXE, Material.STONE_HOE, Material.IRON_AXE, Material.IRON_SHOVEL, Material.IRON_PICKAXE, Material.DIAMOND_AXE, Material.DIAMOND_SHOVEL, Material.DIAMOND_PICKAXE, Material.DIAMOND_HOE, Material.IRON_SWORD, Material.DIAMOND_SWORD, Material.NETHERITE_AXE, Material.NETHERITE_HOE, Material.NETHERITE_PICKAXE, Material.NETHERITE_SHOVEL, Material.NETHERITE_SWORD, Material.NETHERITE_HELMET, Material.NETHERITE_CHESTPLATE, Material.NETHERITE_LEGGINGS, Material.NETHERITE_BOOTS }; private final VillagerTradeLimiter instance; - private final NMS nms; public PlayerListener(VillagerTradeLimiter instance) { this.instance = instance; - this.nms = new NMS(Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]); } @EventHandler @@ -60,10 +56,71 @@ public class PlayerListener implements Listener { final Player player = event.getPlayer(); if(Util.isNPC(player)) return; //Skips NPCs this.hotv(player); + this.setIngredients(villager); + this.setData(villager); this.maxDiscount(villager, player); this.maxDemand(villager); } + private void setIngredients(final Villager villager) { + final ConfigurationSection overrides = instance.getCfg().getConfigurationSection("Overrides"); + final NBTEntity villagerNBT = new NBTEntity(villager); + NBTCompoundList recipes = villagerNBT.getCompound("Offers").getCompoundList("Recipes"); + for (NBTCompound recipe : recipes) { + if(overrides != null) { + for(final String override : overrides.getKeys(false)) { + final ConfigurationSection item = this.getItem(recipe, override); + if(item != null) { + if (item.contains("item-1-material")) + recipe.getCompound("buy").setString("id", "minecraft:" + item.getString("item-1-material")); + if (item.contains("item-2-material")) + recipe.getCompound("buyB").setString("id", "minecraft:" + item.getString("item-2-material")); + + if (recipe.getCompound("buy").getString("id") != "minecraft:air" && item.contains("item-1-amount")) { + int cost = item.getInt("item-1-amount"); + if (cost <= 0) + cost = 1; + else if (cost > 64) + cost = 64; + recipe.getCompound("buy").setInteger("Count", cost); + } + + if (recipe.getCompound("buyB").getString("id") != "minecraft:air" && item.contains("item-2-amount")) { + int cost2 = item.getInt("item-2-amount"); + if (cost2 <= 0) + cost2 = 1; + else if (cost2 > 64) + cost2 = 64; + recipe.getCompound("buyB").setInteger("Count", cost2); + } + break; + } + } + } + } + } + + private void setData(final Villager villager) { + final ConfigurationSection overrides = instance.getCfg().getConfigurationSection("Overrides"); + final NBTEntity villagerNBT = new NBTEntity(villager); + NBTCompoundList recipes = villagerNBT.getCompound("Offers").getCompoundList("Recipes"); + for (NBTCompound recipe : recipes) { + if(overrides != null) { + for(final String override : overrides.getKeys(false)) { + final ConfigurationSection item = this.getItem(recipe, override); + if(item != null) { + if (item.contains("uses")) { + int uses = item.getInt("uses"); + if (uses > 0) + recipe.setInteger("maxUses", uses); + } + break; + } + } + } + } + } + //Hero of the Village effect limiter feature private void hotv(final Player player) { final PotionEffectType effect = PotionEffectType.HERO_OF_THE_VILLAGE; @@ -82,43 +139,44 @@ public class PlayerListener implements Listener { //MaxDiscount feature - limits the lowest discounted price to a % of the base price private void maxDiscount(final Villager villager, final Player player) { - final List recipes = villager.getRecipes(); - int a = 0, b = 0, c = 0, d = 0, e = 0; + int majorPositiveValue = 0, minorPositiveValue = 0, tradingValue = 0, minorNegativeValue = 0, majorNegativeValue = 0; - final NBTContainer vnbt = new NBTContainer(this.nms, villager); - final NBTTagList gossips = new NBTTagList(this.nms, vnbt.getTag().get("Gossips")); - final NBTContainer pnbt = new NBTContainer(this.nms, player); - final String puuid = Util.intArrayToString(pnbt.getTag().getIntArray("UUID")); - - for (int i = 0; i < gossips.size(); ++i) { - final NBTTagCompound gossip = gossips.getCompound(i); - final String type = gossip.getString("Type"); - final String tuuid = Util.intArrayToString(gossip.getIntArray("Target")); - final int value = gossip.getInt("Value"); - if (tuuid.equals(puuid)) { - switch(type) { - case "trading": c = value; break; - case "minor_positive": b = value; break; - case "minor_negative": d = value; break; - case "major_positive": a = value; break; - case "major_negative": e = value; break; - default: break; + NBTEntity nbtEntity = new NBTEntity(villager); + final NBTEntity playerNBT = new NBTEntity(player); + final String playerUUID = Util.intArrayToString(playerNBT.getIntArray("UUID")); + if (nbtEntity.hasKey("Gossips")) { + NBTCompoundList gossips = nbtEntity.getCompoundList("Gossips"); + for (NBTCompound gossip : gossips) { + final String type = gossip.getString("Type"); + final String targetUUID = Util.intArrayToString(gossip.getIntArray("Target")); + final int value = gossip.getInteger("Value"); + if (targetUUID == playerUUID) { + switch (type) { + case "trading": tradingValue = value; break; + case "minor_positive": minorPositiveValue = value; break; + case "minor_negative": minorNegativeValue = value; break; + case "major_positive": majorPositiveValue = value; break; + case "major_negative": majorNegativeValue = value; break; + default: break; + } } } } final ConfigurationSection overrides = instance.getCfg().getConfigurationSection("Overrides"); - final ArrayList finalRecipes = new ArrayList<>(); - for (final MerchantRecipe recipe : recipes) { - final int x = recipe.getIngredients().get(0).getAmount(); - final float p0 = this.getPriceMultiplier(recipe); - final int w = 5 * a + b + c - d - 5 * e; - final float y = x - p0 * w; + final NBTEntity villagerNBT = new NBTEntity(villager); + NBTCompoundList recipes = villagerNBT.getCompound("Offers").getCompoundList("Recipes"); + List remove = new ArrayList<>(); + for (NBTCompound recipe : recipes) { + final int ingredientAmount = recipe.getCompound("buy").getInteger("Count"); + final float priceMultiplier = this.getPriceMultiplier(recipe); + final int valueModifier = 5 * majorPositiveValue + minorPositiveValue + tradingValue - minorNegativeValue - 5 * majorNegativeValue; + final float finalValue = ingredientAmount - priceMultiplier * valueModifier; boolean disabled = false; double maxDiscount = instance.getCfg().getDouble("MaxDiscount", 0.3); if(overrides != null) { - for(final String k : overrides.getKeys(false)) { - final ConfigurationSection item = this.getItem(recipe, k); + for(final String override : overrides.getKeys(false)) { + final ConfigurationSection item = this.getItem(recipe, override); if(item != null) { disabled = item.getBoolean("Disabled", false); maxDiscount = item.getDouble("MaxDiscount", maxDiscount); @@ -127,53 +185,49 @@ public class PlayerListener implements Listener { } } if(maxDiscount >= 0.0 && maxDiscount <= 1.0) { - if(y < x * (1.0 - maxDiscount) && y != x) { - recipe.setPriceMultiplier(x * (float)maxDiscount / w); + if(finalValue < ingredientAmount * (1.0 - maxDiscount) && finalValue != ingredientAmount) { + recipe.setFloat("priceMultiplier", ingredientAmount * (float)maxDiscount / valueModifier); } else { - recipe.setPriceMultiplier(p0); + recipe.setFloat("priceMultiplier", priceMultiplier); } } else { - recipe.setPriceMultiplier(p0); + recipe.setFloat("priceMultiplier", priceMultiplier); } - if(!disabled) finalRecipes.add(recipe); + if(disabled) + remove.add(recipe); } - villager.setRecipes(finalRecipes); - Bukkit.getScheduler().runTaskLater(instance, () -> villager.setRecipes(recipes), 0); + remove.forEach(rem -> { recipes.remove(rem); }); } //MaxDemand feature - limits demand-based price increases private void maxDemand(final Villager villager) { - List recipes = villager.getRecipes(); - final NBTContainer vnbt = new NBTContainer(this.nms, villager); - final NBTTagCompound vtag = vnbt.getTag(); - final NBTTagList recipes2 = new NBTTagList(this.nms, vtag.getCompound("Offers").get("Recipes")); - + final NBTEntity villagerNBT = new NBTEntity(villager); final ConfigurationSection overrides = instance.getCfg().getConfigurationSection("Overrides"); - for(int i = 0; i < recipes2.size(); ++i) { - final NBTTagCompound recipe2 = recipes2.getCompound(i); - final int demand = recipe2.getInt("demand"); - int maxDemand = instance.getCfg().getInt("MaxDemand", -1); - if(overrides != null) { - for(final String k : overrides.getKeys(false)) { - final ConfigurationSection item = this.getItem(recipes.get(i), k); - if(item != null) { - maxDemand = item.getInt("MaxDemand", maxDemand); - break; + if (villagerNBT.hasKey("Offers")) { + NBTCompoundList recipes = villagerNBT.getCompound("Offers").getCompoundList("Recipes"); + for (NBTCompound recipe : recipes) { + final int demand = recipe.getInteger("demand"); + int maxDemand = instance.getCfg().getInt("MaxDemand", -1); + if (overrides != null) { + for (String override : overrides.getKeys(false)) { + final ConfigurationSection item = this.getItem(recipe, override); + if(item != null) { + maxDemand = item.getInt("MaxDemand", maxDemand); + break; + } } } - } - if(maxDemand >= 0 && demand > maxDemand) { - recipe2.setInt("demand", maxDemand); + if(maxDemand >= 0 && demand > maxDemand) { + recipe.setInteger("demand", maxDemand); + } } } - villager.getInventory().clear(); - vnbt.saveTag(villager, vtag); } //Returns the price multiplier for a given trade - private float getPriceMultiplier(final MerchantRecipe recipe) { + private float getPriceMultiplier(final NBTCompound recipe) { float p = 0.05f; - final Material type = recipe.getResult().getType(); + final Material type = recipe.getItemStack("sell").getType(); for(int length = MATERIALS.length, i = 0; i < length; ++i) { if(type == MATERIALS[i]) { p = 0.2f; @@ -184,7 +238,7 @@ public class PlayerListener implements Listener { } //Returns the configured settings for a trade - private ConfigurationSection getItem(final MerchantRecipe recipe, final String k) { + private ConfigurationSection getItem(final NBTCompound recipe, final String k) { final ConfigurationSection item = instance.getCfg().getConfigurationSection("Overrides."+k); if(item == null) return null; @@ -198,25 +252,26 @@ public class PlayerListener implements Listener { try { //Return the enchanted book item if there's a number in the item name final int level = Integer.parseInt(words[words.length-1]); - if(recipe.getResult().getType() != Material.ENCHANTED_BOOK) return null; - final EnchantmentStorageMeta meta = (EnchantmentStorageMeta)recipe.getResult().getItemMeta(); - final Enchantment enchantment = EnchantmentWrapper.getByKey(NamespacedKey.minecraft(k.substring(0, k.lastIndexOf("_")))); - if(meta == null || enchantment == null) return null; - if(meta.hasStoredEnchant(enchantment) && meta.getStoredEnchantLevel(enchantment) == level) return item; - return null; + if(recipe.getItemStack("sell").getType() == Material.ENCHANTED_BOOK) { + final EnchantmentStorageMeta meta = (EnchantmentStorageMeta) recipe.getItemStack("sell").getItemMeta(); + final Enchantment enchantment = EnchantmentWrapper.getByKey(NamespacedKey.minecraft(k.substring(0, k.lastIndexOf("_")))); + if (meta == null || enchantment == null) return null; + if (meta.hasStoredEnchant(enchantment) && meta.getStoredEnchantLevel(enchantment) == level) return item; + } } catch(NumberFormatException e) { //Return the item if the item name is valid - if(this.verify(recipe, Material.matchMaterial(k))) return item; + if(this.verify(recipe, Material.matchMaterial(k))) + return item; return null; } catch(Exception e2) { //Send an error message Util.errorMsg(e2); - return null; } + return null; } //Verifies that an item exists in the villager's trade - private boolean verify(final MerchantRecipe recipe, final Material material) { - return ((recipe.getResult().getType() == material) || (recipe.getIngredients().get(0).getType() == material)); + private boolean verify(final NBTCompound recipe, final Material material) { + return ((recipe.getItemStack("sell").getType() == material) || (recipe.getItemStack("buy").getType() == material)); } } diff --git a/src/com/pretzel/dev/villagertradelimiter/nms/CraftEntity.java b/src/com/pretzel/dev/villagertradelimiter/nms/CraftEntity.java deleted file mode 100644 index a14f61a..0000000 --- a/src/com/pretzel/dev/villagertradelimiter/nms/CraftEntity.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.pretzel.dev.villagertradelimiter.nms; - -import com.pretzel.dev.villagertradelimiter.lib.Util; -import org.bukkit.entity.Entity; - -public class CraftEntity { - private final NMS nms; - private final Class c; - - public CraftEntity(final NMS nms) { - this.nms = nms; - this.c = nms.getCraftBukkitClass("entity.CraftEntity"); - } - - public Object getHandle(final Entity entity) { - try { - return nms.getMethod(this.c, "getHandle").invoke(this.c.cast(entity)); - } catch (Exception e) { - Util.errorMsg(e); - return null; - } - } -} diff --git a/src/com/pretzel/dev/villagertradelimiter/nms/NBTCompound.java b/src/com/pretzel/dev/villagertradelimiter/nms/NBTCompound.java new file mode 100644 index 0000000..669c75a --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/NBTCompound.java @@ -0,0 +1,839 @@ +package com.pretzel.dev.villagertradelimiter.nms; + +import java.io.OutputStream; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Objects; +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 org.bukkit.inventory.ItemStack; + +import com.pretzel.dev.villagertradelimiter.nms.utils.MinecraftVersion; +import com.pretzel.dev.villagertradelimiter.nms.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 + * @return True if the key is set + */ + public Boolean hasKey(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 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(); + } + } + + /** + * @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(); + } + + /** + * @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 "{}"; + 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/com/pretzel/dev/villagertradelimiter/nms/NBTCompoundList.java b/src/com/pretzel/dev/villagertradelimiter/nms/NBTCompoundList.java new file mode 100644 index 0000000..0391a8e --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/NBTCompoundList.java @@ -0,0 +1,106 @@ +package com.pretzel.dev.villagertradelimiter.nms; + +import com.pretzel.dev.villagertradelimiter.nms.utils.MinecraftVersion; +import com.pretzel.dev.villagertradelimiter.nms.utils.nmsmappings.ClassWrapper; +import com.pretzel.dev.villagertradelimiter.nms.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/com/pretzel/dev/villagertradelimiter/nms/NBTContainer.java b/src/com/pretzel/dev/villagertradelimiter/nms/NBTContainer.java index a69d654..d9d0b76 100644 --- a/src/com/pretzel/dev/villagertradelimiter/nms/NBTContainer.java +++ b/src/com/pretzel/dev/villagertradelimiter/nms/NBTContainer.java @@ -1,44 +1,82 @@ package com.pretzel.dev.villagertradelimiter.nms; -import com.pretzel.dev.villagertradelimiter.lib.Util; -import org.bukkit.entity.Entity; +import java.io.InputStream; -public class NBTContainer { - private final NMS nms; - private final Entity entity; - private final NBTTagCompound tag; +import com.pretzel.dev.villagertradelimiter.nms.utils.nmsmappings.ClassWrapper; +import com.pretzel.dev.villagertradelimiter.nms.utils.nmsmappings.ObjectCreator; +import com.pretzel.dev.villagertradelimiter.nms.utils.nmsmappings.ReflectionMethod; - public NBTContainer(final NMS nms, final Entity entity) { - this.nms = nms; - this.entity = entity; - this.tag = this.loadTag(); - } +/** + * A Standalone {@link NBTCompound} implementation. All data is just kept inside + * this Object. + * + * @author tr7zw + * + */ +public class NBTContainer extends NBTCompound { - public NBTTagCompound loadTag() { - final CraftEntity craftEntity = new CraftEntity(nms); - final NMSEntity nmsEntity = new NMSEntity(nms); - final Class tgc; - if(nms.getVersion().compareTo("v1_17_R1") < 0) - tgc = nms.getNMSClass("server."+nms.getVersion()+".NBTTagCompound"); - else - tgc = nms.getNMSClass("nbt.NBTTagCompound"); - try { - final NBTTagCompound tag = new NBTTagCompound(nms, tgc.getDeclaredConstructor().newInstance()); - nmsEntity.save(craftEntity.getHandle(this.entity), tag); - return tag; - } catch (Exception e) { - Util.errorMsg(e); - return null; - } - } + private Object nbt; - public void saveTag(final Entity entity, final NBTTagCompound tag) { - final CraftEntity craftEntity = new CraftEntity(nms); - final NMSEntity nmsEntity = new NMSEntity(nms); - nmsEntity.load(craftEntity.getHandle(entity), tag); - } + /** + * Creates an empty, standalone NBTCompound + */ + public NBTContainer() { + super(null, null); + nbt = ObjectCreator.NMS_NBTTAGCOMPOUND.getInstance(); + } - public NBTTagCompound getTag() { - return this.tag; - } -} \ No newline at end of file + /** + * 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/com/pretzel/dev/villagertradelimiter/nms/NBTDoubleList.java b/src/com/pretzel/dev/villagertradelimiter/nms/NBTDoubleList.java new file mode 100644 index 0000000..13aa7e3 --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/NBTDoubleList.java @@ -0,0 +1,45 @@ +package com.pretzel.dev.villagertradelimiter.nms; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import com.pretzel.dev.villagertradelimiter.nms.utils.nmsmappings.ClassWrapper; +import com.pretzel.dev.villagertradelimiter.nms.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/com/pretzel/dev/villagertradelimiter/nms/NBTEntity.java b/src/com/pretzel/dev/villagertradelimiter/nms/NBTEntity.java new file mode 100644 index 0000000..e28ab28 --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/NBTEntity.java @@ -0,0 +1,54 @@ +package com.pretzel.dev.villagertradelimiter.nms; + +import org.bukkit.entity.Entity; + +import de.tr7zw.annotations.FAUtil; +import com.pretzel.dev.villagertradelimiter.nms.utils.MinecraftVersion; +import com.pretzel.dev.villagertradelimiter.nms.utils.annotations.AvailableSince; +import com.pretzel.dev.villagertradelimiter.nms.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() { + return NBTReflectionUtil.getEntityNBTTagCompound(NBTReflectionUtil.getNMSEntity(ent)); + } + + @Override + protected void setCompound(Object compound) { + 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/com/pretzel/dev/villagertradelimiter/nms/NBTFile.java b/src/com/pretzel/dev/villagertradelimiter/nms/NBTFile.java new file mode 100644 index 0000000..34ea160 --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/NBTFile.java @@ -0,0 +1,80 @@ +package com.pretzel.dev.villagertradelimiter.nms; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +import com.pretzel.dev.villagertradelimiter.nms.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()) { + FileInputStream inputsteam = new FileInputStream(file); + nbt = NBTReflectionUtil.readNBT(inputsteam); + } else { + nbt = ObjectCreator.NMS_NBTTAGCOMPOUND.getInstance(); + save(); + } + } + + /** + * Saves the data to the file + * + * @throws IOException + */ + public void save() throws IOException { + try { + getWriteLock().lock(); + if (!file.exists()) { + file.getParentFile().mkdirs(); + if (!file.createNewFile()) + throw new IOException("Unable to create file at " + file.getAbsolutePath()); + } + FileOutputStream outStream = new FileOutputStream(file); + NBTReflectionUtil.writeNBT(nbt, outStream); + } 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; + } + +} diff --git a/src/com/pretzel/dev/villagertradelimiter/nms/NBTFloatList.java b/src/com/pretzel/dev/villagertradelimiter/nms/NBTFloatList.java new file mode 100644 index 0000000..db48fb8 --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/NBTFloatList.java @@ -0,0 +1,45 @@ +package com.pretzel.dev.villagertradelimiter.nms; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import com.pretzel.dev.villagertradelimiter.nms.utils.nmsmappings.ClassWrapper; +import com.pretzel.dev.villagertradelimiter.nms.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/com/pretzel/dev/villagertradelimiter/nms/NBTIntegerList.java b/src/com/pretzel/dev/villagertradelimiter/nms/NBTIntegerList.java new file mode 100644 index 0000000..630484e --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/NBTIntegerList.java @@ -0,0 +1,45 @@ +package com.pretzel.dev.villagertradelimiter.nms; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import com.pretzel.dev.villagertradelimiter.nms.utils.nmsmappings.ClassWrapper; +import com.pretzel.dev.villagertradelimiter.nms.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/com/pretzel/dev/villagertradelimiter/nms/NBTItem.java b/src/com/pretzel/dev/villagertradelimiter/nms/NBTItem.java new file mode 100644 index 0000000..b5cfd8e --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/NBTItem.java @@ -0,0 +1,176 @@ +package com.pretzel.dev.villagertradelimiter.nms; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import com.pretzel.dev.villagertradelimiter.nms.utils.nmsmappings.ReflectionMethod; + +/** + * 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.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!"); + } + 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/com/pretzel/dev/villagertradelimiter/nms/NBTList.java b/src/com/pretzel/dev/villagertradelimiter/nms/NBTList.java new file mode 100644 index 0000000..cf6294a --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/NBTList.java @@ -0,0 +1,429 @@ +package com.pretzel.dev.villagertradelimiter.nms; + +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 com.pretzel.dev.villagertradelimiter.nms.utils.MinecraftVersion; +import com.pretzel.dev.villagertradelimiter.nms.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/com/pretzel/dev/villagertradelimiter/nms/NBTListCompound.java b/src/com/pretzel/dev/villagertradelimiter/nms/NBTListCompound.java new file mode 100644 index 0000000..15b0276 --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/NBTListCompound.java @@ -0,0 +1,42 @@ +package com.pretzel.dev.villagertradelimiter.nms; + +/** + * 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/com/pretzel/dev/villagertradelimiter/nms/NBTLongList.java b/src/com/pretzel/dev/villagertradelimiter/nms/NBTLongList.java new file mode 100644 index 0000000..3a6dab3 --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/NBTLongList.java @@ -0,0 +1,45 @@ +package com.pretzel.dev.villagertradelimiter.nms; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import com.pretzel.dev.villagertradelimiter.nms.utils.nmsmappings.ClassWrapper; +import com.pretzel.dev.villagertradelimiter.nms.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/com/pretzel/dev/villagertradelimiter/nms/NBTPersistentDataContainer.java b/src/com/pretzel/dev/villagertradelimiter/nms/NBTPersistentDataContainer.java new file mode 100644 index 0000000..e684602 --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/NBTPersistentDataContainer.java @@ -0,0 +1,31 @@ +package com.pretzel.dev.villagertradelimiter.nms; + +import java.util.Map; + +import org.bukkit.persistence.PersistentDataContainer; + +import com.pretzel.dev.villagertradelimiter.nms.utils.nmsmappings.ReflectionMethod; + +public class NBTPersistentDataContainer extends NBTCompound { + + private final PersistentDataContainer container; + + protected 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/com/pretzel/dev/villagertradelimiter/nms/NBTReflectionUtil.java b/src/com/pretzel/dev/villagertradelimiter/nms/NBTReflectionUtil.java new file mode 100644 index 0000000..3c18a40 --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/NBTReflectionUtil.java @@ -0,0 +1,625 @@ +package com.pretzel.dev.villagertradelimiter.nms; + +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 org.bukkit.block.BlockState; +import org.bukkit.entity.Entity; +import org.bukkit.inventory.meta.ItemMeta; + +import com.pretzel.dev.villagertradelimiter.nms.utils.GsonWrapper; +import com.pretzel.dev.villagertradelimiter.nms.utils.MinecraftVersion; +import com.pretzel.dev.villagertradelimiter.nms.utils.nmsmappings.ClassWrapper; +import com.pretzel.dev.villagertradelimiter.nms.utils.nmsmappings.ObjectCreator; +import com.pretzel.dev.villagertradelimiter.nms.utils.nmsmappings.ReflectionMethod; + +/** + * 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) { + 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 { + 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/com/pretzel/dev/villagertradelimiter/nms/NBTStringList.java b/src/com/pretzel/dev/villagertradelimiter/nms/NBTStringList.java new file mode 100644 index 0000000..b877213 --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/NBTStringList.java @@ -0,0 +1,42 @@ +package com.pretzel.dev.villagertradelimiter.nms; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import com.pretzel.dev.villagertradelimiter.nms.utils.nmsmappings.ClassWrapper; +import com.pretzel.dev.villagertradelimiter.nms.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/com/pretzel/dev/villagertradelimiter/nms/NBTTagCompound.java b/src/com/pretzel/dev/villagertradelimiter/nms/NBTTagCompound.java deleted file mode 100644 index a8a8d73..0000000 --- a/src/com/pretzel/dev/villagertradelimiter/nms/NBTTagCompound.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.pretzel.dev.villagertradelimiter.nms; - -import com.pretzel.dev.villagertradelimiter.lib.Util; - -public class NBTTagCompound { - private final NMS nms; - private final Class c; - private final Object self; - - public NBTTagCompound(final NMS nms, final Object self) { - this.nms = nms; - this.self = self; - this.c = self.getClass(); - } - - public Object get(final String key) { - try { - return this.nms.getMethod(this.c, "get", String.class).invoke(this.self, key); - } catch (Exception e) { - Util.errorMsg(e); - return null; - } - } - - public String getString(final String key) { - try { - return (String)this.nms.getMethod(this.c, "getString", String.class).invoke(this.self, key); - } catch (Exception e) { - Util.errorMsg(e); - return null; - } - } - - public int getInt(final String key) { - try { - return (int)this.nms.getMethod(this.c, "getInt", String.class).invoke(this.self, key); - } catch (Exception e) { - Util.errorMsg(e); - return Integer.MIN_VALUE; - } - } - - public int[] getIntArray(final String key) { - try { - return (int[])this.nms.getMethod(this.c, "getIntArray", String.class).invoke(this.self, key); - } catch (Exception e) { - Util.errorMsg(e); - return null; - } - } - - public NBTTagCompound getCompound(final String key) { - try { - return new NBTTagCompound(this.nms, this.nms.getMethod(this.self.getClass(), "getCompound", String.class).invoke(this.self, key)); - } catch (Exception e) { - Util.errorMsg(e); - return null; - } - } - - public void setInt(final String key, final int value) { - try { - this.nms.getMethod(this.c, "setInt", String.class, Integer.TYPE).invoke(this.self, key, value); - } catch (Exception e) { - Util.errorMsg(e); - } - } - - public Object getSelf() { return this.self; } - public Class getC() { return this.c; } -} diff --git a/src/com/pretzel/dev/villagertradelimiter/nms/NBTTagList.java b/src/com/pretzel/dev/villagertradelimiter/nms/NBTTagList.java deleted file mode 100644 index 2f167aa..0000000 --- a/src/com/pretzel/dev/villagertradelimiter/nms/NBTTagList.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.pretzel.dev.villagertradelimiter.nms; - -import com.pretzel.dev.villagertradelimiter.lib.Util; - -public class NBTTagList -{ - private final NMS nms; - private final Object self; - private final Class c; - - public NBTTagList(final NMS nms, final Object self) { - this.nms = nms; - this.self = self; - if(nms.getVersion().compareTo("v1_17_R1") < 0) - this.c = nms.getNMSClass("server."+nms.getVersion()+".NBTTagList"); - else - this.c = nms.getNMSClass("nbt.NBTTagList"); - } - - public NBTTagCompound getCompound(final int index) { - try { - return new NBTTagCompound(this.nms, this.nms.getMethod(this.c, "getCompound", Integer.TYPE).invoke(this.self, index)); - } catch (Exception e) { - Util.errorMsg(e); - return null; - } - } - - public int size() { - try { - return (int)this.nms.getMethod(this.c, "size").invoke(this.self, new Object[0]); - } catch (Exception e) { - Util.errorMsg(e); - return -1; - } - } - - public Object getSelf() { - return this.self; - } -} diff --git a/src/com/pretzel/dev/villagertradelimiter/nms/NBTTileEntity.java b/src/com/pretzel/dev/villagertradelimiter/nms/NBTTileEntity.java new file mode 100644 index 0000000..e0ca4f1 --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/NBTTileEntity.java @@ -0,0 +1,64 @@ +package com.pretzel.dev.villagertradelimiter.nms; + +import org.bukkit.block.BlockState; + +import de.tr7zw.annotations.FAUtil; +import com.pretzel.dev.villagertradelimiter.nms.utils.MinecraftVersion; +import com.pretzel.dev.villagertradelimiter.nms.utils.annotations.AvailableSince; +import com.pretzel.dev.villagertradelimiter.nms.utils.annotations.CheckUtil; + +/** + * 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() { + return NBTReflectionUtil.getTileEntityNBTTagCompound(tile); + } + + @Override + protected void setCompound(Object compound) { + 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 (hasKey("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/com/pretzel/dev/villagertradelimiter/nms/NBTType.java b/src/com/pretzel/dev/villagertradelimiter/nms/NBTType.java new file mode 100644 index 0000000..0a21b47 --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/NBTType.java @@ -0,0 +1,48 @@ +package com.pretzel.dev.villagertradelimiter.nms; + +/** + * 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/com/pretzel/dev/villagertradelimiter/nms/NMS.java b/src/com/pretzel/dev/villagertradelimiter/nms/NMS.java deleted file mode 100644 index 31e589f..0000000 --- a/src/com/pretzel/dev/villagertradelimiter/nms/NMS.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.pretzel.dev.villagertradelimiter.nms; - -import com.pretzel.dev.villagertradelimiter.lib.Util; - -import java.lang.reflect.Method; -import java.util.HashMap; - -public class NMS -{ - private final HashMap> nmsClasses; - private final HashMap> bukkitClasses; - private final HashMap methods; - private final String version; - - public NMS(final String version) { - this.nmsClasses = new HashMap<>(); - this.bukkitClasses = new HashMap<>(); - this.methods = new HashMap<>(); - this.version = version; - } - - public Class getNMSClass(final String name) { - if(this.nmsClasses.containsKey(name)) { - return this.nmsClasses.get(name); - } - - try { - Class c = Class.forName("net.minecraft."+name); - this.nmsClasses.put(name, c); - return c; - } catch (Exception e) { - Util.errorMsg(e); - return this.nmsClasses.put(name, null); - } - } - - public Class getCraftBukkitClass(final String name) { - if(this.bukkitClasses.containsKey(name)) { - return this.bukkitClasses.get(name); - } - - try { - Class c = Class.forName("org.bukkit.craftbukkit." + this.version + "." + name); - this.bukkitClasses.put(name, c); - return c; - } catch (Exception e) { - Util.errorMsg(e); - return this.bukkitClasses.put(name, null); - } - } - - public Method getMethod(final Class invoker, final String name) throws NoSuchMethodException { - return this.getMethod(invoker, name, null, null); - } - - public Method getMethod(final Class invoker, final String name, final Class type) throws NoSuchMethodException { - return this.getMethod(invoker, name, type, null); - } - - public Method getMethod(final Class invoker, final String name, final Class type, final Class type2) throws NoSuchMethodException { - if(this.methods.containsKey(name) && this.methods.get(name).getDeclaringClass().equals(invoker)) { - return this.methods.get(name); - } - Method method; - try { - if(type2 != null) { - method = invoker.getMethod(name, type, type2); - } else if(type != null) { - method = invoker.getMethod(name, type); - } else { - method = invoker.getMethod(name); - } - } catch (Exception e) { - Util.errorMsg(e); - return this.methods.put(name, null); - } - this.methods.put(name, method); - return method; - } - - public String getVersion() { return this.version; } -} diff --git a/src/com/pretzel/dev/villagertradelimiter/nms/NMSEntity.java b/src/com/pretzel/dev/villagertradelimiter/nms/NMSEntity.java deleted file mode 100644 index eaea011..0000000 --- a/src/com/pretzel/dev/villagertradelimiter/nms/NMSEntity.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.pretzel.dev.villagertradelimiter.nms; - -import com.pretzel.dev.villagertradelimiter.lib.Util; - -public class NMSEntity { - private final NMS nms; - private final Class c; - - public NMSEntity(final NMS nms) { - this.nms = nms; - if(nms.getVersion().compareTo("v1_17_R1") < 0) - this.c = nms.getNMSClass("server."+nms.getVersion()+".Entity"); - else - this.c = nms.getNMSClass("world.entity.Entity"); - } - - public void save(final Object nmsEntity, final NBTTagCompound tag) { - try { - nms.getMethod(this.c, "save", tag.getC()).invoke(nmsEntity, tag.getSelf()); - } catch (Exception e) { - Util.errorMsg(e); - } - } - - public void load(final Object nmsEntity, final NBTTagCompound tag) { - try { - nms.getMethod(this.c, "load", tag.getC()).invoke(nmsEntity, tag.getSelf()); - } catch (NoSuchMethodException e) { - try { - nms.getMethod(this.c, "f", tag.getC()).invoke(nmsEntity, tag.getSelf()); - } catch (Exception e2) { - Util.errorMsg(e2); - } - } catch (Exception e3) { - Util.errorMsg(e3); - } - } -} diff --git a/src/com/pretzel/dev/villagertradelimiter/nms/NbtApiException.java b/src/com/pretzel/dev/villagertradelimiter/nms/NbtApiException.java new file mode 100644 index 0000000..c6336ff --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/NbtApiException.java @@ -0,0 +1,56 @@ +package com.pretzel.dev.villagertradelimiter.nms; + +/** + * 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; + + /** + * + */ + public NbtApiException() { + super(); + } + + /** + * @param message + * @param cause + * @param enableSuppression + * @param writableStackTrace + */ + public NbtApiException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + /** + * @param message + * @param cause + */ + public NbtApiException(String message, Throwable cause) { + super(message, cause); + } + + /** + * @param message + */ + public NbtApiException(String message) { + super(message); + } + + /** + * @param cause + */ + public NbtApiException(Throwable cause) { + super(cause); + } + +} diff --git a/src/com/pretzel/dev/villagertradelimiter/nms/utils/ApiMetricsLite.java b/src/com/pretzel/dev/villagertradelimiter/nms/utils/ApiMetricsLite.java new file mode 100644 index 0000000..3ddfa97 --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/utils/ApiMetricsLite.java @@ -0,0 +1,388 @@ +package com.pretzel.dev.villagertradelimiter.nms.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; + +import static com.pretzel.dev.villagertradelimiter.nms.utils.MinecraftVersion.getLogger; + +/** + * 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) { + 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) { + 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) { + 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) { + 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) { + System.out.println("[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) { + 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/com/pretzel/dev/villagertradelimiter/nms/utils/GsonWrapper.java b/src/com/pretzel/dev/villagertradelimiter/nms/utils/GsonWrapper.java new file mode 100644 index 0000000..847228c --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/utils/GsonWrapper.java @@ -0,0 +1,54 @@ +package com.pretzel.dev.villagertradelimiter.nms.utils; + +import com.google.gson.Gson; + +import com.pretzel.dev.villagertradelimiter.nms.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/com/pretzel/dev/villagertradelimiter/nms/utils/MinecraftVersion.java b/src/com/pretzel/dev/villagertradelimiter/nms/utils/MinecraftVersion.java new file mode 100644 index 0000000..f868455 --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/utils/MinecraftVersion.java @@ -0,0 +1,210 @@ +package com.pretzel.dev.villagertradelimiter.nms.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); + + private static MinecraftVersion version; + private static Boolean hasGsonSupport; + 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.9.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; + } + + public String getPackageName() { + if(this == UNKNOWN) { + return values()[values().length-1].name().replace("MC", "v"); + } + 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] Wasn't able to find NMS Support! 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!", ex); + } + }).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; + } + + /** + * 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/com/pretzel/dev/villagertradelimiter/nms/utils/ReflectionUtil.java b/src/com/pretzel/dev/villagertradelimiter/nms/utils/ReflectionUtil.java new file mode 100644 index 0000000..d94bef9 --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/utils/ReflectionUtil.java @@ -0,0 +1,53 @@ +package com.pretzel.dev.villagertradelimiter.nms.utils; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import com.pretzel.dev.villagertradelimiter.nms.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/com/pretzel/dev/villagertradelimiter/nms/utils/VersionChecker.java b/src/com/pretzel/dev/villagertradelimiter/nms/utils/VersionChecker.java new file mode 100644 index 0000000..67f40dd --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/utils/VersionChecker.java @@ -0,0 +1,106 @@ +package com.pretzel.dev.villagertradelimiter.nms.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 com.pretzel.dev.villagertradelimiter.nms.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"; + + 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 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 nbt-api or the plugin that contains the api!"); + + } else if (versionDifference == 0) { + MinecraftVersion.getLogger().log(Level.INFO, "[NBTAPI] The NBT-API seems to be up-to-date!"); + } else if (versionDifference == 1) { + MinecraftVersion.getLogger().log(Level.WARNING, "[NBTAPI] The NBT-API at '" + NBTItem.class.getPackage() + + "' seems to be a future Version, not yet released on Spigot/CurseForge!"); + MinecraftVersion.getLogger().log(Level.WARNING, "[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/com/pretzel/dev/villagertradelimiter/nms/utils/annotations/AvailableSince.java b/src/com/pretzel/dev/villagertradelimiter/nms/utils/annotations/AvailableSince.java new file mode 100644 index 0000000..c638ae2 --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/utils/annotations/AvailableSince.java @@ -0,0 +1,17 @@ +package com.pretzel.dev.villagertradelimiter.nms.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 com.pretzel.dev.villagertradelimiter.nms.utils.MinecraftVersion; + +@Retention(RUNTIME) +@Target({ METHOD }) +public @interface AvailableSince { + + MinecraftVersion version(); + +} \ No newline at end of file diff --git a/src/com/pretzel/dev/villagertradelimiter/nms/utils/annotations/CheckUtil.java b/src/com/pretzel/dev/villagertradelimiter/nms/utils/annotations/CheckUtil.java new file mode 100644 index 0000000..b0efa0c --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/utils/annotations/CheckUtil.java @@ -0,0 +1,16 @@ +package com.pretzel.dev.villagertradelimiter.nms.utils.annotations; + +import java.lang.reflect.Method; + +import com.pretzel.dev.villagertradelimiter.nms.NbtApiException; +import com.pretzel.dev.villagertradelimiter.nms.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/com/pretzel/dev/villagertradelimiter/nms/utils/nmsmappings/ClassWrapper.java b/src/com/pretzel/dev/villagertradelimiter/nms/utils/nmsmappings/ClassWrapper.java new file mode 100644 index 0000000..e6f9f85 --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/utils/nmsmappings/ClassWrapper.java @@ -0,0 +1,103 @@ +package com.pretzel.dev.villagertradelimiter.nms.utils.nmsmappings; + +import static com.pretzel.dev.villagertradelimiter.nms.utils.MinecraftVersion.getLogger; + +import java.util.logging.Level; + +import com.pretzel.dev.villagertradelimiter.nms.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_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 { + 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/com/pretzel/dev/villagertradelimiter/nms/utils/nmsmappings/MojangToMapping.java b/src/com/pretzel/dev/villagertradelimiter/nms/utils/nmsmappings/MojangToMapping.java new file mode 100644 index 0000000..115b8aa --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/utils/nmsmappings/MojangToMapping.java @@ -0,0 +1,80 @@ +package com.pretzel.dev.villagertradelimiter.nms.utils.nmsmappings; + +import java.util.HashMap; +import java.util.Map; + +/** + * 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_"); + } + + }; + + public static Map getMapping(){ + return MC1_18R1; + } + +} diff --git a/src/com/pretzel/dev/villagertradelimiter/nms/utils/nmsmappings/ObjectCreator.java b/src/com/pretzel/dev/villagertradelimiter/nms/utils/nmsmappings/ObjectCreator.java new file mode 100644 index 0000000..0c60fd0 --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/utils/nmsmappings/ObjectCreator.java @@ -0,0 +1,56 @@ +package com.pretzel.dev.villagertradelimiter.nms.utils.nmsmappings; + +import java.lang.reflect.Constructor; +import java.util.logging.Level; + +import com.pretzel.dev.villagertradelimiter.nms.NbtApiException; +import com.pretzel.dev.villagertradelimiter.nms.utils.MinecraftVersion; + +import static com.pretzel.dev.villagertradelimiter.nms.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/com/pretzel/dev/villagertradelimiter/nms/utils/nmsmappings/PackageWrapper.java b/src/com/pretzel/dev/villagertradelimiter/nms/utils/nmsmappings/PackageWrapper.java new file mode 100644 index 0000000..7779c4a --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/utils/nmsmappings/PackageWrapper.java @@ -0,0 +1,29 @@ +package com.pretzel.dev.villagertradelimiter.nms.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/com/pretzel/dev/villagertradelimiter/nms/utils/nmsmappings/ReflectionMethod.java b/src/com/pretzel/dev/villagertradelimiter/nms/utils/nmsmappings/ReflectionMethod.java new file mode 100644 index 0000000..22640e0 --- /dev/null +++ b/src/com/pretzel/dev/villagertradelimiter/nms/utils/nmsmappings/ReflectionMethod.java @@ -0,0 +1,225 @@ +package com.pretzel.dev.villagertradelimiter.nms.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 com.pretzel.dev.villagertradelimiter.nms.NbtApiException; +import com.pretzel.dev.villagertradelimiter.nms.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; + if(!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(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){ + System.out.println("[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; + } + } + +}