From 47982a6e2876e8536713139a9c0c72fbac611b00 Mon Sep 17 00:00:00 2001 From: Jowcey Date: Mon, 13 Dec 2021 16:37:00 +0000 Subject: [PATCH] New Features + 1.18 Update --- .../VillagerTradeLimiter.jar | Bin 0 -> 145565 bytes pom.xml | 16 + .../listeners/PlayerListener.java | 199 +++-- .../villagertradelimiter/nms/CraftEntity.java | 23 - .../villagertradelimiter/nms/NBTCompound.java | 839 ++++++++++++++++++ .../nms/NBTCompoundList.java | 106 +++ .../nms/NBTContainer.java | 112 ++- .../nms/NBTDoubleList.java | 45 + .../villagertradelimiter/nms/NBTEntity.java | 54 ++ .../dev/villagertradelimiter/nms/NBTFile.java | 80 ++ .../nms/NBTFloatList.java | 45 + .../nms/NBTIntegerList.java | 45 + .../dev/villagertradelimiter/nms/NBTItem.java | 176 ++++ .../dev/villagertradelimiter/nms/NBTList.java | 429 +++++++++ .../nms/NBTListCompound.java | 42 + .../villagertradelimiter/nms/NBTLongList.java | 45 + .../nms/NBTPersistentDataContainer.java | 31 + .../nms/NBTReflectionUtil.java | 625 +++++++++++++ .../nms/NBTStringList.java | 42 + .../nms/NBTTagCompound.java | 71 -- .../villagertradelimiter/nms/NBTTagList.java | 41 - .../nms/NBTTileEntity.java | 64 ++ .../dev/villagertradelimiter/nms/NBTType.java | 48 + .../dev/villagertradelimiter/nms/NMS.java | 82 -- .../villagertradelimiter/nms/NMSEntity.java | 38 - .../nms/NbtApiException.java | 56 ++ .../nms/utils/ApiMetricsLite.java | 388 ++++++++ .../nms/utils/GsonWrapper.java | 54 ++ .../nms/utils/MinecraftVersion.java | 210 +++++ .../nms/utils/ReflectionUtil.java | 53 ++ .../nms/utils/VersionChecker.java | 106 +++ .../nms/utils/annotations/AvailableSince.java | 17 + .../nms/utils/annotations/CheckUtil.java | 16 + .../nms/utils/nmsmappings/ClassWrapper.java | 103 +++ .../utils/nmsmappings/MojangToMapping.java | 80 ++ .../nms/utils/nmsmappings/ObjectCreator.java | 56 ++ .../nms/utils/nmsmappings/PackageWrapper.java | 29 + .../utils/nmsmappings/ReflectionMethod.java | 225 +++++ 38 files changed, 4327 insertions(+), 364 deletions(-) create mode 100644 out/artifacts/VillagerTradeLimiter_jar/VillagerTradeLimiter.jar delete mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/CraftEntity.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NBTCompound.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NBTCompoundList.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NBTDoubleList.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NBTEntity.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NBTFile.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NBTFloatList.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NBTIntegerList.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NBTItem.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NBTList.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NBTListCompound.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NBTLongList.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NBTPersistentDataContainer.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NBTReflectionUtil.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NBTStringList.java delete mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NBTTagCompound.java delete mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NBTTagList.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NBTTileEntity.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NBTType.java delete mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NMS.java delete mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NMSEntity.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/NbtApiException.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/utils/ApiMetricsLite.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/utils/GsonWrapper.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/utils/MinecraftVersion.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/utils/ReflectionUtil.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/utils/VersionChecker.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/utils/annotations/AvailableSince.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/utils/annotations/CheckUtil.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/utils/nmsmappings/ClassWrapper.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/utils/nmsmappings/MojangToMapping.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/utils/nmsmappings/ObjectCreator.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/utils/nmsmappings/PackageWrapper.java create mode 100644 src/com/pretzel/dev/villagertradelimiter/nms/utils/nmsmappings/ReflectionMethod.java diff --git a/out/artifacts/VillagerTradeLimiter_jar/VillagerTradeLimiter.jar b/out/artifacts/VillagerTradeLimiter_jar/VillagerTradeLimiter.jar new file mode 100644 index 0000000000000000000000000000000000000000..d936cb196a57cb00e4cedcce9ea94e0570a097ba GIT binary patch literal 145565 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%0AZ) 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%0A{5Z7MyH;kx4z5bV| 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 literal 0 HcmV?d00001 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; + } + } + +}