package crazypants.enderio.machine.crafter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.Container;
import net.minecraft.inventory.InventoryCrafting;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.CraftingManager;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.FakePlayer;
import com.enderio.core.common.util.ItemUtil;
import com.mojang.authlib.GameProfile;
import cpw.mods.fml.common.gameevent.PlayerEvent.ItemCraftedEvent;
import crazypants.enderio.ModObject;
import crazypants.enderio.config.Config;
import crazypants.enderio.machine.AbstractPowerConsumerEntity;
import crazypants.enderio.machine.FakePlayerEIO;
import crazypants.enderio.machine.IItemBuffer;
import crazypants.enderio.machine.SlotDefinition;
import crazypants.enderio.power.BasicCapacitor;
import crazypants.enderio.power.Capacitors;
import crazypants.enderio.power.ICapacitor;
public class TileCrafter extends AbstractPowerConsumerEntity implements IItemBuffer {
DummyCraftingGrid craftingGrid = new DummyCraftingGrid();
private final List<ItemStack> containerItems;
private boolean bufferStacks = true;
private long ticksSinceLastCraft = 0;
private FakePlayer playerInst;
public TileCrafter() {
super(new SlotDefinition(9, 1));
containerItems = new ArrayList<ItemStack>();
}
@Override
public void onCapacitorTypeChange() {
ICapacitor refCap = getCapacitor();
int maxUse = getPowerUsePerTick(getCapacitorType());
int io = Math.max(maxUse, refCap.getMaxEnergyExtracted());
setCapacitor(new BasicCapacitor(io * 4, refCap.getMaxEnergyStored(), io));
}
@Override
public String getMachineName() {
return ModObject.blockCrafter.unlocalisedName;
}
@Override
protected boolean isMachineItemValidForSlot(int slot, ItemStack itemstack) {
if(!slotDefinition.isInputSlot(slot)) {
return false;
}
return craftingGrid.inv[slot] != null && compareDamageable(itemstack, craftingGrid.inv[slot]);
}
@Override
public boolean isActive() {
return false;
}
@Override
protected boolean processTasks(boolean redstoneCheckPassed) {
ticksSinceLastCraft++;
if(!redstoneCheckPassed || !craftingGrid.hasValidRecipe() || !canMergeOutput() || !hasRequiredPower()) {
return false;
}
int ticksPerCraft = getTicksPerCraft(getCapacitorType());
if(ticksSinceLastCraft <= ticksPerCraft) {
return false;
}
ticksSinceLastCraft = 0;
// process buffered container items
if(!containerItems.isEmpty()) {
Iterator<ItemStack> iter = containerItems.iterator();
while (iter.hasNext()) {
ItemStack stack = iter.next();
if(inventory[9] == null) {
inventory[9] = stack;
iter.remove();
} else if(ItemUtil.areStackMergable(inventory[9], stack) && inventory[9].stackSize + stack.stackSize <= inventory[9].getMaxStackSize()) {
inventory[9].stackSize += stack.stackSize;
iter.remove();
}
}
return false;
}
if(craftRecipe()) {
int used = Math.min(getEnergyStored(), Config.crafterRfPerCraft);
setEnergyStored(getEnergyStored() - used);
}
return false;
}
private boolean hasRequiredPower() {
return getEnergyStored() >= Config.crafterRfPerCraft;
}
@Override
public int getPowerUsePerTick() {
return getPowerUsePerTick(getCapacitorType());
}
public int getPowerUsePerTick(Capacitors type) {
int ticks = getTicksPerCraft(type);
return (int)Math.ceil(Config.crafterRfPerCraft / (double)ticks);
}
public int getTicksPerCraft(Capacitors type) {
if(type == Capacitors.BASIC_CAPACITOR) {
return 20;
} else if(type == Capacitors.ACTIVATED_CAPACITOR) {
return 10;
} else {
return 2;
}
}
static boolean compareDamageable(ItemStack stack, ItemStack req) {
if(stack.isItemEqual(req)) {
return true;
}
if(stack.isItemStackDamageable() && stack.getItem() == req.getItem()) {
return stack.getItemDamage() < stack.getMaxDamage();
}
return false;
}
private static final UUID uuid = UUID.fromString("9b381cae-3c95-4a64-b958-1e25b0a4c790");
private static final GameProfile DUMMY_PROFILE = new GameProfile(uuid, "[EioCrafter]");
private boolean craftRecipe() {
// (1) Find the items to craft with and put a copy into a temp crafting grid;
// also record what was used to destroy it later
InventoryCrafting inv = new InventoryCrafting(new Container() {
@Override
public boolean canInteractWith(EntityPlayer var1) {
return false;
}
}, 3, 3);
int[] usedItems = new int[9];
for (int j = 0; j < 9; j++) {
ItemStack req = craftingGrid.getStackInSlot(j);
if (req != null) {
for (int i = 0; i < 9; i++) {
if (inventory[i] != null && inventory[i].stackSize > usedItems[i] && compareDamageable(inventory[i], req)) {
req = null;
usedItems[i]++;
ItemStack craftingItem = inventory[i].copy();
craftingItem.stackSize = 1;
inv.setInventorySlotContents(j, craftingItem);
break;
}
}
if (req != null) {
return false;
}
}
}
// (2) Try to craft with the temp grid
ItemStack output = CraftingManager.getInstance().findMatchingRecipe(inv, worldObj);
// (3) If we got a result, ...
if (output != null) {
if (playerInst == null) {
playerInst = new FakePlayerEIO(worldObj, getLocation(), DUMMY_PROFILE);
}
MinecraftForge.EVENT_BUS.post(new ItemCraftedEvent(playerInst, output, inv));
// (3a) ... remove the used up items and ...
for (int i = 0; i < 9; i++) {
for (int j = 0; j < usedItems[i] && inventory[i] != null; j++) {
setInventorySlotContents(i, eatOneItemForCrafting(inventory[i].copy()));
}
}
// (3b) ... put the result into its slot
if (inventory[9] == null) {
setInventorySlotContents(9, output);
} else if (ItemUtil.areStackMergable(inventory[9], output)) {
ItemStack cur = inventory[9].copy();
cur.stackSize += output.stackSize;
if (cur.stackSize > cur.getMaxStackSize()) {
// we check beforehand that there is enough free space, but some mod may return different
// amounts based on the nbt of the input items (e.g. magical wood)
ItemStack overflow = cur.copy();
overflow.stackSize = cur.stackSize - cur.getMaxStackSize();
cur.stackSize = cur.getMaxStackSize();
containerItems.add(overflow);
}
setInventorySlotContents(9, cur);
} else {
// some mod may return different nbt based on the nbt of the input items (e.g. TE machines?)
containerItems.add(output);
}
} else {
// Crafting failed. This is not supposed to happen, but if a recipe is nbt-sensitive, it can.
// To avoid being stuck in a dead loop, we flush the non-working input items.
for (int j = 0; j < 9; j++) {
if (usedItems[j] > 0 && inventory[j] != null) {
ItemStack rejected = inventory[j].copy();
rejected.stackSize = Math.min(inventory[j].stackSize, usedItems[j]);
containerItems.add(rejected);
if (inventory[j].stackSize <= usedItems[j]) {
inventory[j] = null;
} else {
inventory[j].stackSize -= usedItems[j];
}
}
}
}
return true;
}
private ItemStack eatOneItemForCrafting(ItemStack avail) {
// 101 special cases for container items
if (avail.getItem().hasContainerItem(avail)) {
ItemStack used = avail.getItem().getContainerItem(avail);
if (used == null) {
// The promised container item does not exist
avail.stackSize--;
} else if (used.isItemStackDamageable() && used.getItemDamage() > used.getMaxDamage()) {
// Container item was used up
used = null;
avail.stackSize--;
} else if (used.isItemEqual(avail)) {
if (used.isItemStackDamageable()) {
// Container item is the same, but may have a different damage value
if (avail.stackSize == 1) {
// itemstack was used up, container item can replace it
avail = used;
} else {
// itemstack was not used up, container item goes into overflow
// (impossible case with vanilla and mods that play by the rules)
containerItems.add(used.copy());
avail.stackSize--;
}
} else {
// Container item is exactly the same: item should not be used up
// Nothing to do here
}
} else {
// Container item is different (e.g. bucket for lava bucket) and goes into the overflow
containerItems.add(used.copy());
avail.stackSize--;
}
} else {
// no container item, use up one item of the stack
avail.stackSize--;
}
if (avail.stackSize == 0) {
avail = null;
}
return avail;
}
private boolean canMergeOutput() {
if(inventory[9] == null) {
return true;
}
ItemStack output = craftingGrid.getOutput();
if(!ItemUtil.areStackMergable(inventory[9], output)) {
return false;
}
return output.getMaxStackSize() >= (inventory[9].stackSize + output.stackSize);
}
@Override
public int getInventoryStackLimit() {
return bufferStacks ? 64 : 1;
}
@Override
public boolean isBufferStacks() {
return bufferStacks;
}
@Override
public void setBufferStacks(boolean bufferStacks) {
this.bufferStacks = bufferStacks;
}
@Override
public void readCommon(NBTTagCompound nbtRoot) {
super.readCommon(nbtRoot);
NBTTagCompound craftRoot = nbtRoot.getCompoundTag("craftingGrid");
craftingGrid.readFromNBT(craftRoot);
if(nbtRoot.hasKey("bufferStacks")) {
bufferStacks = nbtRoot.getBoolean("bufferStacks");
} else {
bufferStacks = true;
}
containerItems.clear();
NBTTagList itemList = (NBTTagList) nbtRoot.getTag("containerItems");
if(itemList != null) {
for (int i = 0; i < itemList.tagCount(); i++) {
NBTTagCompound itemStack = itemList.getCompoundTagAt(i);
containerItems.add(ItemStack.loadItemStackFromNBT(itemStack));
}
}
}
@Override
public void writeCommon(NBTTagCompound nbtRoot) {
super.writeCommon(nbtRoot);
NBTTagCompound craftingRoot = new NBTTagCompound();
craftingGrid.writeToNBT(craftingRoot);
nbtRoot.setTag("craftingGrid", craftingRoot);
nbtRoot.setBoolean("bufferStacks", bufferStacks);
if (containerItems.isEmpty()) {
nbtRoot.removeTag("containerItems");
} else {
NBTTagList itemList = new NBTTagList();
for (ItemStack stack : containerItems) {
NBTTagCompound itemStackNBT = new NBTTagCompound();
stack.writeToNBT(itemStackNBT);
itemList.appendTag(itemStackNBT);
}
nbtRoot.setTag("containerItems", itemList);
}
}
public void updateCraftingOutput() {
InventoryCrafting inv = new InventoryCrafting(new Container() {
@Override
public boolean canInteractWith(EntityPlayer var1) {
return false;
}
}, 3, 3);
for (int i = 0; i < 9; i++) {
inv.setInventorySlotContents(i, craftingGrid.getStackInSlot(i));
}
ItemStack matches = CraftingManager.getInstance().findMatchingRecipe(inv, worldObj);
craftingGrid.setInventorySlotContents(9, matches);
markDirty();
}
}