001package net.minecraftforge.oredict;
002
003import java.util.ArrayList;
004import java.util.HashMap;
005import java.util.Iterator;
006import java.util.List;
007import java.util.Map;
008import java.util.Map.Entry;
009
010import net.minecraft.block.Block;
011import net.minecraft.item.Item;
012import net.minecraft.item.ItemStack;
013import net.minecraft.item.crafting.CraftingManager;
014import net.minecraft.item.crafting.IRecipe;
015import net.minecraft.item.crafting.ShapedRecipes;
016import net.minecraft.item.crafting.ShapelessRecipes;
017import net.minecraftforge.common.MinecraftForge;
018import net.minecraftforge.event.Event;
019
020public class OreDictionary
021{
022    private static boolean hasInit = false;
023    private static int maxID = 0;
024    private static HashMap<String, Integer> oreIDs = new HashMap<String, Integer>();
025    private static HashMap<Integer, ArrayList<ItemStack>> oreStacks = new HashMap<Integer, ArrayList<ItemStack>>();
026
027
028    /**
029     * Minecraft changed from -1 to Short.MAX_VALUE in 1.5 release for the "block wildcard". Use this in case it
030     * changes again.
031     */
032    public static final int WILDCARD_VALUE = Short.MAX_VALUE;
033
034    static {
035        initVanillaEntries();
036    }
037
038    public static void initVanillaEntries()
039    {
040        if (!hasInit)
041        {
042            registerOre("logWood",     new ItemStack(Block.wood, 1, WILDCARD_VALUE));
043            registerOre("plankWood",   new ItemStack(Block.planks, 1, WILDCARD_VALUE));
044            registerOre("slabWood",    new ItemStack(Block.woodSingleSlab, 1, WILDCARD_VALUE));
045            registerOre("stairWood",   Block.stairsWoodOak);
046            registerOre("stairWood",   Block.stairsWoodBirch);
047            registerOre("stairWood",   Block.stairsWoodJungle);
048            registerOre("stairWood",   Block.stairsWoodSpruce);
049            registerOre("stickWood",   Item.stick);
050            registerOre("treeSapling", new ItemStack(Block.sapling, 1, WILDCARD_VALUE));
051            registerOre("treeLeaves",  new ItemStack(Block.leaves, 1, WILDCARD_VALUE));
052        }
053
054        // Build our list of items to replace with ore tags
055        Map<ItemStack, String> replacements = new HashMap<ItemStack, String>();
056        replacements.put(new ItemStack(Block.planks, 1, WILDCARD_VALUE), "plankWood");
057        replacements.put(new ItemStack(Item.stick), "stickWood");
058
059        // Register dyes
060        String[] dyes =
061        {
062            "dyeBlack",
063            "dyeRed",
064            "dyeGreen",
065            "dyeBrown",
066            "dyeBlue",
067            "dyePurple",
068            "dyeCyan",
069            "dyeLightGray",
070            "dyeGray",
071            "dyePink",
072            "dyeLime",
073            "dyeYellow",
074            "dyeLightBlue",
075            "dyeMagenta",
076            "dyeOrange",
077            "dyeWhite"
078        };
079
080        for(int i = 0; i < 16; i++)
081        {
082            ItemStack dye = new ItemStack(Item.dyePowder, 1, i);
083            if (!hasInit)
084            {
085                registerOre(dyes[i], dye);
086            }
087            replacements.put(dye, dyes[i]);
088        }
089        hasInit = true;
090
091        ItemStack[] replaceStacks = replacements.keySet().toArray(new ItemStack[replacements.keySet().size()]);
092
093        // Ignore recipes for the following items
094        ItemStack[] exclusions = new ItemStack[]
095        {
096            new ItemStack(Block.blockLapis),
097            new ItemStack(Item.cookie),
098        };
099
100        List recipes = CraftingManager.getInstance().getRecipeList();
101        List<IRecipe> recipesToRemove = new ArrayList<IRecipe>();
102        List<IRecipe> recipesToAdd = new ArrayList<IRecipe>();
103
104        // Search vanilla recipes for recipes to replace
105        for(Object obj : recipes)
106        {
107            if(obj instanceof ShapedRecipes)
108            {
109                ShapedRecipes recipe = (ShapedRecipes)obj;
110                ItemStack output = recipe.getRecipeOutput();
111                if (output != null && containsMatch(false, exclusions, output))
112                {
113                    continue;
114                }
115
116                if(containsMatch(true, recipe.recipeItems, replaceStacks))
117                {
118                    recipesToRemove.add(recipe);
119                    recipesToAdd.add(new ShapedOreRecipe(recipe, replacements));
120                }
121            }
122            else if(obj instanceof ShapelessRecipes)
123            {
124                ShapelessRecipes recipe = (ShapelessRecipes)obj;
125                ItemStack output = recipe.getRecipeOutput();
126                if (output != null && containsMatch(false, exclusions, output))
127                {
128                    continue;
129                }
130
131                if(containsMatch(true, (ItemStack[])recipe.recipeItems.toArray(new ItemStack[recipe.recipeItems.size()]), replaceStacks))
132                {
133                    recipesToRemove.add((IRecipe)obj);
134                    IRecipe newRecipe = new ShapelessOreRecipe(recipe, replacements);
135                    recipesToAdd.add(newRecipe);
136                }
137            }
138        }
139
140        recipes.removeAll(recipesToRemove);
141        recipes.addAll(recipesToAdd);
142        if (recipesToRemove.size() > 0)
143        {
144            System.out.println("Replaced " + recipesToRemove.size() + " ore recipies");
145        }
146    }
147
148    /**
149     * Gets the integer ID for the specified ore name.
150     * If the name does not have a ID it assigns it a new one.
151     *
152     * @param name The unique name for this ore 'oreIron', 'ingotIron', etc..
153     * @return A number representing the ID for this ore type
154     */
155    public static int getOreID(String name)
156    {
157        Integer val = oreIDs.get(name);
158        if (val == null)
159        {
160            val = maxID++;
161            oreIDs.put(name, val);
162            oreStacks.put(val, new ArrayList<ItemStack>());
163        }
164        return val;
165    }
166
167    /**
168     * Reverse of getOreID, will not create new entries.
169     *
170     * @param id The ID to translate to a string
171     * @return The String name, or "Unknown" if not found.
172     */
173    public static String getOreName(int id)
174    {
175        for (Map.Entry<String, Integer> entry : oreIDs.entrySet())
176        {
177            if (id == entry.getValue())
178            {
179                return entry.getKey();
180            }
181        }
182        return "Unknown";
183    }
184
185    /**
186     * Gets the integer ID for the specified item stack.
187     * If the item stack is not linked to any ore, this will return -1 and no new entry will be created.
188     *
189     * @param itemStack The item stack of the ore.
190     * @return A number representing the ID for this ore type, or -1 if couldn't find it.
191     */
192    public static int getOreID(ItemStack itemStack)
193    {
194        if (itemStack == null)
195        {
196            return -1;
197        }
198
199        for(Entry<Integer, ArrayList<ItemStack>> ore : oreStacks.entrySet())
200        {
201            for(ItemStack target : ore.getValue())
202            {
203                if(itemStack.itemID == target.itemID && (target.getItemDamage() == WILDCARD_VALUE || itemStack.getItemDamage() == target.getItemDamage()))
204                {
205                    return ore.getKey();
206                }
207            }
208        }
209        return -1; // didn't find it.
210    }
211
212    /**
213     * Retrieves the ArrayList of items that are registered to this ore type.
214     * Creates the list as empty if it did not exist.
215     *
216     * @param name The ore name, directly calls getOreID
217     * @return An arrayList containing ItemStacks registered for this ore
218     */
219    public static ArrayList<ItemStack> getOres(String name)
220    {
221        return getOres(getOreID(name));
222    }
223
224    /**
225     * Retrieves a list of all unique ore names that are already registered.
226     *
227     * @return All unique ore names that are currently registered.
228     */
229    public static String[] getOreNames()
230    {
231        return oreIDs.keySet().toArray(new String[oreIDs.keySet().size()]);
232    }
233
234    /**
235     * Retrieves the ArrayList of items that are registered to this ore type.
236     * Creates the list as empty if it did not exist.
237     *
238     * @param id The ore ID, see getOreID
239     * @return An arrayList containing ItemStacks registered for this ore
240     */
241    public static ArrayList<ItemStack> getOres(Integer id)
242    {
243        ArrayList<ItemStack> val = oreStacks.get(id);
244        if (val == null)
245        {
246            val = new ArrayList<ItemStack>();
247            oreStacks.put(id, val);
248        }
249        return val;
250    }
251
252    private static boolean containsMatch(boolean strict, ItemStack[] inputs, ItemStack... targets)
253    {
254        for (ItemStack input : inputs)
255        {
256            for (ItemStack target : targets)
257            {
258                if (itemMatches(target, input, strict))
259                {
260                    return true;
261                }
262            }
263        }
264        return false;
265    }
266
267    public static boolean itemMatches(ItemStack target, ItemStack input, boolean strict)
268    {
269        if (input == null && target != null || input != null && target == null)
270        {
271            return false;
272        }
273        return (target.itemID == input.itemID && ((target.getItemDamage() == WILDCARD_VALUE && !strict) || target.getItemDamage() == input.getItemDamage()));
274    }
275
276    //Convenience functions that make for cleaner code mod side. They all drill down to registerOre(String, int, ItemStack)
277    public static void registerOre(String name, Item      ore){ registerOre(name, new ItemStack(ore));  }
278    public static void registerOre(String name, Block     ore){ registerOre(name, new ItemStack(ore));  }
279    public static void registerOre(String name, ItemStack ore){ registerOre(name, getOreID(name), ore); }
280    public static void registerOre(int    id,   Item      ore){ registerOre(id,   new ItemStack(ore));  }
281    public static void registerOre(int    id,   Block     ore){ registerOre(id,   new ItemStack(ore));  }
282    public static void registerOre(int    id,   ItemStack ore){ registerOre(getOreName(id), id, ore);   }
283
284    /**
285     * Registers a ore item into the dictionary.
286     * Raises the registerOre function in all registered handlers.
287     *
288     * @param name The name of the ore
289     * @param id The ID of the ore
290     * @param ore The ore's ItemStack
291     */
292    private static void registerOre(String name, int id, ItemStack ore)
293    {
294        ArrayList<ItemStack> ores = getOres(id);
295        ore = ore.copy();
296        ores.add(ore);
297        MinecraftForge.EVENT_BUS.post(new OreRegisterEvent(name, ore));
298    }
299
300    public static class OreRegisterEvent extends Event
301    {
302        public final String Name;
303        public final ItemStack Ore;
304
305        public OreRegisterEvent(String name, ItemStack ore)
306        {
307            this.Name = name;
308            this.Ore = ore;
309        }
310    }
311}