package com.dre.brewery.recipe; import com.dre.brewery.P; import com.dre.brewery.filedata.BConfig; import com.dre.brewery.utility.BUtil; 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 = BUtil.loadCfgStringList(cfg, id + ".material"); if (load != null && !load.isEmpty()) { if ((materials = loadMaterials(load)) == null) { return null; } } else { materials = new ArrayList<>(0); } load = BUtil.loadCfgStringList(cfg, id + ".name"); 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 = BUtil.loadCfgStringList(cfg, id + ".lore"); 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(); } }