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.

This commit is contained in:
Lex Manos 2013-10-30 14:08:03 -07:00
parent a47cca7e42
commit a4fa417114
2 changed files with 294 additions and 2 deletions

View file

@ -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()
{
@ -154,6 +157,10 @@ public class ForgeDummyContainer extends DummyModContainer implements WorldAcces
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)
{

View file

@ -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<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: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<Class> 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<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--);
}
}
}