package com.dre.brewery; 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.recipe.PotionColor; import com.dre.brewery.utility.BUtil; import org.bukkit.Material; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.inventory.BrewerInventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.PotionMeta; import org.bukkit.potion.PotionData; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionType; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; import java.io.*; import java.security.InvalidKeyException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; /** * Represents the liquid in the brewed Potions */ public class Brew implements Cloneable { public static final byte SAVE_VER = 1; private static long saveSeed; private static List prevSaveSeeds = new ArrayList<>(); // Save Seeds that have been used in the past, stored to decode brews made at that time public static Map legacyPotions = new HashMap<>(); public static long installTime = System.currentTimeMillis(); // plugin install time in millis after epoch private BIngredients ingredients; private int quality; private int alc; private byte distillRuns; private float ageTime; private float wood; private BRecipe currentRecipe; // Recipe this Brew is currently based off. May change between modifications and is often null when not modifying private boolean unlabeled; private boolean persistent; // Only for legacy private boolean immutable; // static/immutable potions should not be changed private boolean stripped; // Most Brewing information removed, only drinking and rough quality information available. Brew should not change anymore private int lastUpdate; // last update in hours after install time private boolean needsSave; // There was a change that has not yet been saved /** * A new Brew with only ingredients */ public Brew(BIngredients ingredients) { this.ingredients = ingredients; touch(); } /** * A Brew with quality, alc and recipe already set */ public Brew(int quality, int alc, BRecipe recipe, BIngredients ingredients) { this.ingredients = ingredients; this.quality = quality; this.alc = alc; this.currentRecipe = recipe; touch(); } /** * Loading a Brew with all values set */ public Brew(BIngredients ingredients, int quality, int alc, byte distillRuns, float ageTime, float wood, String recipe, boolean unlabeled, boolean immutable, int lastUpdate) { this.ingredients = ingredients; this.quality = quality; this.alc = alc; this.distillRuns = distillRuns; this.ageTime = ageTime; this.wood = wood; this.unlabeled = unlabeled; this.immutable = immutable; this.lastUpdate = lastUpdate; setRecipeFromString(recipe); } // Loading from InputStream private Brew() { } /** * returns a Brew by ItemMeta * * @param meta The meta to get the brew from * @return The Brew if meta is a brew, null if not */ @Nullable public static Brew get(ItemMeta meta) { if (!P.useNBT && !meta.hasLore()) return null; Brew brew = load(meta); if (brew == null && meta instanceof PotionMeta && ((PotionMeta) meta).hasCustomEffect(PotionEffectType.REGENERATION)) { // Load Legacy return getFromPotionEffect(((PotionMeta) meta), false); } return brew; } /** * returns a Brew by ItemStack * * @param item The Item to get the brew from * @return The Brew if item is a brew, null if not */ @Nullable public static Brew get(ItemStack item) { if (item.getType() != Material.POTION) return null; if (!item.hasItemMeta()) return null; ItemMeta meta = item.getItemMeta(); assert meta != null; if (!P.useNBT && !meta.hasLore()) return null; Brew brew = load(meta); if (brew == null && meta instanceof PotionMeta && ((PotionMeta) meta).hasCustomEffect(PotionEffectType.REGENERATION)) { // Load Legacy and convert brew = getFromPotionEffect(((PotionMeta) meta), true); if (brew == null) return null; new BrewLore(brew, (PotionMeta) meta).removeLegacySpacing(); brew.save(meta); item.setItemMeta(meta); } else if (brew != null && brew.needsSave) { // Brew needs saving from a previous format if (P.useNBT) { new BrewLore(brew, (PotionMeta) meta).removeLoreData(); P.p.debugLog("removed Data from Lore"); } brew.save(meta); item.setItemMeta(meta); } return brew; } // Legacy Brew Loading private static Brew getFromPotionEffect(PotionMeta potionMeta, boolean remove) { for (PotionEffect effect : potionMeta.getCustomEffects()) { if (effect.getType().equals(PotionEffectType.REGENERATION)) { if (effect.getDuration() < -1) { if (remove) { Brew b = legacyPotions.get(effect.getDuration()); if (b != null) { potionMeta.removeCustomEffect(PotionEffectType.REGENERATION); if (b.persistent) { return b; } else { return legacyPotions.remove(effect.getDuration()); } } return null; } else { return legacyPotions.get(effect.getDuration()); } } } } return null; } /** * returns a Brew by its UID * * @deprecated Does not work anymore with new save system */ @Deprecated public static Brew get(int uid) { if (uid < -1) { if (!legacyPotions.containsKey(uid)) { P.p.errorLog("Database failure! unable to find UID " + uid + " of a custom Potion!"); return null;// throw some exception? } } else { return null; } return legacyPotions.get(uid); } /** * returns UID of custom Potion item * * @deprecated Does not work anymore with new save system */ @Deprecated public static int getUID(ItemStack item) { return getUID((PotionMeta) item.getItemMeta()); } // returns UID of custom Potion meta // Does not work anymore with new save system /** * returns UID of custom Potion meta * * @deprecated Does not work anymore with new save system */ @Deprecated public static int getUID(PotionMeta potionMeta) { if (potionMeta.hasCustomEffect(PotionEffectType.REGENERATION)) { for (PotionEffect effect : potionMeta.getCustomEffects()) { if (effect.getType().equals(PotionEffectType.REGENERATION)) { if (effect.getDuration() < -1) { return effect.getDuration(); } } } } return 0; } // generate an UID /*public static int generateUID() { int uid = -2; while (potions.containsKey(uid)) { uid -= 1; } return uid; }*/ /** * returns the recipe with the given name, recalculates if not found */ public boolean setRecipeFromString(String name) { currentRecipe = null; if (name != null && !name.equals("")) { for (BRecipe recipe : BRecipe.getAllRecipes()) { if (recipe.getRecipeName().equalsIgnoreCase(name)) { currentRecipe = recipe; return true; } } if (quality > 0) { currentRecipe = ingredients.getBestRecipe(wood, ageTime, distillRuns > 0); if (currentRecipe != null) { /*if (!immutable) { this.quality = calcQuality(); }*/ 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("A Brew was made from Recipe: '" + name + "' which could not be found!"); } } } return false; } public boolean reloadRecipe() { return currentRecipe == null || setRecipeFromString(currentRecipe.getRecipeName()); } // Copy a Brew with a new unique ID and return its item // Not needed anymore /*public ItemStack copy(ItemStack item) { ItemStack copy = item.clone(); int uid = generateUID(); clone(uid); PotionMeta meta = (PotionMeta) copy.getItemMeta(); if (!P.use1_14) { // This is due to the Duration Modifier, that is removed in 1.14 uid *= 4; } meta.addCustomEffect((PotionEffectType.REGENERATION).createEffect(uid, 0), true); copy.setItemMeta(meta); return copy; }*/ public boolean isSimilar(Brew brew) { if (brew == null) return false; if (equals(brew)) return true; return quality == brew.quality && alc == brew.alc && distillRuns == brew.distillRuns && Float.compare(brew.ageTime, ageTime) == 0 && Float.compare(brew.wood, wood) == 0 && unlabeled == brew.unlabeled && persistent == brew.persistent && immutable == brew.immutable && stripped == brew.stripped && ingredients.equals(brew.ingredients) && (Objects.equals(currentRecipe, brew.currentRecipe)); } /** * Clones this instance */ @Override public Brew clone() { try { Brew brew = (Brew) super.clone(); brew.ingredients = ingredients.copy(); return brew; } catch (CloneNotSupportedException e) { throw new InternalError(e); } } @Override public String toString() { return "Brew{" + ingredients + " ingredients" + ", quality=" + quality + ", alc=" + alc + ", distillRuns=" + distillRuns + ", ageTime=" + ageTime + ", wood=" + wood + ", currentRecipe=" + currentRecipe + ", unlabeled=" + unlabeled + ", immutable=" + immutable + ", stripped=" + stripped + '}'; } // remove potion from file (drinking, despawning, combusting, cmdDeleting, should be more!) // Not needed anymore /*public void remove(ItemStack item) { if (!persistent) { potions.remove(getUID(item)); } }*/ /** * calculate alcohol from recipe */ @Contract(pure = true) public int calcAlcohol() { if (quality == 0) { // Give bad potions some alc int badAlc = 0; if (distillRuns > 1) { badAlc = distillRuns; } if (ageTime > 10) { badAlc += 5; } else if (ageTime > 2) { badAlc += 3; } if (currentRecipe != null) { return badAlc; } else { return badAlc / 2; } } if (currentRecipe != null) { int alc = currentRecipe.getAlcohol(); if (currentRecipe.needsDistilling()) { if (distillRuns == 0) { return 0; } // bad quality can decrease alc by up to 40% alc *= 1 - ((float) (10 - quality) * 0.04f); // distillable Potions should have half alc after one and full alc after all needed distills alc /= 2; alc *= 1.0F + ((float) distillRuns / currentRecipe.getDistillRuns()); } else { // quality decides 10% - 100% alc *= ((float) quality / 10.0f); } if (alc > 0) { return alc; } } return 0; } /** * calculating quality */ @Contract(pure = true) public int calcQuality() { // calculate quality from all of the factors float quality = ingredients.getIngredientQuality(currentRecipe) + ingredients.getCookingQuality(currentRecipe, distillRuns > 0); if (currentRecipe.needsToAge() || ageTime > 0.5) { quality += ingredients.getWoodQuality(currentRecipe, wood) + ingredients.getAgeQuality(currentRecipe, ageTime); quality /= 4; } else { quality /= 2; } return Math.round(quality); } public int getQuality() { return quality; } public boolean canDistill() { if (immutable) return false; if (currentRecipe != null) { return currentRecipe.getDistillRuns() > distillRuns; } else { return distillRuns < 6; } } public void updateCustomModelData(ItemMeta meta) { if (!P.use1_14) return; if (currentRecipe != null && currentRecipe.getCmData() != null) { int cm; if (quality > 7) { cm = currentRecipe.getCmData()[2]; } else if (quality > 3) { cm = currentRecipe.getCmData()[1]; } else { cm = currentRecipe.getCmData()[0]; } if (cm == 0) { meta.setCustomModelData(null); } else { meta.setCustomModelData(cm); } } else { meta.setCustomModelData(null); } } /** * Get Special Drink Effects */ public List getEffects() { if (currentRecipe != null && quality > 0) { return currentRecipe.getEffects(); } return null; } /** * Set unlabeled to true to hide the numbers in Lore * * @param item The Item this Brew is on */ public void unLabel(ItemStack item) { if (unlabeled) return; unlabeled = true; ItemMeta meta = item.getItemMeta(); if (meta instanceof PotionMeta && meta.hasLore()) { BrewLore lore = new BrewLore(this, ((PotionMeta) meta)); if (distillRuns > 0) { lore.updateDistillLore(false); } if (ageTime >= 1) { lore.updateAgeLore(false); } lore.updateQualityStars(false); lore.updateAlc(false); lore.write(); item.setItemMeta(meta); } } /** * Sealing the Brew to make it Immutable, Unlabeled and Stripped *

This makes it easier to sell in shops as Brews that are mostly the same will be equal after * * @param potion The Item this Brew is on */ public void seal(ItemStack potion) { if (stripped) return; ItemMeta origMeta = potion.getItemMeta(); if (!(origMeta instanceof PotionMeta)) return; if (quality == 1) { quality = 2; } else if (quality % 2 == 1) { quality--; } alc = calcAlcohol(); setStatic(true, potion); unLabel(potion); PotionMeta meta = (PotionMeta) potion.getItemMeta(); BrewLore lore = new BrewLore(this, meta); lore.updateQualityStars(false, true); lore.write(); stripped = true; ingredients = new BIngredients(); ageTime = 0; wood = -1; touch(); BrewModifyEvent modifyEvent = new BrewModifyEvent(this, meta, BrewModifyEvent.Type.SEAL); P.p.getServer().getPluginManager().callEvent(modifyEvent); if (modifyEvent.isCancelled()) { // As the brew and everything connected to it is only saved on the meta from now on, // restoring the origMeta is enough in this case potion.setItemMeta(origMeta); return; } save(meta); potion.setItemMeta(meta); } /** * Do some regular updates. *

Not really used, apart from legacy potion timed purge */ public void touch() { lastUpdate = (int) ((double) (System.currentTimeMillis() - installTime) / 3600000D); } public int getOrCalcAlc() { return alc > 0 ? alc : (alc = calcAlcohol()); } public void setAlc(int alc) { this.alc = alc; } public byte getDistillRuns() { return distillRuns; } public float getAgeTime() { return ageTime; } public float getWood() { return wood; } public BIngredients getIngredients() { return ingredients; } public boolean hasRecipe() { return currentRecipe != null; } public BRecipe getCurrentRecipe() { return currentRecipe; } public boolean isStatic() { return immutable; } public boolean isImmutable() { return immutable; } public boolean isUnlabeled() { return unlabeled; } public boolean isStripped() { return stripped; } public boolean isSealed() { return stripped && immutable; } public boolean needsSave() { return needsSave; } public void setNeedsSave(boolean needsSave) { this.needsSave = needsSave; } /** * Set the Static flag, so potion is unchangeable */ public void setStatic(boolean immutable, ItemStack potion) { if (!immutable && isStripped()) { throw new IllegalStateException("Cannot make stripped Brews non-static"); } if (!P.use1_9 && currentRecipe != null && canDistill()) { if (immutable) { currentRecipe.getColor().colorBrew(((PotionMeta) potion.getItemMeta()), potion, false); } else { currentRecipe.getColor().colorBrew(((PotionMeta) potion.getItemMeta()), potion, true); } } this.immutable = immutable; } public int getLastUpdate() { return lastUpdate; } // Distilling section --------------- /** * distill all custom potions in the brewer * * @param inv The Inventory of the Distiller * @param contents The Brews in the 3 slots of the Inventory */ public static void distillAll(BrewerInventory inv, Brew[] contents) { for (int slot = 0; slot < 3; slot++) { if (contents[slot] != null) { ItemStack slotItem = inv.getItem(slot); PotionMeta potionMeta = (PotionMeta) slotItem.getItemMeta(); contents[slot].distillSlot(slotItem, potionMeta); } } } /** * distill custom potion in a distiller slot * * @param slotItem The item in the slot * @param potionMeta The meta of the item */ public void distillSlot(ItemStack slotItem, PotionMeta potionMeta) { if (immutable) return; distillRuns += 1; BrewLore lore = new BrewLore(this, potionMeta); BRecipe recipe = ingredients.getDistillRecipe(wood, ageTime); if (recipe != null) { // distillRuns will have an effect on the amount of alcohol, not the quality currentRecipe = recipe; quality = calcQuality(); lore.addOrReplaceEffects(getEffects(), quality); potionMeta.setDisplayName(P.p.color("&f" + recipe.getName(quality))); recipe.getColor().colorBrew(potionMeta, slotItem, canDistill()); } else { quality = 0; lore.removeEffects(); potionMeta.setDisplayName(P.p.color("&f" + P.p.languageReader.get("Brew_DistillUndefined"))); PotionColor.GREY.colorBrew(potionMeta, slotItem, canDistill()); } alc = calcAlcohol(); updateCustomModelData(potionMeta); // Distill Lore if (currentRecipe != null && BConfig.colorInBrewer != BrewLore.hasColorLore(potionMeta)) { lore.convertLore(BConfig.colorInBrewer); } else { lore.updateQualityStars(BConfig.colorInBrewer); lore.updateCustomLore(); lore.updateDistillLore(BConfig.colorInBrewer); } lore.updateAlc(true); lore.write(); touch(); BrewModifyEvent modifyEvent = new BrewModifyEvent(this, potionMeta, BrewModifyEvent.Type.DISTILL); P.p.getServer().getPluginManager().callEvent(modifyEvent); if (modifyEvent.isCancelled()) { // As the brew and everything connected to it is only saved on the meta from now on, // not saving the brew into potionMeta is enough to not change anything in case of cancel return; } save(potionMeta); slotItem.setItemMeta(potionMeta); } public int getDistillTimeNextRun() { if (!canDistill()) { return -1; } if (currentRecipe != null) { return currentRecipe.getDistillTime(); } BRecipe recipe = ingredients.getDistillRecipe(wood, ageTime); if (recipe != null) { return recipe.getDistillTime(); } return 0; } // Ageing Section ------------------ public void age(ItemStack item, float time, byte woodType) { if (immutable) return; PotionMeta potionMeta = (PotionMeta) item.getItemMeta(); BrewLore lore = new BrewLore(this, potionMeta); ageTime += time; // if younger than half a day, it shouldnt get aged form if (ageTime > 0.5) { if (wood == 0) { wood = woodType; } else if (wood != woodType) { woodShift(time, woodType); } BRecipe recipe = ingredients.getAgeRecipe(wood, ageTime, distillRuns > 0); if (recipe != null) { currentRecipe = recipe; quality = calcQuality(); lore.addOrReplaceEffects(getEffects(), quality); potionMeta.setDisplayName(P.p.color("&f" + recipe.getName(quality))); 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()); } } alc = calcAlcohol(); updateCustomModelData(potionMeta); // Lore if (currentRecipe != null && BConfig.colorInBarrels != BrewLore.hasColorLore(potionMeta)) { lore.convertLore(BConfig.colorInBarrels); } else { if (ageTime >= 1) { lore.updateAgeLore(BConfig.colorInBarrels); } if (ageTime > 0.5) { if (BConfig.colorInBarrels) { lore.updateWoodLore(true); } lore.updateQualityStars(BConfig.colorInBarrels); lore.updateCustomLore(); lore.updateAlc(false); } } lore.write(); touch(); BrewModifyEvent modifyEvent = new BrewModifyEvent(this, potionMeta, BrewModifyEvent.Type.AGE); P.p.getServer().getPluginManager().callEvent(modifyEvent); if (modifyEvent.isCancelled()) { // As the brew and everything connected to it is only saved on the meta from now on, // not saving the brew into potionMeta is enough to not change anything in case of cancel return; } save(potionMeta); item.setItemMeta(potionMeta); } /** * Slowly shift the wood of the Brew to the new Type */ public void woodShift(float time, byte to) { if (immutable) return; float factor = 1; if (ageTime > 5) { factor = 2; } else if (ageTime > 10) { factor = 2; factor += ageTime / 10F; } if (wood > to) { wood -= time / factor; if (wood < to) { wood = to; } } else { wood += time / factor; if (wood > to) { wood = to; } } } /** * Create a new Item of this Brew. A BrewModifyEvent type CREATE will be called. * * @param recipe Recipe is required if the brew doesn't have a currentRecipe * @return The created Item, null if the Event is cancelled */ public ItemStack createItem(BRecipe recipe) { return createItem(recipe, true); } /** * Create a new Item of this Brew. * * @param recipe Recipe is required if the brew doesn't have a currentRecipe * @param event Set event to true if a BrewModifyEvent type CREATE should be called and may be cancelled. Only then may this method return null * @return The created Item, null if the Event is cancelled */ @Contract("_, false -> !null") public ItemStack createItem(BRecipe recipe, boolean event) { if (recipe == null) { recipe = getCurrentRecipe(); } if (recipe == null) { throw new IllegalArgumentException("Argument recipe can't be null if the brew doesn't have a currentRecipe"); } ItemStack potion = new ItemStack(Material.POTION); PotionMeta potionMeta = (PotionMeta) potion.getItemMeta(); recipe.getColor().colorBrew(potionMeta, potion, false); updateCustomModelData(potionMeta); potionMeta.setDisplayName(P.p.color("&f" + recipe.getName(quality))); //if (!P.use1_14) { // Before 1.14 the effects duration would strangely be only a quarter of what we tell it to be // This is due to the Duration Modifier, that is removed in 1.14 // uid *= 4; //} // This effect stores the UID in its Duration //potionMeta.addCustomEffect((PotionEffectType.REGENERATION).createEffect((uid * 4), 0), true); BrewLore lore = new BrewLore(this, potionMeta); lore.convertLore(false); lore.addOrReplaceEffects(recipe.getEffects(), quality); lore.write(); touch(); if (event) { BrewModifyEvent modifyEvent = new BrewModifyEvent(this, potionMeta, BrewModifyEvent.Type.CREATE); P.p.getServer().getPluginManager().callEvent(modifyEvent); if (modifyEvent.isCancelled()) { return null; } } save(potionMeta); potion.setItemMeta(potionMeta); P.p.metricsForCreate(true); return potion; } /** * Performant way of checking if this item is a Brew. *

Does not give any guarantees that get() will return notnull for this item, i.e. if it is a brew but the data is corrupt * * @param item The Item to check * @return True if the item is a brew */ public static boolean isBrew(ItemStack item) { if (item == null || item.getType() != Material.POTION) return false; if (!item.hasItemMeta()) return false; ItemMeta meta = item.getItemMeta(); assert meta != null; if (!P.useNBT && !meta.hasLore()) return false; if (P.useNBT) { // Check for Data on PersistentDataContainer if (NBTLoadStream.hasDataInMeta(meta)) { return true; } } // If either NBT is not supported or no data was found in NBT, try finding data in lore if (meta.hasLore()) { // Find the Data Identifier in Lore return BUtil.indexOfStart(meta.getLore(), LoreLoadStream.IDENTIFIER) > -1; } return false; } private static Brew load(ItemMeta meta) { InputStream itemLoadStream = null; if (P.useNBT) { // Try loading the Item Data from PersistentDataContainer NBTLoadStream nbtStream = new NBTLoadStream(meta); if (nbtStream.hasData()) { itemLoadStream = nbtStream; } } if (itemLoadStream == null) { // If either NBT is not supported or no data was found in NBT, try loading from Lore try { itemLoadStream = new Base91DecoderStream(new LoreLoadStream(meta, 0)); } catch (IllegalArgumentException ignored) { // No Brew data found in Meta return null; } } XORUnscrambleStream unscrambler = new XORUnscrambleStream(itemLoadStream, saveSeed, prevSaveSeeds); try (DataInputStream in = new DataInputStream(unscrambler)) { boolean parityFailed = false; if (in.readByte() != 86) { P.p.errorLog("Parity check failed on Brew while loading, trying to load anyways!"); parityFailed = true; } Brew brew = new Brew(); byte ver = in.readByte(); switch (ver) { case 1: unscrambler.start(); brew.loadFromStream(in, ver); break; default: if (parityFailed) { P.p.errorLog("Failed to load Brew. Maybe something corrupted the Lore of the Item?"); } else { P.p.errorLog("Brew has data stored in v" + ver + " this Plugin version supports up to v" + SAVE_VER); } return null; } XORUnscrambleStream.SuccessType successType = unscrambler.getSuccessType(); if (successType == XORUnscrambleStream.SuccessType.PREV_SEED) { P.p.debugLog("Converting Brew from previous Seed"); brew.setNeedsSave(true); } else if ((BConfig.enableEncode && !brew.isStripped()) != (successType == XORUnscrambleStream.SuccessType.MAIN_SEED)) { // We have either enabled encode and the data was not encoded or the other way round P.p.debugLog("Converting Brew to new encode setting"); brew.setNeedsSave(true); } else if (P.useNBT && itemLoadStream instanceof Base91DecoderStream) { // We are on a version that supports nbt but the data is still in the lore of the item // Just save it again so that it gets saved to nbt P.p.debugLog("Converting Brew to NBT"); brew.setNeedsSave(true); } return brew; } catch (IOException e) { P.p.errorLog("IO Error while loading Brew"); e.printStackTrace(); } catch (InvalidKeyException e) { P.p.errorLog("Failed to load Brew, has the data key 'encodeKey' in the config.yml been changed?"); e.printStackTrace(); } return null; } private void loadFromStream(DataInputStream in, byte dataVersion) throws IOException { quality = in.readByte(); int bools = in.readUnsignedByte(); if ((bools & 64) != 0) { alc = in.readShort(); } if ((bools & 1) != 0) { distillRuns = in.readByte(); } if ((bools & 2) != 0) { ageTime = in.readFloat(); } if ((bools & 4) != 0) { wood = in.readFloat(); } String recipe = null; if ((bools & 8) != 0) { recipe = in.readUTF(); } unlabeled = (bools & 16) != 0; immutable = (bools & 32) != 0; stripped = (bools & 128) != 0; ingredients = BIngredients.load(in, dataVersion); setRecipeFromString(recipe); } /** * Save brew data into meta: lore/nbt. *

Should be called after any changes made to the brew */ public void save(ItemMeta meta) { OutputStream itemSaveStream; if (P.useNBT) { itemSaveStream = new NBTSaveStream(meta); } else { itemSaveStream = new Base91EncoderStream(new LoreSaveStream(meta, 0)); } XORScrambleStream scrambler = new XORScrambleStream(itemSaveStream, saveSeed); try (DataOutputStream out = new DataOutputStream(scrambler)) { out.writeByte(86); // Parity/sanity out.writeByte(SAVE_VER); // Version // If Stripped of data, we can save everything unscrambled if (BConfig.enableEncode && !isStripped()) { scrambler.start(); } else { scrambler.startUnscrambled(); } saveToStream(out); } catch (IOException e) { P.p.errorLog("IO Error while saving Brew"); e.printStackTrace(); } } /** * Save brew data into the meta/lore of the specified item. *

The meta on the item changes, so to make further changes to the meta, item.getItemMeta() has to be called again after this * * @param item The item to save this brew into */ public void save(ItemStack item) { ItemMeta meta; if (!item.hasItemMeta()) { meta = P.p.getServer().getItemFactory().getItemMeta(item.getType()); } else { meta = item.getItemMeta(); } save(meta); item.setItemMeta(meta); } public void saveToStream(DataOutputStream out) throws IOException { if (quality > 10) { quality = 10; } alc = Math.min(alc, Short.MAX_VALUE); out.writeByte((byte) quality); int bools = 0; bools |= ((distillRuns != 0) ? 1 : 0); bools |= (ageTime > 0 ? 2 : 0); bools |= (wood != -1 ? 4 : 0); bools |= (currentRecipe != null ? 8 : 0); bools |= (unlabeled ? 16 : 0); bools |= (immutable ? 32 : 0); bools |= (alc > 0 ? 64 : 0); bools |= (stripped ? 128 : 0); out.writeByte(bools); if (alc > 0) { out.writeShort(alc); } if (distillRuns != 0) { out.writeByte(distillRuns); } if (ageTime > 0) { out.writeFloat(ageTime); } if (wood != -1) { out.writeFloat(wood); } if (currentRecipe != null) { out.writeUTF(currentRecipe.getRecipeName()); } ingredients.save(out); } public static void loadPrevSeeds(ConfigurationSection section) { if (section.contains("prevSaveSeeds")) { prevSaveSeeds = section.getLongList("prevSaveSeeds"); if (!prevSaveSeeds.contains(saveSeed)) { prevSaveSeeds.add(saveSeed); } } } public static void writePrevSeeds(ConfigurationSection section) { if (!prevSaveSeeds.isEmpty()) { section.set("prevSaveSeeds", prevSaveSeeds); } } public static void loadSeed(ConfigurationSection config, File file) { saveSeed = config.getLong("encodeKey", 0); if (saveSeed == 0) { while (saveSeed == 0) { saveSeed = new SecureRandom().nextLong(); } ConfigUpdater updater = new ConfigUpdater(file); updater.setEncodeKey(saveSeed); updater.saveConfig(); } if (!prevSaveSeeds.contains(saveSeed)) { prevSaveSeeds.add(saveSeed); } } public static boolean noLegacy() { return legacyPotions.isEmpty(); } /** * Load potion data from data file for backwards compatibility */ public static void loadLegacy(BIngredients ingredients, int uid, int quality, int alc, byte distillRuns, float ageTime, float wood, String recipe, boolean unlabeled, boolean persistent, boolean stat, int lastUpdate) { Brew brew = new Brew(ingredients, quality, alc, distillRuns, ageTime, wood, recipe, unlabeled, stat, lastUpdate); brew.persistent = persistent; if (brew.lastUpdate <= 0) { // We failed to save the lastUpdate, restart the countdown brew.touch(); } legacyPotions.put(uid, brew); } /** * remove legacy potiondata for an item */ public static void removeLegacy(ItemStack item) { if (legacyPotions.isEmpty()) return; if (!item.hasItemMeta()) return; ItemMeta meta = item.getItemMeta(); if (!(meta instanceof PotionMeta)) return; getFromPotionEffect(((PotionMeta) meta), true); } public void convertPre1_9(ItemStack item) { removeLegacy(item); PotionMeta potionMeta = ((PotionMeta) item.getItemMeta()); assert potionMeta != null; BrewLore lore = new BrewLore(this, potionMeta); lore.removeEffects(); if (hasRecipe()) { currentRecipe.getColor().colorBrew(potionMeta, item, canDistill()); } else { PotionColor.GREY.colorBrew(potionMeta, item, canDistill()); } lore.removeLegacySpacing(); save(potionMeta); item.setItemMeta(potionMeta); } public void convertPre1_11(ItemStack item) { removeLegacy(item); PotionMeta potionMeta = ((PotionMeta) item.getItemMeta()); assert potionMeta != null; potionMeta.setBasePotionData(new PotionData(PotionType.UNCRAFTABLE)); BrewLore lore = new BrewLore(this, potionMeta); lore.removeEffects(); if (hasRecipe()) { lore.addOrReplaceEffects(currentRecipe.getEffects(), getQuality()); currentRecipe.getColor().colorBrew(potionMeta, item, canDistill()); } else { PotionColor.GREY.colorBrew(potionMeta, item, canDistill()); } lore.removeLegacySpacing(); save(potionMeta); item.setItemMeta(potionMeta); } /** * Saves all data, * Legacy method to save to data file. */ public static void saveLegacy(ConfigurationSection config) { for (Map.Entry entry : legacyPotions.entrySet()) { int uid = entry.getKey(); Brew brew = entry.getValue(); ConfigurationSection idConfig = config.createSection("" + uid); // not saving unneccessary data if (brew.quality != 0) { idConfig.set("quality", brew.quality); } if (brew.alc > 0) { idConfig.set("alc", brew.alc); } if (brew.distillRuns != 0) { idConfig.set("distillRuns", brew.distillRuns); } if (brew.ageTime != 0) { idConfig.set("ageTime", brew.ageTime); } if (brew.wood != -1) { idConfig.set("wood", brew.wood); } if (brew.currentRecipe != null) { idConfig.set("recipe", brew.currentRecipe.getRecipeName()); } if (brew.unlabeled) { idConfig.set("unlabeled", true); } if (brew.persistent) { idConfig.set("persist", true); } if (brew.immutable) { idConfig.set("stat", true); } if (brew.lastUpdate > 0) { idConfig.set("lastUpdate", brew.lastUpdate); } // save the ingredients idConfig.set("ingId", brew.ingredients.saveLegacy(config.getParent())); } } }