From 7f2f9d75dd1cf99d73646682e32e3e5347d5ab6a Mon Sep 17 00:00:00 2001 From: Sn0wStorm Date: Wed, 6 Nov 2019 19:44:52 +0100 Subject: [PATCH] Rewrite of the Custom item into RecipeItem and Ingredient with subclasses Implemented adding custom items to Ingredients Added support for plugin-items --- pom.xml | 16 +- resources/config/v13/de/config.yml | 6 +- resources/config/v13/en/config.yml | 4 + src/com/dre/brewery/BCauldron.java | 79 +++-- src/com/dre/brewery/BIngredients.java | 97 ++++-- src/com/dre/brewery/BPlayer.java | 1 + src/com/dre/brewery/Barrel.java | 2 +- src/com/dre/brewery/Brew.java | 23 +- src/com/dre/brewery/P.java | 12 +- src/com/dre/brewery/api/BreweryApi.java | 2 +- .../brewery/api/events/IngedientAddEvent.java | 19 +- src/com/dre/brewery/filedata/BConfig.java | 35 +- src/com/dre/brewery/filedata/BData.java | 319 +++++++++-------- src/com/dre/brewery/filedata/DataSave.java | 2 +- .../integration/IntegrationListener.java | 46 ++- .../{ => barrel}/CitadelBarrel.java | 12 +- .../{ => barrel}/GriefPreventionBarrel.java | 2 +- .../integration/{ => barrel}/LWCBarrel.java | 2 +- .../{ => barrel}/LogBlockBarrel.java | 2 +- .../integration/{ => barrel}/WGBarrel.java | 2 +- .../integration/{ => barrel}/WGBarrel5.java | 2 +- .../integration/{ => barrel}/WGBarrel6.java | 2 +- .../integration/{ => barrel}/WGBarrel7.java | 2 +- .../integration/item/BreweryPluginItem.java | 30 ++ .../integration/item/MMOItemsPluginItem.java | 32 ++ .../integration/item/SlimefunPluginItem.java | 35 ++ .../brewery/listeners/CommandListener.java | 1 + .../dre/brewery/listeners/WorldListener.java | 4 +- src/com/dre/brewery/lore/BrewLore.java | 4 +- .../brewery/{ => recipe}/BCauldronRecipe.java | 65 +++- src/com/dre/brewery/{ => recipe}/BEffect.java | 3 +- src/com/dre/brewery/{ => recipe}/BRecipe.java | 101 ++++-- src/com/dre/brewery/recipe/CustomItem.java | 291 ++++++++++++++++ .../brewery/recipe/CustomMatchAnyItem.java | 231 ++++++++++++ src/com/dre/brewery/recipe/Ingredient.java | 88 +++++ src/com/dre/brewery/recipe/ItemLoader.java | 28 ++ src/com/dre/brewery/recipe/PluginItem.java | 213 ++++++++++++ src/com/dre/brewery/recipe/RecipeItem.java | 314 +++++++++++++++++ src/com/dre/brewery/recipe/SimpleItem.java | 151 ++++++++ src/com/dre/brewery/utility/CustomItem.java | 329 ------------------ test/com/dre/brewery/RecipeTests.java | 25 +- 41 files changed, 2010 insertions(+), 624 deletions(-) rename src/com/dre/brewery/integration/{ => barrel}/CitadelBarrel.java (95%) rename src/com/dre/brewery/integration/{ => barrel}/GriefPreventionBarrel.java (96%) rename src/com/dre/brewery/integration/{ => barrel}/LWCBarrel.java (98%) rename src/com/dre/brewery/integration/{ => barrel}/LogBlockBarrel.java (99%) rename src/com/dre/brewery/integration/{ => barrel}/WGBarrel.java (81%) rename src/com/dre/brewery/integration/{ => barrel}/WGBarrel5.java (97%) rename src/com/dre/brewery/integration/{ => barrel}/WGBarrel6.java (95%) rename src/com/dre/brewery/integration/{ => barrel}/WGBarrel7.java (98%) create mode 100644 src/com/dre/brewery/integration/item/BreweryPluginItem.java create mode 100644 src/com/dre/brewery/integration/item/MMOItemsPluginItem.java create mode 100644 src/com/dre/brewery/integration/item/SlimefunPluginItem.java rename src/com/dre/brewery/{ => recipe}/BCauldronRecipe.java (67%) rename src/com/dre/brewery/{ => recipe}/BEffect.java (98%) rename src/com/dre/brewery/{ => recipe}/BRecipe.java (80%) create mode 100644 src/com/dre/brewery/recipe/CustomItem.java create mode 100644 src/com/dre/brewery/recipe/CustomMatchAnyItem.java create mode 100644 src/com/dre/brewery/recipe/Ingredient.java create mode 100644 src/com/dre/brewery/recipe/ItemLoader.java create mode 100644 src/com/dre/brewery/recipe/PluginItem.java create mode 100644 src/com/dre/brewery/recipe/RecipeItem.java create mode 100644 src/com/dre/brewery/recipe/SimpleItem.java delete mode 100644 src/com/dre/brewery/utility/CustomItem.java diff --git a/pom.xml b/pom.xml index 1a66a85..fd2ea62 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,7 @@ src + test @@ -87,7 +88,7 @@ http://maven.sk89q.com/repo/ - + jitpack.io https://jitpack.io @@ -226,6 +227,19 @@ + + com.github.TheBusyBiscuit + Slimefun4 + 4bb9abd247 + provided + + + + com.dre + ExtPluginBridge + 1.0 + provided + org.bstats bstats-bukkit diff --git a/resources/config/v13/de/config.yml b/resources/config/v13/de/config.yml index fb0f925..686080d 100644 --- a/resources/config/v13/de/config.yml +++ b/resources/config/v13/de/config.yml @@ -192,6 +192,8 @@ cauldron: # Halte ein Item in der Hand und benutze /brew ItemName um dessen Material herauszufinden und für ein Rezept zu benutzen # (Item-ids anstatt Material können in Bukkit nicht mehr benutzt werden) # Eine Liste von allen Materialien kann hier gefunden werden: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html +# Plugin Items mit 'Plugin:Id' (Im Moment ExoticGarden, Slimefun, MMOItems, Brewery) +# Oder ein oben definiertes Custom Item # cookingtime: Zeit in Echtminuten die die Zutaten kochen müssen # distillruns: Wie oft destilliert werden muss für vollen Alkoholgehalt (0=ohne Destillieren) # distilltime: Wie lange (in sekunden) ein Destillations-Durchlauf braucht (0=Standard Zeit von 40 sek) MC Standard wäre 20 sek @@ -219,8 +221,8 @@ recipes: - Diamond/1 - Spruce_Planks/8 - Bedrock/1 -# - Brewery:Weißbier/2 -# - ExoticGarden:Grape/3 + - Brewery:Weißbier/2 +# - ExoticGarden:Grape/3 - bsp-item/1 cookingtime: 3 distillruns: 2 diff --git a/resources/config/v13/en/config.yml b/resources/config/v13/en/config.yml index 3e5050f..fdd0ed9 100644 --- a/resources/config/v13/en/config.yml +++ b/resources/config/v13/en/config.yml @@ -193,6 +193,8 @@ cauldron: # With an item in your hand, use /brew ItemName to get its material for use in a recipe # (Item-ids instead of material are not supported by bukkit anymore and will not work) # A list of materials can be found here: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html +# Plugin items with 'plugin:id' (Currently supporting ExoticGarden, Slimefun, MMOItems, Brewery) +# Or a custom item defined above # cookingtime: Time in real minutes ingredients have to boil # distillruns: How often it has to be distilled for full alcohol (0=without distilling) # distilltime: How long (in seconds) one distill-run takes (0=Default time of 40 sec) MC Default would be 20 sec @@ -220,6 +222,8 @@ recipes: - Diamond/1 - Spruce_Planks/8 - Bedrock/1 + - Brewery:Wheatbeer/2 +# - ExoticGarden:Grape/3 cookingtime: 3 distillruns: 2 distilltime: 60 diff --git a/src/com/dre/brewery/BCauldron.java b/src/com/dre/brewery/BCauldron.java index 3552f66..b8558f2 100644 --- a/src/com/dre/brewery/BCauldron.java +++ b/src/com/dre/brewery/BCauldron.java @@ -1,6 +1,8 @@ package com.dre.brewery; import com.dre.brewery.api.events.IngedientAddEvent; +import com.dre.brewery.recipe.BCauldronRecipe; +import com.dre.brewery.recipe.RecipeItem; import com.dre.brewery.utility.BUtil; import com.dre.brewery.utility.LegacyUtil; import org.bukkit.Effect; @@ -28,7 +30,7 @@ public class BCauldron { private BIngredients ingredients = new BIngredients(); private final Block block; private int state = 1; - private boolean someRemoved = false; + private boolean changed = false; public BCauldron(Block block) { this.block = block; @@ -48,22 +50,22 @@ public class BCauldron { if (!BUtil.isChunkLoaded(block) || LegacyUtil.isFireForCauldron(block.getRelative(BlockFace.DOWN))) { // add a minute to cooking time state++; - if (someRemoved) { - ingredients = ingredients.clone(); - someRemoved = false; + if (changed) { + ingredients = ingredients.copy(); + changed = false; } } } // add an ingredient to the cauldron - public void add(ItemStack ingredient) { + public void add(ItemStack ingredient, RecipeItem rItem) { if (ingredient == null || ingredient.getType() == Material.AIR) return; - if (someRemoved) { // TODO no need to clone - ingredients = ingredients.clone(); - someRemoved = false; + if (changed) { + ingredients = ingredients.copy(); + changed = false; } - ingredient = new ItemStack(ingredient.getType(), 1, ingredient.getDurability()); - ingredients.add(ingredient); + + ingredients.add(ingredient, rItem); block.getWorld().playEffect(block.getLocation(), Effect.EXTINGUISH, 0); if (state > 1) { state--; @@ -90,10 +92,20 @@ public class BCauldron { bcauldron = new BCauldron(block); } - IngedientAddEvent event = new IngedientAddEvent(player, block, bcauldron, ingredient); + if (!BCauldronRecipe.acceptedMaterials.contains(ingredient.getType()) && !ingredient.hasItemMeta()) { + // Extremely fast way to check for most items + return false; + } + // If the Item is on the list, or customized, we have to do more checks + RecipeItem rItem = RecipeItem.getMatchingRecipeItem(ingredient, false); + if (rItem == null) { + return false; + } + + IngedientAddEvent event = new IngedientAddEvent(player, block, bcauldron, ingredient.clone(), rItem); P.p.getServer().getPluginManager().callEvent(event); if (!event.isCancelled()) { - bcauldron.add(event.getIngredient()); + bcauldron.add(event.getIngredient(), event.getRecipeItem()); return event.willTakeItem(); } else { return false; @@ -127,7 +139,7 @@ public class BCauldron { if (cauldron.getLevel() <= 0) { bcauldrons.remove(this); } else { - someRemoved = true; + changed = true; } } else { @@ -144,7 +156,7 @@ public class BCauldron { if (data == 0) { bcauldrons.remove(this); } else { - someRemoved = true; + changed = true; } } // Bukkit Bug, inventory not updating while in event so this @@ -250,30 +262,25 @@ public class BCauldron { } if (item == null) return; - // add ingredient to cauldron that meet the previous conditions - if (BCauldronRecipe.acceptedMaterials.contains(materialInHand)) { + if (!player.hasPermission("brewery.cauldron.insert")) { + P.p.msg(player, P.p.languageReader.get("Perms_NoCauldronInsert")); + return; + } + if (ingredientAdd(clickedBlock, item, player)) { + boolean isBucket = item.getType().equals(Material.WATER_BUCKET) + || item.getType().equals(Material.LAVA_BUCKET) + || item.getType().equals(Material.MILK_BUCKET); + if (item.getAmount() > 1) { + item.setAmount(item.getAmount() - 1); - if (!player.hasPermission("brewery.cauldron.insert")) { - P.p.msg(player, P.p.languageReader.get("Perms_NoCauldronInsert")); - return; - } - - if (ingredientAdd(clickedBlock, item, player)) { - boolean isBucket = item.getType().equals(Material.WATER_BUCKET) - || item.getType().equals(Material.LAVA_BUCKET) - || item.getType().equals(Material.MILK_BUCKET); - if (item.getAmount() > 1) { - item.setAmount(item.getAmount() - 1); - - if (isBucket) { - giveItem(player, new ItemStack(Material.BUCKET)); - } + if (isBucket) { + giveItem(player, new ItemStack(Material.BUCKET)); + } + } else { + if (isBucket) { + setItemInHand(event, Material.BUCKET, handSwap); } else { - if (isBucket) { - setItemInHand(event, Material.BUCKET, handSwap); - } else { - setItemInHand(event, Material.AIR, handSwap); - } + setItemInHand(event, Material.AIR, handSwap); } } } diff --git a/src/com/dre/brewery/BIngredients.java b/src/com/dre/brewery/BIngredients.java index 9d3b0fd..e301a52 100644 --- a/src/com/dre/brewery/BIngredients.java +++ b/src/com/dre/brewery/BIngredients.java @@ -1,7 +1,13 @@ package com.dre.brewery; import com.dre.brewery.api.events.brew.BrewModifyEvent; +import com.dre.brewery.lore.Base91EncoderStream; import com.dre.brewery.lore.BrewLore; +import com.dre.brewery.recipe.BCauldronRecipe; +import com.dre.brewery.recipe.BRecipe; +import com.dre.brewery.recipe.Ingredient; +import com.dre.brewery.recipe.ItemLoader; +import com.dre.brewery.recipe.RecipeItem; import com.dre.brewery.utility.PotionColor; import org.bukkit.Material; import org.bukkit.configuration.ConfigurationSection; @@ -9,19 +15,18 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.PotionMeta; import org.jetbrains.annotations.Nullable; +import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; public class BIngredients { - private static int lastId = 0; + private static int lastId = 0; // Legacy private int id; // Legacy - private List ingredients = new ArrayList<>(); + private List ingredients = new ArrayList<>(); private int cookedTime; // Represents ingredients in Cauldron, Brew @@ -32,7 +37,7 @@ public class BIngredients { } // Load from File - public BIngredients(List ingredients, int cookedTime) { + public BIngredients(List ingredients, int cookedTime) { this.ingredients = ingredients; this.cookedTime = cookedTime; //this.id = lastId; @@ -40,7 +45,7 @@ public class BIngredients { } // Load from legacy Brew section - public BIngredients(List ingredients, int cookedTime, boolean legacy) { + public BIngredients(List ingredients, int cookedTime, boolean legacy) { this(ingredients, cookedTime); if (legacy) { this.id = lastId; @@ -48,15 +53,32 @@ public class BIngredients { } } - // Add an ingredient to this + // Force add an ingredient to this + // Will not check if item is acceptable public void add(ItemStack ingredient) { - for (ItemStack item : ingredients) { - if (item.isSimilar(ingredient)) { - item.setAmount(item.getAmount() + ingredient.getAmount()); + for (Ingredient existing : ingredients) { + if (existing.matches(ingredient)) { + existing.setAmount(existing.getAmount() + 1); return; } } - ingredients.add(ingredient); + + Ingredient ing = RecipeItem.getMatchingRecipeItem(ingredient, true).toIngredient(ingredient); + ing.setAmount(1); + ingredients.add(ing); + } + + // Add an ingredient to this with corresponding RecipeItem + public void add(ItemStack ingredient, RecipeItem rItem) { + Ingredient ingredientItem = rItem.toIngredient(ingredient); + for (Ingredient existing : ingredients) { + if (existing.isSimilar(ingredientItem)) { + existing.setAmount(existing.getAmount() + 1); + return; + } + } + ingredientItem.setAmount(1); + ingredients.add(ingredientItem); } // returns an Potion item with cooked ingredients @@ -140,8 +162,8 @@ public class BIngredients { // returns amount of ingredients public int getIngredientsCount() { int count = 0; - for (ItemStack item : ingredients) { - count += item.getAmount(); + for (Ingredient ing : ingredients) { + count += ing.getAmount(); } return count; } @@ -265,7 +287,7 @@ public class BIngredients { // when ingredients are not complete return -1; } - for (ItemStack ingredient : ingredients) { + for (Ingredient ingredient : ingredients) { int amountInRecipe = recipe.amountOf(ingredient); count = ingredient.getAmount(); if (amountInRecipe == 0) { @@ -346,12 +368,7 @@ public class BIngredients { } // Creates a copy ingredients - @Override - public BIngredients clone() { - try { - super.clone(); - } catch (CloneNotSupportedException ignored) { - } + public BIngredients copy() { BIngredients copy = new BIngredients(); copy.ingredients.addAll(ingredients); copy.cookedTime = cookedTime; @@ -399,21 +416,25 @@ public class BIngredients { public void save(DataOutputStream out) throws IOException { out.writeInt(cookedTime); out.writeByte(ingredients.size()); - for (ItemStack item : ingredients) { - out.writeUTF(item.getType().name()); - out.writeShort(item.getAmount()); - out.writeShort(item.getDurability()); + for (Ingredient ing : ingredients) { + ing.saveTo(out); + out.writeShort(Math.min(ing.getAmount(), Short.MAX_VALUE)); } } - public static BIngredients load(DataInputStream in) throws IOException { + public static BIngredients load(DataInputStream in, short dataVersion) throws IOException { int cookedTime = in.readInt(); byte size = in.readByte(); - List ing = new ArrayList<>(size); + List ing = new ArrayList<>(size); for (; size > 0; size--) { - Material mat = Material.getMaterial(in.readUTF()); - if (mat != null) { - ing.add(new ItemStack(mat, in.readShort(), in.readShort())); + ItemLoader itemLoader = new ItemLoader(dataVersion, in, in.readUTF()); + if (Ingredient.LOADERS.containsKey(itemLoader.getSaveID())) { + Ingredient loaded = Ingredient.LOADERS.get(itemLoader.getSaveID()).apply(itemLoader); + int amount = in.readShort(); + if (loaded != null) { + loaded.setAmount(amount); + ing.add(loaded); + } } } return new BIngredients(ing, cookedTime); @@ -421,8 +442,7 @@ public class BIngredients { // saves data into main Ingredient section. Returns the save id // Only needed for legacy potions - @Deprecated - public int save(ConfigurationSection config) { + public int saveLegacy(ConfigurationSection config) { String path = "Ingredients." + id; if (cookedTime != 0) { config.set(path + ".cookedTime", cookedTime); @@ -432,13 +452,26 @@ public class BIngredients { } //convert the ingredient Material to String - public Map serializeIngredients() { + /*public Map serializeIngredients() { Map mats = new HashMap<>(); for (ItemStack item : ingredients) { String mat = item.getType().name() + "," + item.getDurability(); mats.put(mat, item.getAmount()); } return mats; + }*/ + + // Serialize Ingredients to String for storing in yml, ie for Cauldrons + public String serializeIngredients() { + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + try (DataOutputStream out = new DataOutputStream(new Base91EncoderStream(byteStream))) { + out.writeByte(Brew.SAVE_VER); + save(out); + } catch (IOException e) { + e.printStackTrace(); + return ""; + } + return byteStream.toString(); } } diff --git a/src/com/dre/brewery/BPlayer.java b/src/com/dre/brewery/BPlayer.java index 7db9de3..cecfcc5 100644 --- a/src/com/dre/brewery/BPlayer.java +++ b/src/com/dre/brewery/BPlayer.java @@ -6,6 +6,7 @@ import com.dre.brewery.api.events.PlayerPukeEvent; import com.dre.brewery.api.events.PlayerPushEvent; import com.dre.brewery.api.events.brew.BrewDrinkEvent; import com.dre.brewery.filedata.BConfig; +import com.dre.brewery.recipe.BEffect; import com.dre.brewery.utility.BUtil; import org.apache.commons.lang.mutable.MutableInt; import org.bukkit.Location; diff --git a/src/com/dre/brewery/Barrel.java b/src/com/dre/brewery/Barrel.java index 0c07086..7f06861 100644 --- a/src/com/dre/brewery/Barrel.java +++ b/src/com/dre/brewery/Barrel.java @@ -5,7 +5,7 @@ import com.dre.brewery.api.events.barrel.BarrelCreateEvent; import com.dre.brewery.api.events.barrel.BarrelDestroyEvent; import com.dre.brewery.api.events.barrel.BarrelRemoveEvent; import com.dre.brewery.filedata.BConfig; -import com.dre.brewery.integration.LogBlockBarrel; +import com.dre.brewery.integration.barrel.LogBlockBarrel; import com.dre.brewery.lore.BrewLore; import com.dre.brewery.utility.BUtil; import com.dre.brewery.utility.BoundingBox; diff --git a/src/com/dre/brewery/Brew.java b/src/com/dre/brewery/Brew.java index d23ef3e..b882c63 100644 --- a/src/com/dre/brewery/Brew.java +++ b/src/com/dre/brewery/Brew.java @@ -4,6 +4,8 @@ import com.dre.brewery.api.events.brew.BrewModifyEvent; import com.dre.brewery.filedata.BConfig; import com.dre.brewery.filedata.ConfigUpdater; import com.dre.brewery.lore.*; +import com.dre.brewery.recipe.BEffect; +import com.dre.brewery.recipe.BRecipe; import com.dre.brewery.utility.PotionColor; import org.bukkit.Material; import org.bukkit.configuration.ConfigurationSection; @@ -26,6 +28,7 @@ import java.util.Map; public class Brew { // represents the liquid in the brewed Potions + public static final byte SAVE_VER = 1; private static long saveSeed; private static List prevSaveSeeds = new ArrayList<>(); // Save Seeds that have been used in the past, stored to decode brews made at that time public static Map legacyPotions = new HashMap<>(); @@ -207,10 +210,10 @@ public class Brew { if (!immutable) { this.quality = calcQuality(); } - P.p.log("Brew was made from Recipe: '" + name + "' which could not be found. '" + currentRecipe.getRecipeName() + "' used instead!"); + P.p.log("A Brew was made from Recipe: '" + name + "' which could not be found. '" + currentRecipe.getRecipeName() + "' used instead!"); return true; } else { - P.p.errorLog("Brew was made from Recipe: '" + name + "' which could not be found!"); + P.p.errorLog("A Brew was made from Recipe: '" + name + "' which could not be found!"); } } } @@ -570,7 +573,9 @@ public class Brew { recipe.getColor().colorBrew(potionMeta, item, canDistill()); } else { quality = 0; + lore.convertLore(false); lore.removeEffects(); + currentRecipe = null; potionMeta.setDisplayName(P.p.color("&f" + P.p.languageReader.get("Brew_BadPotion"))); PotionColor.GREY.colorBrew(potionMeta, item, canDistill()); } @@ -714,7 +719,7 @@ public class Brew { case 1: unscrambler.start(); - brew.loadFromStream(in); + brew.loadFromStream(in, ver); break; default: @@ -748,7 +753,7 @@ public class Brew { return null; } - private void loadFromStream(DataInputStream in) throws IOException { + private void loadFromStream(DataInputStream in, byte dataVersion) throws IOException { quality = in.readByte(); int bools = in.readUnsignedByte(); if ((bools & 1) != 0) { @@ -767,7 +772,7 @@ public class Brew { unlabeled = (bools & 16) != 0; //persistent = (bools & 32) != 0; immutable = (bools & 32) != 0; - ingredients = BIngredients.load(in); + ingredients = BIngredients.load(in, dataVersion); setRecipeFromString(recipe); } @@ -782,7 +787,7 @@ public class Brew { XORScrambleStream scrambler = new XORScrambleStream(itemSaveStream, saveSeed); try (DataOutputStream out = new DataOutputStream(scrambler)) { out.writeByte(86); // Parity/sanity - out.writeByte(1); // Version + out.writeByte(SAVE_VER); // Version if (BConfig.enableEncode) { scrambler.start(); } else { @@ -890,6 +895,7 @@ public class Brew { public void convertPre19(ItemStack item) { removeLegacy(item); PotionMeta potionMeta = ((PotionMeta) item.getItemMeta()); + assert potionMeta != null; if (hasRecipe()) { BrewLore lore = new BrewLore(this, potionMeta); lore.removeEffects(); @@ -905,8 +911,7 @@ public class Brew { // Saves all data // Legacy method to save to data file - @Deprecated - public static void save(ConfigurationSection config) { + public static void saveLegacy(ConfigurationSection config) { for (Map.Entry entry : legacyPotions.entrySet()) { int uid = entry.getKey(); Brew brew = entry.getValue(); @@ -940,7 +945,7 @@ public class Brew { idConfig.set("lastUpdate", brew.lastUpdate); } // save the ingredients - idConfig.set("ingId", brew.ingredients.save(config.getParent())); + idConfig.set("ingId", brew.ingredients.saveLegacy(config.getParent())); } } diff --git a/src/com/dre/brewery/P.java b/src/com/dre/brewery/P.java index 1a9cf2b..ca744a9 100644 --- a/src/com/dre/brewery/P.java +++ b/src/com/dre/brewery/P.java @@ -6,8 +6,10 @@ import com.dre.brewery.filedata.DataSave; import com.dre.brewery.filedata.LanguageReader; import com.dre.brewery.filedata.UpdateChecker; import com.dre.brewery.integration.IntegrationListener; -import com.dre.brewery.integration.LogBlockBarrel; +import com.dre.brewery.integration.barrel.LogBlockBarrel; import com.dre.brewery.listeners.*; +import com.dre.brewery.recipe.BCauldronRecipe; +import com.dre.brewery.recipe.BRecipe; import com.dre.brewery.utility.BUtil; import com.dre.brewery.utility.LegacyUtil; import org.apache.commons.lang.math.NumberUtils; @@ -442,8 +444,12 @@ public class P extends JavaPlugin { BCauldron.bcauldrons.clear(); BRecipe.recipes.clear(); BCauldronRecipe.acceptedMaterials.clear(); + BCauldronRecipe.acceptedCustom.clear(); + BCauldronRecipe.acceptedSimple.clear(); BCauldronRecipe.recipes.clear(); BConfig.customItems.clear(); + BConfig.hasSlimefun = null; + BConfig.hasMMOItems = null; BPlayer.clear(); Brew.legacyPotions.clear(); Wakeup.wakeups.clear(); @@ -461,8 +467,12 @@ public class P extends JavaPlugin { // clear all existent config Data BRecipe.recipes.clear(); BCauldronRecipe.acceptedMaterials.clear(); + BCauldronRecipe.acceptedCustom.clear(); + BCauldronRecipe.acceptedSimple.clear(); BCauldronRecipe.recipes.clear(); BConfig.customItems.clear(); + BConfig.hasSlimefun = null; + BConfig.hasMMOItems = null; DistortChat.words.clear(); DistortChat.ignoreText.clear(); DistortChat.commands = null; diff --git a/src/com/dre/brewery/api/BreweryApi.java b/src/com/dre/brewery/api/BreweryApi.java index 0a4d536..2f829ab 100644 --- a/src/com/dre/brewery/api/BreweryApi.java +++ b/src/com/dre/brewery/api/BreweryApi.java @@ -1,7 +1,7 @@ package com.dre.brewery.api; import com.dre.brewery.BCauldron; -import com.dre.brewery.BRecipe; +import com.dre.brewery.recipe.BRecipe; import com.dre.brewery.Barrel; import com.dre.brewery.Brew; import org.apache.commons.lang.NotImplementedException; diff --git a/src/com/dre/brewery/api/events/IngedientAddEvent.java b/src/com/dre/brewery/api/events/IngedientAddEvent.java index 60bde23..ecedda3 100644 --- a/src/com/dre/brewery/api/events/IngedientAddEvent.java +++ b/src/com/dre/brewery/api/events/IngedientAddEvent.java @@ -1,6 +1,7 @@ package com.dre.brewery.api.events; import com.dre.brewery.BCauldron; +import com.dre.brewery.recipe.RecipeItem; import com.dre.brewery.utility.LegacyUtil; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; @@ -21,14 +22,16 @@ public class IngedientAddEvent extends PlayerEvent implements Cancellable { private final Block block; private final BCauldron cauldron; private ItemStack ingredient; + private RecipeItem rItem; private boolean cancelled; private boolean takeItem = true; - public IngedientAddEvent(Player who, Block block, BCauldron bCauldron, ItemStack ingredient) { + public IngedientAddEvent(Player who, Block block, BCauldron bCauldron, ItemStack ingredient, RecipeItem rItem) { super(who); this.block = block; cauldron = bCauldron; - this.ingredient = ingredient.clone(); + this.rItem = rItem; + this.ingredient = ingredient; } public Block getBlock() { @@ -39,6 +42,15 @@ public class IngedientAddEvent extends PlayerEvent implements Cancellable { return cauldron; } + /** + * The Recipe item that matches the ingredient. + * This might not be the only recipe item that will match the ingredient + * Will be recalculated if the Ingredient is changed with the setIngredient Method + */ + public RecipeItem getRecipeItem() { + return rItem; + } + // Get the item currently being added to the cauldron by the player // Can be changed directly (mutable) or with the setter Method // The amount is ignored and always one added @@ -49,8 +61,11 @@ public class IngedientAddEvent extends PlayerEvent implements Cancellable { // Set the ingredient added to the cauldron to something else // Will always be accepted, even when not in a recipe or the cooked list // The amount is ignored and always one added + // This also recalculates the recipeItem! public void setIngredient(ItemStack ingredient) { this.ingredient = ingredient; + // The Ingredient has been changed. Recalculate RecipeItem! + rItem = RecipeItem.getMatchingRecipeItem(ingredient, true); } // If the amount of the item in the players hand should be decreased diff --git a/src/com/dre/brewery/filedata/BConfig.java b/src/com/dre/brewery/filedata/BConfig.java index 03fe142..8d6e60a 100644 --- a/src/com/dre/brewery/filedata/BConfig.java +++ b/src/com/dre/brewery/filedata/BConfig.java @@ -1,18 +1,29 @@ package com.dre.brewery.filedata; -import com.dre.brewery.*; -import com.dre.brewery.integration.WGBarrel; -import com.dre.brewery.integration.WGBarrel5; -import com.dre.brewery.integration.WGBarrel6; -import com.dre.brewery.integration.WGBarrel7; +import com.dre.brewery.Brew; +import com.dre.brewery.DistortChat; +import com.dre.brewery.MCBarrel; +import com.dre.brewery.P; +import com.dre.brewery.integration.barrel.WGBarrel; +import com.dre.brewery.integration.barrel.WGBarrel5; +import com.dre.brewery.integration.barrel.WGBarrel6; +import com.dre.brewery.integration.barrel.WGBarrel7; +import com.dre.brewery.integration.item.BreweryPluginItem; +import com.dre.brewery.integration.item.MMOItemsPluginItem; +import com.dre.brewery.integration.item.SlimefunPluginItem; +import com.dre.brewery.recipe.BCauldronRecipe; +import com.dre.brewery.recipe.BRecipe; +import com.dre.brewery.recipe.PluginItem; +import com.dre.brewery.recipe.RecipeItem; import com.dre.brewery.utility.BUtil; -import com.dre.brewery.utility.CustomItem; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.command.CommandSender; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; @@ -38,6 +49,8 @@ public class BConfig { public static boolean useGP; //GriefPrevention public static boolean hasVault; // Vault public static boolean useCitadel; // CivCraft/DevotedMC Citadel + public static Boolean hasSlimefun = null; // Slimefun ; Null if not checked + public static Boolean hasMMOItems = null; // MMOItems ; Null if not checked // Barrel public static boolean openEverywhere; @@ -61,7 +74,7 @@ public class BConfig { public static boolean alwaysShowAlc; // Always show alc% //Item - public static List customItems = new ArrayList<>(); + public static List customItems = new ArrayList<>(); public static P p = P.p; @@ -189,12 +202,18 @@ public class BConfig { Brew.loadSeed(config, file); + PluginItem.registerForConfig("brewery", BreweryPluginItem::new); + PluginItem.registerForConfig("mmoitems", MMOItemsPluginItem::new); + PluginItem.registerForConfig("slimefun", SlimefunPluginItem::new); + PluginItem.registerForConfig("exoticgarden", SlimefunPluginItem::new); + // Loading custom items ConfigurationSection configSection = config.getConfigurationSection("customItems"); if (configSection != null) { for (String custId : configSection.getKeys(false)) { - CustomItem custom = CustomItem.fromConfig(configSection, custId); + RecipeItem custom = RecipeItem.fromConfigCustom(configSection, custId); if (custom != null) { + custom.makeImmutable(); customItems.add(custom); } else { p.errorLog("Loading the Custom Item with id: '" + custId + "' failed!"); diff --git a/src/com/dre/brewery/filedata/BData.java b/src/com/dre/brewery/filedata/BData.java index 96209eb..3f4d6f7 100644 --- a/src/com/dre/brewery/filedata/BData.java +++ b/src/com/dre/brewery/filedata/BData.java @@ -1,6 +1,11 @@ package com.dre.brewery.filedata; import com.dre.brewery.*; +import com.dre.brewery.lore.Base91DecoderStream; +import com.dre.brewery.recipe.CustomItem; +import com.dre.brewery.recipe.Ingredient; +import com.dre.brewery.recipe.PluginItem; +import com.dre.brewery.recipe.SimpleItem; import com.dre.brewery.utility.BUtil; import com.dre.brewery.utility.BoundingBox; import org.apache.commons.lang.ArrayUtils; @@ -12,14 +17,12 @@ import org.bukkit.block.Block; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.inventory.ItemStack; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; +import java.io.IOException; +import java.util.*; public class BData { @@ -47,18 +50,30 @@ public class BData { } } + // Register Item Loaders + CustomItem.registerItemLoader(); + SimpleItem.registerItemLoader(); + PluginItem.registerItemLoader(); + // loading Ingredients into ingMap + // Only for Legacy Brews Map ingMap = new HashMap<>(); ConfigurationSection section = data.getConfigurationSection("Ingredients"); if (section != null) { for (String id : section.getKeys(false)) { - ConfigurationSection matSection = section.getConfigurationSection(id + ".mats"); - if (matSection != null) { - // matSection has all the materials + amount as Integers - ArrayList ingredients = deserializeIngredients(matSection); - ingMap.put(id, new BIngredients(ingredients, section.getInt(id + ".cookedTime", 0), true)); + if (section.isConfigurationSection(id + ".mats")) { + // Old way of saving + ConfigurationSection matSection = section.getConfigurationSection(id + ".mats"); + if (matSection != null) { + // matSection has all the materials + amount as Integers + List ingredients = oldDeserializeIngredients(matSection); + ingMap.put(id, new BIngredients(ingredients, section.getInt(id + ".cookedTime", 0), true)); + } else { + P.p.errorLog("Ingredient id: '" + id + "' incomplete in data.yml"); + } } else { - P.p.errorLog("Ingredient id: '" + id + "' incomplete in data.yml"); + // New way of saving ingredients + ingMap.put(id, deserializeIngredients(section.getString(id + ".mats"))); } } } @@ -110,9 +125,9 @@ public class BData { for (World world : P.p.getServer().getWorlds()) { if (world.getName().startsWith("DXL_")) { - loadWorldData(BUtil.getDxlName(world.getName()), world); + loadWorldData(BUtil.getDxlName(world.getName()), world, data); } else { - loadWorldData(world.getUID().toString(), world); + loadWorldData(world.getUID().toString(), world, data); } } @@ -121,8 +136,19 @@ public class BData { } } - public static ArrayList deserializeIngredients(ConfigurationSection matSection) { - ArrayList ingredients = new ArrayList<>(); + public static BIngredients deserializeIngredients(String mat) { + try (DataInputStream in = new DataInputStream(new Base91DecoderStream(new ByteArrayInputStream(mat.getBytes())))) { + byte ver = in.readByte(); + return BIngredients.load(in, ver); + } catch (IOException e) { + e.printStackTrace(); + return new BIngredients(); + } + } + + // Loading from the old way of saving ingredients + public static List oldDeserializeIngredients(ConfigurationSection matSection) { + List ingredients = new ArrayList<>(); for (String mat : matSection.getKeys(false)) { String[] matSplit = mat.split(","); Material m = Material.getMaterial(matSplit[0]); @@ -135,10 +161,13 @@ public class BData { P.p.debugLog("converting Data Material from " + matSplit[0] + " to " + m); } if (m == null) continue; - ItemStack item = new ItemStack(m, matSection.getInt(mat)); + SimpleItem item; if (matSplit.length == 2) { - item.setDurability((short) P.p.parseInt(matSplit[1])); + item = new SimpleItem(m, (short) P.p.parseInt(matSplit[1])); + } else { + item = new SimpleItem(m); } + item.setAmount(matSection.getInt(mat)); ingredients.add(item); } return ingredients; @@ -156,134 +185,146 @@ public class BData { } // loads BIngredients from an ingredient section - public static BIngredients loadIngredients(ConfigurationSection section) { - if (section != null) { - return new BIngredients(deserializeIngredients(section), 0); + public static BIngredients loadCauldronIng(ConfigurationSection section, String path) { + if (section.isConfigurationSection(path)) { + // Old way of saving + ConfigurationSection matSection = section.getConfigurationSection(path); + if (matSection != null) { + // matSection has all the materials + amount as Integers + return new BIngredients(oldDeserializeIngredients(section), 0); + } else { + P.p.errorLog("Cauldron is missing Ingredient Section"); + return new BIngredients(); + } } else { - P.p.errorLog("Cauldron is missing Ingredient Section"); + // New way of saving ingredients + return deserializeIngredients(section.getString(path)); } - return new BIngredients(); } // load Block locations of given world - public static void loadWorldData(String uuid, World world) { + public static void loadWorldData(String uuid, World world, FileConfiguration data) { - File file = new File(P.p.getDataFolder(), "data.yml"); - if (file.exists()) { - - FileConfiguration data = YamlConfiguration.loadConfiguration(file); - - // loading BCauldron - if (data.contains("BCauldron." + uuid)) { - ConfigurationSection section = data.getConfigurationSection("BCauldron." + uuid); - for (String cauldron : section.getKeys(false)) { - // block is splitted into x/y/z - String block = section.getString(cauldron + ".block"); - if (block != null) { - String[] splitted = block.split("/"); - if (splitted.length == 3) { - - Block worldBlock = world.getBlockAt(P.p.parseInt(splitted[0]), P.p.parseInt(splitted[1]), P.p.parseInt(splitted[2])); - BIngredients ingredients = loadIngredients(section.getConfigurationSection(cauldron + ".ingredients")); - int state = section.getInt(cauldron + ".state", 1); - - new BCauldron(worldBlock, ingredients, state); - } else { - P.p.errorLog("Incomplete Block-Data in data.yml: " + section.getCurrentPath() + "." + cauldron); - } - } else { - P.p.errorLog("Missing Block-Data in data.yml: " + section.getCurrentPath() + "." + cauldron); - } - } + if (data == null) { + File file = new File(P.p.getDataFolder(), "data.yml"); + if (file.exists()) { + data = YamlConfiguration.loadConfiguration(file); + } else { + return; } - - // loading Barrel - if (data.contains("Barrel." + uuid)) { - ConfigurationSection section = data.getConfigurationSection("Barrel." + uuid); - for (String barrel : section.getKeys(false)) { - // block spigot is splitted into x/y/z - String spigot = section.getString(barrel + ".spigot"); - if (spigot != null) { - String[] splitted = spigot.split("/"); - if (splitted.length == 3) { - - // load itemStacks from invSection - ConfigurationSection invSection = section.getConfigurationSection(barrel + ".inv"); - Block block = world.getBlockAt(P.p.parseInt(splitted[0]), P.p.parseInt(splitted[1]), P.p.parseInt(splitted[2])); - float time = (float) section.getDouble(barrel + ".time", 0.0); - byte sign = (byte) section.getInt(barrel + ".sign", 0); - - BoundingBox box = null; - if (section.contains(barrel + ".bounds")) { - String[] bds = section.getString(barrel + ".bounds", "").split(","); - if (bds.length == 6) { - box = new BoundingBox(P.p.parseInt(bds[0]), P.p.parseInt(bds[1]), P.p.parseInt(bds[2]), P.p.parseInt(bds[3]), P.p.parseInt(bds[4]), P.p.parseInt(bds[5])); - } - } else if (section.contains(barrel + ".st")) { - // Convert from Stair and Wood Locations to BoundingBox - String[] st = section.getString(barrel + ".st", "").split(","); - String[] wo = section.getString(barrel + ".wo", "").split(","); - int woLength = wo.length; - if (woLength <= 1) { - woLength = 0; - } - String[] points = new String[st.length + woLength]; - System.arraycopy(st, 0, points, 0, st.length); - if (woLength > 1) { - System.arraycopy(wo, 0, points, st.length, woLength); - } - int[] locs = ArrayUtils.toPrimitive(Arrays.stream(points).map(s -> P.p.parseInt(s)).toArray(Integer[]::new)); - box = BoundingBox.fromPoints(locs); - } - - Barrel b; - if (invSection != null) { - b = new Barrel(block, sign, box, invSection.getValues(true), time); - } else { - // Barrel has no inventory - b = new Barrel(block, sign, box, null, time); - } - - // In case Barrel Block locations were missing and could not be recreated: do not add the barrel - if (b.getBody().getBounds() != null) { - Barrel.barrels.add(b); - } - - } else { - P.p.errorLog("Incomplete Block-Data in data.yml: " + section.getCurrentPath() + "." + barrel); - } - } else { - P.p.errorLog("Missing Block-Data in data.yml: " + section.getCurrentPath() + "." + barrel); - } - } - } - - // loading Wakeup - if (data.contains("Wakeup." + uuid)) { - ConfigurationSection section = data.getConfigurationSection("Wakeup." + uuid); - for (String wakeup : section.getKeys(false)) { - // loc of wakeup is splitted into x/y/z/pitch/yaw - String loc = section.getString(wakeup); - if (loc != null) { - String[] splitted = loc.split("/"); - if (splitted.length == 5) { - - double x = NumberUtils.toDouble(splitted[0]); - double y = NumberUtils.toDouble(splitted[1]); - double z = NumberUtils.toDouble(splitted[2]); - float pitch = NumberUtils.toFloat(splitted[3]); - float yaw = NumberUtils.toFloat(splitted[4]); - Location location = new Location(world, x, y, z, yaw, pitch); - - Wakeup.wakeups.add(new Wakeup(location)); - - } else { - P.p.errorLog("Incomplete Location-Data in data.yml: " + section.getCurrentPath() + "." + wakeup); - } - } - } - } - } + + // loading BCauldron + if (data.contains("BCauldron." + uuid)) { + ConfigurationSection section = data.getConfigurationSection("BCauldron." + uuid); + for (String cauldron : section.getKeys(false)) { + // block is splitted into x/y/z + String block = section.getString(cauldron + ".block"); + if (block != null) { + String[] splitted = block.split("/"); + if (splitted.length == 3) { + + Block worldBlock = world.getBlockAt(P.p.parseInt(splitted[0]), P.p.parseInt(splitted[1]), P.p.parseInt(splitted[2])); + BIngredients ingredients = loadCauldronIng(section, cauldron + ".ingredients"); + int state = section.getInt(cauldron + ".state", 1); + + new BCauldron(worldBlock, ingredients, state); + } else { + P.p.errorLog("Incomplete Block-Data in data.yml: " + section.getCurrentPath() + "." + cauldron); + } + } else { + P.p.errorLog("Missing Block-Data in data.yml: " + section.getCurrentPath() + "." + cauldron); + } + } + } + + // loading Barrel + if (data.contains("Barrel." + uuid)) { + ConfigurationSection section = data.getConfigurationSection("Barrel." + uuid); + for (String barrel : section.getKeys(false)) { + // block spigot is splitted into x/y/z + String spigot = section.getString(barrel + ".spigot"); + if (spigot != null) { + String[] splitted = spigot.split("/"); + if (splitted.length == 3) { + + // load itemStacks from invSection + ConfigurationSection invSection = section.getConfigurationSection(barrel + ".inv"); + Block block = world.getBlockAt(P.p.parseInt(splitted[0]), P.p.parseInt(splitted[1]), P.p.parseInt(splitted[2])); + float time = (float) section.getDouble(barrel + ".time", 0.0); + byte sign = (byte) section.getInt(barrel + ".sign", 0); + + BoundingBox box = null; + if (section.contains(barrel + ".bounds")) { + String[] bds = section.getString(barrel + ".bounds", "").split(","); + if (bds.length == 6) { + box = new BoundingBox(P.p.parseInt(bds[0]), P.p.parseInt(bds[1]), P.p.parseInt(bds[2]), P.p.parseInt(bds[3]), P.p.parseInt(bds[4]), P.p.parseInt(bds[5])); + } + } else if (section.contains(barrel + ".st")) { + // Convert from Stair and Wood Locations to BoundingBox + String[] st = section.getString(barrel + ".st", "").split(","); + String[] wo = section.getString(barrel + ".wo", "").split(","); + int woLength = wo.length; + if (woLength <= 1) { + woLength = 0; + } + String[] points = new String[st.length + woLength]; + System.arraycopy(st, 0, points, 0, st.length); + if (woLength > 1) { + System.arraycopy(wo, 0, points, st.length, woLength); + } + int[] locs = ArrayUtils.toPrimitive(Arrays.stream(points).map(s -> P.p.parseInt(s)).toArray(Integer[]::new)); + box = BoundingBox.fromPoints(locs); + } + + Barrel b; + if (invSection != null) { + b = new Barrel(block, sign, box, invSection.getValues(true), time); + } else { + // Barrel has no inventory + b = new Barrel(block, sign, box, null, time); + } + + // In case Barrel Block locations were missing and could not be recreated: do not add the barrel + + if (b.getBody().getBounds() != null) { + Barrel.barrels.add(b); + } + + } else { + P.p.errorLog("Incomplete Block-Data in data.yml: " + section.getCurrentPath() + "." + barrel); + } + } else { + P.p.errorLog("Missing Block-Data in data.yml: " + section.getCurrentPath() + "." + barrel); + } + } + } + + // loading Wakeup + if (data.contains("Wakeup." + uuid)) { + ConfigurationSection section = data.getConfigurationSection("Wakeup." + uuid); + for (String wakeup : section.getKeys(false)) { + // loc of wakeup is splitted into x/y/z/pitch/yaw + String loc = section.getString(wakeup); + if (loc != null) { + String[] splitted = loc.split("/"); + if (splitted.length == 5) { + + double x = NumberUtils.toDouble(splitted[0]); + double y = NumberUtils.toDouble(splitted[1]); + double z = NumberUtils.toDouble(splitted[2]); + float pitch = NumberUtils.toFloat(splitted[3]); + float yaw = NumberUtils.toFloat(splitted[4]); + Location location = new Location(world, x, y, z, yaw, pitch); + + Wakeup.wakeups.add(new Wakeup(location)); + + } else { + P.p.errorLog("Incomplete Location-Data in data.yml: " + section.getCurrentPath() + "." + wakeup); + } + } + } + } + } } diff --git a/src/com/dre/brewery/filedata/DataSave.java b/src/com/dre/brewery/filedata/DataSave.java index 579f93e..303c14e 100644 --- a/src/com/dre/brewery/filedata/DataSave.java +++ b/src/com/dre/brewery/filedata/DataSave.java @@ -67,7 +67,7 @@ public class DataSave extends BukkitRunnable { Brew.writePrevSeeds(configFile); if (!Brew.legacyPotions.isEmpty()) { - Brew.save(configFile.createSection("Brew")); + Brew.saveLegacy(configFile.createSection("Brew")); } if (!BCauldron.bcauldrons.isEmpty() || oldData.contains("BCauldron")) { diff --git a/src/com/dre/brewery/integration/IntegrationListener.java b/src/com/dre/brewery/integration/IntegrationListener.java index 5d69e2d..7b77de1 100644 --- a/src/com/dre/brewery/integration/IntegrationListener.java +++ b/src/com/dre/brewery/integration/IntegrationListener.java @@ -1,18 +1,30 @@ package com.dre.brewery.integration; import com.dre.brewery.Barrel; -import com.dre.brewery.utility.LegacyUtil; import com.dre.brewery.P; import com.dre.brewery.api.events.barrel.BarrelAccessEvent; import com.dre.brewery.api.events.barrel.BarrelDestroyEvent; import com.dre.brewery.api.events.barrel.BarrelRemoveEvent; import com.dre.brewery.filedata.BConfig; +import com.dre.brewery.integration.barrel.GriefPreventionBarrel; +import com.dre.brewery.integration.barrel.LWCBarrel; +import com.dre.brewery.integration.barrel.LogBlockBarrel; +import com.dre.brewery.integration.item.MMOItemsPluginItem; +import com.dre.brewery.recipe.BCauldronRecipe; +import com.dre.brewery.recipe.RecipeItem; +import com.dre.brewery.utility.LegacyUtil; +import net.Indyuce.mmoitems.MMOItems; +import net.Indyuce.mmoitems.api.item.NBTItem; +import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.plugin.Plugin; public class IntegrationListener implements Listener { @@ -173,4 +185,36 @@ public class IntegrationListener implements Listener { } } } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onInteract(PlayerInteractEvent event) { + // Cancel the Interact Event early, so MMOItems does not act before us and consume the item when trying to add item to Cauldron + if (!P.use1_9) return; + if (BConfig.hasMMOItems == null) { + BConfig.hasMMOItems = P.p.getServer().getPluginManager().isPluginEnabled("MMOItems"); + } + if (!BConfig.hasMMOItems) return; + try { + if (event.getAction() == Action.RIGHT_CLICK_BLOCK && event.hasItem() && event.getHand() == EquipmentSlot.HAND) { + if (event.getClickedBlock() != null && event.getClickedBlock().getType() == Material.CAULDRON) { + NBTItem item = MMOItems.plugin.getNMS().getNBTItem(event.getItem()); + if (item.hasType()) { + for (RecipeItem rItem : BCauldronRecipe.acceptedCustom) { + if (rItem instanceof MMOItemsPluginItem) { + MMOItemsPluginItem mmo = ((MMOItemsPluginItem) rItem); + if (mmo.matches(event.getItem())) { + event.setCancelled(true); + P.p.playerListener.onPlayerInteract(event); + return; + } + } + } + } + } + } + } catch (Throwable e) { + P.p.errorLog("Could not check MMOItems for Item"); + e.printStackTrace(); + } + } } diff --git a/src/com/dre/brewery/integration/CitadelBarrel.java b/src/com/dre/brewery/integration/barrel/CitadelBarrel.java similarity index 95% rename from src/com/dre/brewery/integration/CitadelBarrel.java rename to src/com/dre/brewery/integration/barrel/CitadelBarrel.java index cb36a80..c519baf 100644 --- a/src/com/dre/brewery/integration/CitadelBarrel.java +++ b/src/com/dre/brewery/integration/barrel/CitadelBarrel.java @@ -1,4 +1,4 @@ -package com.dre.brewery.integration; +package com.dre.brewery.integration.barrel; import org.bukkit.block.Block; import org.bukkit.entity.Player; @@ -13,7 +13,7 @@ import vg.civcraft.mc.citadel.reinforcement.Reinforcement; /** * Basic Citadel support to prevent randos from stealing your barrel aging brews - * + * * @author ProgrammerDan */ public class CitadelBarrel { @@ -21,11 +21,11 @@ public class CitadelBarrel { public static boolean checkAccess(Player player, Block sign) { ReinforcementManager manager = Citadel.getReinforcementManager(); - + Reinforcement rein = manager.getReinforcement(sign); - + if (rein == null) return true; // no protections in place. - + if (rein instanceof PlayerReinforcement) { PlayerReinforcement prein = (PlayerReinforcement) rein; if (prein.canAccessChests(player)) { @@ -35,7 +35,7 @@ public class CitadelBarrel { return true; } // no support for multiblock atm, would require namelayer support. - + // special locked, or no access. brewery.msg(player, brewery.languageReader.get("Error_NoBarrelAccess")); return false; diff --git a/src/com/dre/brewery/integration/GriefPreventionBarrel.java b/src/com/dre/brewery/integration/barrel/GriefPreventionBarrel.java similarity index 96% rename from src/com/dre/brewery/integration/GriefPreventionBarrel.java rename to src/com/dre/brewery/integration/barrel/GriefPreventionBarrel.java index ae05283..9337bed 100644 --- a/src/com/dre/brewery/integration/GriefPreventionBarrel.java +++ b/src/com/dre/brewery/integration/barrel/GriefPreventionBarrel.java @@ -1,4 +1,4 @@ -package com.dre.brewery.integration; +package com.dre.brewery.integration.barrel; import com.dre.brewery.P; import com.dre.brewery.api.events.barrel.BarrelAccessEvent; diff --git a/src/com/dre/brewery/integration/LWCBarrel.java b/src/com/dre/brewery/integration/barrel/LWCBarrel.java similarity index 98% rename from src/com/dre/brewery/integration/LWCBarrel.java rename to src/com/dre/brewery/integration/barrel/LWCBarrel.java index 7831527..cad18b5 100644 --- a/src/com/dre/brewery/integration/LWCBarrel.java +++ b/src/com/dre/brewery/integration/barrel/LWCBarrel.java @@ -1,4 +1,4 @@ -package com.dre.brewery.integration; +package com.dre.brewery.integration.barrel; import com.dre.brewery.Barrel; import com.dre.brewery.P; diff --git a/src/com/dre/brewery/integration/LogBlockBarrel.java b/src/com/dre/brewery/integration/barrel/LogBlockBarrel.java similarity index 99% rename from src/com/dre/brewery/integration/LogBlockBarrel.java rename to src/com/dre/brewery/integration/barrel/LogBlockBarrel.java index 371c0bf..6602d66 100644 --- a/src/com/dre/brewery/integration/LogBlockBarrel.java +++ b/src/com/dre/brewery/integration/barrel/LogBlockBarrel.java @@ -1,4 +1,4 @@ -package com.dre.brewery.integration; +package com.dre.brewery.integration.barrel; import com.dre.brewery.utility.LegacyUtil; import com.dre.brewery.P; diff --git a/src/com/dre/brewery/integration/WGBarrel.java b/src/com/dre/brewery/integration/barrel/WGBarrel.java similarity index 81% rename from src/com/dre/brewery/integration/WGBarrel.java rename to src/com/dre/brewery/integration/barrel/WGBarrel.java index 25e29b9..033124b 100644 --- a/src/com/dre/brewery/integration/WGBarrel.java +++ b/src/com/dre/brewery/integration/barrel/WGBarrel.java @@ -1,4 +1,4 @@ -package com.dre.brewery.integration; +package com.dre.brewery.integration.barrel; import org.bukkit.block.Block; import org.bukkit.entity.Player; diff --git a/src/com/dre/brewery/integration/WGBarrel5.java b/src/com/dre/brewery/integration/barrel/WGBarrel5.java similarity index 97% rename from src/com/dre/brewery/integration/WGBarrel5.java rename to src/com/dre/brewery/integration/barrel/WGBarrel5.java index a71666a..0d0ebd1 100644 --- a/src/com/dre/brewery/integration/WGBarrel5.java +++ b/src/com/dre/brewery/integration/barrel/WGBarrel5.java @@ -1,4 +1,4 @@ -package com.dre.brewery.integration; +package com.dre.brewery.integration.barrel; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; diff --git a/src/com/dre/brewery/integration/WGBarrel6.java b/src/com/dre/brewery/integration/barrel/WGBarrel6.java similarity index 95% rename from src/com/dre/brewery/integration/WGBarrel6.java rename to src/com/dre/brewery/integration/barrel/WGBarrel6.java index f89fa53..d3b243b 100644 --- a/src/com/dre/brewery/integration/WGBarrel6.java +++ b/src/com/dre/brewery/integration/barrel/WGBarrel6.java @@ -1,4 +1,4 @@ -package com.dre.brewery.integration; +package com.dre.brewery.integration.barrel; import com.sk89q.worldguard.bukkit.RegionQuery; diff --git a/src/com/dre/brewery/integration/WGBarrel7.java b/src/com/dre/brewery/integration/barrel/WGBarrel7.java similarity index 98% rename from src/com/dre/brewery/integration/WGBarrel7.java rename to src/com/dre/brewery/integration/barrel/WGBarrel7.java index 0d1de5d..e06dc29 100644 --- a/src/com/dre/brewery/integration/WGBarrel7.java +++ b/src/com/dre/brewery/integration/barrel/WGBarrel7.java @@ -1,4 +1,4 @@ -package com.dre.brewery.integration; +package com.dre.brewery.integration.barrel; import org.bukkit.block.Block; diff --git a/src/com/dre/brewery/integration/item/BreweryPluginItem.java b/src/com/dre/brewery/integration/item/BreweryPluginItem.java new file mode 100644 index 0000000..a815612 --- /dev/null +++ b/src/com/dre/brewery/integration/item/BreweryPluginItem.java @@ -0,0 +1,30 @@ +package com.dre.brewery.integration.item; + +import com.dre.brewery.Brew; +import com.dre.brewery.recipe.BRecipe; +import com.dre.brewery.recipe.PluginItem; +import org.bukkit.ChatColor; +import org.bukkit.inventory.ItemStack; + +/** + * For recipes that use Brewery Items as input + */ +public class BreweryPluginItem extends PluginItem { + +// When implementing this, put Brewery as softdepend in your plugin.yml! +// We're calling this as server start: +// PluginItem.registerForConfig("brewery", BreweryPluginItem::new); + + @Override + public boolean matches(ItemStack item) { + Brew brew = Brew.get(item); + if (brew != null) { + BRecipe recipe = brew.getCurrentRecipe(); + if (recipe != null) { + return recipe.getRecipeName().equalsIgnoreCase(getItemId()) || recipe.getName(10).equalsIgnoreCase(getItemId()); + } + return ChatColor.stripColor(item.getItemMeta().getDisplayName()).equalsIgnoreCase(getItemId()); + } + return false; + } +} diff --git a/src/com/dre/brewery/integration/item/MMOItemsPluginItem.java b/src/com/dre/brewery/integration/item/MMOItemsPluginItem.java new file mode 100644 index 0000000..d4df22e --- /dev/null +++ b/src/com/dre/brewery/integration/item/MMOItemsPluginItem.java @@ -0,0 +1,32 @@ +package com.dre.brewery.integration.item; + +import com.dre.brewery.P; +import com.dre.brewery.filedata.BConfig; +import com.dre.brewery.recipe.PluginItem; +import net.Indyuce.mmoitems.MMOItems; +import net.Indyuce.mmoitems.api.item.NBTItem; +import org.bukkit.inventory.ItemStack; + +public class MMOItemsPluginItem extends PluginItem { + +// When implementing this, put Brewery as softdepend in your plugin.yml! +// We're calling this as server start: +// PluginItem.registerForConfig("mmoitems", MMOItemsPluginItem::new); + + @Override + public boolean matches(ItemStack item) { + if (BConfig.hasMMOItems == null) { + BConfig.hasMMOItems = P.p.getServer().getPluginManager().isPluginEnabled("MMOItems"); + } + if (!BConfig.hasMMOItems) return false; + + try { + NBTItem nbtItem = MMOItems.plugin.getNMS().getNBTItem(item); + return nbtItem.hasType() && nbtItem.getString("MMOITEMS_ITEM_ID").equalsIgnoreCase(getItemId()); + } catch (Throwable e) { + e.printStackTrace(); + P.p.errorLog("Could not check MMOItems for Item ID"); + return false; + } + } +} diff --git a/src/com/dre/brewery/integration/item/SlimefunPluginItem.java b/src/com/dre/brewery/integration/item/SlimefunPluginItem.java new file mode 100644 index 0000000..fe33094 --- /dev/null +++ b/src/com/dre/brewery/integration/item/SlimefunPluginItem.java @@ -0,0 +1,35 @@ +package com.dre.brewery.integration.item; + +import com.dre.brewery.P; +import com.dre.brewery.filedata.BConfig; +import com.dre.brewery.recipe.PluginItem; +import me.mrCookieSlime.Slimefun.Objects.SlimefunItem.SlimefunItem; +import org.bukkit.inventory.ItemStack; + +public class SlimefunPluginItem extends PluginItem { + +// When implementing this, put Brewery as softdepend in your plugin.yml! +// We're calling this as server start: +// PluginItem.registerForConfig("slimefun", SlimefunPluginItem::new); +// PluginItem.registerForConfig("exoticgarden", SlimefunPluginItem::new); + + @Override + public boolean matches(ItemStack item) { + if (BConfig.hasSlimefun == null) { + BConfig.hasSlimefun = P.p.getServer().getPluginManager().isPluginEnabled("Slimefun"); + } + if (!BConfig.hasSlimefun) return false; + + try { + SlimefunItem sfItem = SlimefunItem.getByItem(item); + if (sfItem != null) { + return sfItem.getID().equalsIgnoreCase(getItemId()); + } + } catch (Throwable e) { + e.printStackTrace(); + P.p.errorLog("Could not check Slimefun for Item ID"); + return false; + } + return false; + } +} diff --git a/src/com/dre/brewery/listeners/CommandListener.java b/src/com/dre/brewery/listeners/CommandListener.java index be9f50c..9ff7c1b 100644 --- a/src/com/dre/brewery/listeners/CommandListener.java +++ b/src/com/dre/brewery/listeners/CommandListener.java @@ -3,6 +3,7 @@ package com.dre.brewery.listeners; import com.dre.brewery.*; import com.dre.brewery.api.events.brew.BrewModifyEvent; import com.dre.brewery.filedata.BConfig; +import com.dre.brewery.recipe.BRecipe; import com.dre.brewery.utility.BUtil; import org.bukkit.Material; import org.bukkit.command.Command; diff --git a/src/com/dre/brewery/listeners/WorldListener.java b/src/com/dre/brewery/listeners/WorldListener.java index a945622..b19b03c 100644 --- a/src/com/dre/brewery/listeners/WorldListener.java +++ b/src/com/dre/brewery/listeners/WorldListener.java @@ -19,9 +19,9 @@ public class WorldListener implements Listener { World world = event.getWorld(); if (world.getName().startsWith("DXL_")) { - BData.loadWorldData(BUtil.getDxlName(world.getName()), world); + BData.loadWorldData(BUtil.getDxlName(world.getName()), world, null); } else { - BData.loadWorldData(world.getUID().toString(), world); + BData.loadWorldData(world.getUID().toString(), world, null); } } diff --git a/src/com/dre/brewery/lore/BrewLore.java b/src/com/dre/brewery/lore/BrewLore.java index 8251fa8..a7bf2eb 100644 --- a/src/com/dre/brewery/lore/BrewLore.java +++ b/src/com/dre/brewery/lore/BrewLore.java @@ -1,8 +1,8 @@ package com.dre.brewery.lore; -import com.dre.brewery.BEffect; +import com.dre.brewery.recipe.BEffect; import com.dre.brewery.BIngredients; -import com.dre.brewery.BRecipe; +import com.dre.brewery.recipe.BRecipe; import com.dre.brewery.Brew; import com.dre.brewery.P; import com.dre.brewery.filedata.BConfig; diff --git a/src/com/dre/brewery/BCauldronRecipe.java b/src/com/dre/brewery/recipe/BCauldronRecipe.java similarity index 67% rename from src/com/dre/brewery/BCauldronRecipe.java rename to src/com/dre/brewery/recipe/BCauldronRecipe.java index 6968829..de8cc6b 100644 --- a/src/com/dre/brewery/BCauldronRecipe.java +++ b/src/com/dre/brewery/recipe/BCauldronRecipe.java @@ -1,11 +1,10 @@ -package com.dre.brewery; +package com.dre.brewery.recipe; -import com.dre.brewery.utility.CustomItem; +import com.dre.brewery.P; import com.dre.brewery.utility.PotionColor; import com.dre.brewery.utility.Tuple; import org.bukkit.Material; import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -17,10 +16,12 @@ import java.util.stream.Collectors; public class BCauldronRecipe { public static List recipes = new ArrayList<>(); - public static Set acceptedMaterials = EnumSet.noneOf(Material.class); + public static List acceptedCustom = new ArrayList<>(); // All accepted custom and other items + public static Set acceptedSimple = EnumSet.noneOf(Material.class); // All accepted simple items + public static Set acceptedMaterials = EnumSet.noneOf(Material.class); // Fast cache for all accepted Materials private String name; - private List> ingredients; // Item and amount + private List ingredients; //private List particles private PotionColor color; private List lore; @@ -72,7 +73,7 @@ public class BCauldronRecipe { } @NotNull - public List> getIngredients() { + public List getIngredients() { return ingredients; } @@ -92,28 +93,26 @@ public class BCauldronRecipe { * If all Ingredients and their amounts are equal, returns 10 * Returns something between 0 and 10 if all ingredients present, but differing amounts, depending on how much the amount differs. */ - public float getIngredientMatch(List items) { + public float getIngredientMatch(List items) { if (items.size() < ingredients.size()) { return 0; } float match = 10; - search: for (Tuple ing : ingredients) { - for (ItemStack item : items) { - if (ing.a().matches(item)) { - double difference = Math.abs(ing.b() - item.getAmount()); + search: for (RecipeItem recipeIng : ingredients) { + for (Ingredient ing : items) { + if (recipeIng.matches(ing)) { + double difference = Math.abs(recipeIng.getAmount() - ing.getAmount()); if (difference >= 1000) { return 0; } // The Item Amount is the determining part here, the higher the better. // But let the difference in amount to what the recipe expects have a tiny factor as well. // This way for the same amount, the recipe with the lower difference wins. - double factor = item.getAmount() * (1.0 - (difference / 1000.0)) ; + double factor = ing.getAmount() * (1.0 - (difference / 1000.0)) ; //double mod = 0.1 + (0.9 * Math.exp(-0.03 * difference)); // logarithmic curve from 1 to 0.1 double mod = 1 + (0.9 * -Math.exp(-0.03 * factor)); // logarithmic curve from 0.1 to 1, small for a low factor - P.p.debugLog("Mod for " + ing.a() + "/" + ing.b() + ": " + mod); - assert mod >= 0.1; - assert mod <= 1; // TODO Test + P.p.debugLog("Mod for " + recipeIng + ": " + mod); @@ -138,4 +137,40 @@ public class BCauldronRecipe { public String toString() { return "BCauldronRecipe{" + name + '}'; } + + /*public static boolean acceptItem(ItemStack item) { + if (acceptedMaterials.contains(item.getType())) { + // Extremely fast way to check for most items + return true; + } + if (!item.hasItemMeta()) { + return false; + } + // If the Item is not on the list, but customized, we have to do more checks + ItemMeta meta = item.getItemMeta(); + assert meta != null; + if (meta.hasDisplayName() || meta.hasLore()) { + for (BItem bItem : acceptedCustom) { + if (bItem.matches(item)) { + return true; + } + } + } + return false; + } + + @Nullable + public static RecipeItem acceptItem(ItemStack item) { + if (!acceptedMaterials.contains(item.getType()) && !item.hasItemMeta()) { + // Extremely fast way to check for most items + return null; + } + // If the Item is on the list, or customized, we have to do more checks + for (RecipeItem rItem : acceptedItems) { + if (rItem.matches(item)) { + return rItem; + } + } + return null; + }*/ } diff --git a/src/com/dre/brewery/BEffect.java b/src/com/dre/brewery/recipe/BEffect.java similarity index 98% rename from src/com/dre/brewery/BEffect.java rename to src/com/dre/brewery/recipe/BEffect.java index 4d516bd..0dc282e 100644 --- a/src/com/dre/brewery/BEffect.java +++ b/src/com/dre/brewery/recipe/BEffect.java @@ -1,5 +1,6 @@ -package com.dre.brewery; +package com.dre.brewery.recipe; +import com.dre.brewery.P; import com.dre.brewery.utility.BUtil; import org.bukkit.entity.Player; import org.bukkit.inventory.meta.PotionMeta; diff --git a/src/com/dre/brewery/BRecipe.java b/src/com/dre/brewery/recipe/BRecipe.java similarity index 80% rename from src/com/dre/brewery/BRecipe.java rename to src/com/dre/brewery/recipe/BRecipe.java index 2e107ce..8d99f42 100644 --- a/src/com/dre/brewery/BRecipe.java +++ b/src/com/dre/brewery/recipe/BRecipe.java @@ -1,7 +1,9 @@ -package com.dre.brewery; +package com.dre.brewery.recipe; +import com.dre.brewery.BIngredients; +import com.dre.brewery.Brew; +import com.dre.brewery.P; import com.dre.brewery.filedata.BConfig; -import com.dre.brewery.utility.CustomItem; import com.dre.brewery.utility.PotionColor; import com.dre.brewery.utility.Tuple; import org.bukkit.Material; @@ -12,14 +14,13 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; public class BRecipe { public static List recipes = new ArrayList<>(); private String[] name; - private List> ingredients = new ArrayList<>(); // Items and amounts + private List ingredients = new ArrayList<>(); // Items and amounts private int cookingTime; // time to cook in cauldron private byte distillruns; // runs through the brewer private int distillTime; // time for one distill run in seconds @@ -96,7 +97,7 @@ public class BRecipe { return recipe; } - public static List> loadIngredients(ConfigurationSection cfg, String recipeId) { + public static List loadIngredients(ConfigurationSection cfg, String recipeId) { List ingredientsList; if (cfg.isString(recipeId + ".ingredients")) { ingredientsList = new ArrayList<>(1); @@ -107,7 +108,7 @@ public class BRecipe { if (ingredientsList == null) { return null; } - List> ingredients = new ArrayList<>(ingredientsList.size()); + List ingredients = new ArrayList<>(ingredientsList.size()); listLoop: for (String item : ingredientsList) { String[] ingredParts = item.split("/"); int amount = 1; @@ -121,19 +122,51 @@ public class BRecipe { String[] matParts; if (ingredParts[0].contains(",")) { matParts = ingredParts[0].split(","); - } else if (ingredParts[0].contains(":")) { - matParts = ingredParts[0].split(":"); } else if (ingredParts[0].contains(";")) { matParts = ingredParts[0].split(";"); } else { matParts = ingredParts[0].split("\\."); } + // Check if this is a Plugin Item + String[] pluginItem = matParts[0].split(":"); + if (pluginItem.length > 1) { + RecipeItem custom = PluginItem.fromConfig(pluginItem[0], pluginItem[1]); + if (custom != null) { + custom.setAmount(amount); + custom.makeImmutable(); + ingredients.add(custom); + BCauldronRecipe.acceptedCustom.add(custom); + continue; + } else { + // TODO Maybe load later ie on first use of recipe? + P.p.errorLog(recipeId + ": Could not Find Plugin: " + ingredParts[1]); + return null; + } + } + // Try to find this Ingredient as Custom Item - for (CustomItem custom : BConfig.customItems) { - if (custom.getId().equalsIgnoreCase(matParts[0])) { - ingredients.add(new Tuple<>(custom, amount)); - BCauldronRecipe.acceptedMaterials.addAll(custom.getMaterials()); + for (RecipeItem custom : BConfig.customItems) { + if (custom.getConfigId().equalsIgnoreCase(matParts[0])) { + custom = custom.getMutableCopy(); + custom.setAmount(amount); + custom.makeImmutable(); + ingredients.add(custom); + if (custom.hasMaterials()) { + BCauldronRecipe.acceptedMaterials.addAll(custom.getMaterials()); + } + // Add it as acceptedCustom + if (!BCauldronRecipe.acceptedCustom.contains(custom)) { + BCauldronRecipe.acceptedCustom.add(custom); + /*if (custom instanceof PluginItem || !custom.hasMaterials()) { + BCauldronRecipe.acceptedCustom.add(custom); + } else if (custom instanceof CustomMatchAnyItem) { + CustomMatchAnyItem ma = (CustomMatchAnyItem) custom; + if (ma.hasNames() || ma.hasLore()) { + BCauldronRecipe.acceptedCustom.add(ma); + } + }*/ + } continue listLoop; } } @@ -163,14 +196,17 @@ public class BRecipe { } } if (mat != null) { - CustomItem custom; + RecipeItem rItem; if (durability > -1) { - custom = CustomItem.asSimpleItem(mat, durability); + rItem = new SimpleItem(mat, durability); } else { - custom = CustomItem.asSimpleItem(mat); + rItem = new SimpleItem(mat); } - ingredients.add(new Tuple<>(custom, amount)); + rItem.setAmount(amount); + rItem.makeImmutable(); + ingredients.add(rItem); BCauldronRecipe.acceptedMaterials.add(mat); + BCauldronRecipe.acceptedSimple.add(mat); } else { P.p.errorLog(recipeId + ": Unknown Material: " + ingredParts[0]); return null; @@ -297,14 +333,14 @@ public class BRecipe { } // true if given list misses an ingredient - public boolean isMissingIngredients(List list) { + public boolean isMissingIngredients(List list) { if (list.size() < ingredients.size()) { return true; } - for (Tuple ingredient : ingredients) { + for (RecipeItem rItem : ingredients) { boolean matches = false; - for (ItemStack used : list) { - if (ingredient.a().matches(used)) { + for (Ingredient used : list) { + if (rItem.matches(used)) { matches = true; break; } @@ -327,13 +363,18 @@ public class BRecipe { } /** - * Create a Brew from this Recipe with best values. Quality can be set, but will reset to 10 if put in a barrel + * Create a Brew from this Recipe with best values. Quality can be set, but will reset to 10 if unset immutable and put in a barrel * * @param quality The Quality of the Brew * @return The created Brew */ public Brew createBrew(int quality) { - List list = ingredients.stream().map(ing -> ing.a().createDummy(ing.b())).collect(Collectors.toList()); + List list = new ArrayList<>(ingredients.size()); + for (RecipeItem rItem : ingredients) { + Ingredient ing = rItem.toIngredientGeneric(); + ing.setAmount(rItem.getAmount()); + list.add(ing); + } BIngredients bIngredients = new BIngredients(list, cookingTime); @@ -343,11 +384,21 @@ public class BRecipe { // Getter + // how many of a specific ingredient in the recipe + public int amountOf(Ingredient ing) { + for (RecipeItem rItem : ingredients) { + if (rItem.matches(ing)) { + return rItem.getAmount(); + } + } + return 0; + } + // how many of a specific ingredient in the recipe public int amountOf(ItemStack item) { - for (Tuple ingredient : ingredients) { - if (ingredient.a().matches(item)) { - return ingredient.b(); + for (RecipeItem rItem : ingredients) { + if (rItem.matches(item)) { + return rItem.getAmount(); } } return 0; diff --git a/src/com/dre/brewery/recipe/CustomItem.java b/src/com/dre/brewery/recipe/CustomItem.java new file mode 100644 index 0000000..dd83ab5 --- /dev/null +++ b/src/com/dre/brewery/recipe/CustomItem.java @@ -0,0 +1,291 @@ +package com.dre.brewery.recipe; + +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Minecraft Item with custon name and lore. + * Mostly used for Custom Items of the Config, but also for general custom items + */ +public class CustomItem extends RecipeItem implements Ingredient { + + private Material mat; + private String name; + private List lore; + + public CustomItem() { + } + + public CustomItem(Material mat) { + this.mat = mat; + } + + public CustomItem(Material mat, String name, List lore) { + this.mat = mat; + this.name = name; + this.lore = lore; + } + + public CustomItem(ItemStack item) { + mat = item.getType(); + if (!item.hasItemMeta()) { + return; + } + ItemMeta itemMeta = item.getItemMeta(); + assert itemMeta != null; + if (itemMeta.hasDisplayName()) { + name = itemMeta.getDisplayName(); + } + if (itemMeta.hasLore()) { + lore = itemMeta.getLore(); + } + } + + @Override + public boolean hasMaterials() { + return mat != null; + } + + public boolean hasName() { + return name != null; + } + + public boolean hasLore() { + return lore != null && !lore.isEmpty(); + } + + @Override + public List getMaterials() { + List l = new ArrayList<>(1); + l.add(mat); + return l; + } + + @Nullable + public Material getMaterial() { + return mat; + } + + protected void setMat(Material mat) { + this.mat = mat; + } + + @Nullable + public String getName() { + return name; + } + + protected void setName(String name) { + this.name = name; + } + + @Nullable + public List getLore() { + return lore; + } + + protected void setLore(List lore) { + this.lore = lore; + } + + @NotNull + @Override + public Ingredient toIngredient(ItemStack forItem) { + return ((CustomItem) getMutableCopy()); + } + + @NotNull + @Override + public Ingredient toIngredientGeneric() { + return ((CustomItem) getMutableCopy()); + } + + @Override + public boolean matches(Ingredient ingredient) { + if (isSimilar(ingredient)) { + return true; + } + if (ingredient instanceof RecipeItem) { + RecipeItem rItem = ((RecipeItem) ingredient); + if (rItem instanceof SimpleItem) { + // If the recipe item is just a simple item, only match if we also only define material + // If this is a custom item with more info, we don't want to match a simple item + return hasMaterials() && !hasLore() && !hasName() && getMaterial() == ((SimpleItem) rItem).getMaterial(); + } else if (rItem instanceof CustomItem) { + // If the other is a CustomItem as well and not Similar to ours, it might have more data and we still match + CustomItem other = ((CustomItem) rItem); + if (mat == null || mat == other.mat) { + if (!hasName() || (other.name != null && name.equalsIgnoreCase(other.name))) { + return !hasLore() || lore == other.lore || (other.hasLore() && matchLore(other.lore)); + } + } + } + } + return false; + } + + @Override + public boolean matches(ItemStack item) { + if (mat != null) { + if (item.getType() != mat) { + return false; + } + } + if (name == null && !hasLore()) { + return true; + } + if (!item.hasItemMeta()) { + return false; + } + ItemMeta meta = item.getItemMeta(); + assert meta != null; + if (name != null) { + if (!meta.hasDisplayName() || !name.equalsIgnoreCase(meta.getDisplayName())) { + return false; + } + } + + if (hasLore()) { + if (!meta.hasLore()) { + return false; + } + return matchLore(meta.getLore()); + } + return true; + } + + /** + * If this item has lore that matches the given lore. + * It matches if our lore is contained in the given lore consecutively, ignoring color of the given lore. + * + * @param usedLore The given lore to match + * @return True if the given lore contains our lore consecutively + */ + public boolean matchLore(List usedLore) { + if (lore == null) return true; + int lastIndex = 0; + boolean foundFirst = false; + for (String line : lore) { + do { + if (lastIndex == usedLore.size()) { + // There is more in lore than in usedLore, bad + return false; + } + String usedLine = usedLore.get(lastIndex); + if (line.equalsIgnoreCase(usedLine) || line.equalsIgnoreCase(ChatColor.stripColor(usedLine))) { + // If the line is correct, we have found our first and we want all consecutive lines to also equal + foundFirst = true; + } else if (foundFirst) { + // If a consecutive line is not equal, thats bad + return false; + } + lastIndex++; + // If we once found one correct line, iterate over 'lore' consecutively + } while (!foundFirst); + } + return true; + } + + // We don't compare id here + @Override + public boolean isSimilar(Ingredient item) { + if (this == item) { + return true; + } + if (item instanceof CustomItem) { + CustomItem ci = ((CustomItem) item); + return mat == ci.mat && Objects.equals(name, ci.name) && Objects.equals(lore, ci.lore); + } + return false; + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) return false; + if (obj instanceof CustomItem) { + return isSimilar(((CustomItem) obj)); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), mat, name, lore); + } + + @Override + public String toString() { + return "CustomItem{" + + "id=" + getConfigId() + + ", mat=" + (mat != null ? mat.name().toLowerCase() : "null") + + ", name='" + name + '\'' + + ", loresize: " + (lore != null ? lore.size() : 0) + + '}'; + } + + @Override + public void saveTo(DataOutputStream out) throws IOException { + out.writeUTF("CI"); + if (mat != null) { + out.writeBoolean(true); + out.writeUTF(mat.name()); + } else { + out.writeBoolean(false); + } + if (name != null) { + out.writeBoolean(true); + out.writeUTF(name); + } else { + out.writeBoolean(false); + } + if (lore != null) { + short size = (short) Math.min(lore.size(), Short.MAX_VALUE); + out.writeShort(size); + for (int i = 0; i < size; i++) { + out.writeUTF(lore.get(i)); + } + } else { + out.writeShort(0); + } + } + + public static CustomItem loadFrom(ItemLoader loader) { + try { + DataInputStream in = loader.getInputStream(); + CustomItem item = new CustomItem(); + if (in.readBoolean()) { + item.mat = Material.getMaterial(in.readUTF()); + } + if (in.readBoolean()) { + item.name = in.readUTF(); + } + short size = in.readShort(); + if (size > 0) { + item.lore = new ArrayList<>(size); + for (short i = 0; i < size; i++) { + item.lore.add(in.readUTF()); + } + } + return item; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + // Needs to be called at Server start + public static void registerItemLoader() { + Ingredient.registerForItemLoader("CI", CustomItem::loadFrom); + } +} diff --git a/src/com/dre/brewery/recipe/CustomMatchAnyItem.java b/src/com/dre/brewery/recipe/CustomMatchAnyItem.java new file mode 100644 index 0000000..840747d --- /dev/null +++ b/src/com/dre/brewery/recipe/CustomMatchAnyItem.java @@ -0,0 +1,231 @@ +package com.dre.brewery.recipe; + +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Custom Item that matches any one of the given info. + * Does not implement Ingredient, as it can not directly be added to an ingredient + */ +public class CustomMatchAnyItem extends RecipeItem { + + private List materials; + private List names; + private List lore; + + + @Override + public boolean hasMaterials() { + return materials != null && !materials.isEmpty(); + } + + public boolean hasNames() { + return names != null && !names.isEmpty(); + } + + public boolean hasLore() { + return lore != null && !lore.isEmpty(); + } + + @Override + @Nullable + public List getMaterials() { + return materials; + } + + protected void setMaterials(List materials) { + this.materials = materials; + } + + @Nullable + public List getNames() { + return names; + } + + protected void setNames(List names) { + this.names = names; + } + + @Nullable + public List getLore() { + return lore; + } + + protected void setLore(List lore) { + this.lore = lore; + } + + @NotNull + @Override + public Ingredient toIngredient(ItemStack forItem) { + // We only use the one part of this item that actually matched the given item to add to ingredients + Material mat = getMaterialMatch(forItem); + if (mat != null) { + return new CustomItem(mat); + } + String name = getNameMatch(forItem); + if (name != null) { + return new CustomItem(null, name, null); + } + String l = getLoreMatch(forItem); + if (l != null) { + List lore = new ArrayList<>(1); + lore.add(l); + return new CustomItem(null, null, lore); + } + + // Shouldnt happen + return new SimpleItem(Material.GOLDEN_HOE); + } + + @NotNull + @Override + public Ingredient toIngredientGeneric() { + if (hasMaterials()) { + return new CustomItem(materials.get(0)); + } + if (hasNames()) { + return new CustomItem(null, names.get(0), null); + } + if (hasLore()) { + List l = new ArrayList<>(1); + l.add(lore.get(0)); + return new CustomItem(null, null, l); + } + + // Shouldnt happen + return new SimpleItem(Material.GOLDEN_HOE); + } + + public Material getMaterialMatch(ItemStack item) { + if (!hasMaterials()) return null; + + Material usedMat = item.getType(); + for (Material mat : materials) { + if (usedMat == mat) { + return mat; + } + } + return null; + } + + public String getNameMatch(ItemStack item) { + if (!item.hasItemMeta() || !hasNames()) { + return null; + } + ItemMeta meta = item.getItemMeta(); + assert meta != null; + if (meta.hasDisplayName()) { + return getNameMatch(meta.getDisplayName()); + } + return null; + } + + public String getNameMatch(String usedName) { + if (!hasNames()) return null; + + for (String name : names) { + if (name.equalsIgnoreCase(usedName)) { + return name; + } + } + return null; + } + + public String getLoreMatch(ItemStack item) { + if (!item.hasItemMeta() || !hasLore()) { + return null; + } + ItemMeta meta = item.getItemMeta(); + assert meta != null; + if (meta.hasLore()) { + return getLoreMatch(meta.getLore()); + } + return null; + } + + public String getLoreMatch(List usedLore) { + if (!hasLore()) return null; + + for (String line : this.lore) { + for (String usedLine : usedLore) { + if (line.equalsIgnoreCase(usedLine) || line.equalsIgnoreCase(ChatColor.stripColor(usedLine))) { + return line; + } + } + } + return null; + } + + @Override + public boolean matches(ItemStack item) { + if (getMaterialMatch(item) != null) { + return true; + } + if (getNameMatch(item) != null) { + return true; + } + return getLoreMatch(item) != null; + } + + @Override + public boolean matches(Ingredient ingredient) { + // Ingredient can not be CustomMatchAnyItem, so we don't need to/can't check for similarity. + if (ingredient instanceof CustomItem) { + // If the custom item has any of our data, we match + CustomItem ci = ((CustomItem) ingredient); + if (hasMaterials() && ci.hasMaterials()) { + if (materials.contains(ci.getMaterial())) { + return true; + } + } + if (hasNames() && ci.hasName()) { + if (getNameMatch(ci.getName()) != null) { + return true; + } + } + if (hasLore() && ci.hasLore()) { + return getLoreMatch(ci.getLore()) != null; + } + } else if (ingredient instanceof SimpleItem) { + // If we contain the Material of the Simple Item, we match + SimpleItem si = (SimpleItem) ingredient; + return hasMaterials() && materials.contains(si.getMaterial()); + } + return false; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + CustomMatchAnyItem that = (CustomMatchAnyItem) o; + return Objects.equals(materials, that.materials) && + Objects.equals(names, that.names) && + Objects.equals(lore, that.lore); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), materials, names, lore); + } + + @Override + public String toString() { + return "CustomMatchAnyItem{" + + "id=" + getConfigId() + + ", materials: " + (materials != null ? materials.size() : 0) + + ", names:" + (names != null ? names.size() : 0) + + ", loresize: " + (lore != null ? lore.size() : 0) + + '}'; + } +} diff --git a/src/com/dre/brewery/recipe/Ingredient.java b/src/com/dre/brewery/recipe/Ingredient.java new file mode 100644 index 0000000..9d8ab64 --- /dev/null +++ b/src/com/dre/brewery/recipe/Ingredient.java @@ -0,0 +1,88 @@ +package com.dre.brewery.recipe; + +import org.bukkit.inventory.ItemStack; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +/** + * Item used in a BIngredients, inside BCauldron or Brew. + * Represents the Items used as ingredients in the Brewing process + * Can be a copy of a recipe item + * Will be saved and loaded with a DataStream + * Each implementing class needs to register a static function as Item Loader + */ +public interface Ingredient { + + Map> LOADERS = new HashMap<>(); + + /** + * Register a Static function as function that takes an ItemLoader, containing a DataInputStream. + * Using the Stream it constructs a corresponding Ingredient for the chosen SaveID + * + * @param saveID The SaveID should be a small identifier like "AB" + * @param loadFct The Static Function that loads the Item, i.e. + * public static AItem loadFrom(ItemLoader loader) + */ + static void registerForItemLoader(String saveID, Function loadFct) { + LOADERS.put(saveID, loadFct); + } + + /** + * Unregister the ItemLoader + * + * @param saveID the chosen SaveID + */ + static void unRegisterItemLoader(String saveID) { + LOADERS.remove(saveID); + } + + + /** + * Saves this Ingredient to the DataOutputStream. + * The first data HAS to be storing the SaveID like: + * out.writeUTF("AB"); + * Amount will be saved automatically and does not have to be saved here. + * Saving is done to Brew or for BCauldron into data.yml + * + * @param out The outputstream to write to + * @throws IOException Any IOException + */ + void saveTo(DataOutputStream out) throws IOException; + + int getAmount(); + + void setAmount(int amount); + + /** + * Does this Ingredient match the given ItemStack + * + * @param item The given ItemStack to match + * @return true if all required data is contained on the item + */ + boolean matches(ItemStack item); + + /* + * Does this Item match the given RecipeItem. + * An IngredientItem matches a RecipeItem if all required info of the RecipeItem are fulfilled on this IngredientItem + * This does not imply that the same holds the other way round, as this item might have more info than needed + * + * + * @param recipeItem The recipeItem whose requirements need to be fulfilled + * @return True if this matches the required info of the recipeItem + */ + //boolean matches(RecipeItem recipeItem); + + /** + * The other Ingredient is Similar if it is equal except amount + * + * @param item The item to check similarity with + * @return True if this is equal to item except for amount + */ + boolean isSimilar(Ingredient item); + + +} diff --git a/src/com/dre/brewery/recipe/ItemLoader.java b/src/com/dre/brewery/recipe/ItemLoader.java new file mode 100644 index 0000000..720a63e --- /dev/null +++ b/src/com/dre/brewery/recipe/ItemLoader.java @@ -0,0 +1,28 @@ +package com.dre.brewery.recipe; + +import java.io.DataInputStream; + +public class ItemLoader { + + private final int version; + private final DataInputStream in; + private final String saveID; + + public ItemLoader(int version, DataInputStream in, String saveID) { + this.version = version; + this.in = in; + this.saveID = saveID; + } + + public int getVersion() { + return version; + } + + public DataInputStream getInputStream() { + return in; + } + + public String getSaveID() { + return saveID; + } +} diff --git a/src/com/dre/brewery/recipe/PluginItem.java b/src/com/dre/brewery/recipe/PluginItem.java new file mode 100644 index 0000000..8234e87 --- /dev/null +++ b/src/com/dre/brewery/recipe/PluginItem.java @@ -0,0 +1,213 @@ +package com.dre.brewery.recipe; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; + +/** + * An Item of a Recipe or as Ingredient in a Brew that corresponds to an item from another plugin. + * See /integration/item for examples on how to extend this class. + * This class stores items as name of the plugin and item id + */ +public abstract class PluginItem extends RecipeItem implements Ingredient { + + private static Map> constructors = new HashMap<>(); + + private String plugin; + private String itemId; + + /** + * New Empty PluginItem + */ + public PluginItem() { + } + + /** + * New PluginItem with both fields already set + * + * @param plugin The name of the Plugin + * @param itemId The ItemID + */ + public PluginItem(String plugin, String itemId) { + this.plugin = plugin; + this.itemId = itemId; + } + + + @Override + public boolean hasMaterials() { + return false; + } + + @Override + public List getMaterials() { + return null; + } + + public String getPlugin() { + return plugin; + } + + public String getItemId() { + return itemId; + } + + protected void setPlugin(String plugin) { + this.plugin = plugin; + } + + protected void setItemId(String itemId) { + this.itemId = itemId; + } + + /** + * Called after Loading this Plugin Item from Config, or (by default) from Ingredients. + * Allows Override to define custom actions after an Item was constructed + */ + protected void onConstruct() { + } + + /** + * Does this PluginItem Match the other Ingredient. + * By default it matches exactly when they are similar, i.e. also a PluginItem with same parameters + * + * @param ingredient The ingredient that needs to fulfill the requirements + * @return True if the ingredient matches the required info of this + */ + @Override + public boolean matches(Ingredient ingredient) { + return isSimilar(ingredient); + } + + @NotNull + @Override + public Ingredient toIngredient(ItemStack forItem) { + return ((PluginItem) getMutableCopy()); + } + + @NotNull + @Override + public Ingredient toIngredientGeneric() { + return ((PluginItem) getMutableCopy()); + } + + @Override + public boolean isSimilar(Ingredient item) { + if (item instanceof PluginItem) { + return Objects.equals(plugin, ((PluginItem) item).plugin) && Objects.equals(itemId, ((PluginItem) item).itemId); + } + return false; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + PluginItem item = (PluginItem) o; + return Objects.equals(plugin, item.plugin) && + Objects.equals(itemId, item.itemId); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), plugin, itemId); + } + + @Override + public void saveTo(DataOutputStream out) throws IOException { + out.writeUTF("PI"); + out.writeUTF(plugin); + out.writeUTF(itemId); + } + + /** + * Called when loading this Plugin Item from Ingredients (of a Brew) + * The default loading is the same as loading from Config + * + * @param loader The ItemLoader from which to load the data, use loader.getInputStream() + * @return The constructed PluginItem + */ + public static PluginItem loadFrom(ItemLoader loader) { + try { + DataInputStream in = loader.getInputStream(); + String plugin = in.readUTF(); + String itemId = in.readUTF(); + PluginItem item = fromConfig(plugin, itemId); + if (item == null) { + // Plugin not found when loading from Item, use a generic PluginItem that never matches other items + item = new PluginItem(plugin, itemId) { + @Override + public boolean matches(ItemStack item) { + return false; + } + }; + } + return item; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * Needs to be called at Server start + * Registers the chosen SaveID and the loading Method for loading from Brew or BCauldron + */ + public static void registerItemLoader() { + Ingredient.registerForItemLoader("PI", PluginItem::loadFrom); + } + + + /** + * Called when loading trying to find a config defined Plugin Item, or by default also when loading from ingredients + * Will call a registered constructor matching the given plugin identifier + * + * @param plugin The Identifier of the Plugin used in the config + * @param itemId The Identifier of the Item belonging to this Plugin used in the config + * @return The Plugin Item if found, or null if there is no plugin for the given String + */ + @Nullable + public static PluginItem fromConfig(String plugin, String itemId) { + plugin = plugin.toLowerCase(); + if (constructors.containsKey(plugin)) { + PluginItem item = constructors.get(plugin).get(); + item.setPlugin(plugin); + item.setItemId(itemId); + item.onConstruct(); + return item; + } + return null; + } + + + /** + * This needs to be called at Server Start before Brewery loads its data. + * When implementing this, put Brewery as softdepend in your plugin.yml! + * Registers a Constructor that returns a new or cloned instance of a PluginItem + * This Constructor will be called when loading a Plugin Item from Config or by default from ingredients + * After the Constructor is called, the plugin and itemid will be set on the new instance + * Finally the onConstruct is called. + * + * @param pluginId The ID to use in the config + * @param constructor The constructor i.e. YourPluginItem::new + */ + public static void registerForConfig(String pluginId, Supplier constructor) { + constructors.put(pluginId.toLowerCase(), constructor); + } + + public static void unRegisterForConfig(String pluginId) { + constructors.remove(pluginId.toLowerCase()); + } + +} diff --git a/src/com/dre/brewery/recipe/RecipeItem.java b/src/com/dre/brewery/recipe/RecipeItem.java new file mode 100644 index 0000000..6ff6d67 --- /dev/null +++ b/src/com/dre/brewery/recipe/RecipeItem.java @@ -0,0 +1,314 @@ +package com.dre.brewery.recipe; + +import com.dre.brewery.P; +import com.dre.brewery.filedata.BConfig; +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * Item that can be used in a Recipe. + * They are not necessarily only loaded from config + * They are immutable if used in a recipe. If one implements Ingredient, + * it can be used as mutable copy directly in a + * BIngredients. Otherwise it needs to be converted to an Ingredient + */ +public abstract class RecipeItem implements Cloneable { + + private String cfgId; + private int amount; + private boolean immutable = false; + + + /** + * Does this RecipeItem match the given ItemStack? + * Used to determine if the given item corresponds to this recipeitem + * + * @param item The ItemStack for comparison + * @return True if the given item matches this recipeItem + */ + public abstract boolean matches(ItemStack item); + + /** + * Does this Item match the given Ingredient. + * A RecipeItem matches an Ingredient if all required info of the RecipeItem are fulfilled on the Ingredient + * This does not imply that the same holds the other way round, as the ingredient item might have more info than needed + * + * + * @param ingredient The ingredient that needs to fulfill the requirements + * @return True if the ingredient matches the required info of this + */ + public abstract boolean matches(Ingredient ingredient); + + /** + * Get the Corresponding Ingredient Item. For Items implementing Ingredient, just getMutableCopy() + * This is called when this recipe item is added to a BIngredients + * + * @param forItem The ItemStack that has previously matched this RecipeItem. Used if the resulting Ingredient needs more info from the ItemStack + * @return The IngredientItem corresponding to this RecipeItem + */ + @NotNull + public abstract Ingredient toIngredient(ItemStack forItem); + + /** + * Gets a Generic Ingredient for this recipe item + */ + @NotNull + public abstract Ingredient toIngredientGeneric(); + + /** + * @return True if this recipeItem has one or more materials that could classify an item. if true, getMaterials() is NotNull + */ + public abstract boolean hasMaterials(); + + /** + * @return List of one or more Materials this recipeItem uses. + */ + @Nullable + public abstract List getMaterials(); + + /** + * @return The Id this Item uses in the config in the custom-items section + */ + @Nullable + public String getConfigId() { + return cfgId; + } + + /** + * @return The Amount of this Item in a Recipe + */ + public int getAmount() { + return amount; + } + + /** + * Set the Amount of this Item in a Recipe + * The amount can not be set on an existing item in a recipe or existing custom item. + * To change amount you need to use getMutableCopy() and change the amount on the copy + * + * @param amount The new amount + */ + public void setAmount(int amount) { + if (immutable) throw new IllegalStateException("Setting amount only possible on mutable copy"); + this.amount = amount; + } + + /** + * Makes this Item immutable, for example when loaded from config. Used so if this is added to BIngredients, + * it needs to be cloned before changing anything like amount + */ + public void makeImmutable() { + immutable = true; + } + + /** + * Gets a shallow clone of this RecipeItem whose fields like amount can be changed. + * + * @return A mutable copy of this + */ + public RecipeItem getMutableCopy() { + try { + RecipeItem i = (RecipeItem) super.clone(); + i.immutable = false; + return i; + } catch (CloneNotSupportedException e) { + throw new InternalError(e); + } + } + + /** + * Tries to find a matching RecipeItem for this item. It checks custom items and if it has found a unique custom item + * it will return that. If there are multiple matching custom items, a new CustomItem with all item info is returned + * If there is no matching CustomItem, it will return a SimpleItem with the items type + * + * @param item The Item for which to find a matching RecipeItem + * @param acceptAll If true it will accept any item and return a SimpleItem even if not on the accepted list + * If false it will return null if the item is not acceptable by the Cauldron + * @return The Matched CustomItem, new CustomItem with all item info or SimpleItem + */ + @Nullable + @Contract("_, true -> !null") + public static RecipeItem getMatchingRecipeItem(ItemStack item, boolean acceptAll) { + RecipeItem rItem = null; + boolean multiMatch = false; + for (RecipeItem ri : BCauldronRecipe.acceptedCustom) { + // If we already have a multi match, only check if there is a PluginItem that matches more strictly + if (!multiMatch || (ri instanceof PluginItem)) { + if (ri.matches(item)) { + // If we match a plugin item, thats a very strict match, so immediately return it + if (ri instanceof PluginItem) { + return ri; + } + if (rItem == null) { + rItem = ri; + } else { + multiMatch = true; + } + } + } + } + if (multiMatch) { + // We have multiple Custom Items matching, so just store all item info + return new CustomItem(item); + } + if (rItem == null && (acceptAll || BCauldronRecipe.acceptedSimple.contains(item.getType()))) { + // No Custom item found + if (P.use1_13) { + return new SimpleItem(item.getType()); + } else { + //noinspection deprecation + return new SimpleItem(item.getType(), item.getDurability()); + } + } + return rItem; + } + + @Nullable + public static RecipeItem fromConfigCustom(ConfigurationSection cfg, String id) { + RecipeItem rItem; + if (cfg.getBoolean(id + ".matchAny", false)) { + rItem = new CustomMatchAnyItem(); + } else { + rItem = new CustomItem(); + } + + rItem.cfgId = id; + rItem.immutable = true; + + List materials; + List names; + List lore; + + List load = null; + String path = id + ".material"; + if (cfg.isString(path)) { + load = new ArrayList<>(1); + load.add(cfg.getString(path)); + } else if (cfg.isList(path)) { + load = cfg.getStringList(path); + } + if (load != null && !load.isEmpty()) { + if ((materials = loadMaterials(load)) == null) { + return null; + } + } else { + materials = new ArrayList<>(0); + } + + load = null; + path = id + ".name"; + if (cfg.isString(path)) { + load = new ArrayList<>(1); + load.add(cfg.getString(path)); + } else if (cfg.isList(path)) { + load = cfg.getStringList(path); + } + if (load != null && !load.isEmpty()) { + names = load.stream().map(l -> P.p.color(l)).collect(Collectors.toList()); + if (P.use1_13) { + // In 1.13 trailing Color white is removed from display names + names = names.stream().map(l -> l.startsWith("§f") ? l.substring(2) : l).collect(Collectors.toList()); + } + } else { + names = new ArrayList<>(0); + } + + load = null; + path = id + ".lore"; + if (cfg.isString(path)) { + load = new ArrayList<>(1); + load.add(cfg.getString(path)); + } else if (cfg.isList(path)) { + load = cfg.getStringList(path); + } + if (load != null && !load.isEmpty()) { + lore = load.stream().map(l -> P.p.color(l)).collect(Collectors.toList()); + } else { + lore = new ArrayList<>(0); + } + + if (materials.isEmpty() && names.isEmpty() && lore.isEmpty()) { + P.p.errorLog("No Config Entries found for Custom Item"); + return null; + } + + if (rItem instanceof CustomItem) { + CustomItem cItem = ((CustomItem) rItem); + if (!materials.isEmpty()) { + cItem.setMat(materials.get(0)); + } + if (!names.isEmpty()) { + cItem.setName(names.get(0)); + } + cItem.setLore(lore); + } else { + CustomMatchAnyItem maItem = (CustomMatchAnyItem) rItem; + maItem.setMaterials(materials); + maItem.setNames(names); + maItem.setLore(lore); + } + + return rItem; + } + + @Nullable + protected static List loadMaterials(List ingredientsList) { + List materials = new ArrayList<>(ingredientsList.size()); + for (String item : ingredientsList) { + String[] ingredParts = item.split("/"); + if (ingredParts.length == 2) { + P.p.errorLog("Item Amount can not be specified for Custom Items: " + item); + return null; + } + Material mat = Material.matchMaterial(ingredParts[0]); + + if (mat == null && BConfig.hasVault) { + try { + net.milkbowl.vault.item.ItemInfo vaultItem = net.milkbowl.vault.item.Items.itemByString(ingredParts[0]); + if (vaultItem != null) { + mat = vaultItem.getType(); + } + } catch (Exception e) { + P.p.errorLog("Could not check vault for Item Name"); + e.printStackTrace(); + } + } + if (mat != null) { + materials.add(mat); + } else { + P.p.errorLog("Unknown Material: " + ingredParts[0]); + return null; + } + } + return materials; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof RecipeItem)) return false; + RecipeItem that = (RecipeItem) o; + return amount == that.amount && + immutable == that.immutable && + Objects.equals(cfgId, that.cfgId); + } + + @Override + public int hashCode() { + return Objects.hash(cfgId, amount, immutable); + } + + @Override + public String toString() { + return "RecipeItem{(" + getClass().getSimpleName() + ") ID: " + getConfigId() + " Materials: " + (hasMaterials() ? getMaterials().size() : 0) + " Amount: " + getAmount(); + } +} diff --git a/src/com/dre/brewery/recipe/SimpleItem.java b/src/com/dre/brewery/recipe/SimpleItem.java new file mode 100644 index 0000000..db3a7a4 --- /dev/null +++ b/src/com/dre/brewery/recipe/SimpleItem.java @@ -0,0 +1,151 @@ +package com.dre.brewery.recipe; + +import com.dre.brewery.P; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Simple Minecraft Item with just Material + */ +public class SimpleItem extends RecipeItem implements Ingredient { + + private Material mat; + private short dur; // Old Mc + + + public SimpleItem(Material mat) { + this(mat, (short) 0); + } + + public SimpleItem(Material mat, short dur) { + this.mat = mat; + this.dur = dur; + } + + @Override + public boolean hasMaterials() { + return mat != null; + } + + public Material getMaterial() { + return mat; + } + + @Override + public List getMaterials() { + List l = new ArrayList<>(1); + l.add(mat); + return l; + } + + @NotNull + @Override + public Ingredient toIngredient(ItemStack forItem) { + return ((SimpleItem) getMutableCopy()); + } + + @NotNull + @Override + public Ingredient toIngredientGeneric() { + return ((SimpleItem) getMutableCopy()); + } + + @Override + public boolean matches(ItemStack item) { + if (!mat.equals(item.getType())) { + return false; + } + //noinspection deprecation + return P.use1_13 || dur == item.getDurability(); + } + + @Override + public boolean matches(Ingredient ingredient) { + if (isSimilar(ingredient)) { + return true; + } + if (ingredient instanceof RecipeItem) { + if (!((RecipeItem) ingredient).hasMaterials()) { + return false; + } + if (ingredient instanceof CustomItem) { + // Only match if the Custom Item also only defines material + // If the custom item has more info like name and lore, it is not supposed to match a simple item + CustomItem ci = (CustomItem) ingredient; + return !ci.hasLore() && !ci.hasName() && mat == ci.getMaterial(); + } + } + return false; + } + + @Override + public boolean isSimilar(Ingredient item) { + if (this == item) { + return true; + } + if (item instanceof SimpleItem) { + SimpleItem si = ((SimpleItem) item); + return si.mat == mat && si.dur == dur; + } + return false; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + SimpleItem item = (SimpleItem) o; + return dur == item.dur && + mat == item.mat; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), mat, dur); + } + + @Override + public String toString() { + return "SimpleItem{" + + "mat=" + mat.name().toLowerCase() + + " amount=" + getAmount() + + '}'; + } + + @Override + public void saveTo(DataOutputStream out) throws IOException { + out.writeUTF("SI"); + out.writeUTF(mat.name()); + out.writeShort(dur); + } + + public static SimpleItem loadFrom(ItemLoader loader) { + try { + DataInputStream in = loader.getInputStream(); + Material mat = Material.getMaterial(in.readUTF()); + if (mat != null) { + SimpleItem item = new SimpleItem(mat, in.readShort()); + return item; + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + // Needs to be called at Server start + public static void registerItemLoader() { + Ingredient.registerForItemLoader("SI", SimpleItem::loadFrom); + } + +} + diff --git a/src/com/dre/brewery/utility/CustomItem.java b/src/com/dre/brewery/utility/CustomItem.java deleted file mode 100644 index 8ec6849..0000000 --- a/src/com/dre/brewery/utility/CustomItem.java +++ /dev/null @@ -1,329 +0,0 @@ -package com.dre.brewery.utility; - -import com.dre.brewery.P; -import com.dre.brewery.filedata.BConfig; -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -public class CustomItem { - private String id; - private boolean simple; // Simple Custom Item is just materials.get(0) and durability for old mc - private boolean matchAny; // If only one of the values needs to match - private short dur; // Old Mc - private List materials; - private List names; - private List lore; - - public static CustomItem asSimpleItem(Material mat) { - return asSimpleItem(mat, (short) 0); - } - - public static CustomItem asSimpleItem(Material mat, short dur) { - CustomItem it = new CustomItem(); - it.simple = true; - it.dur = dur; - it.materials = new ArrayList<>(1); - it.materials.add(mat); - return it; - } - - @Nullable - public static CustomItem fromConfig(ConfigurationSection cfg, String id) { - CustomItem custom = new CustomItem(); - - custom.id = id; - custom.matchAny = cfg.getBoolean(id + ".matchAny", false); - - List load = null; - String path = id + ".material"; - if (cfg.isString(path)) { - load = new ArrayList<>(1); - load.add(cfg.getString(path)); - } else if (cfg.isList(path)) { - load = cfg.getStringList(path); - } - if (load != null && !load.isEmpty()) { - custom.materials = new ArrayList<>(load.size()); - if (!custom.loadMaterials(load)) { - return null; - } - } else { - custom.materials = new ArrayList<>(0); - } - - load = null; - path = id + ".name"; - if (cfg.isString(path)) { - load = new ArrayList<>(1); - load.add(cfg.getString(path)); - } else if (cfg.isList(path)) { - load = cfg.getStringList(path); - } - if (load != null && !load.isEmpty()) { - custom.names = load.stream().map(l -> P.p.color(l)).collect(Collectors.toList()); - if (P.use1_13) { - // In 1.13 trailing Color white is removed from display names - custom.names = custom.names.stream().map(l -> l.startsWith("§f") ? l.substring(2) : l).collect(Collectors.toList()); - } - } else { - custom.names = new ArrayList<>(0); - } - - load = null; - path = id + ".lore"; - if (cfg.isString(path)) { - load = new ArrayList<>(1); - load.add(cfg.getString(path)); - } else if (cfg.isList(path)) { - load = cfg.getStringList(path); - } - if (load != null && !load.isEmpty()) { - custom.lore = load.stream().map(l -> P.p.color(l)).collect(Collectors.toList()); - } else { - custom.lore = new ArrayList<>(0); - } - - if (custom.materials.isEmpty() && custom.names.isEmpty() && custom.lore.isEmpty()) { - P.p.errorLog("No Config Entries found for Custom Item"); - return null; - } - - return custom; - } - - private boolean loadMaterials(List ingredientsList) { - for (String item : ingredientsList) { - String[] ingredParts = item.split("/"); - if (ingredParts.length == 2) { - P.p.errorLog("Item Amount can not be specified for Custom Items: " + item); - return false; - } - Material mat = Material.matchMaterial(ingredParts[0]); - - if (mat == null && BConfig.hasVault) { - try { - net.milkbowl.vault.item.ItemInfo vaultItem = net.milkbowl.vault.item.Items.itemByString(ingredParts[0]); - if (vaultItem != null) { - mat = vaultItem.getType(); - } - } catch (Exception e) { - P.p.errorLog("Could not check vault for Item Name"); - e.printStackTrace(); - } - } - if (mat != null) { - materials.add(mat); - } else { - P.p.errorLog("Unknown Material: " + ingredParts[0]); - return false; - } - } - return true; - } - - public String getId() { - return id; - } - - public boolean isSimple() { - return simple; - } - - public boolean isMatchAny() { - return matchAny; - } - - public List getMaterials() { - return materials; - } - - public Material getSimpleMaterial() { - return materials.get(0); - } - - public List getNames() { - return names; - } - - public List getLore() { - return lore; - } - - public boolean matches(ItemStack usedItem) { - if (simple) { - return matchSimple(usedItem); - } else if (matchAny){ - return matchAny(usedItem); - } else { - return matchOne(usedItem); - } - } - - private boolean matchSimple(ItemStack usedItem) { - if (!materials.get(0).equals(usedItem.getType())) { - return false; - } - //noinspection deprecation - return P.use1_13 || dur == usedItem.getDurability(); - } - - private boolean matchAny(ItemStack usedItem) { - Material usedMat = usedItem.getType(); - for (Material mat : materials) { - if (usedMat == mat) { - return true; - } - } - if (!usedItem.hasItemMeta()) { - return false; - } - ItemMeta meta = usedItem.getItemMeta(); - assert meta != null; - if (meta.hasDisplayName()) { - String usedName = meta.getDisplayName(); - for (String name : names) { - if (name.equalsIgnoreCase(usedName)) { - return true; - } - } - } - - if (meta.hasLore()) { - List usedLore = meta.getLore(); - assert usedLore != null; - for (String line : this.lore) { - for (String usedLine : usedLore) { - if (line.equalsIgnoreCase(usedLine) || line.equalsIgnoreCase(ChatColor.stripColor(usedLine))) { - return true; - } - } - } - } - return false; - } - - private boolean matchOne(ItemStack item) { - if (!materials.isEmpty()) { - if (item.getType() != materials.get(0)) { - return false; - } - } - if (names.isEmpty() && lore.isEmpty()) { - return true; - } - if (!item.hasItemMeta()) { - return false; - } - ItemMeta meta = item.getItemMeta(); - assert meta != null; - if (!names.isEmpty()) { - if (!meta.hasDisplayName() || !names.get(0).equalsIgnoreCase(meta.getDisplayName())) { - return false; - } - } - - - if (!lore.isEmpty()) { - if (!meta.hasLore()) { - return false; - } - - int lastIndex = 0; - List usedLore = meta.getLore(); - assert usedLore != null; - boolean foundFirst = false; - for (String line : lore) { - do { - if (lastIndex == usedLore.size()) { - // There is more in lore than in usedLore, bad - return false; - } - String usedLine = usedLore.get(lastIndex); - if (line.equalsIgnoreCase(usedLine) || line.equalsIgnoreCase(ChatColor.stripColor(usedLine))) { - // If the line is correct, we have found our first and we want all consecutive lines to also equal - foundFirst = true; - } else if (foundFirst) { - // If a consecutive line is not equal, thats bad - return false; - } - lastIndex++; - // If we once found one correct line, iterate over 'lore' consecutively - } while (!foundFirst); - } - } - return true; - } - - @NotNull - public ItemStack createDummy(int amount) { - if (simple) { - if (P.use1_13) { - return new ItemStack(getSimpleMaterial(), amount); - } else { - //noinspection deprecation - return new ItemStack(getSimpleMaterial(), amount, dur); - } - } else if (matchAny) { - if (!materials.isEmpty()) { - return new ItemStack(materials.get(0), amount); - } else if (!names.isEmpty()) { - ItemStack item = new ItemStack(Material.DIAMOND_HOE, amount); - ItemMeta meta = item.hasItemMeta() ? item.getItemMeta() : P.p.getServer().getItemFactory().getItemMeta(Material.DIAMOND_HOE); - assert meta != null; - meta.setDisplayName(names.get(0)); - item.setItemMeta(meta); - return item; - } else if (!lore.isEmpty()) { - ItemStack item = new ItemStack(Material.DIAMOND_HOE, amount); - ItemMeta meta = item.hasItemMeta() ? item.getItemMeta() : P.p.getServer().getItemFactory().getItemMeta(Material.DIAMOND_HOE); - assert meta != null; - List l = new ArrayList<>(); - l.add(lore.get(0)); - meta.setLore(l); - item.setItemMeta(meta); - return item; - } - return new ItemStack(Material.DIAMOND_HOE, amount); - } else { - ItemStack item; - ItemMeta meta; - if (!materials.isEmpty()) { - item = new ItemStack(materials.get(0), amount); - meta = item.hasItemMeta() ? item.getItemMeta() : P.p.getServer().getItemFactory().getItemMeta(materials.get(0)); - } else { - item = new ItemStack(Material.DIAMOND_HOE, amount); - meta = item.hasItemMeta() ? item.getItemMeta() : P.p.getServer().getItemFactory().getItemMeta(Material.DIAMOND_HOE); - } - assert meta != null; - if (!names.isEmpty()) { - meta.setDisplayName(names.get(0)); - item.setItemMeta(meta); - } - if (!lore.isEmpty()) { - meta.setLore(lore); - item.setItemMeta(meta); - } - return item; - } - } - - @Override - public String toString() { - if (simple) { - return "CustomItem{Simple: " + getSimpleMaterial().name().toLowerCase() + "}"; - } - if (materials == null || names == null || lore == null) { - return "CustomItem{" + id + "}"; - } - return "CustomItem{" + id + ": " + (matchAny ? "MatchAny, " : "MatchOne, ") + materials.size() + " Materials, " + names.size() + " Names, " + lore.size() + " Lore}"; - } -} diff --git a/test/com/dre/brewery/RecipeTests.java b/test/com/dre/brewery/RecipeTests.java index 284d325..6b9e63c 100644 --- a/test/com/dre/brewery/RecipeTests.java +++ b/test/com/dre/brewery/RecipeTests.java @@ -1,5 +1,10 @@ package com.dre.brewery; +import com.dre.brewery.recipe.BCauldronRecipe; +import com.dre.brewery.recipe.BRecipe; +import com.dre.brewery.recipe.Ingredient; +import com.dre.brewery.recipe.RecipeItem; +import com.dre.brewery.recipe.SimpleItem; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; @@ -24,9 +29,12 @@ public class RecipeTests { int y = recipe.amountOf(new ItemStack(Material.NETHER_BRICK)); - List list = new ArrayList<>(); - list.add(new ItemStack(Material.DIAMOND_HOE, 3)); - list.add(new ItemStack(Material.RED_MUSHROOM, 1)); + List list = new ArrayList<>(); + Ingredient ing = new SimpleItem(Material.DIAMOND_HOE); + ing.setAmount(3); + list.add(ing); + ing = new SimpleItem(Material.RED_MUSHROOM); + list.add(ing); for (int i = 1; i < 20; i++) { list.get(0).setAmount(i + 3); list.get(1).setAmount(i); @@ -46,5 +54,16 @@ public class RecipeTests { } P.p.debugLog("Found best for i:" + i + " " + best); } + + item = new ItemStack(Material.BARRIER); + itemMeta = item.getItemMeta(); + l = new ArrayList<>(); + l.add("Eine Tür"); + l.add("§6Besonders gut geschützt"); + itemMeta.setLore(l); + itemMeta.setDisplayName("Mauer"); + item.setItemMeta(itemMeta); + + RecipeItem.getMatchingRecipeItem(item, false); } }