Rewrite of the Custom item into RecipeItem

and Ingredient with subclasses
Implemented adding custom items to Ingredients
Added support for plugin-items
This commit is contained in:
Sn0wStorm
2019-11-06 19:44:52 +01:00
parent d0263c611c
commit 7f2f9d75dd
41 changed files with 2010 additions and 624 deletions
@@ -0,0 +1,176 @@
package com.dre.brewery.recipe;
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.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class BCauldronRecipe {
public static List<BCauldronRecipe> recipes = new ArrayList<>();
public static List<RecipeItem> acceptedCustom = new ArrayList<>(); // All accepted custom and other items
public static Set<Material> acceptedSimple = EnumSet.noneOf(Material.class); // All accepted simple items
public static Set<Material> acceptedMaterials = EnumSet.noneOf(Material.class); // Fast cache for all accepted Materials
private String name;
private List<RecipeItem> ingredients;
//private List<String> particles
private PotionColor color;
private List<String> lore;
@Nullable
public static BCauldronRecipe fromConfig(ConfigurationSection cfg, String id) {
BCauldronRecipe recipe = new BCauldronRecipe();
recipe.name = cfg.getString(id + ".name");
if (recipe.name != null) {
recipe.name = P.p.color(recipe.name);
} else {
P.p.errorLog("Missing name for Cauldron-Recipe: " + id);
return null;
}
recipe.ingredients = BRecipe.loadIngredients(cfg, id);
if (recipe.ingredients == null || recipe.ingredients.isEmpty()) {
P.p.errorLog("No ingredients for Cauldron-Recipe: " + recipe.name);
return null;
}
String col = cfg.getString(id + ".color");
if (col != null) {
recipe.color = PotionColor.fromString(col);
} else {
recipe.color = PotionColor.CYAN;
}
if (recipe.color == PotionColor.WATER && !col.equals("WATER")) {
recipe.color = PotionColor.CYAN;
// Don't throw error here as old mc versions will not know even the default colors
//P.p.errorLog("Invalid Color '" + col + "' in Cauldron-Recipe: " + recipe.name);
//return null;
}
List<Tuple<Integer,String>> lore = BRecipe.loadLore(cfg, id + ".lore");
if (lore != null && !lore.isEmpty()) {
recipe.lore = lore.stream().map(Tuple::second).collect(Collectors.toList());
}
return recipe;
}
@NotNull
public String getName() {
return name;
}
@NotNull
public List<RecipeItem> getIngredients() {
return ingredients;
}
@NotNull
public PotionColor getColor() {
return color;
}
@Nullable
public List<String> getLore() {
return lore;
}
/**
* Find how much these ingredients match the given ones from 0-10.
* If any ingredient is missing, returns 0
* 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<Ingredient> items) {
if (items.size() < ingredients.size()) {
return 0;
}
float match = 10;
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 = 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 " + recipeIng + ": " + mod);
match *= mod;
continue search;
}
}
return 0;
}
if (items.size() > ingredients.size()) {
// If there are too many items in the List, multiply the match by 0.1 per Item thats too much
float tooMuch = items.size() - ingredients.size();
float mod = 0.1f / tooMuch;
match *= mod;
}
P.p.debugLog("Match for Cauldron Recipe " + name + ": " + match);
return match;
}
@Override
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;
}*/
}
+132
View File
@@ -0,0 +1,132 @@
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;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
public class BEffect {
private PotionEffectType type;
private short minlvl;
private short maxlvl;
private short minduration;
private short maxduration;
private boolean hidden = false;
public BEffect(String effectString) {
String[] effectSplit = effectString.split("/");
String effect = effectSplit[0];
if (effect.equalsIgnoreCase("WEAKNESS") ||
effect.equalsIgnoreCase("INCREASE_DAMAGE") ||
effect.equalsIgnoreCase("SLOW") ||
effect.equalsIgnoreCase("SPEED") ||
effect.equalsIgnoreCase("REGENERATION")) {
// hide these effects as they put crap into lore
// Dont write Regeneration into Lore, its already there storing data!
hidden = true;
} else if (effect.endsWith("X")) {
hidden = true;
effect = effect.substring(0, effect.length() - 1);
}
type = PotionEffectType.getByName(effect);
if (type == null) {
P.p.errorLog("Effect: " + effect + " does not exist!");
return;
}
if (effectSplit.length == 3) {
String[] range = effectSplit[1].split("-");
if (type.isInstant()) {
setLvl(range);
} else {
setLvl(range);
range = effectSplit[2].split("-");
setDuration(range);
}
} else if (effectSplit.length == 2) {
String[] range = effectSplit[1].split("-");
if (type.isInstant()) {
setLvl(range);
} else {
setDuration(range);
maxlvl = 3;
minlvl = 1;
}
} else {
maxduration = 20;
minduration = 10;
maxlvl = 3;
minlvl = 1;
}
}
private void setLvl(String[] range) {
if (range.length == 1) {
maxlvl = (short) P.p.parseInt(range[0]);
minlvl = 1;
} else {
maxlvl = (short) P.p.parseInt(range[1]);
minlvl = (short) P.p.parseInt(range[0]);
}
}
private void setDuration(String[] range) {
if (range.length == 1) {
maxduration = (short) P.p.parseInt(range[0]);
minduration = (short) (maxduration / 8);
} else {
maxduration = (short) P.p.parseInt(range[1]);
minduration = (short) P.p.parseInt(range[0]);
}
}
public PotionEffect generateEffect(int quality) {
int duration = calcDuration(quality);
int lvl = calcLvl(quality);
if (lvl < 1 || (duration < 1 && !type.isInstant())) {
return null;
}
duration *= 20;
if (!P.use1_14) {
duration /= type.getDurationModifier();
}
return type.createEffect(duration, lvl - 1);
}
public void apply(int quality, Player player) {
PotionEffect effect = generateEffect(quality);
if (effect != null) {
BUtil.reapplyPotionEffect(player, effect, true);
}
}
public int calcDuration(float quality) {
return (int) Math.round(minduration + ((maxduration - minduration) * (quality / 10.0)));
}
public int calcLvl(float quality) {
return (int) Math.round(minlvl + ((maxlvl - minlvl) * (quality / 10.0)));
}
public void writeInto(PotionMeta meta, int quality) {
if ((calcDuration(quality) > 0 || type.isInstant()) && calcLvl(quality) > 0) {
meta.addCustomEffect(type.createEffect(0, 0), true);
} else {
meta.removeCustomEffect(type);
}
}
public boolean isValid() {
return type != null && minlvl >= 0 && maxlvl >= 0 && minduration >= 0 && maxduration >= 0;
}
public boolean isHidden() {
return hidden;
}
}
+517
View File
@@ -0,0 +1,517 @@
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.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;
import java.util.ArrayList;
import java.util.List;
public class BRecipe {
public static List<BRecipe> recipes = new ArrayList<>();
private String[] name;
private List<RecipeItem> 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
private byte wood; // type of wood the barrel has to consist of
private int age; // time in minecraft days for the potions to age in barrels
private PotionColor color; // color of the destilled/finished potion
private int difficulty; // difficulty to brew the potion, how exact the instruction has to be followed
private int alcohol; // Alcohol in perfect potion
private List<Tuple<Integer, String>> lore; // Custom Lore on the Potion. The int is for Quality Lore, 0 = any, 1,2,3 = Bad,Middle,Good
private ArrayList<BEffect> effects = new ArrayList<>(); // Special Effects when drinking
public BRecipe() {
}
@Nullable
public static BRecipe fromConfig(ConfigurationSection configSectionRecipes, String recipeId) {
BRecipe recipe = new BRecipe();
String nameList = configSectionRecipes.getString(recipeId + ".name");
if (nameList != null) {
String[] name = nameList.split("/");
if (name.length > 2) {
recipe.name = name;
} else {
recipe.name = new String[1];
recipe.name[0] = name[0];
}
} else {
P.p.errorLog(recipeId + ": Recipe Name missing or invalid!");
return null;
}
if (recipe.getRecipeName() == null || recipe.getRecipeName().length() < 1) {
P.p.errorLog(recipeId + ": Recipe Name invalid");
return null;
}
recipe.ingredients = loadIngredients(configSectionRecipes, recipeId);
if (recipe.ingredients == null || recipe.ingredients.isEmpty()) {
P.p.errorLog("No ingredients for: " + recipe.getRecipeName());
return null;
}
recipe.cookingTime = configSectionRecipes.getInt(recipeId + ".cookingtime", 1);
int dis = configSectionRecipes.getInt(recipeId + ".distillruns", 0);
if (dis > Byte.MAX_VALUE) {
recipe.distillruns = Byte.MAX_VALUE;
} else {
recipe.distillruns = (byte) dis;
}
recipe.distillTime = configSectionRecipes.getInt(recipeId + ".distilltime", 0) * 20;
recipe.wood = (byte) configSectionRecipes.getInt(recipeId + ".wood", 0);
recipe.age = configSectionRecipes.getInt(recipeId + ".age", 0);
recipe.difficulty = configSectionRecipes.getInt(recipeId + ".difficulty", 0);
recipe.alcohol = configSectionRecipes.getInt(recipeId + ".alcohol", 0);
String col = configSectionRecipes.getString(recipeId + ".color", "BLUE");
recipe.color = PotionColor.fromString(col);
if (recipe.color == PotionColor.WATER && !col.equals("WATER")) {
P.p.errorLog("Invalid Color '" + col + "' in Recipe: " + recipe.getRecipeName());
return null;
}
recipe.lore = loadLore(configSectionRecipes, recipeId + ".lore");
List<String> effectStringList = configSectionRecipes.getStringList(recipeId + ".effects");
if (effectStringList != null) {
for (String effectString : effectStringList) {
BEffect effect = new BEffect(effectString);
if (effect.isValid()) {
recipe.effects.add(effect);
} else {
P.p.errorLog("Error adding Effect to Recipe: " + recipe.getRecipeName());
}
}
}
return recipe;
}
public static List<RecipeItem> loadIngredients(ConfigurationSection cfg, String recipeId) {
List<String> ingredientsList;
if (cfg.isString(recipeId + ".ingredients")) {
ingredientsList = new ArrayList<>(1);
ingredientsList.add(cfg.getString(recipeId + ".ingredients", "x"));
} else {
ingredientsList = cfg.getStringList(recipeId + ".ingredients");
}
if (ingredientsList == null) {
return null;
}
List<RecipeItem> ingredients = new ArrayList<>(ingredientsList.size());
listLoop: for (String item : ingredientsList) {
String[] ingredParts = item.split("/");
int amount = 1;
if (ingredParts.length == 2) {
amount = P.p.parseInt(ingredParts[1]);
if (amount < 1) {
P.p.errorLog(recipeId + ": Invalid Item Amount: " + ingredParts[1]);
return null;
}
}
String[] matParts;
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 (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;
}
}
Material mat = Material.matchMaterial(matParts[0]);
short durability = -1;
if (matParts.length == 2) {
durability = (short) P.p.parseInt(matParts[1]);
}
if (mat == null && BConfig.hasVault) {
try {
net.milkbowl.vault.item.ItemInfo vaultItem = net.milkbowl.vault.item.Items.itemByString(matParts[0]);
if (vaultItem != null) {
mat = vaultItem.getType();
if (durability == -1 && vaultItem.getSubTypeId() != 0) {
durability = vaultItem.getSubTypeId();
}
if (mat.name().contains("LEAVES")) {
if (durability > 3) {
durability -= 4; // Vault has leaves with higher durability
}
}
}
} catch (Exception e) {
P.p.errorLog("Could not check vault for Item Name");
e.printStackTrace();
}
}
if (mat != null) {
RecipeItem rItem;
if (durability > -1) {
rItem = new SimpleItem(mat, durability);
} else {
rItem = new SimpleItem(mat);
}
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;
}
}
return ingredients;
}
@Nullable
public static List<Tuple<Integer, String>> loadLore(ConfigurationSection cfg, String path) {
List<String> load = null;
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) {
List<Tuple<Integer, String>> lore = new ArrayList<>(load.size());
for (String line : load) {
line = P.p.color(line);
int plus = 0;
if (line.startsWith("+++")) {
plus = 3;
line = line.substring(3);
} else if (line.startsWith("++")) {
plus = 2;
line = line.substring(2);
} else if (line.startsWith("+")) {
plus = 1;
line = line.substring(1);
}
if (line.startsWith(" ")) {
line = line.substring(1);
}
if (!line.startsWith("§")) {
line = "§9" + line;
}
lore.add(new Tuple<>(plus, line));
}
return lore;
}
return null;
}
// check every part of the recipe for validity
public boolean isValid() {
if (ingredients == null || ingredients.isEmpty()) {
P.p.errorLog("No ingredients could be loaded for Recipe: " + getRecipeName());
return false;
}
if (cookingTime < 1) {
P.p.errorLog("Invalid cooking time '" + cookingTime + "' in Recipe: " + getRecipeName());
return false;
}
if (distillruns < 0) {
P.p.errorLog("Invalid distillruns '" + distillruns + "' in Recipe: " + getRecipeName());
return false;
}
if (distillTime < 0) {
P.p.errorLog("Invalid distilltime '" + distillTime + "' in Recipe: " + getRecipeName());
return false;
}
if (wood < 0 || wood > 6) {
P.p.errorLog("Invalid wood type '" + wood + "' in Recipe: " + getRecipeName());
return false;
}
if (age < 0) {
P.p.errorLog("Invalid age time '" + age + "' in Recipe: " + getRecipeName());
return false;
}
if (difficulty < 0 || difficulty > 10) {
P.p.errorLog("Invalid difficulty '" + difficulty + "' in Recipe: " + getRecipeName());
return false;
}
if (alcohol < 0) {
P.p.errorLog("Invalid alcohol '" + alcohol + "' in Recipe: " + getRecipeName());
return false;
}
return true;
}
// allowed deviation to the recipes count of ingredients at the given difficulty
public int allowedCountDiff(int count) {
if (count < 8) {
count = 8;
}
int allowedCountDiff = Math.round((float) ((11.0 - difficulty) * (count / 10.0)));
if (allowedCountDiff == 0) {
return 1;
}
return allowedCountDiff;
}
// allowed deviation to the recipes cooking-time at the given difficulty
public int allowedTimeDiff(int time) {
if (time < 8) {
time = 8;
}
int allowedTimeDiff = Math.round((float) ((11.0 - difficulty) * (time / 10.0)));
if (allowedTimeDiff == 0) {
return 1;
}
return allowedTimeDiff;
}
// difference between given and recipe-wanted woodtype
public float getWoodDiff(float wood) {
return Math.abs(wood - this.wood);
}
public boolean isCookingOnly() {
return age == 0 && distillruns == 0;
}
public boolean needsDistilling() {
return distillruns != 0;
}
public boolean needsToAge() {
return age != 0;
}
// true if given list misses an ingredient
public boolean isMissingIngredients(List<Ingredient> list) {
if (list.size() < ingredients.size()) {
return true;
}
for (RecipeItem rItem : ingredients) {
boolean matches = false;
for (Ingredient used : list) {
if (rItem.matches(used)) {
matches = true;
break;
}
}
if (!matches) {
return true;
}
}
return false;
}
/**
* Create a Potion from this Recipe with best values. Quality can be set, but will reset to 10 if put in a barrel
*
* @param quality The Quality of the Brew
* @return The Created Item
*/
public ItemStack create(int quality) {
return createBrew(quality).createItem(this);
}
/**
* 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<Ingredient> 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);
return new Brew(bIngredients, quality, distillruns, getAge(), wood, getRecipeName(), false, true, 0);
}
// 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 (RecipeItem rItem : ingredients) {
if (rItem.matches(item)) {
return rItem.getAmount();
}
}
return 0;
}
// Same as getName(5)
public String getRecipeName() {
return getName(5);
}
// name that fits the quality
public String getName(int quality) {
if (name.length > 2) {
if (quality <= 3) {
return name[0];
} else if (quality <= 7) {
return name[1];
} else {
return name[2];
}
} else {
return name[0];
}
}
// If one of the quality names equalIgnoreCase given name
public boolean hasName(String name) {
for (String test : this.name) {
if (test.equalsIgnoreCase(name)) {
return true;
}
}
return false;
}
public int getCookingTime() {
return cookingTime;
}
public byte getDistillRuns() {
return distillruns;
}
public int getDistillTime() {
return distillTime;
}
@NotNull
public PotionColor getColor() {
return color;
}
// get the woodtype
public byte getWood() {
return wood;
}
public float getAge() {
return (float) age;
}
public int getDifficulty() {
return difficulty;
}
public int getAlcohol() {
return alcohol;
}
public boolean hasLore() {
return lore != null && !lore.isEmpty();
}
@Nullable
public List<Tuple<Integer, String>> getLore() {
return lore;
}
@Nullable
public List<String> getLoreForQuality(int quality) {
if (lore == null) return null;
int plus;
if (quality <= 3) {
plus = 1;
} else if (quality <= 7) {
plus = 2;
} else {
plus = 3;
}
List<String> list = new ArrayList<>(lore.size());
for (Tuple<Integer, String> line : lore) {
if (line.first() == 0 || line.first() == plus) {
list.add(line.second());
}
}
return list;
}
public ArrayList<BEffect> getEffects() {
return effects;
}
@Override
public String toString() {
return "BRecipe{" + getRecipeName() + '}';
}
public static BRecipe get(String name) {
for (BRecipe recipe : recipes) {
if (recipe.getRecipeName().equalsIgnoreCase(name)) {
return recipe;
}
}
return null;
}
}
+291
View File
@@ -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<String> lore;
public CustomItem() {
}
public CustomItem(Material mat) {
this.mat = mat;
}
public CustomItem(Material mat, String name, List<String> 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<Material> getMaterials() {
List<Material> 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<String> getLore() {
return lore;
}
protected void setLore(List<String> 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<String> 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);
}
}
@@ -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<Material> materials;
private List<String> names;
private List<String> 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<Material> getMaterials() {
return materials;
}
protected void setMaterials(List<Material> materials) {
this.materials = materials;
}
@Nullable
public List<String> getNames() {
return names;
}
protected void setNames(List<String> names) {
this.names = names;
}
@Nullable
public List<String> getLore() {
return lore;
}
protected void setLore(List<String> 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<String> 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<String> 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<String> 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) +
'}';
}
}
@@ -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<String, Function<ItemLoader, Ingredient>> 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<ItemLoader, Ingredient> 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);
}
@@ -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;
}
}
+213
View File
@@ -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<String, Supplier<PluginItem>> 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<Material> 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<PluginItem> constructor) {
constructors.put(pluginId.toLowerCase(), constructor);
}
public static void unRegisterForConfig(String pluginId) {
constructors.remove(pluginId.toLowerCase());
}
}
+314
View File
@@ -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<Material> 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<Material> materials;
List<String> names;
List<String> lore;
List<String> 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<Material> loadMaterials(List<String> ingredientsList) {
List<Material> 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();
}
}
+151
View File
@@ -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<Material> getMaterials() {
List<Material> 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);
}
}