From a4fa417114777bbeb5a910819bd208556c86e835 Mon Sep 17 00:00:00 2001 From: Lex Manos Date: Wed, 30 Oct 2013 14:08:03 -0700 Subject: [PATCH] Add new recipe sorter that is called after all mods are initalized. This is disabled by default in 1.6 to not break current worlds as it may change machine's recipy outputs. Will enable by default in 1.7. Players may enable it in the forge config. --- .../common/ForgeDummyContainer.java | 20 +- .../minecraftforge/oredict/RecipeSorter.java | 276 ++++++++++++++++++ 2 files changed, 294 insertions(+), 2 deletions(-) create mode 100644 common/net/minecraftforge/oredict/RecipeSorter.java diff --git a/common/net/minecraftforge/common/ForgeDummyContainer.java b/common/net/minecraftforge/common/ForgeDummyContainer.java index 11a90f773..92671c14b 100644 --- a/common/net/minecraftforge/common/ForgeDummyContainer.java +++ b/common/net/minecraftforge/common/ForgeDummyContainer.java @@ -15,6 +15,7 @@ import net.minecraftforge.common.network.ForgeConnectionHandler; import net.minecraftforge.common.network.ForgeNetworkHandler; import net.minecraftforge.common.network.ForgePacketHandler; import net.minecraftforge.common.network.ForgeTinyPacketHandler; +import net.minecraftforge.oredict.RecipeSorter; import net.minecraftforge.server.command.ForgeCommand; import com.google.common.eventbus.EventBus; @@ -29,13 +30,14 @@ import cpw.mods.fml.common.Loader; import cpw.mods.fml.common.ModMetadata; import cpw.mods.fml.common.WorldAccessContainer; import cpw.mods.fml.common.event.FMLConstructionEvent; +import cpw.mods.fml.common.event.FMLLoadCompleteEvent; import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.event.FMLServerStartingEvent; import cpw.mods.fml.common.network.FMLNetworkHandler; import cpw.mods.fml.common.network.NetworkMod; - import static net.minecraftforge.common.ForgeVersion.*; +import static net.minecraftforge.common.Configuration.*; @NetworkMod( channels = "FORGE", @@ -54,6 +56,7 @@ public class ForgeDummyContainer extends DummyModContainer implements WorldAcces public static double zombieSummonBaseChance = 0.1; public static int[] blendRanges = { 20, 15, 10, 5 }; public static float zombieBabyChance = 0.05f; + public static boolean shouldSortRecipies = false; public ForgeDummyContainer() { @@ -153,7 +156,11 @@ public class ForgeDummyContainer extends DummyModContainer implements WorldAcces prop = config.get(Configuration.CATEGORY_GENERAL, "zombieBabyChance", 0.05); prop.comment = "Chance that a zombie (or subclass) is a baby. Allows changing the zombie spawning mechanic."; zombieBabyChance = (float) prop.getDouble(0.05); - + + prop = config.get(CATEGORY_GENERAL, "sortRecipies", shouldSortRecipies); + prop.comment = "Set to true to enable the post initlization sorting of crafting recipes using Froge's sorter. May cause desyncing on conflicting recipies. ToDo: Set to true by default in 1.7"; + shouldSortRecipies = prop.getBoolean(shouldSortRecipies); + if (config.hasChanged()) { config.save(); @@ -195,6 +202,15 @@ public class ForgeDummyContainer extends DummyModContainer implements WorldAcces ForgeChunkManager.loadConfiguration(); } + @Subscribe + public void onAvalible(FMLLoadCompleteEvent evt) + { + if (shouldSortRecipies) + { + RecipeSorter.sortCraftManager(); + } + } + @Subscribe public void serverStarting(FMLServerStartingEvent evt) { diff --git a/common/net/minecraftforge/oredict/RecipeSorter.java b/common/net/minecraftforge/oredict/RecipeSorter.java new file mode 100644 index 000000000..e112fb5c3 --- /dev/null +++ b/common/net/minecraftforge/oredict/RecipeSorter.java @@ -0,0 +1,276 @@ +package net.minecraftforge.oredict; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +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.DummyModContainer; +import cpw.mods.fml.common.FMLLog; +import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.ModContainer; +import cpw.mods.fml.common.toposort.TopologicalSort; +import cpw.mods.fml.common.toposort.TopologicalSort.DirectedGraph; +import cpw.mods.fml.common.versioning.ArtifactVersion; +import net.minecraft.item.crafting.CraftingManager; +import net.minecraft.item.crafting.IRecipe; +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.*; + +public class RecipeSorter implements Comparator +{ + public enum Category + { + UNKNOWN, + SHAPELESS, + SHAPED + }; + + private static class SortEntry + { + private String name; + private Class cls; + private Category cat; + List before = Lists.newArrayList(); + List 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 categories = Maps.newHashMap(); + private static Map types = Maps.newHashMap(); + private static Map entries = Maps.newHashMap(); + private static Map 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:fireworks", RecipeFireworks.class, SHAPELESS, "after:minecraft:shapeless"); + register("minecraft:armordyes", RecipesArmorDyes.class, SHAPELESS, "after:minecraft:shapeless"); + register("minecraft:mapcloning", RecipesMapCloning.class, SHAPELESS, "after:minecraft:shapeless"); + + 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 warned = Sets.newHashSet(); + 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) + { + cls = cls.getSuperclass(); + while (cls != Object.class) + { + 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 (!INSTANCE.warned.contains(cls)) + { + FMLLog.fine(" 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 sorter = new DirectedGraph(); + sorter.addNode(before); + sorter.addNode(after); + sorter.addEdge(before, after); + + for (Map.Entry entry : entries.entrySet()) + { + sorter.addNode(entry.getValue()); + } + + for (Map.Entry 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 sorted = TopologicalSort.topologicalSort(sorter); + int x = sorted.size(); + for (SortEntry entry : sorted) + { + FMLLog.fine(" %d: %s", x, entry); + priorities.put(entry.cls, x--); + } + } +}