diff --git a/.gitignore b/.gitignore index 84cfec0c..db4a5cb2 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,4 @@ Icon Network Trash Folder Temporary Items .apdisk +/classes/ diff --git a/src/main/java/codersafterdark/reskillable/api/data/ItemInfo.java b/src/main/java/codersafterdark/reskillable/api/data/ItemInfo.java index 4902b720..c852592f 100644 --- a/src/main/java/codersafterdark/reskillable/api/data/ItemInfo.java +++ b/src/main/java/codersafterdark/reskillable/api/data/ItemInfo.java @@ -60,4 +60,13 @@ public Item getItem() { public int getMetadata() { return metadata; } + + @Override + public String toString() { + return "ItemInfo{" + + "metadata=" + metadata + + ", item=" + item + + ", tag=" + tag + + '}'; + } } diff --git a/src/main/java/codersafterdark/reskillable/base/LevelLockHandler.java b/src/main/java/codersafterdark/reskillable/base/LevelLockHandler.java index 44955bdd..d25a4da7 100644 --- a/src/main/java/codersafterdark/reskillable/base/LevelLockHandler.java +++ b/src/main/java/codersafterdark/reskillable/base/LevelLockHandler.java @@ -1,5 +1,6 @@ package codersafterdark.reskillable.base; +import codersafterdark.reskillable.Reskillable; import codersafterdark.reskillable.api.ReskillableAPI; import codersafterdark.reskillable.api.data.*; import codersafterdark.reskillable.base.configs.ConfigHandler; @@ -29,57 +30,34 @@ import net.minecraftforge.event.world.BlockEvent.BreakEvent; import net.minecraftforge.fml.common.eventhandler.Event; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -import net.minecraftforge.oredict.OreDictionary; import org.apache.logging.log4j.Level; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.*; public class LevelLockHandler { - public static final String[] DEFAULT_SKILL_LOCKS = new String[]{"minecraft:iron_shovel:*=reskillable:gathering|5", "minecraft:iron_axe:*=reskillable:gathering|5", "minecraft:iron_sword:*=reskillable:attack|5", "minecraft:iron_pickaxe:*=reskillable:mining|5", "minecraft:iron_hoe:*=reskillable:farming|5", "minecraft:iron_helmet:*=reskillable:defense|5", "minecraft:iron_chestplate:*=reskillable:defense|5", "minecraft:iron_leggings:*=reskillable:defense|5", "minecraft:iron_boots:*=reskillable:defense|5", "minecraft:golden_shovel:*=reskillable:gathering|5,reskillable:magic|5", "minecraft:golden_axe:*=reskillable:gathering|5,reskillable:magic|5", "minecraft:golden_sword:*=reskillable:attack|5,reskillable:magic|5", "minecraft:golden_pickaxe:*=reskillable:mining|5,reskillable:magic|5", "minecraft:golden_hoe:*=reskillable:farming|5,reskillable:magic|5", "minecraft:golden_helmet:*=reskillable:defense|5,reskillable:magic|5", "minecraft:golden_chestplate:*=reskillable:defense|5,reskillable:magic|5", "minecraft:golden_leggings:*=reskillable:defense|5,reskillable:magic|5", "minecraft:golden_boots:*=reskillable:defense|5,reskillable:magic|5", "minecraft:diamond_shovel:*=reskillable:gathering|16", "minecraft:diamond_axe:*=reskillable:gathering|16", "minecraft:diamond_sword:*=reskillable:attack|16", "minecraft:diamond_pickaxe:*=reskillable:mining|16", "minecraft:diamond_hoe:*=reskillable:farming|16", "minecraft:diamond_helmet:*=reskillable:defense|16", "minecraft:diamond_chestplate:*=reskillable:defense|16", "minecraft:diamond_leggings:*=reskillable:defense|16", "minecraft:diamond_boots:*=reskillable:defense|16", "minecraft:shears:*=reskillable:farming|5,reskillable:gathering|5", "minecraft:fishing_rod:*=reskillable:gathering|8", "minecraft:shield:*=reskillable:defense|8", "minecraft:bow:*=reskillable:attack|8", "minecraft:ender_pearl=reskillable:magic|8", "minecraft:ender_eye=reskillable:magic|16,reskillable:building|8", "minecraft:elytra:*=reskillable:defense|16,reskillable:agility|24,reskillable:magic|16", "minecraft:lead=reskillable:farming|5", "minecraft:end_crystal=reskillable:building|24,reskillable:magic|32", "minecraft:iron_horse_armor:*=reskillable:defense|5,reskillable:agility|5", "minecraft:golden_horse_armor:*=reskillable:defense|5,reskillable:magic|5,reskillable:agility|5", "minecraft:diamond_horse_armor:*=reskillable:defense|16,reskillable:agility|16", "minecraft:fireworks=reskillable:agility|24", "minecraft:dye:15=reskillable:farming|12", "minecraft:saddle=reskillable:agility|12", "minecraft:redstone=reskillable:building|5", "minecraft:redstone_torch=reskillable:building|5", "minecraft:skull:1=reskillable:building|20,reskillable:attack|20,reskillable:defense|20"}; private static final Map locks = new HashMap<>(); //This should stay private to ensure that it is added to correctly public static RequirementHolder EMPTY_LOCK = new RequirementHolder(); private static Map, List>> lockTypesMap = new HashMap<>(); private static Map> fuzzyLockInfo = new HashMap<>(); - private static String[] configLocks; + private static Map> lockNameMap = new HashMap<>(); - public static void loadFromConfig(String[] configValues) { - configLocks = configValues; + static { + registerLockKeyName("itemstack", ItemInfo.class); + registerLockKeyName("mod", ModLockKey.class); + registerLockKeyName("nbt", GenericNBTLockKey.class); } public static void setupLocks() { registerDefaultLockKeys(); - if (configLocks != null) { - for (String s : configLocks) { - String[] tokens = s.split("="); - if (tokens.length == 2) { - String itemName = tokens[0].toLowerCase(); - String[] itemParts = itemName.split(":"); - if (itemParts.length == 1) { - addModLock(itemName, RequirementHolder.fromString(tokens[1])); //itemName is really the mod name - continue; - } - int metadata = 0; - if (itemParts.length > 2) { - String meta = itemParts[2]; - try { - if (meta.equals("*")) { - metadata = OreDictionary.WILDCARD_VALUE; - } else { - metadata = Integer.parseInt(meta); - } - itemName = itemParts[0] + ':' + itemParts[1]; - } catch (NumberFormatException ignored) { - //Do nothing if the meta is not a valid number or wildcard (Maybe it somehow is part of the item name) - } - } - Item item = Item.getByNameOrId(itemName); - if (item != null) { - addLock(new ItemStack(item, 1, metadata), RequirementHolder.fromString(tokens[1])); - } - } - } + + try { + ConfigHandler.loadJSONLocks(); + } catch (IOException e) { + Reskillable.logger.error("Failed to load jsons", e); } + } private static void registerDefaultLockKeys() { @@ -109,6 +87,23 @@ public static void registerLockKey(Class lockTypeClass, Class lockClass) { + lockNameMap.put(name, lockClass); + } + + public static Class getLockKeyClass(String name) { + Class key = lockNameMap.get(name); + if (key == null) { + try { + key = (Class) Class.forName(name); + } catch (ClassNotFoundException | ClassCastException e) { + Reskillable.logger.error("Failed to load LockKeyClass: ", e); + } + } + + return key; + } + /** * Adds locks to the given key. * diff --git a/src/main/java/codersafterdark/reskillable/base/configs/ConfigHandler.java b/src/main/java/codersafterdark/reskillable/base/configs/ConfigHandler.java index 9f979afd..dae2f1ef 100644 --- a/src/main/java/codersafterdark/reskillable/base/configs/ConfigHandler.java +++ b/src/main/java/codersafterdark/reskillable/base/configs/ConfigHandler.java @@ -1,52 +1,55 @@ package codersafterdark.reskillable.base.configs; +import codersafterdark.reskillable.Reskillable; import codersafterdark.reskillable.base.LevelLockHandler; +import codersafterdark.reskillable.base.configs.json.LockJson; +import codersafterdark.reskillable.base.configs.json.LockTypeJsonFactory; import codersafterdark.reskillable.lib.LibMisc; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.config.Configuration; import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.HashMap; +import java.util.List; import java.util.Map; import static codersafterdark.reskillable.base.configs.ConfigUtilities.loadPropBool; public class ConfigHandler { - ///////////////// - // Directories // - ///////////////// - private static File configDir; - private static File jsonDir; - - ///////////// // Configs // ///////////// public static Configuration mainConfig; - public static Gson locks; - - //////////////////// - // Default Values // - //////////////////// /// Main Config /// public static boolean disableSheepWool = true; public static boolean enforceFakePlayers = true; + + //////////////////// + // Default Values // + //////////////////// public static boolean enableTabs = true; public static boolean enableLevelUp = true; public static boolean hideRequirements = true; - public static Map cachedConfigs = new HashMap<>(); + ///////////////// + // Directories // + ///////////////// + private static File configDir; + private static File jsonDir; public static void init(File file) { generateFolder(file); mainConfig = new Configuration(new File(configDir.getPath(), "reskillable.cfg")); mainConfig.load(); loadData(); - loadJSONLocks(); cachedConfigs.put(LibMisc.MOD_ID, mainConfig); MinecraftForge.EVENT_BUS.register(ConfigListener.class); } @@ -58,33 +61,42 @@ public static void loadData() { enableLevelUp = loadPropBool(mainConfig, "Enable Level-Up Button", "Set this to false to remove the level-up button if you don't want to use another means to leveling-up skills!", true); hideRequirements = loadPropBool(mainConfig, "Hide Requirements", "Set this to false to not require holding down the shift key to view requirements!", true); - String desc = "Set requirements for items in this list. Each entry is composed of the item key and the requirements\n" - + "The item key is in the simple mod:item_id format. Optionally, it can be in mod:item_id:metadata, if you want to match metadata.\n" - + "The requirements are in a comma separated list, each in a key|value format. For example, to make an iron pickaxe require 5 mining\n" - + "and 5 building, you'd use the following string:\n" - + "\"minecraft:iron_pickaxe=mining|5,building|5\"\n\n" - + "Item usage can also be locked behind an advancement, by using adv|id. For example, to make the elytra require the \"Acquire Hardware.\" advancement\n" - + "you'd use the following string:\n" - + "\"minecraft:elytra=adv|minecraft:story/smelt_iron\"\n\n" - + "Skill requirements and advancements can be mixed and matched, so you can make an item require both, if you want.\n" - + "You can also lock placed blocks from being used or broken, in the same manner.\n\n" - + "Locks defined here apply to all the following cases: Right clicking an item, placing a block, breaking a block, using a block that's placed,\n" - + "left clicking an item, using an item to break any block, and equipping an armor item.\n\n" - + "You can lock entire mods by just using their name as the left argument. You can then specify specific items to not be locked,\n" - + "by defining their lock in the normal way. If you want an item to not be locked in this way, use \"none\" after the ="; - String[] locks = mainConfig.getStringList("Skill Locks", Configuration.CATEGORY_GENERAL, LevelLockHandler.DEFAULT_SKILL_LOCKS, desc); - - LevelLockHandler.loadFromConfig(locks); - if (mainConfig.hasChanged()) { mainConfig.save(); } } - public static void loadJSONLocks(){ - File mainLocks = new File(jsonDir, "defaultLocks.json"); - String json = locks.toJson(LevelLockHandler.DEFAULT_SKILL_LOCKS); - ConfigUtilities.writeStringToFile(json, mainLocks); + public static void loadJSONLocks() throws IOException { + Reskillable.logger.info("Starting to load json"); + if (!jsonDir.exists() && jsonDir.mkdir()) { + Reskillable.logger.warn("Couldn't create json lock file directory"); + } + + File[] list = jsonDir.listFiles(); + if (list != null && list.length == 0) { + Files.copy(ConfigHandler.class.getResourceAsStream("/defaultLocks.json"), Paths.get(jsonDir.getAbsolutePath(), "defaultLocks.json")); + } + + Files.walk(jsonDir.toPath()) + .filter(Files::isRegularFile) + .filter(path -> path.toString().endsWith(".json")) + .sorted() + .forEachOrdered(path -> { + Reskillable.logger.info("Starting to load from file " + path); + + try (FileReader reader = new FileReader(path.toFile())) { + List obj = LockTypeJsonFactory.constructGSON().fromJson(reader, new TypeToken>() { + }.getType()); + Reskillable.logger.info("Locks loaded " + obj.size() + ": " + obj); + + for (LockJson lockJson : obj) { + LevelLockHandler.addLockByKey(lockJson.getLockKey(), lockJson.getRequirements()); + } + + } catch (IOException | JsonParseException e) { + Reskillable.logger.error("Couldn't load json from " + path, e); + } + }); } @@ -99,6 +111,5 @@ public static void generateFolder(File file) { } configDir = dir; jsonDir = dir2; - locks = new GsonBuilder().setPrettyPrinting().create(); } } diff --git a/src/main/java/codersafterdark/reskillable/base/configs/json/LockJson.java b/src/main/java/codersafterdark/reskillable/base/configs/json/LockJson.java new file mode 100644 index 00000000..da0bc416 --- /dev/null +++ b/src/main/java/codersafterdark/reskillable/base/configs/json/LockJson.java @@ -0,0 +1,30 @@ +package codersafterdark.reskillable.base.configs.json; + +import codersafterdark.reskillable.api.data.LockKey; +import codersafterdark.reskillable.api.data.RequirementHolder; + +public class LockJson { + private RequirementHolder requirements; + private LockKey lockKey; + + public LockJson(RequirementHolder requirements, LockKey lockKey) { + this.requirements = requirements; + this.lockKey = lockKey; + } + + public RequirementHolder getRequirements() { + return requirements; + } + + public LockKey getLockKey() { + return lockKey; + } + + @Override + public String toString() { + return "LockJson{" + + "requirements=" + requirements + + ", lockKey=" + lockKey + + '}'; + } +} diff --git a/src/main/java/codersafterdark/reskillable/base/configs/json/LockTypeJsonFactory.java b/src/main/java/codersafterdark/reskillable/base/configs/json/LockTypeJsonFactory.java new file mode 100644 index 00000000..15ec5df7 --- /dev/null +++ b/src/main/java/codersafterdark/reskillable/base/configs/json/LockTypeJsonFactory.java @@ -0,0 +1,40 @@ +package codersafterdark.reskillable.base.configs.json; + +import codersafterdark.reskillable.api.data.ItemInfo; +import codersafterdark.reskillable.base.configs.json.parsers.CustomItemInfoJson; +import codersafterdark.reskillable.base.configs.json.parsers.CustomItemJson; +import codersafterdark.reskillable.base.configs.json.parsers.CustomLockJson; +import codersafterdark.reskillable.base.configs.json.parsers.CustomNBTJson; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializer; +import net.minecraft.item.Item; +import net.minecraft.nbt.NBTTagCompound; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; + +public class LockTypeJsonFactory { + private static Map deserializerMap = new HashMap<>(); + + static { + registerDeserializer(LockJson.class, new CustomLockJson()); + registerDeserializer(Item.class, new CustomItemJson()); + registerDeserializer(ItemInfo.class, new CustomItemInfoJson()); + registerDeserializer(NBTTagCompound.class, new CustomNBTJson()); + } + + public static void registerDeserializer(Type type, JsonDeserializer deserializer) { + deserializerMap.put(type, deserializer); + } + + public static Gson constructGSON() { + GsonBuilder builder = new GsonBuilder() + .setPrettyPrinting(); + + deserializerMap.forEach(builder::registerTypeAdapter); + + return builder.create(); + } +} diff --git a/src/main/java/codersafterdark/reskillable/base/configs/json/parsers/CustomItemInfoJson.java b/src/main/java/codersafterdark/reskillable/base/configs/json/parsers/CustomItemInfoJson.java new file mode 100644 index 00000000..714c9701 --- /dev/null +++ b/src/main/java/codersafterdark/reskillable/base/configs/json/parsers/CustomItemInfoJson.java @@ -0,0 +1,30 @@ +package codersafterdark.reskillable.base.configs.json.parsers; + +import codersafterdark.reskillable.api.data.ItemInfo; +import com.google.gson.*; +import net.minecraft.item.Item; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.oredict.OreDictionary; + +import java.lang.reflect.Type; + +public class CustomItemInfoJson implements JsonDeserializer { + @Override + public ItemInfo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject obj = json.getAsJsonObject(); + JsonElement itemElem = obj.get("item"); + + if (itemElem == null) + throw new JsonParseException("No key 'item' given in ItemInfo json."); + + Item item = context.deserialize(itemElem, Item.class); + int meta = !obj.has("metadata") ? 0 : obj.get("metadata").getAsString().equals("*") ? OreDictionary.WILDCARD_VALUE : obj.get("metadata").getAsInt(); + JsonElement nbtJson = obj.get("nbt"); + NBTTagCompound tagCompound = null; + if (nbtJson != null) { + tagCompound = context.deserialize(nbtJson, NBTTagCompound.class); + } + + return new ItemInfo(item, meta, tagCompound); + } +} diff --git a/src/main/java/codersafterdark/reskillable/base/configs/json/parsers/CustomItemJson.java b/src/main/java/codersafterdark/reskillable/base/configs/json/parsers/CustomItemJson.java new file mode 100644 index 00000000..da4da5f5 --- /dev/null +++ b/src/main/java/codersafterdark/reskillable/base/configs/json/parsers/CustomItemJson.java @@ -0,0 +1,18 @@ +package codersafterdark.reskillable.base.configs.json.parsers; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import net.minecraft.item.Item; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.common.registry.ForgeRegistries; + +import java.lang.reflect.Type; + +public class CustomItemJson implements JsonDeserializer { + @Override + public Item deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + return ForgeRegistries.ITEMS.getValue(new ResourceLocation(json.getAsString())); + } +} diff --git a/src/main/java/codersafterdark/reskillable/base/configs/json/parsers/CustomLockJson.java b/src/main/java/codersafterdark/reskillable/base/configs/json/parsers/CustomLockJson.java new file mode 100644 index 00000000..c48abf85 --- /dev/null +++ b/src/main/java/codersafterdark/reskillable/base/configs/json/parsers/CustomLockJson.java @@ -0,0 +1,36 @@ +package codersafterdark.reskillable.base.configs.json.parsers; + +import codersafterdark.reskillable.api.data.LockKey; +import codersafterdark.reskillable.api.data.RequirementHolder; +import codersafterdark.reskillable.base.LevelLockHandler; +import codersafterdark.reskillable.base.configs.json.LockJson; +import com.google.gson.*; + +import java.lang.reflect.Type; + +public class CustomLockJson implements JsonDeserializer { + @Override + public LockJson deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject obj = json.getAsJsonObject(); + String type = obj.get("type").getAsString(); + Class clazz = LevelLockHandler.getLockKeyClass(type); + + + // Construct the holder + JsonArray array = obj.getAsJsonArray("requirements"); + String[] stringArray = new String[array.size()]; + for (int i = 0; i < array.size(); i++) { + stringArray[i] = array.get(i).getAsString(); + } + + RequirementHolder holder = RequirementHolder.fromStringList(stringArray); + + // Construct the LockKey + obj.remove("type"); + obj.remove("requirements"); + + LockKey key = context.deserialize(obj, clazz); + + return new LockJson(holder, key); + } +} diff --git a/src/main/java/codersafterdark/reskillable/base/configs/json/parsers/CustomNBTJson.java b/src/main/java/codersafterdark/reskillable/base/configs/json/parsers/CustomNBTJson.java new file mode 100644 index 00000000..9ffc8de0 --- /dev/null +++ b/src/main/java/codersafterdark/reskillable/base/configs/json/parsers/CustomNBTJson.java @@ -0,0 +1,22 @@ +package codersafterdark.reskillable.base.configs.json.parsers; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import net.minecraft.nbt.JsonToNBT; +import net.minecraft.nbt.NBTException; +import net.minecraft.nbt.NBTTagCompound; + +import java.lang.reflect.Type; + +public class CustomNBTJson implements JsonDeserializer { + @Override + public NBTTagCompound deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + try { + return JsonToNBT.getTagFromJson(json.toString()); + } catch (NBTException e) { + throw new JsonParseException("Failed to read nbt from: " + json.toString(), e); + } + } +} diff --git a/src/main/resources/defaultLocks.json b/src/main/resources/defaultLocks.json new file mode 100644 index 00000000..e97f3934 --- /dev/null +++ b/src/main/resources/defaultLocks.json @@ -0,0 +1,374 @@ +[ + { + "type": "itemstack", + "item": "minecraft:iron_shovel", + "metadata": "*", + "requirements": [ + "reskillable:gathering|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:iron_axe", + "metadata": "*", + "requirements": [ + "reskillable:gathering|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:iron_sword", + "metadata": "*", + "requirements": [ + "reskillable:attack|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:iron_pickaxe", + "metadata": "*", + "requirements": [ + "reskillable:mining|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:iron_hoe", + "metadata": "*", + "requirements": [ + "reskillable:farming|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:iron_helmet", + "metadata": "*", + "requirements": [ + "reskillable:defense|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:iron_chestplate", + "metadata": "*", + "requirements": [ + "reskillable:defense|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:iron_leggings", + "metadata": "*", + "requirements": [ + "reskillable:defense|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:iron_boots", + "metadata": "*", + "requirements": [ + "reskillable:defense|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:golden_shovel", + "metadata": "*", + "requirements": [ + "reskillable:gathering|5", + "reskillable:magic|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:golden_axe", + "metadata": "*", + "requirements": [ + "reskillable:gathering|5", + "reskillable:magic|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:golden_sword", + "metadata": "*", + "requirements": [ + "reskillable:attack|5", + "reskillable:magic|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:golden_pickaxe", + "metadata": "*", + "requirements": [ + "reskillable:mining|5", + "reskillable:magic|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:golden_hoe", + "metadata": "*", + "requirements": [ + "reskillable:farming|5", + "reskillable:magic|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:golden_helmet", + "metadata": "*", + "requirements": [ + "reskillable:defense|5", + "reskillable:magic|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:golden_chestplate", + "metadata": "*", + "requirements": [ + "reskillable:defense|5", + "reskillable:magic|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:golden_leggings", + "metadata": "*", + "requirements": [ + "reskillable:defense|5", + "reskillable:magic|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:golden_boots", + "metadata": "*", + "requirements": [ + "reskillable:defense|5", + "reskillable:magic|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:diamond_shovel", + "metadata": "*", + "requirements": [ + "reskillable:gathering|16" + ] + }, + { + "type": "itemstack", + "item": "minecraft:diamond_axe", + "metadata": "*", + "requirements": [ + "reskillable:gathering|16" + ] + }, + { + "type": "itemstack", + "item": "minecraft:diamond_sword", + "metadata": "*", + "requirements": [ + "reskillable:attack|16" + ] + }, + { + "type": "itemstack", + "item": "minecraft:diamond_pickaxe", + "metadata": "*", + "requirements": [ + "reskillable:mining|16" + ] + }, + { + "type": "itemstack", + "item": "minecraft:diamond_hoe", + "metadata": "*", + "requirements": [ + "reskillable:farming|16" + ] + }, + { + "type": "itemstack", + "item": "minecraft:diamond_helmet", + "metadata": "*", + "requirements": [ + "reskillable:defense|16" + ] + }, + { + "type": "itemstack", + "item": "minecraft:diamond_chestplate", + "metadata": "*", + "requirements": [ + "reskillable:defense|16" + ] + }, + { + "type": "itemstack", + "item": "minecraft:diamond_leggings", + "metadata": "*", + "requirements": [ + "reskillable:defense|16" + ] + }, + { + "type": "itemstack", + "item": "minecraft:diamond_boots", + "metadata": "*", + "requirements": [ + "reskillable:defense|16" + ] + }, + { + "type": "itemstack", + "item": "minecraft:shears", + "metadata": "*", + "requirements": [ + "reskillable:farming|5", + "reskillable:gathering|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:fishing_rod", + "metadata": "*", + "requirements": [ + "reskillable:gathering|8" + ] + }, + { + "type": "itemstack", + "item": "minecraft:shield", + "metadata": "*", + "requirements": [ + "reskillable:defense|8" + ] + }, + { + "type": "itemstack", + "item": "minecraft:bow", + "metadata": "*", + "requirements": [ + "reskillable:attack|8" + ] + }, + { + "type": "itemstack", + "item": "minecraft:ender_pearl", + "requirements": [ + "reskillable:magic|8" + ] + }, + { + "type": "itemstack", + "item": "minecraft:ender_eye", + "requirements": [ + "reskillable:magic|16", + "reskillable:building|8" + ] + }, + { + "type": "itemstack", + "item": "minecraft:elytra", + "metadata": "*", + "requirements": [ + "reskillable:defense|16", + "reskillable:agility|24", + "reskillable:magic|16" + ] + }, + { + "type": "itemstack", + "item": "minecraft:lead", + "requirements": [ + "reskillable:farming|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:end_crystal", + "requirements": [ + "reskillable:building|24", + "reskillable:magic|32" + ] + }, + { + "type": "itemstack", + "item": "minecraft:iron_horse_armor", + "metadata": "*", + "requirements": [ + "reskillable:defense|5", + "reskillable:agility|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:golden_horse_armor", + "metadata": "*", + "requirements": [ + "reskillable:defense|5", + "reskillable:magic|5", + "reskillable:agility|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:diamond_horse_armor", + "metadata": "*", + "requirements": [ + "reskillable:defense|16", + "reskillable:agility|16" + ] + }, + { + "type": "itemstack", + "item": "minecraft:fireworks", + "requirements": [ + "reskillable:agility|24" + ] + }, + { + "type": "itemstack", + "item": "minecraft:dye", + "metadata": "15", + "requirements": [ + "reskillable:farming|12" + ] + }, + { + "type": "itemstack", + "item": "minecraft:saddle", + "requirements": [ + "reskillable:agility|12" + ] + }, + { + "type": "itemstack", + "item": "minecraft:redstone", + "requirements": [ + "reskillable:building|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:redstone_torch", + "requirements": [ + "reskillable:building|5" + ] + }, + { + "type": "itemstack", + "item": "minecraft:skull", + "metadata": "1", + "requirements": [ + "reskillable:building|20", + "reskillable:attack|20", + "reskillable:defense|20" + ] + } +] \ No newline at end of file