package net.minecraftforge.oredict; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.base.Joiner; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.common.toposort.TopologicalSort; import cpw.mods.fml.common.toposort.TopologicalSort.DirectedGraph; import net.minecraft.item.crafting.CraftingManager; import net.minecraft.item.crafting.IRecipe; import net.minecraft.item.crafting.RecipeBookCloning; import net.minecraft.item.crafting.RecipeFireworks; import net.minecraft.item.crafting.RecipesArmorDyes; import net.minecraft.item.crafting.RecipesMapCloning; import net.minecraft.item.crafting.RecipesMapExtending; import net.minecraft.item.crafting.ShapedRecipes; import net.minecraft.item.crafting.ShapelessRecipes; import static net.minecraftforge.oredict.RecipeSorter.Category.*; @SuppressWarnings("rawtypes") public class RecipeSorter implements Comparator<IRecipe> { public enum Category { UNKNOWN, SHAPELESS, SHAPED }; private static class SortEntry { private String name; private Class<?> cls; private Category cat; List<String> before = Lists.newArrayList(); List<String> after = Lists.newArrayList(); private SortEntry(String name, Class<?> cls, Category cat, String deps) { this.name = name; this.cls = cls; this.cat = cat; parseDepends(deps); } private void parseDepends(String deps) { if (deps.isEmpty()) return; for (String dep : deps.split(" ")) { if (dep.startsWith("before:")) { before.add(dep.substring(7)); } else if (dep.startsWith("after:")) { after.add(dep.substring(6)); } else { throw new IllegalArgumentException("Invalid dependancy: " + dep); } } } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append("RecipeEntry(\"").append(name).append("\", "); buf.append(cat.name()).append(", "); buf.append(cls == null ? "" : cls.getName()).append(")"); if (before.size() > 0) { buf.append(" Before: ").append(Joiner.on(", ").join(before)); } if (after.size() > 0) { buf.append(" After: ").append(Joiner.on(", ").join(after)); } return buf.toString(); } @Override public int hashCode() { return name.hashCode(); } }; private static Map<Class, Category> categories = Maps.newHashMap(); //private static Map<String, Class> types = Maps.newHashMap(); private static Map<String, SortEntry> entries = Maps.newHashMap(); private static Map<Class, Integer> priorities = Maps.newHashMap(); public static RecipeSorter INSTANCE = new RecipeSorter(); private static boolean isDirty = true; private static SortEntry before = new SortEntry("Before", null, UNKNOWN, ""); private static SortEntry after = new SortEntry("After", null, UNKNOWN, ""); private RecipeSorter() { register("minecraft:shaped", ShapedRecipes.class, SHAPED, "before:minecraft:shapeless"); register("minecraft:mapextending", RecipesMapExtending.class, SHAPED, "after:minecraft:shaped before:minecraft:shapeless"); register("minecraft:shapeless", ShapelessRecipes.class, SHAPELESS, "after:minecraft:shaped"); register("minecraft:bookcloning", RecipeBookCloning.class, SHAPELESS, "after:minecraft:shapeless"); //Size 9 register("minecraft:fireworks", RecipeFireworks.class, SHAPELESS, "after:minecraft:shapeless"); //Size 10 register("minecraft:armordyes", RecipesArmorDyes.class, SHAPELESS, "after:minecraft:shapeless"); //Size 10 register("minecraft:mapcloning", RecipesMapCloning.class, SHAPELESS, "after:minecraft:shapeless"); //Size 10 register("forge:shapedore", ShapedOreRecipe.class, SHAPED, "after:minecraft:shaped before:minecraft:shapeless"); register("forge:shapelessore", ShapelessOreRecipe.class, SHAPELESS, "after:minecraft:shapeless"); } @Override public int compare(IRecipe r1, IRecipe r2) { Category c1 = getCategory(r1); Category c2 = getCategory(r2); if (c1 == SHAPELESS && c2 == SHAPED) return 1; if (c1 == SHAPED && c2 == SHAPELESS) return -1; if (r2.getRecipeSize() < r1.getRecipeSize()) return -1; if (r2.getRecipeSize() > r1.getRecipeSize()) return 1; return getPriority(r2) - getPriority(r1); // high priority value first! } private static Set<Class> warned = Sets.newHashSet(); @SuppressWarnings("unchecked") public static void sortCraftManager() { bake(); FMLLog.fine("Sorting recipies"); warned.clear(); Collections.sort(CraftingManager.getInstance().getRecipeList(), INSTANCE); } public static void register(String name, Class<?> recipe, Category category, String dependancies) { assert(category != UNKNOWN) : "Category must not be unknown!"; isDirty = true; SortEntry entry = new SortEntry(name, recipe, category, dependancies); entries.put(name, entry); setCategory(recipe, category); } public static void setCategory(Class<?> recipe, Category category) { assert(category != UNKNOWN) : "Category must not be unknown!"; categories.put(recipe, category); } public static Category getCategory(IRecipe recipe) { return getCategory(recipe.getClass()); } public static Category getCategory(Class<?> recipe) { Class<?> cls = recipe; Category ret = categories.get(cls); if (ret == null) { while (cls != Object.class) { cls = cls.getSuperclass(); ret = categories.get(cls); if (ret != null) { categories.put(recipe, ret); return ret; } } } return ret == null ? UNKNOWN : ret; } private static int getPriority(IRecipe recipe) { Class<?> cls = recipe.getClass(); Integer ret = priorities.get(cls); if (ret == null) { if (!warned.contains(cls)) { FMLLog.info(" Unknown recipe class! %s Modder please refer to %s", cls.getName(), RecipeSorter.class.getName()); warned.add(cls); } cls = cls.getSuperclass(); while (cls != Object.class) { ret = priorities.get(cls); if (ret != null) { priorities.put(recipe.getClass(), ret); FMLLog.fine(" Parent Found: %d - %s", ret.intValue(), cls.getName()); return ret.intValue(); } } } return ret == null ? 0 : ret.intValue(); } private static void bake() { if (!isDirty) return; FMLLog.fine("Forge RecipeSorter Baking:"); DirectedGraph<SortEntry> sorter = new DirectedGraph<SortEntry>(); sorter.addNode(before); sorter.addNode(after); sorter.addEdge(before, after); for (Map.Entry<String, SortEntry> entry : entries.entrySet()) { sorter.addNode(entry.getValue()); } for (Map.Entry<String, SortEntry> e : entries.entrySet()) { SortEntry entry = e.getValue(); boolean postAdded = false; sorter.addEdge(before, entry); for (String dep : entry.after) { if (entries.containsKey(dep)) { sorter.addEdge(entries.get(dep), entry); } } for (String dep : entry.before) { postAdded = true; sorter.addEdge(entry, after); if (entries.containsKey(dep)) { sorter.addEdge(entry, entries.get(dep)); } } if (!postAdded) { sorter.addEdge(entry, after); } } List<SortEntry> sorted = TopologicalSort.topologicalSort(sorter); int x = sorted.size(); for (SortEntry entry : sorted) { FMLLog.fine(" %d: %s", x, entry); priorities.put(entry.cls, x--); } } }