Package digi.recipeManager

Source Code of digi.recipeManager.Recipes

package digi.recipeManager;

import java.io.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.logging.*;

import net.minecraft.server.*;

import org.bukkit.*;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.*;
import org.bukkit.block.Block;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.inventory.*;
import org.bukkit.inventory.ItemStack;
import org.bukkit.permissions.*;

import digi.recipeManager.data.*;
import digi.recipeManager.data.Item;
import digi.recipeManager.data.Recipe;

public class Recipes
{
  private ItemStack              placeholderItem    = new ItemStack(Material.LONG_GRASS, 0, (short)1337);
  protected List<Craft>            craftRecipes    = new ArrayList<Craft>();
  protected List<Combine>            combineRecipes    = new ArrayList<Combine>();
  protected HashMap<String, Smelt>      smeltRecipes    = new HashMap<String, Smelt>();
  protected HashMap<String, Fuel>        fuels        = new HashMap<String, Fuel>();
 
  private HashMap<String, List<String>>    recipeErrors    = null;
//  private List<String>          recipeErrors    = null;
  private String                currentFile      = null;
  private int                  currentFileLine    = 0;
 
  private int                  craftNum      = 0;
  private int                  combineNum      = 0;
  private boolean                smeltCustomRecipes  = false;
 
  protected HashMap<String, FurnaceData>    furnaceData      = new HashMap<String, FurnaceData>();
  protected HashMap<String, MutableDouble>  furnaceSmelting    = new HashMap<String, MutableDouble>();
  private int                  furnaceTaskId    = 0;
 
  protected boolean              hasExplosive    = false;
  private HashSet<String>            overriddenRecipes  = new HashSet<String>();
 
  private Logger                log;
  private String                NL          = System.getProperty("line.separator");
 
  public Recipes()
  {
    log = RecipeManager.plugin.getLogger();
  }
 
  protected void clearData()
  {
    reset();
   
    placeholderItem = null;
    craftRecipes = null;
    combineRecipes = null;
    smeltRecipes = null;
    fuels = null;
   
    recipeErrors = null;
    currentFile = null;
    currentFileLine = 0;
   
    craftNum = 0;
    combineNum = 0;
   
    furnaceData = null;
    furnaceSmelting = null;
    furnaceTaskId = 0;
   
    overriddenRecipes = null;
   
    log = null;
    NL = null;
  }
 
  /**
   * Gets all CRAFT recipes (workbench shaped)
   *
   * @return
   */
  public List<Craft> getCraftRecipes()
  {
    return craftRecipes;
  }
 
  /**
   * Gets Craft recipe class from the placeholder result so you can get the real result of the recipe
   *
   * @param placeholderResult
   *            the ItemStack result placeholder
   * @return Can be null if not found
   */
  public Craft getCraftRecipe(ItemStack placeholderResult)
  {
    return (!isCustomRecipe(placeholderResult) || craftRecipes.size() >= placeholderResult.getAmount() ? null : craftRecipes.get(placeholderResult.getAmount()));
  }
 
  /**
   * Gets all COMBINE recipes (workbench shapeless)
   *
   * @return
   */
  public List<Combine> getCombineRecipes()
  {
    return combineRecipes;
  }
 
  /**
   * Gets Combine recipe class from the placeholder result so you can get the real result of the recipe
   *
   * @param placeholderResult
   *            the ItemStack result placeholder
   * @return Can be null if not found
   */
  public Combine getCombineRecipe(ItemStack placeholderResult)
  {
    return (!isCustomRecipe(placeholderResult) || combineRecipes.size() >= placeholderResult.getAmount() ? null : combineRecipes.get(placeholderResult.getAmount()));
  }
 
  /**
   * Gets all SMELT recipes (furnace recipes)
   * The string in the hashmap is the ID of the recipe
   *
   * @return
   */
  public HashMap<String, Smelt> getSmeltRecipes()
  {
    return smeltRecipes;
  }
 
  /**
   * Gets Smelt recipe class from the ingredient
   *
   * @param ingredient
   *            the ItemStack ingredient
   * @return Can be null if not found
   */
  public Smelt getSmeltRecipe(ItemStack ingredient)
  {
    ItemData item = new ItemData(ingredient);
    Smelt recipe = smeltRecipes.get(item.getType() + "");
   
    if(recipe == null)
      return smeltRecipes.get(item.getType() + ":" + item.getData());
   
    return recipe;
  }
 
  /**
   * Gets all FUEL recipes (furnace fuels)
   *
   * @return
   */
  public HashMap<String, Fuel> getFuels()
  {
    return fuels;
  }
 
  /**
   * Gets a Fuel recipe class recipe from an ingredient (the fuel itself)
   *
   * @param ingredient
   *            the ItemStack ingredient
   * @return
   */
  public Fuel getFuelRecipe(ItemStack ingredient)
  {
    if(ingredient == null)
      return null;
   
    return getFuelRecipe(new ItemData(ingredient));
  }
 
  /**
   * Gets a Fuel recipe class recipe from an ingredient (the fuel itself)
   *
   * @param ingredient
   *            the ItemData ingredient
   * @return
   */
  public Fuel getFuelRecipe(ItemData ingredient)
  {
    if(ingredient == null)
      return null;
   
    Fuel recipe = fuels.get(ingredient.getType() + "");
   
    if(recipe == null)
      return fuels.get(ingredient.getType() + ":" + ingredient.getData());
   
    return recipe;
  }
 
  /**
   * Returns a FurnaceData class which has some new serval furnace specific methods.
   * If furnace data entry does not exist it will be created and returned, if you don't want this to happen just use the other method with the 2nd argument as false.
   *
   * @param location
   *            location of the furnace
   * @return FurnaceData object
   */
  public FurnaceData getFurnaceData(Location location)
  {
    return getFurnaceData(location, true);
  }
 
  /**
   * Returns a FurnaceData class which has serval furnace specific methods.<br>
   * If 2nd argument is true and the FurnaceData object doesn't exist, it will be created and returned, using false will return null if object doesn't exist.
   *
   * @param location
   *            location of the furnace
   * @return FurnaceData object or null if create is false and object doesn't exist
   */
  public FurnaceData getFurnaceData(Location location, boolean create)
  {
    if(create)
    {
      String locStr = locationToString(location);
     
      if(!furnaceData.containsKey(locStr))
        furnaceData.put(locStr, new FurnaceData());
    }
   
    return furnaceData.get(locationToString(location));
  }
 
  /**
   * Gets the placeholder item that RecipeManager uses for recipe results to track them.
   * Only ID and Data values are appliable, the amount is used as the recipe's ID.
   *
   * @return The item as ItemStack object
   */
  public ItemStack getPlaceholderItem()
  {
    return placeholderItem;
  }
 
  /**
   * Does result fit player's inventory on shift+click or player's cursor on click ?
   *
   * @param player
   *            player for inventory
   * @param result
   *            result in question
   * @param cursor
   *            cursor item if not shift+click
   * @param shiftClick
   *            is shift+click ?
   * @return false if inventory or cursor is full, otherwise true.
   */
  public boolean isResultTakeable(Player player, Item result, ItemStack cursor, boolean shiftClick)
  {
    if(shiftClick)
    {
      for(ItemStack item : player.getInventory().getContents())
      {
        if(item == null || item.getTypeId() == 0 || (result.compareItemStack(item) && item.getAmount() < item.getType().getMaxStackSize()))
          return true;
      }
    }
    else
    {
      if(cursor == null || cursor.getTypeId() == 0 || (result.compareItemStack(cursor) && cursor.getAmount() < cursor.getType().getMaxStackSize()))
        return true;
    }
   
    return false;
  }
 
  /**
   * Check if item is the placeholder item used by RecipeManager to track its recipes
   * Works only for WORKBENCH recipes because only those use placeholders.
   *
   * @param result
   * @return
   */
  public boolean isCustomRecipe(ItemStack result)
  {
    return (result != null && result.getTypeId() == placeholderItem.getTypeId() && result.getDurability() == placeholderItem.getDurability());
  }
 
  /**
   * Checks if recipe is a custom RecipeManager recipe.
   * Works for workbench and furnace recipes as well.
   *
   * @param recipe
   * @return
   */
  public boolean isCustomRecipe(org.bukkit.inventory.Recipe recipe)
  {
    if(recipe instanceof FurnaceRecipe)
    {
      Smelt smelt = getSmeltRecipe(((FurnaceRecipe)recipe).getInput());
     
      return (smelt != null);
    }
   
    return isCustomRecipe(recipe.getResult());
  }
 
  /**
   * Gets all recipes for a single item
   * Useful for printing recipes
   *
   * @param item
   *            item as ItemStack
   * @param ingredient
   *            item is ingredient (true) or result (false) ?
   * @return list of recipes
   */
  public List<Recipe> getRecipesForItem(ItemStack item, boolean ingredient)
  {
    return getRecipesForItem(new Item(item), ingredient);
  }
 
  /**
   * Gets all recipes for a single item
   * Useful for printing recipes
   *
   * @param item
   *            item as Item
   * @param ingredient
   *            item is ingredient (true) or result (false) ?
   * @return list of recipes
   */
  public List<Recipe> getRecipesForItem(Item item, boolean ingredient)
  {
    List<Recipe> recipes = new ArrayList<Recipe>();
   
    for(Craft r : craftRecipes)
    {
      if(ingredient)
      {
        for(ItemData i : r.getIngredients())
        {
          if(item.compareItemData(i))
          {
            recipes.add(r);
            break;
          }
        }
      }
      else
      {
        for(Item i : r.getResults())
        {
          if(item.compareItemData(i))
          {
            recipes.add(r);
            break;
          }
        }
      }
    }
   
    for(Combine r : combineRecipes)
    {
      for(ItemData i : (ingredient ? r.getIngredients() : r.getResults()))
      {
        if(item.compareItemData(i))
        {
          recipes.add(r);
          break;
        }
      }
    }
   
    for(Smelt r : smeltRecipes.values())
    {
      if(item.compareItemData(ingredient ? r.getIngredient() : r.getResult()))
        recipes.add(r);
    }
   
    if(ingredient)
    {
      for(Fuel r : fuels.values())
      {
        if(item.compareItemData(r.getFuel()))
          recipes.add(r);
      }
    }
   
    return recipes;
  }
 
  /* ====================================================================================== */
 
  /**
   * Removes all RecipeManager's custom recipes from the server.
   */
  public void removeCustomRecipes()
  {
    Iterator<org.bukkit.inventory.Recipe> recipes = Bukkit.recipeIterator();
    org.bukkit.inventory.Recipe recipe;
   
    while(recipes.hasNext())
    {
      if((recipe = recipes.next()) != null && isCustomRecipe(recipe))
        recipes.remove();
    }
  }
 
  /**
   * Removes all vanilla Minecraft recipes.<br>
   * Note: prints "(num) recipes" in console, triggered by the game, nothing to worry about tough.
   */
  @SuppressWarnings(
  {
    "unchecked",
    "rawtypes"
  })
  public void removeDefaultRecipes() // removes only vanilla Minecraft recipes
  {
    // remove furnace recipes, easy.
    RecipesFurnace.getInstance().recipes.keySet().removeAll(new RecipesFurnace().recipes.keySet());
   
    Logger logger = Logger.getLogger("Minecraft");
    Filter oldFilter = logger.getFilter();
   
    logger.setFilter(new Filter()
    {
      @Override
      public boolean isLoggable(LogRecord log)
      {
        return !log.getMessage().endsWith(" recipes");
      }
    });
   
    // remove workbench recipes, not so easy...
    List workbenchRaw = new CraftingManager().recipes;
    List<String> workbench = new ArrayList<String>();
   
    logger.setFilter(oldFilter);
   
    for(Object raw : workbenchRaw)
    {
      if(raw instanceof ShapedRecipes)
        workbench.add(shapedToString(((ShapedRecipes)raw).toBukkitRecipe()));
     
      else if(raw instanceof ShapelessRecipes)
        workbench.add(shapelessToString(((ShapelessRecipes)raw).toBukkitRecipe()));
    }
   
    Iterator recipes = CraftingManager.getInstance().getRecipes().iterator();
    Object obj;
   
    while(recipes.hasNext())
    {
      obj = recipes.next();
     
      if(obj instanceof ShapedRecipes)
      {
        if(workbench.contains(shapedToString(((ShapedRecipes)obj).toBukkitRecipe())))
          recipes.remove();
      }
      else if(obj instanceof ShapelessRecipes)
      {
        if(workbench.contains(shapelessToString(((ShapelessRecipes)obj).toBukkitRecipe())))
          recipes.remove();
      }
    }
  }
 
  /**
   * Re-adds vanilla Minecraft recipes to the game, preserving current recipes.<br>
   * NOTE: This is diferent from getServer().resetRecipes(), that removes everything else and leaves only the vanilla recipes.
   */
  @SuppressWarnings("unchecked")
  public void restoreDefaultRecipes()
  {
    Logger logger = Logger.getLogger("Minecraft");
    Filter oldFilter = logger.getFilter();
   
    logger.setFilter(new Filter()
    {
      @Override
      public boolean isLoggable(LogRecord log)
      {
        return !log.getMessage().endsWith(" recipes");
      }
    });
   
    RecipesFurnace.getInstance().recipes.putAll(new RecipesFurnace().recipes);
    CraftingManager.getInstance().recipes.addAll(new CraftingManager().recipes);
   
    logger.setFilter(oldFilter);
  }
 
  protected boolean loadRecipes(boolean simulation)
  {
    if(!simulation)
    {
      reset();
     
      switch(RecipeManager.settings.EXISTING_RECIPES)
      {
        case 'r':
          removeDefaultRecipes();
          break;
       
        case 'c':
          Bukkit.clearRecipes();
          break;
      }
    }
   
    File dir = new File(RecipeManager.plugin.getDataFolder() + File.separator + "recipes");
   
    if(!dir.exists())
      dir.mkdirs();
   
    recipeErrors = new HashMap<String, List<String>>();
   
    loadDirectory(dir, simulation);
   
    boolean hasErrors = !recipeErrors.isEmpty();
   
    if(hasErrors)
    {
      StringBuilder errors = new StringBuilder();
     
      for(Entry<String, List<String>> entry : recipeErrors.entrySet())
      {
        errors.append("" + ChatColor.BOLD + ChatColor.BLUE + "File: " + entry.getKey() + NL);
       
        for(String error : entry.getValue())
        {
          errors.append(ChatColor.WHITE + error + NL);
        }
       
        errors.append(NL);
      }
     
      Messages.log(ChatColor.RED + "There were errors processing the files: " + NL + NL + errors + NL + NL);
     
      try
      {
        File file = new File(RecipeManager.plugin.getDataFolder() + File.separator + "last recipe errors.log");
       
        if(!file.exists())
          file.createNewFile();
       
        BufferedWriter stream = new BufferedWriter(new FileWriter(file, false));
       
        stream.write(ChatColor.stripColor(errors.toString()));
       
        stream.close();
       
        Messages.log(ChatColor.YELLOW + "These errors have been saved in 'server.log' and '" + file.getPath() + "' as well.");
      }
      catch(Exception e)
      {
        e.printStackTrace();
      }
    }
   
    recipeErrors = null;
    currentFile = null;
    currentFileLine = 0;
   
    if(!simulation)
    {
      log.fine("Total loaded:");
      log.fine("  " + craftRecipes.size() + " crafting recipes");
      log.fine("  " + combineRecipes.size() + " combining recipes");
      log.fine("  " + smeltRecipes.size() + " smelting recipes");
      log.fine("  " + fuels.size() + " fuels");
     
      // restart furnace task if any custom time smelt recipes exist
     
      boolean cancelTask = !RecipeManager.settings.COMPATIBILITY_CHUNKEVENTS;
     
      if(!cancelTask)
      {
        if(smeltCustomRecipes)
        {
          if(furnaceSmelting == null)
            furnaceSmelting = new HashMap<String, MutableDouble>();
         
          if(furnaceTaskId <= 0)
            furnaceTaskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(RecipeManager.plugin, new FurnacesTask(RecipeManager.settings.FURNACE_TICKS), 0, RecipeManager.settings.FURNACE_TICKS);
        }
        else
        {
          cancelTask = true;
          furnaceSmelting = null;
        }
      }
     
      if(cancelTask && furnaceTaskId > 0)
      {
        Bukkit.getScheduler().cancelTask(furnaceTaskId);
        furnaceTaskId = -1;
      }
    }
   
    return !hasErrors;
  }
 
  private void loadDirectory(File dir, boolean simulation)
  {
    log.fine("Loading '" + dir.getName() + "' folder...");
   
    String fileName;
   
    for(File file : dir.listFiles())
    {
      fileName = file.getName();
     
      if(file.isDirectory())
      {
        if(fileName.equalsIgnoreCase("disabled"))
        {
          log.fine("Skipped the 'disabled' folder");
          continue;
        }
       
        if(RecipeManager.settings.EXISTING_RECIPES == 'n' && fileName.equalsIgnoreCase("default"))
        {
          log.fine("Skipped the 'default' folder (due to config's 'existing-recipes' set to 'nothing')");
          continue;
        }
       
        loadDirectory(file, simulation);
      }
      else if(fileName.endsWith(".txt"))
      {
        try
        {
          loadDataFile(file, simulation);
        }
        catch(Exception e)
        {
          Messages.log(ChatColor.RED + "Failed to load '" + file.toString() + "', error:");
          e.printStackTrace();
        }
      }
    }
  }
 
  private void loadDataFile(File file, boolean simulation) throws Exception
  {
    String fileName = file.getPath().replace(RecipeManager.plugin.getDataFolder().getPath() + "\\", "");
   
    log.fine("Loading '" + fileName + "' file...");
   
    BufferedReader reader = new BufferedReader(new InputStreamReader(new DataInputStream(new FileInputStream(file))));
    Recipe recipeData = new Recipe();
    String[] error;
    String line;
    currentFile = fileName;
    currentFileLine = -1; // one line behind to be more human friendly
   
    while((line = readLine(reader)) != null)
    {
      if((line = processLine(line, false)) == null)
        continue;
     
      try
      {
        if((line = processRecipeData(line, reader, recipeData)) == null)
          continue;
       
        error = null;
       
        if(line.equalsIgnoreCase("CRAFT"))
          error = craftRecipe(line, reader, new Craft(recipeData), simulation);
        else if(line.equalsIgnoreCase("COMBINE"))
          error = combineRecipe(line, reader, new Combine(recipeData), simulation);
        else if(line.equalsIgnoreCase("SMELT"))
          error = smeltRecipe(line, reader, new Smelt(recipeData), simulation);
        else if(line.equalsIgnoreCase("FUEL"))
          error = fuelRecipe(line, reader, new Fuel(recipeData), simulation);
        else
          error = new String[]
          {
            "<yellow>This line was skipped: '" + line + "'",
            "Possible causes: missing recipe type (CRAFT, SMELT, etc); it's part of a recipe and it has empty lines before it"
          };
       
        if(error != null)
          recipeError(error[0], (error.length > 1 ? error[1] : null));
      }
      catch(Exception e)
      {
        e.printStackTrace();
      }
    }
   
    if(currentFileLine == 0)
      Messages.log("<yellow>Recipe file '" + fileName + "' is empty.");
   
    reader.close();
  }
 
  private String readLine(BufferedReader reader) throws Exception
  {
    currentFileLine++; // to easily keep track of lines
   
    return reader.readLine();
  }
 
  private String processLine(String string, boolean upperCase)
  {
    if(string == null || string.isEmpty())
      return null;
   
    String comments[] =
    {
      "//",
      "#"
    };
   
    int index;
    string = (upperCase ? string.trim().toUpperCase() : string.trim());
   
    for(String comm : comments)
    {
      index = string.indexOf(comm);
     
      if(index == 0)
        return null;
     
      if(index > -1)
        return string.substring(0, index).trim();
    }
   
    return string;
  }
 
  private void recipeError(String error)
  {
    recipeError(error, null);
  }
 
  private void recipeError(String error, String tip)
  {
    List<String> errors = recipeErrors.get(currentFile);
   
    if(errors == null)
      errors = new ArrayList<String>();
   
    errors.add("line " + String.format("%-5d", currentFileLine) + ChatColor.WHITE + error + (tip != null ? NL + ChatColor.DARK_GREEN + "          TIP: " + ChatColor.GRAY + tip : ""));
   
    recipeErrors.put(currentFile, errors);
  }
 
  protected ItemData processItemData(String string, int defaultData, boolean allowData, boolean printRecipeErrors)
  {
    Item item = processItem(string, defaultData, allowData, false, false, printRecipeErrors);
   
    return (item == null ? null : new ItemData(item));
  }
 
  protected Item processItem(String string, int defaultData, boolean allowData, boolean allowAmount, boolean allowEnchantments, boolean printRecipeErrors)
  {
    string = string.trim();
   
    if(string.length() == 0)
      return null;
   
    String itemString[] = string.split("\\|");
    String stringArray[] = itemString[0].trim().split(":");
   
    if(stringArray.length <= 0 || stringArray[0].isEmpty())
      return new Item(0);
   
    stringArray[0] = stringArray[0].trim();
    String alias = RecipeManager.plugin.getAliases().get(stringArray[0]);
   
    if(alias != null)
    {
      if(stringArray.length > 2 && printRecipeErrors)
        recipeError("'" + stringArray[0] + "' is an alias with data and amount.", "You can only set amount e.g.: alias:amount.");
     
      return processItem(string.replace(stringArray[0], alias), defaultData, allowData, allowAmount, allowEnchantments, printRecipeErrors);
    }
   
    Material mat = Material.matchMaterial(stringArray[0]);
   
    if(mat == null)
    {
      if(printRecipeErrors)
        recipeError("Item '" + stringArray[0] + "' does not exist!", "Name could be different, look in readme.txt for links");
     
      return null;
    }
   
    int type = mat.getId();
   
    if(type <= 0)
      return new Item(0);
   
    int data = defaultData;
   
    if(stringArray.length > 1)
    {
      if(allowData)
      {
        try
        {
          stringArray[1] = stringArray[1].trim();
         
          if(stringArray[1].charAt(0) != '*')
            data = Math.max(Integer.valueOf(stringArray[1]), data);
        }
        catch(Exception e)
        {
          if(printRecipeErrors)
            recipeError("Item '" + mat + " has data value that is not a number: '" + stringArray[1] + "', defaulting to " + defaultData);
        }
      }
      else if(printRecipeErrors)
        recipeError("Item '" + mat + "' can't have data value defined in this recipe's slot, data value ignored.");
    }
   
    int amount = 1;
   
    if(stringArray.length > 2)
    {
      if(allowAmount)
      {
        try
        {
          amount = Math.max(Integer.valueOf(stringArray[2].trim()), 1);
        }
        catch(Exception e)
        {
          if(printRecipeErrors)
            recipeError("Item '" + mat + "' has amount value that is not a number: " + stringArray[2] + ", defaulting to 1");
        }
      }
      else if(printRecipeErrors)
        recipeError("Item '" + mat + "' can't have amount defined in this recipe's slot, amount ignored.");
    }
   
    Item item = new Item(type, amount, (short)data);
   
    if(itemString.length > 1)
    {
      if(allowEnchantments)
      {
        if(item.getAmount() > 1)
        {
          if(printRecipeErrors)
            recipeError("Item '" + mat + "' has enchantments and more than 1 amount, it can't have both, amount set to 1.");
         
          item.setAmount(1);
        }
       
        String[] enchants = itemString[1].split(",");
        String[] enchData;
        Enchantment ench;
        int level;
       
        for(String enchant : enchants)
        {
          enchant = enchant.trim();
          enchData = enchant.split(":");
         
          if(enchData.length != 2)
          {
            if(printRecipeErrors)
              recipeError("Enchantments have to be 'ENCHANTMENT:LEVEL' format.", "Look in readme.txt for enchantment list link.");
           
            continue;
          }
         
          ench = Enchantment.getByName(enchData[0]);
         
          if(ench == null)
          {
            try
            {
              ench = Enchantment.getById(Integer.valueOf(enchData[0]));
            }
            catch(Exception e)
            {
              ench = null;
            }
           
            if(ench == null)
            {
              if(printRecipeErrors)
                recipeError("Enchantment '" + enchData[0] + "' does not exist!", "Name or ID could be different, look in readme.txt for enchantments list links.");
             
              continue;
            }
          }
         
          if(enchData[1].equals("MAX"))
            level = ench.getMaxLevel();
         
          else
          {
            try
            {
              level = Integer.valueOf(enchData[1]);
            }
            catch(Exception e)
            {
              if(printRecipeErrors)
                recipeError("Invalid enchantment level: '" + enchData[1] + "' must be a valid number, positive, zero or negative.");
             
              continue;
            }
          }
         
          item.addEnchantment(ench, level);
        }
      }
      else if(printRecipeErrors)
        recipeError("Item '" + mat + "' can't use enchantments in this recipe slot!");
    }
   
    return item;
  }
 
  private String[] craftRecipe(String line, BufferedReader reader, Craft recipe, boolean simulation) throws Exception
  {
    line = processLine(readLine(reader), false); // skip recipe header, read next line
    line = processRecipeData(line, reader, recipe); // check for @flags
   
    if(line == null)
      return new String[]
      {
        "Recipe has no ingredients !"
      };
   
    HashMap<ItemData, Character> itemChars = new HashMap<ItemData, Character>();
    List<String> recipeShape = new ArrayList<String>();
    ItemData[] ingredients = new ItemData[9];
//    Item[] leftovers = new Item[9];
   
    String split[];
//    String itemSplit[];
    String resultRaw = null;
    ItemData itemData;
    Character c;
    int charIndex = 97;
    int itemIndex = 0;
    int rows = 0;
    int largest = 0;
    boolean errors = false;
   
    while(rows < 3)
    {
      if(rows > 0)
        line = processLine(readLine(reader), true);
     
      if(line == null)
        continue;
     
      // if we bump into the result prematurely (smaller recipes)
     
      if(line.charAt(0) == '=')
      {
        resultRaw = line;
        break;
      }
     
      split = line.split("\\+");
     
      int len = Math.min(split.length, 3); // max 3 items per row !
      StringBuilder recipeChars = new StringBuilder();
     
      itemIndex = rows * 3;
      largest = Math.max(len, largest);
     
      for(int i = 0; i < len; i++)
      {
        // splitting line by ">" char to get leftover item (if any)
//        itemSplit = split[i].split(">");
        itemData = processItemData(split[i] /*itemSplit[0]*/, -1, true, true);
       
        if(itemData == null)
        {
          errors = true;
          continue;
        }
       
        if(itemData.getType() > 0)
        {
          Character ic = itemChars.get(itemData);
         
          if(ic == null)
          {
            c = (char)charIndex++;
           
            itemChars.put(itemData, c);
          }
          else
            c = ic;
         
          ingredients[itemIndex] = itemData; // add non-AIR item
        }
        else
          c = ' ';
       
        recipeChars.append(c);
       
        // get leftover item (if any)
        /*
        if(itemData.type != 0 && itemSplit.length > 1)
          leftovers[itemIndex] = Main.processItem(itemSplit[1].trim(), 0, true, true);
        */
       
        itemIndex++;
      }
     
      recipeShape.add(recipeChars.toString());
      rows++;
    }
   
    if(rows == 0)
      return new String[]
      {
        "Recipe doesn't have ingredients !",
        "Consult readme.txt for proper recipe syntax."
      };
   
    // If result wasn't found yet, it's got to be the 4th line
   
    if(resultRaw == null)
      resultRaw = processLine(readLine(reader), true);
   
    List<Item> results = getResults(resultRaw, reader, false, false);
   
    if(results == null)
      return null;
   
    if(errors)
      return new String[]
      {
        "Recipe has some invalid ingredients, fix them!"
      };
   
    // starting the recipe and setting it's result
   
    ShapedRecipe bukkitRecipe = new ShapedRecipe(new ItemStack(placeholderItem.getTypeId(), craftNum, placeholderItem.getDurability()));
    String[] chars = new String[recipeShape.size()];
    int i = 0;
   
    for(String shapeRow : recipeShape)
    {
      chars[i++] = (shapeRow.length() < largest ? (shapeRow + "    ").substring(0, largest) : shapeRow);
    }
   
    bukkitRecipe.shape(chars);
   
    for(Entry<ItemData, Character> entry : itemChars.entrySet())
    {
      bukkitRecipe.setIngredient(entry.getValue(), entry.getKey().getMaterial(), entry.getKey().getData());
    }
   
    if(simulation)
      return null;
   
    String ingredientsString = ingredientsToString(ingredients);
   
    for(Craft r : craftRecipes)
    {
      if(ingredientsString.equals(ingredientsToString(r.getIngredients())))
        return new String[]
        {
          "Recipe already defined in one of your recipe files.",
          (recipe.getOverride() ? "You can't override recipes that are already handled by this plugin because you can simply edit them!" : null)
        };
    }
   
    Iterator<org.bukkit.inventory.Recipe> recipes = Bukkit.getServer().recipeIterator();
    org.bukkit.inventory.Recipe rec;
    ShapedRecipe r;
    int width = chars[0].length();
    int height = chars.length;
    boolean override = recipe.getOverride();
    boolean exists = false;
   
    while(recipes.hasNext())
    {
      rec = recipes.next();
     
      if(rec == null || !(rec instanceof ShapedRecipe) || isCustomRecipe(rec.getResult()))
        continue;
     
      r = (ShapedRecipe)rec;
     
      if(height != r.getShape().length || width != r.getShape()[0].length())
        continue;
     
      if(compareCraftRecipe(ingredients, r))
      {
        exists = true;
       
        if(override)
        {
          recipes.remove();
          overriddenRecipes.add(ingredientsString);
        }
      }
    }
   
    if(override)
    {
      if(!exists && !overriddenRecipes.contains(ingredientsString))
        recipeError("The @override flag couldn't find the original recipe to override, added new recipe instead.", "Maybe shape is diferent, mirrored perhaps ?");
    }
    else
    {
      if(exists)
        return new String[]
        {
          "Recipe already exists! It's either vanilla or added by another plugin/mod.",
          "Add @override flag to the recipe to supercede it."
        };
    }
   
    // finally add the recipe to the server!
   
    if(!Bukkit.addRecipe(bukkitRecipe))
      return new String[]
      {
        "Couldn't add recipe to server, unknown error!"
      };
   
    recipe.setIngredients(ingredients);
    recipe.setResults(results);
   
    craftRecipes.add(craftNum, recipe);
    craftNum++;
   
    return null;
  }
 
  private String ingredientsToString(ItemData[] ingredients)
  {
    StringBuilder str = new StringBuilder("craft");
   
    for(ItemData ingredient : ingredients)
    {
      str.append(',').append(ingredient == null ? "" : ingredient.convertString());
    }
   
    return str.toString();
  }
 
  private String ingredientsToString(List<Item> ingredients)
  {
    StringBuilder str = new StringBuilder("combine");
   
    for(ItemData ingredient : ingredients)
    {
      str.append(',').append(ingredient == null ? "" : ingredient.convertString());
    }
   
    return str.toString();
  }
 
  private boolean compareCraftRecipe(ItemData[] ingredients, ShapedRecipe recipe)
  {
    ItemStack[] matrix = new ItemStack[9];
    Map<Character, ItemStack> items = recipe.getIngredientMap();
    String[] shape = recipe.getShape();
    int slot = 0;
   
    for(int i = 0; i < shape.length; i++)
    {
      for(char col : shape[i].toCharArray())
      {
        matrix[slot] = items.get(col);
        slot++;
      }
     
      slot = ((i + 1) * 3);
    }
   
    for(int i = 0; i < 9; i++)
    {
      if(matrix[i] == null && ingredients[i] == null)
        continue;
     
      if(matrix[i] == null || ingredients[i] == null || ingredients[i].getType() != matrix[i].getTypeId() || (ingredients[i].getData() != -1 && ingredients[i].getData() != matrix[i].getDurability()))
        return false;
    }
   
    return true;
  }
 
  private String[] combineRecipe(String line, BufferedReader reader, Combine recipe, boolean simulation) throws Exception
  {
    line = processLine(readLine(reader), false); // skip recipe header, read next line
    line = processRecipeData(line, reader, recipe); // check for @flags
   
    if(line == null)
      return new String[]
      {
        "Recipe has no ingredients !"
      };
   
    String[] ingredientsRaw = line.split("\\+");
   
    List<Item> results = getResults(processLine(readLine(reader), true), reader, false, false);
   
    if(results == null)
      return null;
   
    ShapelessRecipe bukkitRecipe = new ShapelessRecipe(new ItemStack(placeholderItem.getTypeId(), combineNum, placeholderItem.getDurability()));
    List<Item> ingredients = new ArrayList<Item>();
    Item item;
    int items = 0;
   
    for(String itemRaw : ingredientsRaw)
    {
      item = processItem(itemRaw, -1, true, true, false, true);
     
      if(item == null)
        return new String[]
        {
          "Recipe has some invalid ingredients, fix them!"
        };
     
      if((items += item.getAmount()) > 9)
        return new String[]
        {
          "Combine recipes can't have more than 9 ingredients !"
        };
     
      ingredients.add(item);
      bukkitRecipe.addIngredient(item.getAmount(), item.getMaterial(), item.getData());
    }
   
    if(simulation)
      return null;
   
    for(Combine r : combineRecipes)
    {
      if(compareCombineIngredients(r.getIngredients(), bukkitRecipe.getIngredientList()))
        return new String[]
        {
          "Recipe already defined in one of your recipe files.",
          (recipe.getOverride() ? "You can't override recipes that are already handled by this plugin because you can simply edit them!" : null)
        };
    }
   
    Iterator<org.bukkit.inventory.Recipe> recipes = Bukkit.getServer().recipeIterator();
    org.bukkit.inventory.Recipe rec;
    ShapelessRecipe r;
    String ingredientsString = ingredientsToString(ingredients);
    boolean override = recipe.getOverride();
    boolean exists = false;
   
    while(recipes.hasNext())
    {
      rec = recipes.next();
     
      if(rec == null || !(rec instanceof ShapelessRecipe) || isCustomRecipe(rec.getResult()))
        continue;
     
      r = (ShapelessRecipe)rec;
     
      if(compareCombineIngredients(ingredients, r.getIngredientList()))
      {
        exists = true;
       
        if(override)
        {
          recipes.remove();
          overriddenRecipes.add(ingredientsString);
        }
      }
    }
   
    if(override)
    {
      if(!exists && !overriddenRecipes.contains(ingredientsString))
        recipeError("The @override flag couldn't find the original recipe to override, added new recipe instead.");
    }
    else
    {
      if(exists)
        return new String[]
        {
          "Recipe already exists! It's either vanilla or added by another plugin/mod.",
          "Add @override flag to the recipe to supercede it."
        };
    }
   
    if(!Bukkit.addRecipe(bukkitRecipe))
      return new String[]
      {
        "Couldn't add recipe to server, unknown error"
      };
   
    recipe.setIngredients(ingredients);
    recipe.setResults(results);
   
    combineRecipes.add(combineNum, recipe);
    combineNum++;
   
    return null;
  }
 
  private boolean compareCombineIngredients(List<Item> ingredients, List<ItemStack> recipeIngredients)
  {
    int size = ingredients.size();
   
    if(size != recipeIngredients.size())
      return false;
   
    HashSet<Integer> compared = new HashSet<Integer>();
    boolean found;
   
    for(Item item : ingredients)
    {
      found = false;
     
      for(int i = 0; i < size; i++)
      {
        if(compared.contains(i))
          continue;
       
        if(item.compareItemStack(recipeIngredients.get(i)))
        {
          compared.add(i);
          found = true;
          break;
        }
      }
     
      if(!found)
        return false;
    }
   
    return (compared.size() == size);
  }
 
  private String[] smeltRecipe(String line, BufferedReader reader, Smelt recipe, boolean simulation) throws Exception
  {
    line = processLine(readLine(reader), false); // skip recipe header, read next line
    line = processRecipeData(line, reader, recipe); // check for @flags
   
    if(line == null)
      return new String[]
      {
        "Recipe doesn't have an ingredient !"
      };
   
    String[] split = line.split("%");
   
    if(split.length == 0)
      return new String[]
      {
        "Recipe doesn't have an ingredient !"
      };
   
    List<Item> results = getResults(processLine(readLine(reader), true), reader, true, true);
   
    if(results == null)
      return null;
   
    if(results.size() > 1)
      recipeError("Can't have more than 1 result in this recipe, the rest were ignored.");
   
    Item result = results.get(0);
   
    // TODO: revert allowData to true when data values for furnaces have been fixed
    ItemData ingredient = processItemData(split[0], -1, false, true);
   
    if(ingredient == null)
      return new String[]
      {
        "Invalid ingredient '" + split[0] + "'.",
        "Name could be diferent, look in readme.txt for links."
      };
   
    double minTime = -1.0;
    double maxTime = -1.0;
   
    if(split.length >= 2)
    {
      String[] timeSplit = split[1].trim().split("-");
     
      if(!timeSplit[0].equals("INSTANT"))
      {
        minTime = Double.valueOf(timeSplit[0]);
       
        if(timeSplit.length >= 2)
          maxTime = Double.valueOf(timeSplit[1]);
      }
      else
        minTime = 0.0;
     
      if(maxTime > -1.0 && minTime >= maxTime)
        return new String[]
        {
          "Smelting recipe has minimum time less or equal to maximum time!",
          "Use a single number if you want a fixed value."
        };
    }
   
    // the recipe
   
    if(simulation)
      return null;
   
    if(smeltRecipes.containsKey(ingredient.getType() + (ingredient.getData() == -1 ? "" : ":" + ingredient.getData())))
      return new String[]
      {
        "Recipe for '" + ingredient.printItemData() + "' is already defined in one of your recipe files.",
        (recipe.getOverride() ? "You can't override recipes that are already handled by this plugin because you can simply edit them!" : null)
      };
   
    Iterator<org.bukkit.inventory.Recipe> recipes = Bukkit.getServer().recipeIterator();
    org.bukkit.inventory.Recipe rec;
    FurnaceRecipe r;
    String ingredientsString = "smelt," + ingredient.convertString();
    boolean override = recipe.getOverride();
    boolean exists = false;
   
    while(recipes.hasNext())
    {
      rec = recipes.next();
     
      if(rec == null || !(rec instanceof FurnaceRecipe) || isCustomRecipe(rec.getResult()))
        continue;
     
      r = (FurnaceRecipe)rec;
     
      if(ingredient.compareItemStack(r.getInput()))
      {
        exists = true;
       
        if(override)
        {
          recipes.remove();
          overriddenRecipes.add(ingredientsString);
        }
      }
    }
   
    if(override)
    {
      if(!exists && !overriddenRecipes.contains(ingredientsString))
        recipeError("The @override flag couldn't find the original recipe to override, added new recipe instead.");
    }
    else
    {
      if(exists)
        return new String[]
        {
          "Recipe for '" + ingredient.printItemData() + "' already exists! It's either vanilla or added by another plugin/mod.",
          "Add @override flag to the recipe to supercede it."
        };
    }
   
    if(!Bukkit.addRecipe(new FurnaceRecipe((result.getType() == 0 ? new ItemStack(placeholderItem.getTypeId()) : result.getItemStack()), ingredient.getMaterial(), ingredient.getData())))
      return new String[]
      {
        "Couldn't add recipe to server, unknown error"
      };
   
    recipe.setIngredient(ingredient);
    recipe.setResult(result);
    recipe.setMinTime(minTime);
    recipe.setMaxTime(maxTime);
   
    smeltRecipes.put(ingredient.convertString(), recipe);
   
    if(minTime >= 0.0)
      smeltCustomRecipes = true;
   
    return null;
  }
 
  private String[] fuelRecipe(String line, BufferedReader reader, Fuel recipe, boolean simulation) throws Exception
  {
    line = processLine(readLine(reader), false); // skip recipe header, read next line
    line = processRecipeData(line, reader, recipe); // check for @flags
   
    if(line == null)
      return new String[]
      {
        "Recipe doesn't have an ingredient !"
      };
   
    String[] split = line.split("%");
   
    if(split[1] == null)
      return new String[]
      {
        "Burn time not set !",
        "It must be set after the ingredient like: ingredient % burntime"
      };
   
    String[] timeSplit = split[1].trim().split("-");
    int minTime = Math.max(Integer.valueOf(timeSplit[0]), 1);
    int maxTime = -1;
   
    if(timeSplit.length >= 2)
      maxTime = Math.max(Integer.valueOf(timeSplit[1]), maxTime);
   
    ItemData ingredient = processItemData(split[0], -1, true, true);
   
    if(ingredient == null || ingredient.getType() == 0)
      return new String[]
      {
        "Invalid item: '" + ingredient + "'"
      };
   
    if(minTime <= 0)
      return new String[]
      {
        "Fuel " + ingredient + " can't burn for negative or zero seconds!"
      };
   
    if(maxTime > -1 && minTime >= maxTime)
      return new String[]
      {
        "Fuel " + ingredient + " has minimum time less or equal to maximum time!",
        "Use a single number if you want a fixed value"
      };
   
    if(simulation)
      return null;
   
    String ingredientString = ingredient.convertString();
   
    if(fuels.containsKey(ingredientString))
      return new String[]
      {
        "Fuel " + ingredient.getMaterial() + ":" + ingredient.getData() + " already exists!",
        "Search the recipe files from this plugin, it can't be from other plugins or mods."
      };
   
    recipe.setFuel(ingredient);
    recipe.setMinTime(minTime);
    recipe.setMaxTime(maxTime);
   
    fuels.put(ingredientString, recipe);
   
    return null;
  }
 
  private List<Item> getResults(String line, BufferedReader reader, boolean allowAir, boolean oneResult) throws Exception
  {
    List<Item> results = new ArrayList<Item>();
    Item noPercentItem = null;
    String[] resultRawSplit;
    Item item;
    int totalpercentage = 0;
   
    while(line != null && !line.isEmpty() && line.charAt(0) == '=')
    {
      resultRawSplit = line.substring(1).trim().split("%"); // remove the = character and see if it's got percentage
     
      if(resultRawSplit.length >= 2)
      {
        item = processItem(resultRawSplit[1].trim(), 0, true, true, true, true);
       
        if(item == null || (!allowAir && item.getType() == 0))
        {
          recipeError("Invalid result !");
          return null;
        }
       
        try
        {
          item.setChance(Math.min(Math.max(Integer.valueOf(resultRawSplit[0].trim()), 0), 100));
        }
        catch(Exception e)
        {
          recipeError("Invalid percentage number: " + resultRawSplit[0]);
          return null;
        }
       
        if((totalpercentage += item.getChance()) > 100)
        {
          recipeError("Total result items' chance exceeds 100% !", "Not defining percentage for one item will make its chance fit with the rest until 100%");
          return null;
        }
       
        results.add(item);
      }
      else
      {
        if(resultRawSplit[0] == null)
        {
          recipeError("Missing result !");
          return null;
        }
       
        if(noPercentItem != null)
        {
          recipeError("Can't have more than 1 item without procentage to fill the rest!");
          return null;
        }
       
        noPercentItem = processItem(resultRawSplit[0].trim(), 0, true, true, true, true);
       
        if(noPercentItem == null || (!allowAir && noPercentItem.getType() == 0))
        {
          recipeError("Invalid result !");
          return null;
        }
      }
     
      line = processLine(readLine(reader), true);
    }
   
    if(noPercentItem != null)
    {
      noPercentItem.setChance(100 - totalpercentage);
      results.add(noPercentItem);
    }
    else if(results.isEmpty())
    {
      recipeError("Missing result !");
      return null;
    }
    else if(!oneResult && totalpercentage < 100)
      results.add(new Item(0, 0, (short)0, (100 - totalpercentage)));
   
    return results;
  }
 
  protected void reset()
  {
    removeCustomRecipes();
   
    if(RecipeManager.settings.EXISTING_RECIPES != 'c')
    {
      removeDefaultRecipes();
      restoreDefaultRecipes();
    }
   
    craftRecipes.clear();
    combineRecipes.clear();
    smeltRecipes.clear();
    fuels.clear();
   
    craftNum = 0;
    combineNum = 0;
    smeltCustomRecipes = false;
    hasExplosive = false;
  }
 
  private String processRecipeData(String line, BufferedReader reader, Recipe recipeData) throws Exception
  {
    while(line != null && line.charAt(0) == '@')
    {
      recipeFlags(line, recipeData);
      line = processLine(readLine(reader), false);
    }
   
    return (line == null ? null : line.toUpperCase());
  }
 
  private void recipeFlags(String line, Recipe recipe)
  {
    String[] split = line.split(":", 2);
    String flag = split[0].substring(1).trim();
   
    if(flag.equalsIgnoreCase("log"))
    {
      if(split.length > 1)
      {
        try
        {
          recipe.setLog(split[1].trim().equalsIgnoreCase("true"));
        }
        catch(Exception e)
        {
          recipeError("@" + flag + " only accepts true or false! Set to true.");
          recipe.setLog(true);
        }
      }
      else
        recipe.setLog(true);
     
      return;
    }
   
    if(flag.equalsIgnoreCase("override"))
    {
      if(recipe instanceof Fuel)
      {
        recipeError("@" + flag + " doesn't do anything for FUEL recipes, ignored.");
        return;
      }
     
      if(split.length > 1)
      {
        try
        {
          recipe.setOverride(split[1].trim().equalsIgnoreCase("true"));
        }
        catch(Exception e)
        {
          recipeError("@" + flag + " only accepts true or false! Set to true.");
          recipe.setOverride(true);
        }
      }
      else
        recipe.setOverride(true);
     
      return;
    }
   
    if(split.length < 2)
    {
      recipeError("@" + flag + " doesn't have any values!");
      return;
    }
   
    String value = split[1].trim();
   
    if(flag.equalsIgnoreCase("failmessage"))
    {
      recipe.setFailMessage(value);
      return;
    }
   
    if(flag.equalsIgnoreCase("permission"))
    {
      if(value.equalsIgnoreCase("false"))
      {
        recipe.setPermission(null);
        return;
      }
     
      split = value.split("\\|");
      value = split[0].trim();
      String failMessage = null;
     
      if(split.length > 1)
        failMessage = split[1];
     
      split = value.split("=");
      String perm = split[0].trim();
     
      if(perm.length() < 3 || !perm.matches("[a-zA-Z0-9.]+"))
      {
        recipeError("@" + flag + " has invalid value: '" + perm + "', minimum 3 chars, numbers, letters and dots only!");
        return;
      }
     
      Permission p = Bukkit.getPluginManager().getPermission(perm);
      boolean exists = (p != null);
     
      if(!exists)
      {
        p = new Permission(perm);
        p.setDescription("RecipeManager's recipes");
      }
     
      if(split.length > 1)
      {
        String defValue = split[1].trim();
       
        switch(defValue.charAt(0))
        {
          case 'T':
          case 't':
          {
            p.setDefault(PermissionDefault.TRUE);
            break;
          }
         
          case 'F':
          case 'f':
          {
            p.setDefault(PermissionDefault.FALSE);
            break;
          }
         
          case 'O':
          case 'o':
          {
            p.setDefault(PermissionDefault.OP);
            break;
          }
         
          case 'N':
          case 'n':
          {
            p.setDefault(PermissionDefault.NOT_OP);
            break;
          }
         
          default:
          {
            recipeError("@" + flag + " has invalid default value '" + defValue + "' for permission node '" + perm + "'", "Valid values: true, false, op, non-op");
            return;
          }
        }
       
        log.fine("Set permission " + perm + "'s default value to " + p.getDefault().toString());
      }
     
      recipe.setPermission(new Flag<String>(p.getName(), failMessage));
     
      if(!exists)
        Bukkit.getPluginManager().addPermission(p);
     
      return;
    }
   
    if(flag.equalsIgnoreCase("groups"))
    {
      if(!RecipeManager.permissions.isEnabled())
      {
        recipeError("@" + flag + " can't work without Vault and a permission plugin that supports groups, ignored.");
        return;
      }
     
      if(value.equalsIgnoreCase("false"))
      {
        recipe.setGroups(null);
        return;
      }
     
      split = value.split("\\|");
      value = split[0].trim();
      String failMessage = null;
     
      if(split.length > 1)
        failMessage = split[1];
     
      recipe.setGroups(new Flag<Set<String>>(new HashSet<String>(), failMessage));
      split = value.split(",");
     
      for(String group : split)
      {
        group = group.trim();
       
        if(group.isEmpty())
        {
          recipeError("@" + flag + " has a group that's invalid: '" + group + "'");
          continue;
        }
       
        recipe.getGroups().getValue().add(group);
      }
     
      if(recipe.getGroups().getValue().isEmpty())
        recipe.setGroups(null);
     
      return;
    }
   
    if(flag.equalsIgnoreCase("anygroup"))
    {
      if(!RecipeManager.permissions.isEnabled())
      {
        recipeError("@" + flag + " can't work without Vault and a permission plugin that supports groups, ignored.");
        return;
      }
     
      if(value.equalsIgnoreCase("false"))
      {
        recipe.setAnyGroup(null);
        return;
      }
     
      split = value.split("\\|");
      value = split[0].trim();
      String failMessage = null;
     
      if(split.length > 1)
        failMessage = split[1];
     
      recipe.setAnyGroup(new Flag<Set<String>>(new HashSet<String>(), failMessage));
      split = value.split(",");
     
      for(String group : split)
      {
        group = group.trim();
       
        if(group.isEmpty())
        {
          recipeError("@" + flag + " has a group that's invalid: '" + group + "'");
          continue;
        }
       
        recipe.getAnyGroup().getValue().add(group);
      }
     
      if(recipe.getAnyGroup().getValue().isEmpty())
        recipe.setAnyGroup(null);
     
      return;
    }
   
    if(flag.equalsIgnoreCase("worlds"))
    {
      if(value.equalsIgnoreCase("false"))
      {
        recipe.setWorlds(null);
        return;
      }
     
      split = value.split("\\|");
      value = split[0].trim();
      String failMessage = null;
     
      if(split.length > 1)
        failMessage = split[1];
     
      recipe.setWorlds(new Flag<Set<String>>(new HashSet<String>(), failMessage));
      split = value.split(",");
     
      for(String world : split)
      {
        world = world.trim();
       
        if(world.isEmpty() || Bukkit.getWorld(world) == null)
        {
          recipeError("@" + flag + " has a world that's invalid: '" + world + "'");
          continue;
        }
       
        recipe.getWorlds().getValue().add(world);
      }
     
      if(recipe.getWorlds().getValue().isEmpty())
        recipe.setWorlds(null);
     
      return;
    }
   
    if(flag.equalsIgnoreCase("proximity"))
    {
      if(recipe instanceof Craft || recipe instanceof Combine)
      {
        recipeError("@" + flag + " doesn't do anything for CRAFT or COMBINE recipes, ignored.");
        return;
      }
     
      if(value.equalsIgnoreCase("false"))
      {
        recipe.setProximity(null);
        return;
      }
     
      split = value.split("\\|");
      value = split[0].trim();
      String failMessage = null;
      String successMessage = null;
     
      if(split.length > 1)
      {
        failMessage = split[1].trim();
       
        if(split.length > 2)
          successMessage = split[2].trim();
      }
     
      try
      {
        recipe.setProximity(new Flag<Integer>((value.equalsIgnoreCase("online") ? -1 : Integer.valueOf(value)), failMessage, successMessage));
      }
      catch(Exception e)
      {
        recipeError("@" + flag + " has invalid number value!");
        return;
      }
     
      return;
    }
   
    if(flag.equalsIgnoreCase("explode"))
    {
      if(value.equalsIgnoreCase("false"))
      {
        recipe.setExplode(null);
        return;
      }
     
      split = value.split("\\|");
      value = split[0].trim();
      String message = null;
     
      if(split.length > 1)
        message = split[1].trim();
     
      split = value.split(" ");
     
      if(split.length < 3)
      {
        recipeError("@" + flag + " doesn't have enough arguments! Read readme.txt for requirements.");
        return;
      }
     
      try
      {
        char when = Character.toLowerCase(Character.valueOf(split[0].trim().charAt(0)));
       
        if(when == 'f' && recipe instanceof Fuel)
        {
          recipeError("@" + flag + " can't be set to 'fail' for FUEL recipes because they never fail, ignored.");
          return;
        }
       
        recipe.setExplode(new Flag<int[]>(new int[]
        {
          when,
          Math.min(Math.max(Integer.valueOf(split[1].trim()), 1), 100),
          Math.max(Integer.valueOf(split[2].trim()), 0),
          (split.length > 3 && Boolean.valueOf(split[3].trim()) ? 1 : 0),
        }, message));
       
        hasExplosive = true;
      }
      catch(Exception e)
      {
        recipeError("@" + flag + " has one or more invalid arguments! Read readme.txt for requirements.");
        return;
      }
     
      return;
    }
   
    if(flag.equalsIgnoreCase("command"))
    {
      if(!value.equalsIgnoreCase("false"))
      {
        if(recipe.getCommands() == null)
          recipe.setCommands(new HashSet<String>());
       
        recipe.getCommands().add(value);
      }
      else
        recipe.setCommands(null);
     
      return;
    }
   
    if(flag.equalsIgnoreCase("message"))
    {
      if(!value.equalsIgnoreCase("false"))
      {
        if(recipe.getMessages() == null)
          recipe.setMessages(new HashSet<String>());
       
        recipe.getMessages().add(value);
      }
      else
        recipe.setMessages(null);
     
      return;
    }
   
    if(flag.equalsIgnoreCase("givexp") || flag.equalsIgnoreCase("giveexp") || flag.equalsIgnoreCase("givelevel") || flag.equalsIgnoreCase("givemoney"))
    {
      char type = flag.charAt(4);
     
      if(type == 'm' && !RecipeManager.economy.isEnabled())
      {
        recipeError("@" + flag + " can't work without Vault and an economy plugin, ignored.");
        return;
      }
     
      split = value.split("\\|");
      value = split[0].trim();
      String failMessage = null;
      String successMessage = null;
      Double val = null;
     
      if(!value.equalsIgnoreCase("false"))
      {
        if(split.length > 1)
        {
          failMessage = split[1].trim();
         
          if(split.length > 2)
            successMessage = split[2].trim();
        }
       
        try
        {
          val = (type == 'm' ? Double.valueOf(value) : Integer.valueOf(value));
        }
        catch(Exception e)
        {
          recipeError("@" + flag + " has invalid number value!");
          return;
        }
       
        if(val == 0)
          val = null;
      }
     
      switch(type)
      {
        case 'e': // give[e]xp
        case 'x': // give[x]p
        {
          recipe.setGiveExp(val == null ? null : new Flag<Integer>(val.intValue(), failMessage, successMessage));
          break;
        }
       
        case 'l': // give[l]evel
        {
          recipe.setGiveLevel(val == null ? null : new Flag<Integer>(val.intValue(), failMessage, successMessage));
          break;
        }
       
        case 'm': // give[m]oney
        {
          recipe.setGiveMoney(val == null ? null : new Flag<Double>(val, failMessage, successMessage));
          break;
        }
      }
     
      return;
    }
   
    if(flag.equalsIgnoreCase("minxp") || flag.equalsIgnoreCase("minexp") || flag.equalsIgnoreCase("maxxp") || flag.equalsIgnoreCase("maxexp") || flag.equalsIgnoreCase("minlevel") || flag.equalsIgnoreCase("maxlevel") || flag.equalsIgnoreCase("minmoney") || flag.equalsIgnoreCase("maxmoney"))
    {
      char type = flag.charAt(3);
     
      if(type == 'm' && !RecipeManager.economy.isEnabled())
      {
        recipeError("@" + flag + " can't work without Vault and an economy plugin, ignored.");
        return;
      }
     
      split = value.split("\\|");
      value = split[0].trim();
      String failMessage = null;
      Double val = null;
     
      if(!value.equalsIgnoreCase("false"))
      {
        if(split.length > 1)
          failMessage = split[1].trim();
       
        try
        {
          val = (type == 'm' ? Double.valueOf(value) : Integer.valueOf(value));
        }
        catch(Exception e)
        {
          recipeError("@" + flag + " has invalid number value!");
          return;
        }
       
        if(val <= 0)
          val = null;
      }
     
      switch(type)
      {
        case 'e': // min/max[e]xp
        case 'x': // min/max[x]p
        {
          switch(flag.charAt(2))
          {
            case 'n':
              recipe.setMinExp(val == null ? null : new Flag<Integer>(val.intValue(), failMessage));
              break;
            case 'x':
              recipe.setMaxExp(val == null ? null : new Flag<Integer>(val.intValue(), failMessage));
              break;
          }
         
          break;
        }
       
        case 'l': // min/max[l]evel
        {
          switch(flag.charAt(2))
          {
            case 'n':
              recipe.setMinLevel(val == null ? null : new Flag<Integer>(val.intValue(), failMessage));
              break;
            case 'x':
              recipe.setMaxLevel(val == null ? null : new Flag<Integer>(val.intValue(), failMessage));
              break;
          }
         
          break;
        }
       
        case 'm': // min/max[m]oney
        {
          switch(flag.charAt(2))
          {
            case 'n':
              recipe.setMinMoney(val == null ? null : new Flag<Double>(val, failMessage));
              break;
            case 'x':
              recipe.setMaxMoney(val == null ? null : new Flag<Double>(val, failMessage));
              break;
          }
         
          break;
        }
      }
     
      return;
    }
   
    log.warning("Unknown flag: '" + line + "' !");
  }
 
  protected static String locationToString(Location location)
  {
    return location.getWorld().getName() + ":" + location.getBlockX() + ":" + location.getBlockY() + ":" + location.getBlockZ();
  }
 
  protected static Furnace stringToFurnace(String string)
  {
    String[] split = string.split(":");
    World world = Bukkit.getWorld(split[0]);
   
    if(world == null) // world doesn't exist
      return null;
   
    Block block = world.getBlockAt(Integer.valueOf(split[1]), Integer.valueOf(split[2]), Integer.valueOf(split[3]));
   
    if(block.getType() != Material.BURNING_FURNACE) // not a running furnace
      return null;
   
    BlockState blockState = block.getState();
   
    if(!(blockState instanceof Furnace)) // not really a furnace
      return null;
   
    Furnace furnace = (Furnace)blockState;
   
    if(furnace.getBurnTime() <= 0) // furnace is not really running
      return null;
   
    return furnace;
  }
 
  private String shapedToString(ShapedRecipe recipe)
  {
    StringBuilder str = new StringBuilder();
   
    for(Entry<Character, ItemStack> entry : recipe.getIngredientMap().entrySet())
    {
      if(entry.getKey() != null && entry.getValue() != null)
        str.append(entry.getKey() + "=" + entry.getValue().getType() + ":" + entry.getValue().getDurability() + ";");
    }
   
    for(String row : recipe.getShape())
    {
      str.append(row + ";");
    }
   
    return str.toString();
  }
 
  private String shapelessToString(ShapelessRecipe recipe)
  {
    StringBuilder str = new StringBuilder();
   
    for(ItemStack ingredient : recipe.getIngredientList())
    {
      if(ingredient == null)
        continue;
     
      str.append(ingredient.getType() + ":" + ingredient.getDurability() + ";");
    }
   
    return str.toString();
  }
}
TOP

Related Classes of digi.recipeManager.Recipes

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.