mirror of
https://github.com/exituser/Brewery.git
synced 2026-09-17 07:49:03 +00:00
403 lines
12 KiB
Java
403 lines
12 KiB
Java
package com.dre.brewery.recipe;
|
|
|
|
import com.dre.brewery.P;
|
|
import com.dre.brewery.utility.StringParser;
|
|
import com.dre.brewery.utility.Tuple;
|
|
import org.bukkit.Color;
|
|
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.*;
|
|
import java.util.stream.Collectors;
|
|
|
|
/**
|
|
* A Recipe for the Base Potion coming out of the Cauldron.
|
|
*/
|
|
public class BCauldronRecipe {
|
|
public static List<BCauldronRecipe> recipes = new ArrayList<>();
|
|
public static int numConfigRecipes;
|
|
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 PotionColor color;
|
|
private List<Tuple<Integer, Color>> particleColor = new ArrayList<>();
|
|
private List<String> lore;
|
|
private int cmData; // Custom Model Data
|
|
private boolean saveInData; // If this recipe should be saved in data and loaded again when the server restarts. Applicable to non-config recipes
|
|
|
|
|
|
/**
|
|
* A New Cauldron Recipe with the given name.
|
|
* <p>Use new BCauldronRecipe.Builder() for easier Cauldron Recipe Creation
|
|
*
|
|
* @param name Name of the Cauldron Recipe
|
|
*/
|
|
public BCauldronRecipe(String name) {
|
|
this.name = name;
|
|
color = PotionColor.CYAN;
|
|
}
|
|
|
|
@Nullable
|
|
public static BCauldronRecipe fromConfig(ConfigurationSection cfg, String id) {
|
|
|
|
String name = cfg.getString(id + ".name");
|
|
if (name != null) {
|
|
name = P.p.color(name);
|
|
} else {
|
|
P.p.errorLog("Missing name for Cauldron-Recipe: " + id);
|
|
return null;
|
|
}
|
|
|
|
BCauldronRecipe recipe = new BCauldronRecipe(name);
|
|
|
|
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;
|
|
}
|
|
|
|
for (String entry : cfg.getStringList(id + ".cookParticles")) {
|
|
String[] split = entry.split("/");
|
|
int minute;
|
|
if (split.length == 1) {
|
|
minute = 10;
|
|
} else if (split.length == 2) {
|
|
minute = P.p.parseInt(split[1]);
|
|
} else {
|
|
P.p.errorLog("cookParticle: '" + entry + "' in: " + recipe.name);
|
|
return null;
|
|
}
|
|
if (minute < 1) {
|
|
P.p.errorLog("cookParticle: '" + entry + "' in: " + recipe.name);
|
|
return null;
|
|
}
|
|
PotionColor partCol = PotionColor.fromString(split[0]);
|
|
if (partCol == PotionColor.WATER && !split[0].equals("WATER")) {
|
|
P.p.errorLog("Color of cookParticle: '" + entry + "' in: " + recipe.name);
|
|
return null;
|
|
}
|
|
recipe.particleColor.add(new Tuple<>(minute, partCol.getColor()));
|
|
}
|
|
if (!recipe.particleColor.isEmpty()) {
|
|
// Sort by minute
|
|
recipe.particleColor.sort(Comparator.comparing(Tuple::first));
|
|
}
|
|
|
|
|
|
List<Tuple<Integer,String>> lore = BRecipe.loadQualityStringList(cfg, id + ".lore", StringParser.ParseType.LORE);
|
|
if (lore != null && !lore.isEmpty()) {
|
|
recipe.lore = lore.stream().map(Tuple::second).collect(Collectors.toList());
|
|
}
|
|
|
|
recipe.cmData = cfg.getInt(id + ".customModelData", 0);
|
|
|
|
return recipe;
|
|
}
|
|
|
|
|
|
// Getter
|
|
|
|
@NotNull
|
|
public String getName() {
|
|
return name;
|
|
}
|
|
|
|
@NotNull
|
|
public List<RecipeItem> getIngredients() {
|
|
return ingredients;
|
|
}
|
|
|
|
@NotNull
|
|
public PotionColor getColor() {
|
|
return color;
|
|
}
|
|
|
|
@NotNull
|
|
public List<Tuple<Integer, Color>> getParticleColor() {
|
|
return particleColor;
|
|
}
|
|
|
|
@Nullable
|
|
public List<String> getLore() {
|
|
return lore;
|
|
}
|
|
|
|
public boolean isSaveInData() {
|
|
return saveInData;
|
|
}
|
|
|
|
|
|
// Setter
|
|
|
|
/**
|
|
* When Changing ingredients, Accepted Lists have to be updated in BCauldronRecipe
|
|
*/
|
|
public void setIngredients(@NotNull List<RecipeItem> ingredients) {
|
|
this.ingredients = ingredients;
|
|
}
|
|
|
|
public void setColor(@NotNull PotionColor color) {
|
|
this.color = color;
|
|
}
|
|
|
|
public void setLore(List<String> lore) {
|
|
this.lore = lore;
|
|
}
|
|
|
|
/**
|
|
* Get the Custom Model Data
|
|
*/
|
|
public int getCmData() {
|
|
return cmData;
|
|
}
|
|
|
|
public void setSaveInData(boolean saveInData) {
|
|
this.saveInData = saveInData;
|
|
}
|
|
|
|
/**
|
|
* Find how much these ingredients match the given ones from 0-10.
|
|
* <p>If any ingredient is missing, returns 0
|
|
* <br>Any included item that is not in the recipe, will drive the number down most heavily.
|
|
* <br>More Amount of any item, will logarithmically raise the number
|
|
* <br>Difference in Amount to what the recipe expects will make a tiny difference on the number
|
|
* <p>So apart from unexpected items, more amount of the correct item will make the number go up,
|
|
* with a little dip for difference in expected amount.
|
|
*
|
|
* <p>The thought behind this is, that a given list of ingredients matches this recipe most, when:
|
|
* <br>1. It is not missing ingredients,
|
|
* <br>2. It has no unexpected ingredients
|
|
* <br>3. It has a lot of the matching ingredients, so that for two recipes, both having the same
|
|
* amount of unexpected ingredients, the one matching the item with the highest amounts wins.
|
|
* <br> For Example | Recipe_1: (Wheat*1), Recipe_2: (Sugar*1) | Ingredients: (Wheat*10, Sugar*5), Recipe_1 should win,
|
|
* even though the difference in expected amount (1) is lower for Recipe_2
|
|
* <br>4. It has the least difference in expected ingredient amount.
|
|
*/
|
|
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
|
|
|
|
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
|
|
// So that even if every other ingredient is perfect, a recipe that expects all these items will fare better
|
|
float tooMuch = items.size() - ingredients.size();
|
|
double mod = Math.pow(0.1, tooMuch);
|
|
match *= mod;
|
|
}
|
|
P.p.debugLog("Match for Cauldron Recipe " + name + ": " + match);
|
|
return match;
|
|
}
|
|
|
|
public void updateAcceptedLists() {
|
|
for (RecipeItem ingredient : getIngredients()) {
|
|
if (ingredient.hasMaterials()) {
|
|
BCauldronRecipe.acceptedMaterials.addAll(ingredient.getMaterials());
|
|
}
|
|
if (ingredient instanceof SimpleItem) {
|
|
BCauldronRecipe.acceptedSimple.add(((SimpleItem) ingredient).getMaterial());
|
|
} else {
|
|
// Add it as acceptedCustom
|
|
if (!BCauldronRecipe.acceptedCustom.contains(ingredient)) {
|
|
BCauldronRecipe.acceptedCustom.add(ingredient);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "BCauldronRecipe{" + name + '}';
|
|
}
|
|
|
|
@Nullable
|
|
public static BCauldronRecipe get(String name) {
|
|
for (BCauldronRecipe recipe : recipes) {
|
|
if (recipe.name.equalsIgnoreCase(name)) {
|
|
return recipe;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Gets a Modifiable Sublist of the CauldronRecipes that are loaded by config.
|
|
* <p>Changes are directly reflected by the main list of all recipes
|
|
* <br>Changes to the main List of all CauldronRecipes will make the reference to this sublist invalid
|
|
*
|
|
* <p>After adding or removing elements, CauldronRecipes.numConfigRecipes MUST be updated!
|
|
*/
|
|
public static List<BCauldronRecipe> getConfigRecipes() {
|
|
return recipes.subList(0, numConfigRecipes);
|
|
}
|
|
|
|
/**
|
|
* Gets a Modifiable Sublist of the CauldronRecipes that are added by plugins.
|
|
* <p>Changes are directly reflected by the main list of all recipes
|
|
* <br>Changes to the main List of all CauldronRecipes will make the reference to this sublist invalid
|
|
*/
|
|
public static List<BCauldronRecipe> getAddedRecipes() {
|
|
return recipes.subList(numConfigRecipes, recipes.size());
|
|
}
|
|
|
|
/**
|
|
* Gets the main List of all CauldronRecipes.
|
|
*/
|
|
public static List<BCauldronRecipe> getAllRecipes() {
|
|
return recipes;
|
|
}
|
|
|
|
/*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;
|
|
}*/
|
|
|
|
/**
|
|
* Builder to easily create BCauldron recipes.
|
|
*/
|
|
public static class Builder {
|
|
private BCauldronRecipe recipe;
|
|
|
|
public Builder(String name) {
|
|
recipe = new BCauldronRecipe(name);
|
|
}
|
|
|
|
|
|
public Builder addIngredient(RecipeItem... item) {
|
|
if (recipe.ingredients == null) {
|
|
recipe.ingredients = new ArrayList<>();
|
|
}
|
|
Collections.addAll(recipe.ingredients, item);
|
|
return this;
|
|
}
|
|
|
|
public Builder addIngredient(ItemStack... item) {
|
|
if (recipe.ingredients == null) {
|
|
recipe.ingredients = new ArrayList<>();
|
|
}
|
|
for (ItemStack i : item) {
|
|
recipe.ingredients.add(new CustomItem(i));
|
|
}
|
|
return this;
|
|
}
|
|
|
|
public Builder color(String colorString) {
|
|
recipe.color = PotionColor.fromString(colorString);
|
|
return this;
|
|
}
|
|
|
|
public Builder color(PotionColor color) {
|
|
recipe.color = color;
|
|
return this;
|
|
}
|
|
|
|
public Builder color(Color color) {
|
|
recipe.color = PotionColor.fromColor(color);
|
|
return this;
|
|
}
|
|
|
|
public Builder addParticleColor(int atMinute, Color color) {
|
|
recipe.particleColor.add(new Tuple<>(atMinute, color));
|
|
return this;
|
|
}
|
|
|
|
public Builder addLore(String line) {
|
|
if (recipe.lore == null) {
|
|
recipe.lore = new ArrayList<>();
|
|
}
|
|
recipe.lore.add(line);
|
|
return this;
|
|
}
|
|
|
|
public BCauldronRecipe get() {
|
|
if (recipe.name == null) {
|
|
throw new IllegalArgumentException("CauldronRecipe name is null");
|
|
}
|
|
if (BCauldronRecipe.get(recipe.getName()) != null) {
|
|
throw new IllegalArgumentException("CauldronRecipe with name " + recipe.getName() + " already exists");
|
|
}
|
|
if (recipe.color == null) {
|
|
throw new IllegalArgumentException("CauldronRecipe has no color");
|
|
}
|
|
if (recipe.ingredients == null || recipe.ingredients.isEmpty()) {
|
|
throw new IllegalArgumentException("CauldronRecipe has no ingredients");
|
|
}
|
|
if (!recipe.particleColor.isEmpty()) {
|
|
// Sort particleColor by minute
|
|
recipe.particleColor.sort(Comparator.comparing(Tuple::first));
|
|
}
|
|
return recipe;
|
|
}
|
|
}
|
|
}
|