/**
* Copyright (c) Lambda Innovation, 2013-2016
* This file is part of the AcademyCraft mod.
* https://github.com/LambdaInnovation/AcademyCraft
* Licensed under GPLv3, see project root for more information.
*/
package cn.academy.misc.tutorial.client;
import cn.academy.core.Resources;
import cn.academy.crafting.api.ImagFusorRecipes;
import cn.academy.crafting.api.ImagFusorRecipes.IFRecipe;
import cn.academy.crafting.api.MetalFormerRecipes;
import cn.academy.energy.client.gui.EnergyUIHelper;
import cn.lambdalib.annoreg.core.Registrant;
import cn.lambdalib.annoreg.mc.RegInitCallback;
import cn.lambdalib.cgui.gui.Widget;
import cn.lambdalib.cgui.gui.WidgetContainer;
import cn.lambdalib.cgui.gui.component.DrawTexture;
import cn.lambdalib.cgui.gui.component.TextBox;
import cn.lambdalib.cgui.gui.event.FrameEvent;
import cn.lambdalib.cgui.xml.CGUIDocument;
import cn.lambdalib.util.client.HudUtils;
import cn.lambdalib.util.client.font.IFont.FontAlign;
import cn.lambdalib.util.client.font.IFont.FontOption;
import cn.lambdalib.util.generic.RandUtils;
import cn.lambdalib.util.helper.Color;
import cn.lambdalib.util.helper.GameTimer;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.entity.RenderItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.*;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.StatCollector;
import net.minecraftforge.oredict.ShapedOreRecipe;
import net.minecraftforge.oredict.ShapelessOreRecipe;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static org.lwjgl.opengl.GL11.*;
// TODO Add ImagFusor and MetalFormer handlers
@SideOnly(Side.CLIENT)
@Registrant
public enum RecipeHandler {
instance;
private WidgetContainer windows;
private ResourceLocation tex = Resources.getTexture("guis/tutorial/crafting_grid");
@RegInitCallback
private static void __init() {
instance.windows = CGUIDocument.panicRead(new ResourceLocation("academy:guis/tutorial_windows.xml"));
}
private Field _$ShapedOreRecipe$fieldWidth;
{
try {
_$ShapedOreRecipe$fieldWidth = ShapedOreRecipe.class.getDeclaredField("width");
_$ShapedOreRecipe$fieldWidth.setAccessible(true);
} catch(Exception e) {
throw new RuntimeException("RecipeHandler reflection", e);
}
}
private int getWidth(ShapedOreRecipe recipe) {
try {
return _$ShapedOreRecipe$fieldWidth.getInt(recipe);
} catch(IllegalAccessException e) {
throw new RuntimeException("Can't retrieve width of ShapedOreRecipe", e);
}
}
/**
* This class displays a 3x3 crafting grid of a recipe.
*/
static class CraftingGridDisplay extends Widget {
static final FontOption option = new FontOption(24, FontAlign.CENTER, Color.white());
static final int STEP = 43;
private final StackDisplay[] stacks;
private final StackDisplay output;
private final String description;
CraftingGridDisplay(StackDisplay _output, StackDisplay[] _stacks, String desc) {
stacks = _stacks;
output = _output;
description = desc;
size(196, 128).centered().scale(0.6);
for(int i = 0; i < stacks.length; ++i) {
int col = i % 3, row = i / 3;
StackDisplay original = stacks[i];
if(original != null) {
original.disposed = false;
original.dirty = true;
original.pos(5 + col * STEP, 5 + row * STEP);
addWidget(original);
}
}
output.pos(148 + 5, 44 + 5);
addWidget(output);
listen(FrameEvent.class, (w, e) ->
{
// Renders recipe type hint
String str = StatCollector.translateToLocal("ac.gui.crafttype." + description);
Resources.font().draw(str, transform.width / 2 - 30, -28, option);
});
addComponent(new DrawTexture().setTex(instance.tex));
}
}
/**
* This class displays one stack slot, possibly multiple stacks alternating.
*/
@SideOnly(Side.CLIENT)
static class StackDisplay extends Widget {
private static Minecraft mc = Minecraft.getMinecraft();
private static RenderItem itemRender = RenderItem.getInstance();
static final long ALTERNATE_TIME = 2000;
private final ItemStack[] stacks;
long lastAlternate;
private int current = 0;
public StackDisplay(ItemStack... _stacks) {
stacks = _stacks;
transform.setPos(0, 0).setSize(32, 34);
lastAlternate = GameTimer.getAbsTime() + RandUtils.rangei(-1000, 1000);
if(stacks.length != 0) { // Don't draw anything for empty stack
listen(FrameEvent.class, (w, e) ->
{
long time = GameTimer.getAbsTime();
long dt = time - lastAlternate;
if(dt > ALTERNATE_TIME) {
current = (current + 1) % stacks.length;
lastAlternate = time;
}
ItemStack stack = stacks[current];
// Hover effect
if(e.hovering) {
glColor4d(1, 1, 1, 0.15);
HudUtils.colorRect(0, 0, transform.width, transform.height);
}
// Renders the stack
glPushMatrix();
glScaled(2, 2, 1);
glTranslatef(0, 0, 1.0F);
FontRenderer font = stack.getItem().getFontRenderer(stack);
itemRender.renderItemIntoGUI(font, mc.getTextureManager(), stack, 0, 0);
// WTF, you have opened up lighting???
glDisable(GL_LIGHTING);
glEnable(GL_BLEND);
glPopMatrix();
glPushMatrix();
glTranslated(0, 0, 20);
if (e.hovering) {
EnergyUIHelper.drawTextBox(stack.getDisplayName(),
e.mx + 10, e.my - 17, 1000,
new FontOption(10 / w.scale, FontAlign.LEFT), true);
}
glPopMatrix();
});
}
}
}
private ItemStack[] mapToStacks(Object obj) {
if(obj == null) {
return new ItemStack[0];
}
if(obj instanceof Collection) {
return ((Collection<ItemStack>) obj).toArray(new ItemStack[0]);
}
if(obj instanceof Item) {
Item item = (Item) obj;
return IntStream.range(0, item.getMaxDamage() + 1)
.mapToObj(i -> new ItemStack(item, 1, i))
.toArray(ItemStack[]::new);
}
if(obj instanceof Block) {
Block block = (Block) obj;
Item item = Item.getItemFromBlock(block);
return IntStream.range(0, item.getMaxDamage() + 1)
.mapToObj(i -> new ItemStack(item, 1, i))
.toArray(ItemStack[]::new);
}
return new ItemStack[] { (ItemStack) obj };
}
private StackDisplay[] remap(StackDisplay[] original, int width) {
StackDisplay[] ret = new StackDisplay[9];
for(int i = 0; i < original.length; ++i) {
int row = i / width, col = i % width;
ret[col + row * 3] = original[i];
}
return ret;
}
private StackDisplay[] toDisplay(Object[] objects) {
return Arrays.stream(objects).map(x -> new StackDisplay(mapToStacks(x))).toArray(StackDisplay[]::new);
}
private StackDisplay[] toDisplay(ShapedOreRecipe recipe) {
return remap(toDisplay(recipe.getInput()), getWidth(recipe));
}
private StackDisplay[] toDisplay(ShapedRecipes recipe) {
return remap(toDisplay(recipe.recipeItems), recipe.recipeWidth);
}
private StackDisplay[] toDisplay(ShapelessRecipes recipe) {
return toDisplay(recipe.recipeItems.toArray());
}
private StackDisplay[] toDisplay(ShapelessOreRecipe recipe) {
return toDisplay(recipe.getInput().toArray());
}
private boolean matchStack(ItemStack s1, ItemStack s2) {
if(s1 == null || s2 == null) {
return false;
}
return s1.getItem() == s2.getItem() &&
(!s1.getItem().getHasSubtypes() || s1.getItemDamage() == s2.getItemDamage());
}
private Widget drawMFRecipe(MetalFormerRecipes.RecipeObject recipe) {
// CGUI Rocks
Widget ret = windows.getWidget("MetalFormer").copy();
ret.getWidget("slot_in").addWidget(new StackDisplay(recipe.input).centered());
ret.getWidget("slot_out").addWidget(new StackDisplay(recipe.output).centered());
DrawTexture.get(ret.getWidget("mode")).setTex(recipe.mode.texture);
return ret;
}
private Widget drawIFRecipe(IFRecipe recipe) {
Widget ret = windows.getWidget("ImagFusor").copy();
ret.getWidget("slot_in").addWidget(new StackDisplay(recipe.consumeType).centered());
ret.getWidget("slot_out").addWidget(new StackDisplay(recipe.output).centered());
TextBox.get(ret.getWidget("amount")).setContent(String.valueOf(recipe.consumeLiquid));
return ret;
}
private Widget drawSmeltRecipe(ItemStack in, ItemStack out) {
Widget ret = windows.getWidget("Smelting").copy();
ret.getWidget("slot_in").addWidget(new StackDisplay(in).centered());
ret.getWidget("slot_out").addWidget(new StackDisplay(out).centered());
return ret;
}
public Widget[] recipeOfBlock(Block block) {
return recipeOfItem(Item.getItemFromBlock(block));
}
public Widget[] recipeOfItem(Item item) {
List<ItemStack> lst = new ArrayList<>();
if (item.getHasSubtypes()) {
lst.addAll(IntStream.range(0, item.getMaxDamage())
.mapToObj(i -> new ItemStack(item, 1, i))
.collect(Collectors.toList()));
} else {
lst.add(new ItemStack(item));
}
return lst.stream().flatMap(stk -> Arrays.stream(recipeOfStack(stk))).toArray(Widget[]::new);
}
@SuppressWarnings("unchecked")
public Widget[] recipeOfStack(ItemStack stack) {
List<Widget> ret = new ArrayList<>();
for(IRecipe o : (List<IRecipe>) CraftingManager.getInstance().getRecipeList()) {
if(matchStack(o.getRecipeOutput(), stack)) {
StackDisplay[] arr;
String desc;
if(o instanceof ShapedOreRecipe) {
arr = toDisplay((ShapedOreRecipe) o);
desc = "shaped";
} else if(o instanceof ShapedRecipes) {
arr = toDisplay((ShapedRecipes) o);
desc = "shaped";
} else if(o instanceof ShapelessRecipes) {
arr = toDisplay((ShapelessRecipes) o);
desc = "shapeless";
} else if(o instanceof ShapelessOreRecipe) {
arr = toDisplay((ShapelessOreRecipe) o);
desc = "shapeless";
} else {
throw new RuntimeException("Invalid recipe");
}
ret.add(new CraftingGridDisplay(new StackDisplay(o.getRecipeOutput()), arr, desc));
}
}
{ // IF Recipes
List<Widget> recipes = ImagFusorRecipes.INSTANCE.getAllRecipe().stream()
.filter(recipe -> itemDamageEqual(recipe.output, stack))
.map(instance::drawIFRecipe)
.collect(Collectors.toList());
ret.addAll(recipes);
}
{ // MF Recipes
List<Widget> recipes = MetalFormerRecipes.INSTANCE.getAllRecipes().stream()
.filter(recipe -> itemDamageEqual(recipe.output, stack))
.map(instance::drawMFRecipe)
.collect(Collectors.toList());
ret.addAll(recipes);
}
{ // Smelting
Map<ItemStack, ItemStack> smeltingList = FurnaceRecipes.smelting().getSmeltingList();
smeltingList.entrySet()
.stream()
.filter(entry -> {
ItemStack stack2 = entry.getValue();
return stack2.getItem() == stack.getItem() &&
(stack2.getItemDamage() == 32767 ||
stack2.getItemDamage() == stack.getItemDamage());
})
.forEach(entry -> ret.add(drawSmeltRecipe(entry.getKey(), stack)));
}
return ret.toArray(new Widget[ret.size()]);
}
private boolean itemDamageEqual(ItemStack s1, ItemStack s2) {
if (s1 == null || s2 == null) { return false; }
return s1.getItem() == s2.getItem() && s1.getItemDamage() == s2.getItemDamage();
}
}