/*
* 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--);
}
}
}