diff --git a/Essentials/src/com/earth2me/essentials/Essentials.java b/Essentials/src/com/earth2me/essentials/Essentials.java index d49761f49..09c90abc9 100644 --- a/Essentials/src/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/com/earth2me/essentials/Essentials.java @@ -34,9 +34,12 @@ import com.google.common.collect.Iterables; import net.ess3.api.*; import net.ess3.api.IEssentials; import net.ess3.api.ISettings; +import net.ess3.nms.ItemDbProvider; import net.ess3.nms.PotionMetaProvider; import net.ess3.nms.SpawnEggProvider; import net.ess3.nms.SpawnerProvider; +import net.ess3.nms.flattened.FlatItemDbProvider; +import net.ess3.nms.legacy.LegacyItemDbProvider; import net.ess3.nms.legacy.LegacyPotionMetaProvider; import net.ess3.nms.refl.ReflSpawnEggProvider; import net.ess3.nms.updatedmeta.BasePotionDataProvider; @@ -103,6 +106,7 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { private transient EssentialsTimer timer; private final transient Set vanishedPlayers = new LinkedHashSet<>(); private transient Method oldGetOnlinePlayers; + private transient ItemDbProvider itemDbProvider; private transient SpawnerProvider spawnerProvider; private transient SpawnEggProvider spawnEggProvider; private transient PotionMetaProvider potionMetaProvider; @@ -211,6 +215,12 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { execTimer.mark("Init(Worth/ItemDB)"); jails = new Jails(this); confList.add(jails); + + itemDbProvider = new ProviderFactory<>(getLogger(), + Arrays.asList( + FlatItemDbProvider.class, + LegacyItemDbProvider.class + ), "item database").getProvider(); spawnerProvider = new ProviderFactory<>(getLogger(), Arrays.asList( BlockMetaSpawnerProvider.class, diff --git a/nms/FlattenedProvider/src/net/ess3/nms/flattened/FlatItemDbProvider.java b/nms/FlattenedProvider/src/net/ess3/nms/flattened/FlatItemDbProvider.java new file mode 100644 index 000000000..77f396edd --- /dev/null +++ b/nms/FlattenedProvider/src/net/ess3/nms/flattened/FlatItemDbProvider.java @@ -0,0 +1,75 @@ +package net.ess3.nms.flattened; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonParser; +import net.ess3.nms.ItemDbProvider; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionType; + +import java.util.List; +import java.util.stream.Collectors; + +public class FlatItemDbProvider extends ItemDbProvider { + private static Gson gson = new Gson(); + + + @Override + public Material resolve(String name) { + return null; + } + + @Override + public boolean supportsLegacyIds() { + return false; + } + + @Override + public int getLegacyId(Material material) { + return -1; + } + + @Override + public Material getFromLegacyId(int id) { + return null; + } + + @Override + public String getPrimaryName(ItemStack item) { + return null; + } + + @Override + public List getNames(ItemStack item) { + return null; + } + + @Override + public void rebuild(List lines) { + String json = lines.stream().collect(Collectors.joining("\n")); + JsonArray jsonArray = (new JsonParser()).parse(json).getAsJsonArray(); + jsonArray.forEach(element -> { + if (element.isJsonObject()) { + } + }); + } + + @Override + public boolean tryProvider() { + // Build the database initially so that we can actually test the provider + this.rebuild(this.loadResource("/items.json")); + return super.tryProvider(); + } + + @Override + public String getHumanName() { + return "Post-1.13 item database provider"; + } + + private class MaterialData { + private Material material; + private PotionType potionEnum; + private String potionModifier; + } +} diff --git a/nms/IdProvider/pom.xml b/nms/IdProvider/pom.xml new file mode 100644 index 000000000..3756f2e7f --- /dev/null +++ b/nms/IdProvider/pom.xml @@ -0,0 +1,32 @@ + + + + EssentialsXParent + net.ess3 + 2.15.0 + ../../pom.xml + + 4.0.0 + + IdProvider + + + + net.ess3 + NMSProvider + 2.15.0 + + + net.ess3 + ReflectionProvider + 2.15.0 + + + org.bukkit + bukkit + 1.12.2-R0.1-SNAPSHOT + + + \ No newline at end of file diff --git a/nms/IdProvider/src/net/ess3/nms/ids/LegacyItemDbProvider.java b/nms/IdProvider/src/net/ess3/nms/ids/LegacyItemDbProvider.java new file mode 100644 index 000000000..4a47ccbc1 --- /dev/null +++ b/nms/IdProvider/src/net/ess3/nms/ids/LegacyItemDbProvider.java @@ -0,0 +1,261 @@ +package net.ess3.nms.ids; + +import net.ess3.nms.ItemDbProvider; +import net.ess3.nms.PotionMetaProvider; +import net.ess3.nms.SpawnEggProvider; +import net.ess3.nms.refl.ReflUtil; +import org.apache.commons.lang.StringUtils; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.entity.EntityType; +import org.bukkit.inventory.ItemStack; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class LegacyItemDbProvider extends ItemDbProvider { + private final transient Map items = new HashMap<>(); + private final transient Map> names = new HashMap<>(); + private final transient Map primaryNames = new HashMap<>(); + private final transient Map legacyIds = new HashMap<>(); + private final transient Map durabilities = new HashMap<>(); + private final transient Map nbtData = new HashMap<>(); + + private final transient Pattern splitPattern = Pattern.compile("((.*)[:+',;.](\\d+))"); + private final transient Pattern csvSplitPattern = Pattern.compile("(\"([^\"]*)\"|[^,]*)(,|$)"); + + @Override + public Material resolve(String name) { + return null; + } + + @Override + public ItemStack getStack(String name) throws Exception { + int itemid = 0; + String itemname; + short metaData = 0; + + Matcher parts = splitPattern.matcher(name); + if (parts.matches()) { + itemname = parts.group(2); + metaData = Short.parseShort(parts.group(3)); + } else { + itemname = name; + } + + if (isInt(itemname)) { + itemid = Integer.parseInt(itemname); + } else if (isInt(name)) { + itemid = Integer.parseInt(name); + } else { + itemname = itemname.toLowerCase(Locale.ENGLISH); + } + + if (itemid < 1) { + if (items.containsKey(itemname)) { + itemid = items.get(itemname); + if (durabilities.containsKey(itemname) && metaData == 0) { + metaData = durabilities.get(itemname); + } + } + } + + if (itemid < 1) { + throw new Exception("Unknown item name " + itemname); + } + + ItemData data = legacyIds.get(itemid); + if (data == null) { + throw new Exception("Unknown item ID " + itemid); + } + + Material mat = data.getMaterial(); + ItemStack retval = new ItemStack(mat); + if (nbtData.containsKey(itemname)) { + String nbt = nbtData.get(itemname); + if (nbt.startsWith("*")) { + nbt = nbtData.get(nbt.substring(1)); + } + retval = Bukkit.getServer().getUnsafe().modifyItemStack(retval, nbt); + } + + + Material MOB_SPAWNER; + try { + MOB_SPAWNER = Material.valueOf("SPAWNER"); + } catch (Exception e) { + MOB_SPAWNER = Material.valueOf("MOB_SPAWNER"); + } + if (mat == MOB_SPAWNER) { + if (metaData == 0) metaData = EntityType.PIG.getTypeId(); + try { + retval = getSpawnerProvider().setEntityType(retval, EntityType.fromId(metaData)); + } catch (IllegalArgumentException e) { + throw new Exception("Can't spawn entity ID " + metaData + " from mob spawners."); + } + } else if (mat == Material.MONSTER_EGG) { + EntityType type; + try { + type = EntityType.fromId(metaData); + } catch (IllegalArgumentException e) { + throw new Exception("Can't spawn entity ID " + metaData + " from spawn eggs."); + } + retval = getSpawnEggProvider().createEggItem(type); + } else if (mat.name().endsWith("POTION") + && ReflUtil.getNmsVersionObject().isLowerThan(ReflUtil.V1_11_R1)) { // Only apply this to pre-1.11 as items.csv might only work in 1.11 + retval = getPotionMetaProvider().createPotionItem(mat, metaData); + } else { + retval.setDurability(metaData); + } + retval.setAmount(mat.getMaxStackSize()); + return retval; + } + + @Override + public boolean supportsLegacyIds() { + return true; + } + + @Override + public int getLegacyId(Material material) throws Exception { + for (Map.Entry entry : items.entrySet()) { + if (material.name().toLowerCase(Locale.ENGLISH).equalsIgnoreCase(entry.getKey())) { + return entry.getValue(); + } + } + + throw new Exception("Item ID missing for material " + material.name()); + } + + @Override + public Material getFromLegacyId(int id) { + ItemData data = this.legacyIds.get(id); + if (data == null) { + return null; + } + + return data.getMaterial(); + } + + @Override + public String getPrimaryName(ItemStack item) { + return null; + } + + @Override + public List getNames(ItemStack item) { + return null; + } + + @Override + public void rebuild(List lines) { + durabilities.clear(); + items.clear(); + names.clear(); + primaryNames.clear(); + + lines.stream() + .filter(line -> line.length() > 0 && !(line.charAt(0) == '#')) + .map(this::parseLine) + .filter(itemData -> itemData != null) + .forEach(this::addItem); + + for (List nameList : names.values()) { + nameList.sort(LengthCompare.INSTANCE); + } + } + + private ItemData parseLine(String line) { + String itemName = null; + int numeric = -1; + short data = 0; + String nbt = null; + + int col = 0; + Matcher matcher = csvSplitPattern.matcher(line); + while (matcher.find()) { + String match = matcher.group(1); + if (StringUtils.stripToNull(match) == null) { + continue; + } + match = StringUtils.strip(match.trim(), "\""); + switch (col) { + case 0: + itemName = match.toLowerCase(Locale.ENGLISH); + break; + case 1: + numeric = Integer.parseInt(match); + break; + case 2: + data = Short.parseShort(match); + break; + case 3: + nbt = StringUtils.stripToNull(match); + break; + default: + continue; + } + col++; + } + // Invalid row + if (itemName == null || numeric < 0) { + return null; + } + + Material material = Material.matchMaterial(itemName); + if (material == null) { + return null; + } + + return new ItemData(itemName, material, numeric, data, nbt); + } + + private void addItem(ItemData itemData) { + final String name = itemData.getItemName(); + final int numeric = itemData.getItemNo(); + final short data = itemData.getItemData(); + final String nbt = itemData.getNbt(); + + durabilities.put(name, data); + items.put(name, numeric); + + if (nbt != null) { + nbtData.put(itemData.getItemName(), nbt); + } + + if (names.containsKey(itemData)) { + List nameList = names.get(itemData); + nameList.add(name); + } else { + List nameList = new ArrayList<>(); + nameList.add(name); + names.put(itemData, nameList); + primaryNames.put(itemData, name); + } + + legacyIds.put(numeric, itemData); + } + + @Override + public boolean tryProvider() { + // Build the database initially so that we can actually test the provider + this.rebuild(this.loadResource("/items.csv")); + return super.tryProvider(); + } + + @Override + public String getHumanName() { + return "Pre-1.13 item database provider"; + } + + private boolean isInt(String integer) { + try { + Integer.parseInt(integer); + return true; + } catch (NumberFormatException e) { + return false; + } + } +} diff --git a/nms/NMSProvider/src/net/ess3/nms/ItemDbProvider.java b/nms/NMSProvider/src/net/ess3/nms/ItemDbProvider.java new file mode 100644 index 000000000..9f643be02 --- /dev/null +++ b/nms/NMSProvider/src/net/ess3/nms/ItemDbProvider.java @@ -0,0 +1,240 @@ +package net.ess3.nms; + +import net.ess3.providers.Provider; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionType; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.List; +import java.util.stream.Collectors; + +public abstract class ItemDbProvider implements Provider { + + private SpawnerProvider spawnerProvider; + private SpawnEggProvider spawnEggProvider; + private PotionMetaProvider potionMetaProvider; + + /** + * Resolves a material name to its corresponding Material + * + * @param name The material name to look up + * @return The corresponding Material for the given name + */ + public abstract Material resolve(String name); + + /** + * Whether the provider supports legacy ID values or not. + * + * @return True if the provider supports legacy IDs, otherwise false + */ + public abstract boolean supportsLegacyIds(); + + /** + * Get the legacy ID for the material. + * + * @param material The material to look up + * @return The ID corresponding to the material, or null if not supported + */ + public abstract int getLegacyId(Material material) throws Exception; + + /** + * Get the material for the legacy ID. + * + * @param id The ID to look up + * @return The material corresponding to the ID, or -1 if not supported + */ + public abstract Material getFromLegacyId(int id); + + /** + * Get the primary name for the item in the given stack. + * + * @param item The ItemStack to check + * @return The primary name for the item + */ + public abstract String getPrimaryName(ItemStack item); + + /** + * Get all names for the item in the given stack. + * + * @param item The ItemStack to check + * @return The names for the item + */ + public abstract List getNames(ItemStack item); + + /** + * Rebuild the item database, using the given lines of a file. + * + * @param lines The lines of the file from which the database should be built. + */ + public abstract void rebuild(List lines); + + /** + * Creates a stack of a given item by its name. + * + * @param name The material name to look up + * @return An ItemStack of size 1 of the given item + */ + public ItemStack getStack(String name) throws Exception { + return new ItemStack(resolve(name)); + } + + /** + * Creates a stack with the given amount of a given item by its name. + * + * @param name The material name to look up + * @param amount The amount of items in the returned ItemStack + * @return An ItemStack with the given amount of the given item + */ + public ItemStack getStack(String name, int amount) throws Exception { + ItemStack is = getStack(name); + is.setAmount(amount); + return is; + } + + /** + * Read a resource from the jar. + * Used to build the database before data from a ManagedFile is available. + * + * @param name The name of the resource to load. + * @return The lines of the resource. + */ + protected List loadResource(final String name) { + try (InputStreamReader isr = new InputStreamReader(ItemDbProvider.class.getResourceAsStream(name))) { + BufferedReader br = new BufferedReader(isr); + return br.lines().collect(Collectors.toList()); + } catch (IOException e) { + return null; + } + } + + @Override + public boolean tryProvider() { + try { + getStack("cstone"); + getStack("diorite"); + getStack("steelbar", 5); + getStack("aoepot"); + getStack("skeletonegg", 12); + return true; + } catch (Exception e) { + return false; + } + } + + protected SpawnerProvider getSpawnerProvider() { + return spawnerProvider; + } + + public void setSpawnerProvider(SpawnerProvider spawnerProvider) { + this.spawnerProvider = spawnerProvider; + } + + protected SpawnEggProvider getSpawnEggProvider() { + return spawnEggProvider; + } + + public void setSpawnEggProvider(SpawnEggProvider spawnEggProvider) { + this.spawnEggProvider = spawnEggProvider; + } + + protected PotionMetaProvider getPotionMetaProvider() { + return potionMetaProvider; + } + + public void setPotionMetaProvider(PotionMetaProvider potionMetaProvider) { + this.potionMetaProvider = potionMetaProvider; + } + + public static class ItemData { + final private String itemName; + final private Material material; + private int legacyId; + private short itemData; + final private String nbt; + private PotionData potionData; + + public ItemData(String itemName, Material material, String nbt) { + this.itemName = itemName; + this.material = material; + this.nbt = nbt; + } + + public ItemData(String itemName, Material material, String nbt, PotionData potionData) { + this.itemName = itemName; + this.material = material; + this.nbt = nbt; + this.potionData = potionData; + } + + @Deprecated + public ItemData(String itemName, Material material, final int legacyId, final short itemData, String nbt) { + this.itemName = itemName; + this.material = material; + this.legacyId = legacyId; + this.itemData = itemData; + this.nbt = nbt; + } + + public String getItemName() { + return itemName; + } + + public Material getMaterial() { + return material; + } + + @Deprecated + public int getItemNo() { + return legacyId; + } + + public short getItemData() { + return itemData; + } + + public String getNbt() { + return nbt; + } + + @Override + public int hashCode() { + return (31 * material.hashCode()) ^ itemData; + } + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (!(o instanceof ItemData)) { + return false; + } + ItemData pairo = (ItemData) o; + return this.material == pairo.getMaterial() && this.itemData == pairo.getItemData() && this.nbt.equals(pairo.getNbt()); + } + } + + public static class PotionData { + private PotionType bukkitType; + private String vanillaType; + private boolean isStrong; + private boolean isLong; + } + + protected static class LengthCompare implements java.util.Comparator { + + public static final LengthCompare INSTANCE = new LengthCompare(); + + public LengthCompare() { + super(); + } + + @Override + public int compare(String s1, String s2) { + return s1.length() - s2.length(); + } + } +} diff --git a/pom.xml b/pom.xml index d8faeff12..c4374ac32 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,7 @@ nms/1_8_R2Provider nms/LegacyProvider nms/ReflectionProvider + nms/IdProvider nms/FlattenedProvider