package codechicken.nei.recipe;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.NEIClientUtils;
import codechicken.nei.NEIServerUtils;
import codechicken.nei.PositionedStack;
import codechicken.nei.api.DefaultOverlayRenderer;
import codechicken.nei.api.IOverlayHandler;
import codechicken.nei.api.IRecipeOverlayRenderer;
import codechicken.nei.api.IStackPositioner;
import codechicken.nei.guihook.GuiContainerManager;
import codechicken.nei.guihook.IContainerInputHandler;
import codechicken.nei.guihook.IContainerTooltipHandler;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.inventory.Container;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import java.awt.*;
import java.util.*;
import java.util.List;
import static codechicken.lib.gui.GuiDraw.*;
/**
* A Template Recipe Handler!
* How about that.
* Because it was sooo hard, and more seriously required lots of copied code to make a handler in the past.
* you can now extend this class to make your custom recipe handlers much easier to create.
* Just look at the 5 handlers included by default to work out how to do stuff if you are still stuck.
*/
public abstract class TemplateRecipeHandler implements ICraftingHandler, IUsageHandler {
/**
* This Recipe Handler runs on this internal class
* Fill the recipe array with subclasses of this to make transforming the different types of recipes out there into a nice format for NEI a much easier job.
*/
public abstract class CachedRecipe {
final long offset = System.currentTimeMillis();
/**
* @return The item produced by this recipe, with position
*/
public abstract PositionedStack getResult();
/**
* The ingredients required to produce the result
* Use this if you have more than one ingredient
*
* @return A list of positioned ingredient items.
*/
public List<PositionedStack> getIngredients() {
ArrayList<PositionedStack> stacks = new ArrayList<PositionedStack>();
PositionedStack stack = getIngredient();
if (stack != null) {
stacks.add(stack);
}
return stacks;
}
/**
* @return The ingredient required to produce the result
*/
public PositionedStack getIngredient() {
return null;
}
/**
* Return extra items that are not directly involved in the ingredient->result relationship. Eg fuels.
* Use this if you have more than one other stack
*
* @return A list of positioned items.
*/
public List<PositionedStack> getOtherStacks() {
ArrayList<PositionedStack> stacks = new ArrayList<PositionedStack>();
PositionedStack stack = getOtherStack();
if (stack != null) {
stacks.add(stack);
}
return stacks;
}
/**
* Simple utility
*
* @return The another positioned stack
*/
public PositionedStack getOtherStack() {
return null;
}
/**
* This will perform default cycling of ingredients, mulitItem capable
*
* @return
*/
public List<PositionedStack> getCycledIngredients(int cycle, List<PositionedStack> ingredients) {
for (int itemIndex = 0; itemIndex < ingredients.size(); itemIndex++) {
randomRenderPermutation(ingredients.get(itemIndex), cycle + itemIndex);
}
return ingredients;
}
public void randomRenderPermutation(PositionedStack stack, long cycle) {
Random rand = new Random(cycle + offset);
stack.setPermutationToRender(Math.abs(rand.nextInt()) % stack.items.length);
}
/**
* Set all variable ingredients to this permutation.
*
* @param ingredient
*/
public void setIngredientPermutation(Collection<PositionedStack> ingredients, ItemStack ingredient) {
for (PositionedStack stack : ingredients) {
for (int i = 0; i < stack.items.length; i++) {
if (NEIServerUtils.areStacksSameTypeCrafting(ingredient, stack.items[i])) {
stack.item = stack.items[i];
stack.item.setItemDamage(ingredient.getItemDamage());
if (ingredient.hasTagCompound()) {
stack.item.setTagCompound((NBTTagCompound) ingredient.getTagCompound().copy());
}
stack.items = new ItemStack[] { stack.item };
stack.setPermutationToRender(0);
break;
}
}
}
}
/**
* @param ingredient
* @return true if any of the permutations of any of the ingredients contain this stack
*/
public boolean contains(Collection<PositionedStack> ingredients, ItemStack ingredient) {
for (PositionedStack stack : ingredients) {
if (stack.contains(ingredient)) {
return true;
}
}
return false;
}
/**
* @param ingred
* @return true if any of the permutations of any of the ingredients contain this stack
*/
public boolean contains(Collection<PositionedStack> ingredients, Item ingred) {
for (PositionedStack stack : ingredients) {
if (stack.contains(ingred)) {
return true;
}
}
return false;
}
}
/**
* The Rectangle is an region of the gui relative to the corner of the recipe that will activate the recipe with the corresponding outputId apon being clicked.
* Apply this over fuel icons or arrows that the user may click to see all recipes pertaining to that action.
*/
public static class RecipeTransferRect {
public RecipeTransferRect(Rectangle rectangle, String outputId, Object... results) {
rect = rectangle;
this.outputId = outputId;
this.results = results;
}
public boolean equals(Object obj) {
if (!(obj instanceof RecipeTransferRect)) {
return false;
}
return rect.equals(((RecipeTransferRect) obj).rect);
}
public int hashCode() {
return rect.hashCode();
}
Rectangle rect;
String outputId;
Object[] results;
}
public static class RecipeTransferRectHandler implements IContainerInputHandler, IContainerTooltipHandler {
private static HashMap<Class<? extends GuiContainer>, HashSet<RecipeTransferRect>> guiMap = new HashMap<Class<? extends GuiContainer>, HashSet<RecipeTransferRect>>();
public static void registerRectsToGuis(List<Class<? extends GuiContainer>> classes, List<RecipeTransferRect> rects) {
if (classes == null) {
return;
}
for (Class<? extends GuiContainer> clazz : classes) {
HashSet<RecipeTransferRect> set = guiMap.get(clazz);
if (set == null) {
set = new HashSet<RecipeTransferRect>();
guiMap.put(clazz, set);
}
set.addAll(rects);
}
}
public boolean canHandle(GuiContainer gui) {
return guiMap.containsKey(gui.getClass());
}
@Override
public boolean lastKeyTyped(GuiContainer gui, char keyChar, int keyCode) {
if (!canHandle(gui)) {
return false;
}
if (keyCode == NEIClientConfig.getKeyBinding("gui.recipe")) {
return transferRect(gui, false);
} else if (keyCode == NEIClientConfig.getKeyBinding("gui.usage")) {
return transferRect(gui, true);
}
return false;
}
@Override
public boolean mouseClicked(GuiContainer gui, int mousex, int mousey, int button) {
if (!canHandle(gui)) {
return false;
}
if (button == 0) {
return transferRect(gui, false);
} else if (button == 1) {
return transferRect(gui, true);
}
return false;
}
private boolean transferRect(GuiContainer gui, boolean usage) {
int[] offset = RecipeInfo.getGuiOffset(gui);
return TemplateRecipeHandler.transferRect(gui, guiMap.get(gui.getClass()), offset[0], offset[1], usage);
}
@Override
public void onKeyTyped(GuiContainer gui, char keyChar, int keyID) {
}
@Override
public void onMouseClicked(GuiContainer gui, int mousex, int mousey, int button) {
}
@Override
public void onMouseUp(GuiContainer gui, int mousex, int mousey, int button) {
}
@Override
public boolean keyTyped(GuiContainer gui, char keyChar, int keyID) {
return false;
}
@Override
public boolean mouseScrolled(GuiContainer gui, int mousex, int mousey, int scrolled) {
return false;
}
@Override
public void onMouseScrolled(GuiContainer gui, int mousex, int mousey, int scrolled) {
}
@Override
public List<String> handleTooltip(GuiContainer gui, int mousex, int mousey, List<String> currenttip) {
if (!canHandle(gui)) {
return currenttip;
}
if (GuiContainerManager.shouldShowTooltip(gui) && currenttip.size() == 0) {
int[] offset = RecipeInfo.getGuiOffset(gui);
currenttip = TemplateRecipeHandler.transferRectTooltip(gui, guiMap.get(gui.getClass()), offset[0], offset[1], currenttip);
}
return currenttip;
}
@Override
public List<String> handleItemDisplayName(GuiContainer gui, ItemStack itemstack, List<String> currenttip) {
return currenttip;
}
@Override
public List<String> handleItemTooltip(GuiContainer gui, ItemStack itemstack, int mousex, int mousey, List<String> currenttip) {
return currenttip;
}
@Override
public void onMouseDragged(GuiContainer gui, int mousex, int mousey, int button, long heldTime) {
}
}
static {
GuiContainerManager.addInputHandler(new RecipeTransferRectHandler());
GuiContainerManager.addTooltipHandler(new RecipeTransferRectHandler());
}
/**
* Internal tick counter, initialised to random value and incremented every tick.
* Used for cycling similar ingredients and progress bars.
*/
public int cycleticks = Math.abs((int) System.currentTimeMillis());
/**
* The list of matching recipes
*/
public ArrayList<CachedRecipe> arecipes = new ArrayList<CachedRecipe>();
/**
* A list of transferRects that apon when clicked or R is pressed will open a new recipe.
*/
public LinkedList<RecipeTransferRect> transferRects = new LinkedList<RecipeTransferRect>();
public TemplateRecipeHandler() {
loadTransferRects();
RecipeTransferRectHandler.registerRectsToGuis(getRecipeTransferRectGuis(), transferRects);
}
/**
* Add all RecipeTransferRects to the transferRects list during this call.
* Afterward they may be added to the input handler for the corresponding guis from getRecipeTransferRectGuis
*/
public void loadTransferRects() {
}
/**
* In this function you need to fill up the empty recipe array with recipes.
* The default passes it to a cleaner handler if outputId is an item
*
* @param outputId A String identifier representing the type of output produced. Eg. {"item", "fuel"}
* @param results Objects representing the results that matching recipes must produce.
*/
public void loadCraftingRecipes(String outputId, Object... results) {
if (outputId.equals("item")) {
loadCraftingRecipes((ItemStack) results[0]);
}
}
/**
* Simplified wrapper, implement this and fill the empty recipe array with recipes
*
* @param result The result the recipes must output.
*/
public void loadCraftingRecipes(ItemStack result) {
}
/**
* In this function you need to fill up the empty recipe array with recipes
* The default passes it to a cleaner handler if inputId is an item
*
* @param inputId A String identifier representing the type of ingredients used. Eg. {"item", "fuel"}
* @param ingredients Objects representing the ingredients that matching recipes must contain.
*/
public void loadUsageRecipes(String inputId, Object... ingredients) {
if (inputId.equals("item")) {
loadUsageRecipes((ItemStack) ingredients[0]);
}
}
/**
* Simplified wrapper, implement this and fill the empty recipe array with recipes
*
* @param ingredient The ingredient the recipes must contain.
*/
public void loadUsageRecipes(ItemStack ingredient) {
}
/**
* @return The filepath to the texture to use when drawing this recipe
*/
public abstract String getGuiTexture();
/**
* Simply works with the {@link DefaultOverlayRenderer}
* If the current container has been registered with this identifier, the question mark appears and an overlay guide can be drawn.
*
* @return The overlay identifier of this recipe type.
*/
public String getOverlayIdentifier() {
return null;
}
/**
* Extension point for drawing progress bars and other overlays
*
* @param recipe The recipeIndex being drawn
*/
public void drawExtras(int recipe) {
}
/**
* Draws a texture rectangle that changes size with time.
* Commonly used for progress bars.
*
* @param x X position on screen
* @param y Y position on screen
* @param tx Texture X position
* @param ty Texture Y position
* @param w Texture width
* @param h Texture height
* @param ticks The amount of ticks for the bar to complete
* @param direction 0 right, 1 down, 2 left, 3 up. If bit 3 is set the bar will shrink rather extend
*/
public void drawProgressBar(int x, int y, int tx, int ty, int w, int h, int ticks, int direction) {
drawProgressBar(x, y, tx, ty, w, h, cycleticks % ticks / (float) ticks, direction);
}
/**
* Draws a texture rectangle that changes size with time.
* Commonly used for progress bars.
* If for some reason you don't like the default counter use this and specify the progress percentage.
*
* @param x X position on screen
* @param y Y position on screen
* @param tx Texture X position
* @param ty Texture Y position
* @param w Texture width
* @param h Texture height
* @param completion the percentage of progress bar completion, 0-1
* @param direction 0 right, 1 down, 2 left, 3 up. If bit 3 is set the bar will shrink rather extend
*/
public void drawProgressBar(int x, int y, int tx, int ty, int w, int h, float completion, int direction) {
if (direction > 3) {
completion = 1 - completion;
direction %= 4;
}
int var = (int) (completion * (direction % 2 == 0 ? w : h));
switch (direction) {
case 0://right
drawTexturedModalRect(x, y, tx, ty, var, h);
break;
case 1://down
drawTexturedModalRect(x, y, tx, ty, w, var);
break;
case 2://left
drawTexturedModalRect(x + w - var, y, tx + w - var, ty, var, h);
break;
case 3://up
drawTexturedModalRect(x, y + h - var, tx, ty + h - var, w, var);
break;
}
}
/**
* @return The gui classes to which the transfer rects added in the constructor are to be located over. null if none.
*/
public List<Class<? extends GuiContainer>> getRecipeTransferRectGuis() {
Class<? extends GuiContainer> clazz = getGuiClass();
if (clazz != null) {
LinkedList<Class<? extends GuiContainer>> list = new LinkedList<Class<? extends GuiContainer>>();
list.add(clazz);
return list;
}
return null;
}
/**
* @return The class of the GuiContainer that this recipe would be crafted in.
*/
public Class<? extends GuiContainer> getGuiClass() {
return null;
}
public TemplateRecipeHandler newInstance() {
try {
return getClass().newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public ICraftingHandler getRecipeHandler(String outputId, Object... results) {
TemplateRecipeHandler handler = newInstance();
handler.loadCraftingRecipes(outputId, results);
return handler;
}
public IUsageHandler getUsageHandler(String inputId, Object... ingredients) {
TemplateRecipeHandler handler = newInstance();
handler.loadUsageRecipes(inputId, ingredients);
return handler;
}
public int numRecipes() {
return arecipes.size();
}
public void drawBackground(int recipe) {
GlStateManager.color(1, 1, 1, 1);
changeTexture(getGuiTexture());
drawTexturedModalRect(0, 0, 5, 11, 166, 65);
}
public void drawForeground(int recipe) {
GlStateManager.color(1, 1, 1, 1);
GlStateManager.disableLighting();
changeTexture(getGuiTexture());
drawExtras(recipe);
}
public List<PositionedStack> getIngredientStacks(int recipe) {
return arecipes.get(recipe).getIngredients();
}
public PositionedStack getResultStack(int recipe) {
return arecipes.get(recipe).getResult();
}
public List<PositionedStack> getOtherStacks(int recipe) {
return arecipes.get(recipe).getOtherStacks();
}
public void onUpdate() {
if (!NEIClientUtils.shiftKey()) {
cycleticks++;
}
}
public boolean hasOverlay(GuiContainer gui, Container container, int recipe) {
return RecipeInfo.hasDefaultOverlay(gui, getOverlayIdentifier()) || RecipeInfo.hasOverlayHandler(gui, getOverlayIdentifier());
}
public IRecipeOverlayRenderer getOverlayRenderer(GuiContainer gui, int recipe) {
IStackPositioner positioner = RecipeInfo.getStackPositioner(gui, getOverlayIdentifier());
if (positioner == null) {
return null;
}
return new DefaultOverlayRenderer(getIngredientStacks(recipe), positioner);
}
@Override
public IOverlayHandler getOverlayHandler(GuiContainer gui, int recipe) {
return RecipeInfo.getOverlayHandler(gui, getOverlayIdentifier());
}
@Override
public int recipiesPerPage() {
return 2;
}
@Override
public List<String> handleTooltip(GuiRecipe gui, List<String> currenttip, int recipe) {
if (GuiContainerManager.shouldShowTooltip(gui) && currenttip.size() == 0) {
Point offset = gui.getRecipePosition(recipe);
currenttip = transferRectTooltip(gui, transferRects, offset.x, offset.y, currenttip);
}
return currenttip;
}
@Override
public List<String> handleItemTooltip(GuiRecipe gui, ItemStack stack, List<String> currenttip, int recipe) {
return currenttip;
}
@Override
public boolean keyTyped(GuiRecipe gui, char keyChar, int keyCode, int recipe) {
if (keyCode == NEIClientConfig.getKeyBinding("gui.recipe")) {
return transferRect(gui, recipe, false);
} else if (keyCode == NEIClientConfig.getKeyBinding("gui.usage")) {
return transferRect(gui, recipe, true);
}
return false;
}
@Override
public boolean mouseClicked(GuiRecipe gui, int button, int recipe) {
if (button == 0) {
return transferRect(gui, recipe, false);
} else if (button == 1) {
return transferRect(gui, recipe, true);
}
return false;
}
private boolean transferRect(GuiRecipe gui, int recipe, boolean usage) {
Point offset = gui.getRecipePosition(recipe);
return transferRect(gui, transferRects, offset.x, offset.y, usage);
}
private static boolean transferRect(GuiContainer gui, Collection<RecipeTransferRect> transferRects, int offsetx, int offsety, boolean usage) {
Point pos = getMousePosition();
Point relMouse = new Point(pos.x - gui.guiLeft - offsetx, pos.y - gui.guiTop - offsety);
for (RecipeTransferRect rect : transferRects) {
if (rect.rect.contains(relMouse) && (usage ? GuiUsageRecipe.openRecipeGui(rect.outputId, rect.results) : GuiCraftingRecipe.openRecipeGui(rect.outputId, rect.results))) {
return true;
}
}
return false;
}
private static List<String> transferRectTooltip(GuiContainer gui, Collection<RecipeTransferRect> transferRects, int offsetx, int offsety, List<String> currenttip) {
Point pos = getMousePosition();
Point relMouse = new Point(pos.x - gui.guiLeft - offsetx, pos.y - gui.guiTop - offsety);
for (RecipeTransferRect rect : transferRects) {
if (rect.rect.contains(relMouse)) {
currenttip.add("Recipes");
break;
}
}
return currenttip;
}
}