/* * Minecraft Forge * Copyright (c) 2016. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation version 2.1 * of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ 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 net.minecraftforge.fml.common.FMLLog; import net.minecraftforge.fml.common.toposort.TopologicalSort; import net.minecraftforge.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.RecipeRepairItem; import net.minecraft.item.crafting.RecipeTippedArrow; 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 net.minecraft.item.crafting.ShieldRecipes; import net.minecraft.item.crafting.RecipesBanners.RecipeAddPattern; import net.minecraft.item.crafting.RecipesBanners.RecipeDuplicatePattern; import javax.annotation.Nullable; import static net.minecraftforge.oredict.RecipeSorter.Category.*; public class RecipeSorter implements Comparator<IRecipe> { public enum Category { /** * Do not use UNKNOWN - it is for recipe types with no clear driver */ UNKNOWN, SHAPELESS, SHAPED }; private static class SortEntry { private String name; @Nullable private Class<?> cls; private Category cat; List<String> before = Lists.newArrayList(); List<String> after = Lists.newArrayList(); private SortEntry(String name, @Nullable 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 dependency: " + 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 final 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:shield_deco", ShieldRecipes.Decoration.class, SHAPELESS, "after:minecraft:shapeless"); //Size 2 register("minecraft:repair", RecipeRepairItem.class, SHAPELESS, "after:minecraft:shapeless"); //Size 4 register("minecraft:bookcloning", RecipeBookCloning.class, SHAPELESS, "after:minecraft:shapeless"); //Size 9 register("minecraft:tippedarrow", RecipeTippedArrow.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("minecraft:pattern_dupe", RecipeDuplicatePattern.class, SHAPELESS, "after:minecraft:shapeless"); //Size 2 register("minecraft:pattern_add", RecipeAddPattern.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); int categoryComparison = -c1.compareTo(c2); if (categoryComparison != 0) { return categoryComparison; } else { 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(); public static void sortCraftManager() { bake(); FMLLog.fine("Sorting recipes"); warned.clear(); Collections.sort(CraftingManager.getInstance().getRecipeList(), INSTANCE); } public static void register(String name, Class<?> recipe, Category category, String dependencies) { assert(category != UNKNOWN) : "Category must not be unknown!"; isDirty = true; SortEntry entry = new SortEntry(name, recipe, category, dependencies); 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.bigWarning("Unknown recipe class! %s Modders need to register their recipe types with %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, cls.getName()); return ret; } } } return ret == null ? 0 : ret; } 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--); } } }