[Tutorial][Utility] Shapeless Recipes with ItemStacks(ItemMeta included)

Discussion in 'Spigot Plugin Development' started by Drepic, May 9, 2015.

  1. Drepic

    Supporter

    Hey so I'm looking for this to be peer reviewed for the code snippet wiki. And if it's not particularly good for that I still believe it is helpful and would like to share it.

    Once you have it setup you will be able to add recipes like this. Keep in mind that you can add any ItemStack with any ItemMeta.
    Code (Text):
    RecipeShapeless rec = new RecipeShapeless(new ItemStack(Material.TRIPWIRE_HOOK));
             rec.addIngredient(new ItemStack(Material.SADDLE));
             rec.addIngredient(new ItemStack(Material.POTION));
             rec.register();
    First off you're going to need two classes that I will provide the code for.

    This is the first class. Recipes.class You're going to need to register this class as a Listener and have one instance of it that you can access from the other class(RecipeShapeless.class)

    Code (Text):
    public class Recipes implements Listener{

        //Make sure to register this class as a listener(and only have one instance of this class, that you can access)

        /**
         * Created by: AndrewEpifano
         * Utility to create recipes with custom ItemStacks as opposed to Materials
         */

        ArrayList<RecipeShapeless> shapeless = new ArrayList<RecipeShapeless>();

        public void addShapeless(RecipeShapeless recipe){
            //Add recipe if it's not already added.
            for(RecipeShapeless r : shapeless){
                if(r.match(recipe))return;
            }
            shapeless.add(recipe);
        }

        public void onDisable(){
            //Remove recipes. Call this on server disable
            shapeless.clear();
        }



        @EventHandler
        public void onCraft(PrepareItemCraftEvent event){
            CraftingInventory inv = event.getInventory();
            ItemStack[] items = inv.getContents();
       
            //Mock recipe to match with any possible recipes
            RecipeShapeless r = new RecipeShapeless(new ItemStack(Material.ANVIL));
       
       
            int anvil = 0;
       
            //boolean value used to denote whether it is actually a "special" recipe or not
            boolean anvils = false;
       
            //Add ingredients to mock recipe
            for(ItemStack i : items){
                if(!i.getType().equals(Material.AIR)){
                    if(i.equals(new ItemStack(Material.ANVIL)) && anvil == 0){
                        anvil = 1;
                        continue;
                    }
                    if(anvil == 1){
                        anvils = true;
                    }
                    r.addIngredient(i);
                }
            }
            //Check match
            for(RecipeShapeless recipe : shapeless){
                if(recipe.match(r)){
                    event.getInventory().setResult(recipe.getResult());
                    return;
                }
            }
       
            //It is a "special" recipe but the item information on the ingredients is incorrect. The result must be air.
            if(anvils){
                event.getInventory().setResult(new ItemStack(Material.AIR));
            }
       
        }
    Here is the second class you're going to need, RecipeShapeless.class. In the register() method you're going to need to have access to your plugin and the one instance of the Recipes class.

    Code (Text):
    public class RecipeShapeless {

        /**
         * Created by: AndrewEpifano
         * CustomRecipe class.
         */

        ArrayList<Pair<ItemStack, Integer>> items = new ArrayList<Pair<ItemStack, Integer>>();

        //This is used to identify any special recipe (With anvil as a result)
        ShapelessRecipe registerer = new ShapelessRecipe(new ItemStack(Material.ANVIL));
        ItemStack result;

        public RecipeShapeless(ItemStack it){
            result = it;
        }

        public ArrayList<String> getId(){
            ArrayList<String> array = new ArrayList<String>();
            for(Pair<ItemStack, Integer> pair : items){
                array.add(pair.getA().getType().toString() + pair.getA().getItemMeta().getDisplayName() + pair.getB());
            }
            return array;
        }

        public void addIngredient(ItemStack it){
            boolean contains = false;
            int pairs = -1;
       
            //Go through to check if the ingredient is already present
            for(Pair<ItemStack, Integer> pair : items){
                if(pair.getA().equals(it)){
                    contains = true;
                    pairs = items.indexOf(pair);
                }
            }
       
            //Ingredient already present --> Just add one to the amount of ingredient
            if(contains){
                registerer.addIngredient(it.getData());
                items.get(pairs).setB(items.get(pairs).getB() + 1);
            }else{
                //Otherwise add just one
                registerer.addIngredient(it.getData());
                items.add(new Pair<ItemStack, Integer>(it, 1));
            }
        }


        public void addIngredient(ItemStack it, int i){
            //Adds an ingredient(Itemstack) as well as how many of that ingredient
            for(int n = 0; n < i; n++){
                addIngredient(it);
            }
        }

        public void register(){
       
            //You need your plugin here
            plugin.getServer().addRecipe(registerer);
       
            //This is the Recipes.class(only have one instance of this class)
            //It's recommended to have access of the instance of Recipes.class from the plugin
            plugin.getRecipes().addShapeless(this);
        }

        public ItemStack getResult(){
            return this.result;
        }


        //Is this the same recipe as the passed recipe
        public boolean match(RecipeShapeless recipe){
            Set<String> set1 = new HashSet<String>();
            set1.addAll(recipe.getId());
            Set<String> set2 = new HashSet<String>();
            set2.addAll(getId());
            return set1.equals(set2);
        }

        //This is just a different way to hold information.
        public class Pair<A, B> {
       
            A valueA;
            B valueB;
       
            public Pair(A valueA, B valueB){
                this.valueA = valueA;
                this.valueB = valueB;
            }
       
            public void setA(A a){
                valueA = a;
            }
       
            public void setB(B b){
                valueB = b;
            }
       
            public A getA(){
                return valueA;
            }
       
            public B getB(){
                return valueB;
            }

        }



    }


    Please leave feedback :)
     
    #1 Drepic, May 9, 2015
    Last edited: May 9, 2015
    • Like Like x 4
  2. Drepic

    Supporter

    Again, I'm looking for feedback. Good or bad. Just would like some. Thanks.
     
    • Like Like x 1
  3. I personally like this. Makes shapeless recipes much less of a pain in the arse! I'm not great at judging code quality, but I personally find this very useful and would personally use it! Thanks for sharing, and good luck on getting the snippet to the wiki.
     
  4. Drepic

    Supporter

    Thank you :)
     
  5. @AndrewEpifano
    • You should program to interfaces rather than concrete classes. Use List rather than ArrayList in case of field/return types.
    • Your Pair class should be private.
    • The id should be type + datavalue + amount, Bukkit and Minecraft do not consider the name to be important (afaik).
    • Cache your values or check raw values rather than Strings (which is both preferred and much faster).
     
  6. Drepic

    Supporter

    Thank you I will take that into consideration. However, for the id, I need the name because the recipes are ItemMeta specific.
     
  7. you are using the ID to check for ingredients though, not results. Neither NMS nor Bukkit take the name into account afaik.
     
  8. Drepic

    Supporter

    I check the Display name to make sure that the ingredients have the right display name and are the correct ingredient. Without that just the item type would suffice, which is the same as not having this class.