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

16
pom.xml
View File

@ -14,6 +14,7 @@
<build>
<sourceDirectory>src</sourceDirectory>
<testSourceDirectory>test</testSourceDirectory>
<resources>
<!-- Static resources -->
@ -87,7 +88,7 @@
<url>http://maven.sk89q.com/repo/</url>
</repository>
<repository>
<!-- GriefPrevention -->
<!-- GriefPrevention, SlimeFun -->
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
@ -226,6 +227,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.TheBusyBiscuit</groupId>
<artifactId>Slimefun4</artifactId>
<version>4bb9abd247</version>
<scope>provided</scope>
</dependency>
<dependency>
<!-- Contains proprietary api that we use for integration -->
<groupId>com.dre</groupId>
<artifactId>ExtPluginBridge</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId>

View File

@ -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,7 +221,7 @@ recipes:
- Diamond/1
- Spruce_Planks/8
- Bedrock/1
# - Brewery:Weißbier/2
- Brewery:Weißbier/2
# - ExoticGarden:Grape/3
- bsp-item/1
cookingtime: 3

View File

@ -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

View File

@ -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,14 +262,10 @@ 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)
@ -278,7 +286,6 @@ public class BCauldron {
}
}
}
}
@SuppressWarnings("deprecation")
public static void setItemInHand(PlayerInteractEvent event, Material mat, boolean swapped) {

View File

@ -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<ItemStack> ingredients = new ArrayList<>();
private List<Ingredient> ingredients = new ArrayList<>();
private int cookedTime;
// Represents ingredients in Cauldron, Brew
@ -32,7 +37,7 @@ public class BIngredients {
}
// Load from File
public BIngredients(List<ItemStack> ingredients, int cookedTime) {
public BIngredients(List<Ingredient> 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<ItemStack> ingredients, int cookedTime, boolean legacy) {
public BIngredients(List<Ingredient> 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<ItemStack> ing = new ArrayList<>(size);
List<Ingredient> 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<String, Integer> serializeIngredients() {
/*public Map<String, Integer> serializeIngredients() {
Map<String, Integer> 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();
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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<Long> prevSaveSeeds = new ArrayList<>(); // Save Seeds that have been used in the past, stored to decode brews made at that time
public static Map<Integer, Brew> 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<Integer, Brew> 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()));
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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<CustomItem> customItems = new ArrayList<>();
public static List<RecipeItem> 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!");

View File

@ -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,19 +50,31 @@ public class BData {
}
}
// Register Item Loaders
CustomItem.registerItemLoader();
SimpleItem.registerItemLoader();
PluginItem.registerItemLoader();
// loading Ingredients into ingMap
// Only for Legacy Brews
Map<String, BIngredients> ingMap = new HashMap<>();
ConfigurationSection section = data.getConfigurationSection("Ingredients");
if (section != null) {
for (String id : section.getKeys(false)) {
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
ArrayList<ItemStack> ingredients = deserializeIngredients(matSection);
List<Ingredient> 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 {
// 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<ItemStack> deserializeIngredients(ConfigurationSection matSection) {
ArrayList<ItemStack> 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<Ingredient> oldDeserializeIngredients(ConfigurationSection matSection) {
List<Ingredient> 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,22 +185,34 @@ 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 {
// New way of saving ingredients
return deserializeIngredients(section.getString(path));
}
}
// load Block locations of given world
public static void loadWorldData(String uuid, World world) {
public static void loadWorldData(String uuid, World world, FileConfiguration data) {
if (data == null) {
File file = new File(P.p.getDataFolder(), "data.yml");
if (file.exists()) {
FileConfiguration data = YamlConfiguration.loadConfiguration(file);
data = YamlConfiguration.loadConfiguration(file);
} else {
return;
}
}
// loading BCauldron
if (data.contains("BCauldron." + uuid)) {
@ -184,7 +225,7 @@ public class BData {
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"));
BIngredients ingredients = loadCauldronIng(section, cauldron + ".ingredients");
int state = section.getInt(cauldron + ".state", 1);
new BCauldron(worldBlock, ingredients, state);
@ -245,6 +286,7 @@ public class BData {
}
// 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);
}
@ -286,4 +328,3 @@ public class BData {
}
}
}

View File

@ -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")) {

View File

@ -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();
}
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -1,4 +1,4 @@
package com.dre.brewery.integration;
package com.dre.brewery.integration.barrel;
import com.sk89q.worldguard.bukkit.RegionQuery;

View File

@ -1,4 +1,4 @@
package com.dre.brewery.integration;
package com.dre.brewery.integration.barrel;
import org.bukkit.block.Block;

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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<BCauldronRecipe> recipes = new ArrayList<>();
public static Set<Material> acceptedMaterials = EnumSet.noneOf(Material.class);
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<Tuple<CustomItem, Integer>> ingredients; // Item and amount
private List<RecipeItem> ingredients;
//private List<String> particles
private PotionColor color;
private List<String> lore;
@ -72,7 +73,7 @@ public class BCauldronRecipe {
}
@NotNull
public List<Tuple<CustomItem, Integer>> getIngredients() {
public List<RecipeItem> 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<ItemStack> items) {
public float getIngredientMatch(List<Ingredient> items) {
if (items.size() < ingredients.size()) {
return 0;
}
float match = 10;
search: for (Tuple<CustomItem, Integer> 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;
}*/
}

View File

@ -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;

View File

@ -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<BRecipe> recipes = new ArrayList<>();
private String[] name;
private List<Tuple<CustomItem, Integer>> ingredients = new ArrayList<>(); // Items and amounts
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
@ -96,7 +97,7 @@ public class BRecipe {
return recipe;
}
public static List<Tuple<CustomItem, Integer>> loadIngredients(ConfigurationSection cfg, String recipeId) {
public static List<RecipeItem> loadIngredients(ConfigurationSection cfg, String recipeId) {
List<String> ingredientsList;
if (cfg.isString(recipeId + ".ingredients")) {
ingredientsList = new ArrayList<>(1);
@ -107,7 +108,7 @@ public class BRecipe {
if (ingredientsList == null) {
return null;
}
List<Tuple<CustomItem, Integer>> ingredients = new ArrayList<>(ingredientsList.size());
List<RecipeItem> 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));
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<ItemStack> list) {
public boolean isMissingIngredients(List<Ingredient> list) {
if (list.size() < ingredients.size()) {
return true;
}
for (Tuple<CustomItem, Integer> 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<ItemStack> list = ingredients.stream().map(ing -> ing.a().createDummy(ing.b())).collect(Collectors.toList());
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);
@ -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<CustomItem, Integer> ingredient : ingredients) {
if (ingredient.a().matches(item)) {
return ingredient.b();
for (RecipeItem rItem : ingredients) {
if (rItem.matches(item)) {
return rItem.getAmount();
}
}
return 0;

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);
}
}

View File

@ -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) +
'}';
}
}

View File

@ -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);
}

View File

@ -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;
}
}

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());
}
}

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();
}
}

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);
}
}

View File

@ -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<Material> materials;
private List<String> names;
private List<String> 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<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()) {
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<String> 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<Material> getMaterials() {
return materials;
}
public Material getSimpleMaterial() {
return materials.get(0);
}
public List<String> getNames() {
return names;
}
public List<String> 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<String> 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<String> 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<String> 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}";
}
}

View File

@ -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<ItemStack> list = new ArrayList<>();
list.add(new ItemStack(Material.DIAMOND_HOE, 3));
list.add(new ItemStack(Material.RED_MUSHROOM, 1));
List<Ingredient> 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);
}
}