package cyano.basemetals.registry;
import java.util.*;
import cyano.basemetals.BaseMetals;
import cyano.basemetals.registry.recipe.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.oredict.OreDictionary;
/**
* This class handles all of the recipes for the crack hammer, collectively
* referred to as crusher recipes (the crack hammer is meant to be equivalent to
* the pulverizers and rock crushers from mods like Thermal Expansion). Adding a
* new crusher recipe is as simple as calling one of the
* addNewCrusherRecipe(...) static functions. To get the recipe(s) for a given
* block or item, use
* CrusherRecipeRegistry.getInstance().getRecipeForInputItem(...) or
* CrusherRecipeRegistry.getInstance().getRecipesForOutputItem(...). Added
* crusher recipes will automatically appear in the NEI crusher recipe section.
* <p>
* To see all of the default crusher recipes, look at the source code of the
* class cyano.basemetals.init.Recipes
* </p>
* @author DrCyano
*
*/
public class CrusherRecipeRegistry {
private final List<ICrusherRecipe> recipes = new ArrayList<>();
private final Map<ItemLookupReference,ICrusherRecipe> recipeByInputCache = new HashMap<>();
private final Map<ItemLookupReference,List<ICrusherRecipe>> recipeByOutputCache = new HashMap<>();
private static final Lock initLock = new ReentrantLock();
private static CrusherRecipeRegistry instance = null;
/**
* Gets a singleton instance of CrusherRecipeRegistry
* @return A global instance of CrusherRecipeRegistry
*/
public static CrusherRecipeRegistry getInstance(){
if(instance == null){
initLock.lock();
try{
if(instance == null){
// thread-safe singleton instantiation
instance = new CrusherRecipeRegistry();
}
} finally{
initLock.unlock();
}
}
return instance;
}
/**
* Adds a new crusher recipe (for the crack hammer and other rock crushers)
* where the input item is an OreDictionary name. This means that any item
* registered with the OreDictionary will be converted into the specified
* output item.
* @param oreDictionaryName Name in the ore dictionary (e.g. "logWood")
* @param output The item to create as the result of this crusher recipe.
*/
public static void addNewCrusherRecipe(final String oreDictionaryName, final ItemStack output){ // preferred method
getInstance().addRecipe(new OreDictionaryCrusherRecipe(oreDictionaryName,output));
}
/**
* Adds a new crusher recipe (for the crack hammer and other rock crushers)
* where the input item is specified by an ItemStack. This means that only
* the specified item will be converted into the specified output item.
* @param input Item to be crushed
* @param output The item to create as the result of this crusher recipe.
*/
public static void addNewCrusherRecipe(final ItemStack input, final ItemStack output){
if(input == null || output == null) FMLLog.severe("%s: %s: Crusher recipe not registered because of null input or output. \n %s",
BaseMetals.MODID, CrusherRecipeRegistry.class,
Arrays.toString(Thread.currentThread().getStackTrace()).replace(", ", "\n").replace("[", "").replace("]", "")
);
getInstance().addRecipe(new ArbitraryCrusherRecipe(input,output));
}
/**
* Adds a new crusher recipe (for the crack hammer and other rock crushers)
* where the input item is specified by an Item instance. This means that
* only the specified item will be converted into the specified output item.
* Note that this will assume an item metadata value of 0.
* @param input Item to be crushed
* @param output The item to create as the result of this crusher recipe.
*/
public static void addNewCrusherRecipe(final Item input, final ItemStack output){
getInstance().addRecipe(new ICrusherRecipe(){
@Override
public ItemStack getOutput() {
return output;
}
@Override
public boolean isValidInput(ItemStack in) {
return input.equals(in.getItem());
}
@Override
public Collection<ItemStack> getValidInputs() {
return Arrays.asList(new ItemStack(input));
}
});
}
/**
* Adds a new crusher recipe (for the crack hammer and other rock crushers)
* where the input item is a block. This means that any block of this type
* will be converted into the specified output item. If you want to restrict
* the block inputs to certain metadata values, convert the block into an
* ItemStack instead of providing it as a block instance.
* @param input Block to be crushed
* @param output The item to create as the result of this crusher recipe.
*/
public static void addNewCrusherRecipe(final Block input, final ItemStack output){
getInstance().addRecipe(new ICrusherRecipe(){
@Override
public ItemStack getOutput() {
return output;
}
@Override
public boolean isValidInput(ItemStack in) {
return input.equals(Block.getBlockFromItem(in.getItem()));
}
@Override
public Collection<ItemStack> getValidInputs() {
return Arrays.asList(new ItemStack(input));
}
});
}
/**
* Clears the fast-look-up cache. If recipes are added after the game
* starts, call this method to ensure that the new recipes will be used.
*/
public void clearCache(){
recipeByInputCache.clear();
recipeByOutputCache.clear();
}
/**
* This is the universal method for adding new crusher recipes
* @param crusherRecipe An implementation of the ICrusherRecipe interface.
*/
public void addRecipe(ICrusherRecipe crusherRecipe){
recipes.add(crusherRecipe);
}
/**
* Gets a list of crusher recipes whose output is equal to the specified
* output item. If there are no such recipes, then null is returned (instead
* of an empty list).
* @param output The item resulting from the crushing of another item or block
* @return A list of recipes producing the requested item, or null if no
* such recipes exist
*/
public List<ICrusherRecipe> getRecipesForOutputItem(ItemStack output){
ItemLookupReference ref = new ItemLookupReference(output);
if(recipeByOutputCache.containsKey(ref)){
List<ICrusherRecipe> recipeCache = recipeByOutputCache.get(ref);
if(recipeCache.isEmpty()) return null;
return recipeCache;
} else {
// add recipe cache
List<ICrusherRecipe> recipeCache = new ArrayList<>();
for(ICrusherRecipe r : recipes){
if(ItemStack.areItemsEqual(r.getOutput(), output)){
recipeCache.add(r);
}
}
recipeByOutputCache.put(ref, recipeCache);
if(recipeCache.isEmpty()) return null;
return recipeCache;
}
}
/**
* Gets a list of crusher recipes whose output is equal to the specified
* output item. If there are no such recipes, then null is returned (instead
* of an empty list).
* @param output The block resulting from the crushing of another item or block
* @return A list of recipes producing the requested item, or null if no
* such recipes exist
*/
public List<ICrusherRecipe> getRecipesForOutputItem(IBlockState output){
ItemLookupReference ref = new ItemLookupReference(output);
if(recipeByOutputCache.containsKey(ref)){
List<ICrusherRecipe> recipeCache = recipeByOutputCache.get(ref);
if(recipeCache.isEmpty()) return null;
return recipeCache;
} else {
// add recipe cache
List<ICrusherRecipe> recipeCache = new ArrayList<>();
for(ICrusherRecipe r : recipes){
if(ref.equals(r.getOutput())){
recipeCache.add(r);
}
}
recipeByOutputCache.put(ref, recipeCache);
if(recipeCache.isEmpty()) return null;
return recipeCache;
}
}
/**
* Gets the recipe for crushing the specified item, or null if ther is no
* recipe accepting the item.
* @param input The item/block to crush
* @return The crusher recipe for crushing this item/block, or null if no
* such recipe exists
*/
public ICrusherRecipe getRecipeForInputItem(ItemStack input){
ItemLookupReference ref = new ItemLookupReference(input);
if(recipeByInputCache.containsKey(ref)){
return recipeByInputCache.get(ref);
} else {
for(ICrusherRecipe r : recipes){
if(r.isValidInput(input)){
recipeByInputCache.put(ref, r);
return r;
}
}
// no recipes, cache null result
recipeByInputCache.put(ref, null);
return null;
}
}
/**
* Gets the recipe for crushing the specified item, or null if ther is no
* recipe accepting the item.
* @param input The item/block to crush
* @return The crusher recipe for crushing this item/block, or null if no
* such recipe exists
*/
public ICrusherRecipe getRecipeForInputItem(IBlockState input){
ItemLookupReference ref = new ItemLookupReference(input);
ItemStack stack = new ItemStack(input.getBlock(),1,ref.metaData);
if(recipeByInputCache.containsKey(ref)){
return recipeByInputCache.get(ref);
} else {
for(ICrusherRecipe r : recipes){
if(r.isValidInput(stack)){
recipeByInputCache.put(ref, r);
return r;
}
}
// no recipes, cache null result
recipeByInputCache.put(ref, null);
return null;
}
}
/**
* Gets all registered crusher recipes
* @return An unmodifiable list of all registered crusher recipes
*/
public Collection<ICrusherRecipe> getAllRecipes() {
return Collections.unmodifiableList(recipes);
}
private static final class ItemLookupReference{
final Item item;
final int metaData;
final int hashCache;
public ItemLookupReference(ItemStack inputItem){
item = inputItem.getItem();
metaData = inputItem.getMetadata();
hashCache = item.getUnlocalizedName().hashCode() + (57 * metaData);
}
public ItemLookupReference(IBlockState inputBlock){
item = Item.getItemFromBlock(inputBlock.getBlock());
metaData = inputBlock.getBlock().getMetaFromState(inputBlock);
hashCache = inputBlock.getBlock().getUnlocalizedName().hashCode() + (57 * metaData);
}
@Override
public boolean equals(Object other){
if(other instanceof ItemLookupReference){
ItemLookupReference that = (ItemLookupReference)other;
return this.hashCache == that.hashCache && this.item == that.item && this.metaData == that.metaData;
} else if(other instanceof ItemStack){
ItemStack that = (ItemStack)other;
return this.item == that.getItem() && this.metaData == that.getMetadata();
} else {
return false;
}
}
@Override
public int hashCode(){
return hashCache;
}
}
}