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