Util Creating recipes with specific ingredients/itemstacks

Discussion in 'Resources' started by Zombie_Striker, Aug 3, 2016.

Thread Status:
Not open for further replies.
  1. Offline

    Zombie_Striker

    Description:
    This util allows plugins to require specific itemstacks with certain names or lores. This is useful for plugins that require the "crafter" to use a specific, named item while crafting.

    Code
    Code:
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map.Entry;
    
    import org.bukkit.Bukkit;
    import org.bukkit.Material;
    import org.bukkit.event.EventHandler;
    import org.bukkit.event.Listener;
    import org.bukkit.event.inventory.PrepareItemCraftEvent;
    import org.bukkit.inventory.CraftingInventory;
    import org.bukkit.inventory.ItemStack;
    import org.bukkit.inventory.Recipe;
    import org.bukkit.inventory.ShapedRecipe;
    import org.bukkit.plugin.Plugin;
    
    public class CraftingUtil implements Listener {
    
       private static CraftingUtil p;
       protected static Plugin plugin;
    
       private static List<CraftingRecipe> recipies = new ArrayList<CraftingRecipe>();
    
       /**
        * Call this in your onEnable. This allows for the events to work.
        *
        * @param javaPlugin
        *  : Your main class.
        */
       public static void initialize(Plugin javaPlugin) {
         p = new CraftingUtil();
         Bukkit.getPluginManager().registerEvents(p, javaPlugin);
         plugin = javaPlugin;
       }
    
       /**
        * This adds a special recipe to the list. Make sure you register your
        * recipe before you add it to the list.
        *
        * @param sr
        *  your ShapedRecipe
        * @return the CraftingRecipe instance.
        */
       public static CraftingRecipe addRecipe(Recipe sr) {
         CraftingRecipe cr = p.new CraftingRecipe(sr);
         recipies.add(cr);
         return cr;
       }
    
       /**
        * Removes a special recipe from the list.
        *
        * @param the
        *  Crafting Recipe instace.
        */
       public static void removeRecipe(CraftingRecipe cr) {
         recipies.remove(cr);
       }
    
       @EventHandler
       private void onCraft(PrepareItemCraftEvent event) {
         for (CraftingRecipe recipe : recipies) {
           if (event.getRecipe().getResult()
               .equals(recipe.getRecipe().getResult())
               && isShape(event.getInventory(),
                   (ShapedRecipe) recipe.getRecipe())) {
             for (int slot = 0; slot < recipe.getIngredients().length; slot++) {
               if (recipe.getIngredients()[slot] != null) {
                 if (!itemstacksSim(
                     event.getInventory().getMatrix()[slot],
                     recipe.getIngredients()[slot])) {
                   event.getInventory().setResult(
                       new ItemStack(Material.AIR));
                   break;
                 }
               }
             }
             break;
           }
         }
       }
    
       private boolean itemstacksSim(ItemStack i1, ItemStack i2) {
         if (i1.getType() == i2.getType())
           if (i1.getDurability() == i2.getDurability())
             if (i1.hasItemMeta() && i2.hasItemMeta()) {
               if ((!i1.getItemMeta().hasDisplayName() && !i2
                   .getItemMeta().hasDisplayName())
                   || (i1.getItemMeta().hasDisplayName()
                       && i2.getItemMeta().hasDisplayName() && i1
                       .getItemMeta().getDisplayName()
                       .equals(i2.getItemMeta().getDisplayName())))
                 if ((!i1.getItemMeta().hasLore() && !i2.getItemMeta()
                     .hasLore())
                     || (i1.getItemMeta().hasLore()
                         && i2.getItemMeta().hasLore() && i1
                         .getItemMeta().getLore()
                         .equals(i2.getItemMeta().getLore())))
                   return true;
             } else if (!(i1.hasItemMeta()) && !(i2.hasItemMeta())) {
               return true;
             }
         return false;
       }
    
       private boolean isShape(CraftingInventory inv, ShapedRecipe sr) {
         ItemStack[] mat = inv.getMatrix();
         ItemStack[] mat2 = new ItemStack[9];
         String[] str = sr.getShape();
         for (int i = 0; i < 9; i++) {
           for (Entry<Character, ItemStack> e : sr.getIngredientMap()
               .entrySet()) {
             if (mat[i] == null)
               mat[i] = new ItemStack(Material.AIR);
    
             if (str[i / 3].charAt(i % 3) == ' ') {
               mat2[i] = new ItemStack(Material.AIR);
             } else if (str[i / 3].charAt(i % 3) == e.getKey()) {
               mat2[i] = e.getValue();
             }
    
             if (mat[i].getType() != mat2[i].getType())
               return false;
           }
         }
         return true;
       }
    
       class CraftingRecipe {
    
         private Recipe sr;
         private ItemStack[] ing = new ItemStack[9];
    
         public CraftingRecipe(Recipe sr) {
           this.sr = sr;
         }
    
         /**
          * This is how you specify if an object
          *
          * @param slot
          * @param is
          * @return
          */
         public CraftingRecipe setItemstack(int slot, ItemStack is) {
           ing[slot] = is;
           return this;
         }
    
         public CraftingRecipe setItemstack(int row, int col, ItemStack is) {
           ing[(row * 3) + col] = is;
           return this;
         }
    
         public ItemStack[] getIngredients() {
           return ing;
         }
    
         public Recipe getRecipe() {
           return sr;
         }
       }
    }
    
    
    
    Usage:
    Code:
       @Override
       public void onEnable() {
    //Create and register the base recipie
         ShapedRecipe sr = new ShapedRecipe(new ItemStack(Material.GOLD_AXE)).shape("=  ","  ","  ").setIngredient('=', Material.GOLD_AXE);
         Bukkit.addRecipe(sr);
    
    //Initialize the util
         CraftingUtil.initialize(this);
    
    //Create your special itemstack
         ItemStack is = new ItemStack(Material.GOLD_AXE);
         ItemMeta im = is.getItemMeta();
         im.setDisplayName("special axe");
         is.setItemMeta(im);
    
    //Add the special itemstack.
         CraftingUtil.addRecipe(sr).setItemstack(0,is);
       }
    
    Changelog:
    8/3/2016: Init post
    -fixed displaynames and lores
    -added support for multiple recipes with same result.
     
    Last edited: Aug 3, 2016
  2. Offline

    ArsenArsen

    Nice! As always, good work.
     
  3. Offline

    I Al Istannen

    @Zombie_Striker
    I may be too tired to judge, but whatever.

    "i1.getItemMeta().getLore().equals(i2.getItemMeta().getLore())"
    will throw a NPE if
    "i1.getItemMeta().getLore()"
    is null.
    Your if above doesn't account for this case, as this if can be true if any lore is not null (i1 or i2):
    "i1.getItemMeta().getLore() == null && i2.getItemMeta().getLore() == null".

    And there is "hasLore()" and "hasDisplayName()", which might look cleaner.

    And can't the displayname be null too?
    "i1.getItemMeta().getDisplayName().equals(i2.getItemMeta().getDisplayName())"


    "event.getRecipe().getResult().equals(recipe.getRecipe().getResult())"
    And what if you have two recipes becoming the same base item, but with different name/lore? In this case, only one will ever be checked.
    Like, if you have two recipes for a diamond. One with 3 coal, named "3", resulting in a diamond with the name "3" and one with 3 coal, but named "4", resulting in a diamond with the name "4".

    3 is added before 4.

    The loop will go through the custom recipes. It finds one whose output is a diamond and therefore matches the event ones.
    It is the "3" diamond. But 4 coals were inserted. Now the ingredients don't match up and the recipe will be cancelled. But it is a valid recipe, just for the "4" diamond.

    It is quite likely my line of thought is screwed up, so just say it.
     
  4. Offline

    Zombie_Striker

    Fixed.

    This is something I considered throughout the creation of the util, but I could not figure out a way to compare them. I could not compare the Shapes from the crafting bench, so using the shape and char-key was out as well as grabbing the shape from the crafting table. In short, there is no way around this.

    [edit] Well, I was mistaken. I just remembered the Shapes and char-keys are saved for the ShapedRecipe. Will update soon.

    @I Al Istannen
    Fixed problem #2.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Aug 3, 2016
    I Al Istannen likes this.
  5. Offline

    BlueSkyEngeener

    Is this util no linkback?
    and will it work on furnace / shapeless recipes too?
     
    Last edited: Jan 7, 2017
  6. Offline

    mccrafter1212

    @Zombie_Striker You've actually got to set the class CraftingRecipe to public in order for the Main class of the plugin to access it. So:

    public class CraftingRecipe {
     
Thread Status:
Not open for further replies.

Share This Page