/**
* Copyright (c) 2011-2014, SpaceToad and the BuildCraft Team
* http://www.mod-buildcraft.com
*
* BuildCraft is distributed under the terms of the Minecraft Mod Public
* License 1.0, or MMPL. Please check the contents of the license located in
* http://www.mod-buildcraft.com/MMPL-1.0.txt
*/
package buildcraft.silicon;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import com.google.common.collect.Lists;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.Container;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.ISidedInventory;
import net.minecraft.inventory.InventoryCraftResult;
import net.minecraft.inventory.InventoryCrafting;
import net.minecraft.inventory.SlotCrafting;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.IRecipe;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.WorldServer;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.oredict.OreDictionary;
import buildcraft.BuildCraftSilicon;
import buildcraft.api.core.IInvSlot;
import buildcraft.api.power.ILaserTarget;
import buildcraft.api.tiles.IControllable;
import buildcraft.core.TileBuffer;
import buildcraft.core.inventory.InvUtils;
import buildcraft.core.inventory.InventoryCopy;
import buildcraft.core.inventory.InventoryIterator;
import buildcraft.core.inventory.InventoryMapper;
import buildcraft.core.inventory.SimpleInventory;
import buildcraft.core.inventory.StackHelper;
import buildcraft.core.inventory.Transactor;
import buildcraft.core.inventory.filters.CraftingFilter;
import buildcraft.core.inventory.filters.IStackFilter;
import buildcraft.core.network.PacketIds;
import buildcraft.core.network.PacketSlotChange;
import buildcraft.core.proxy.CoreProxy;
import buildcraft.core.utils.CraftingHelper;
import buildcraft.core.utils.StringUtils;
import buildcraft.core.utils.Utils;
public class TileAdvancedCraftingTable extends TileLaserTableBase implements IInventory, ILaserTarget, ISidedInventory {
private static final int[] SLOTS = Utils.createSlotArray(0, 24);
private static final EnumSet<ForgeDirection> SEARCH_SIDES = EnumSet.of(ForgeDirection.DOWN, ForgeDirection.NORTH, ForgeDirection.SOUTH,
ForgeDirection.EAST, ForgeDirection.WEST);
private static final int REQUIRED_POWER = 5000;
private final CraftingGrid craftingSlots;
private final InventoryMapper invInput;
private final InventoryMapper invOutput;
private SlotCrafting craftSlot;
private boolean craftable;
private boolean justCrafted;
private IRecipe currentRecipe;
private TileBuffer[] cache;
private InventoryCraftResult craftResult;
private InternalInventoryCrafting internalInventoryCrafting;
private final class InternalInventoryCraftingContainer extends Container {
@Override
public boolean canInteractWith(EntityPlayer var1) {
return false;
}
}
private final class CraftingGrid extends SimpleInventory {
public int[][] oreIDs = new int[9][];
public CraftingGrid() {
super(9, "CraftingSlots", 1);
Arrays.fill(oreIDs, new int[0]);
}
@Override
public void setInventorySlotContents(int slotId, ItemStack itemstack) {
super.setInventorySlotContents(slotId, itemstack);
if (TileAdvancedCraftingTable.this.getWorldObj() == null || !TileAdvancedCraftingTable.this.getWorldObj().isRemote) {
int[] id = new int[0];
if (itemstack != null) {
int[] ids = OreDictionary.getOreIDs(itemstack);
if (ids.length > 0) {
id = ids;
}
}
oreIDs[slotId] = id;
}
}
}
private final class InternalInventoryCrafting extends InventoryCrafting {
public int[] hitCount;
private int[] bindings = new int[9];
private ItemStack[] tempStacks;
private boolean useRecipeStack;
private InternalInventoryCrafting() {
super(new InternalInventoryCraftingContainer(), 3, 3);
}
@Override
public ItemStack getStackInSlot(int slot) {
if (slot >= 0 && slot < 9) {
if (useRecipeStack || tempStacks == null) {
return craftingSlots.getStackInSlot(slot);
} else {
if (bindings[slot] >= 0) {
return tempStacks[bindings[slot]];
}
}
}
// vanilla returns null for out of bound stacks in InventoryCrafting as well
return null;
}
@Override
public void setInventorySlotContents(int slot, ItemStack par2ItemStack) {
if (tempStacks != null && slot >= 0 && slot < 9 && bindings[slot] >= 0) {
tempStacks[bindings[slot]] = par2ItemStack;
}
}
@Override
public ItemStack decrStackSize(int slot, int amount) {
if (tempStacks != null && slot >= 0 && slot < 9 && bindings[slot] >= 0) {
if (tempStacks[bindings[slot]].stackSize <= amount) {
ItemStack result = tempStacks[bindings[slot]];
tempStacks[bindings[slot]] = null;
return result;
} else {
ItemStack result = tempStacks[bindings[slot]].splitStack(amount);
if (tempStacks[bindings[slot]].stackSize <= 0) {
tempStacks[bindings[slot]] = null;
}
return result;
}
} else {
return null;
}
}
public void recipeUpdate(boolean flag) {
useRecipeStack = flag;
}
}
public TileAdvancedCraftingTable() {
craftingSlots = new CraftingGrid();
inv.addListener(this);
invInput = new InventoryMapper(inv, 0, 15);
invOutput = new InventoryMapper(inv, 15, 9);
craftResult = new InventoryCraftResult();
}
public WeakReference<EntityPlayer> getInternalPlayer() {
return CoreProxy.proxy.getBuildCraftPlayer((WorldServer) worldObj, xCoord, yCoord + 1, zCoord);
}
@Override
public void writeToNBT(NBTTagCompound data) {
super.writeToNBT(data);
craftingSlots.writeToNBT(data, "craftingSlots");
}
@Override
public void readFromNBT(NBTTagCompound data) {
super.readFromNBT(data);
if (data.hasKey("StorageSlots")) {
inv.readFromNBT(data, "StorageSlots");
}
if (data.hasKey("items")) {
craftingSlots.readFromNBT(data);
} else {
craftingSlots.readFromNBT(data, "craftingSlots");
}
}
@Override
public int getSizeInventory() {
return 24;
}
@Override
public String getInventoryName() {
return StringUtils.localize("tile.assemblyWorkbenchBlock.name");
}
@Override
public void markDirty() {
super.markDirty();
craftable = craftResult.getStackInSlot(0) != null;
}
@Override
public int getRequiredEnergy() {
return craftResult.getStackInSlot(0) != null ? REQUIRED_POWER : 0;
}
@Override
public int getProgressScaled(int i) {
return (getEnergy() * i) / REQUIRED_POWER;
}
@Override
public void invalidate() {
super.invalidate();
cache = null;
}
@Override
public void updateEntity() {
super.updateEntity();
if (worldObj.isRemote) {
return;
}
if (internalInventoryCrafting == null) {
internalInventoryCrafting = new InternalInventoryCrafting();
craftSlot = new SlotCrafting(getInternalPlayer().get(), internalInventoryCrafting, craftResult, 0, 0, 0);
updateRecipe();
}
if (worldObj.isRemote) {
return;
}
if (lastMode == IControllable.Mode.Off) {
return;
}
updateRecipe();
searchNeighborsForIngredients();
locateAndBindIngredients();
updateRecipeOutputDisplay();
justCrafted = false;
if (canCraftAndOutput()) {
if (getEnergy() >= getRequiredEnergy()) {
craftItem();
justCrafted = true;
}
} else {
craftable = false;
internalInventoryCrafting.tempStacks = null;
internalInventoryCrafting.hitCount = null;
setEnergy(0);
}
}
private boolean canCraftAndOutput() {
if (!hasIngredients()) {
return false;
}
ItemStack output = getRecipeOutput();
if (output == null) {
return false;
}
return InvUtils.isRoomForStack(output, ForgeDirection.UP, invOutput);
}
private void locateAndBindIngredients() {
internalInventoryCrafting.tempStacks = new InventoryCopy(inv).getItemStacks();
internalInventoryCrafting.hitCount = new int[internalInventoryCrafting.tempStacks.length];
ItemStack[] inputSlots = internalInventoryCrafting.tempStacks;
for (int gridSlot = 0; gridSlot < craftingSlots.getSizeInventory(); gridSlot++) {
internalInventoryCrafting.bindings[gridSlot] = -1;
if (craftingSlots.getStackInSlot(gridSlot) == null) {
continue;
}
boolean foundMatch = false;
for (int inputSlot = 0; inputSlot < inputSlots.length; inputSlot++) {
if (!isMatchingIngredient(gridSlot, inputSlot)) {
continue;
}
if (internalInventoryCrafting.hitCount[inputSlot] < inputSlots[inputSlot].stackSize
&& internalInventoryCrafting.hitCount[inputSlot] < inputSlots[inputSlot].getMaxStackSize()) {
internalInventoryCrafting.bindings[gridSlot] = inputSlot;
internalInventoryCrafting.hitCount[inputSlot]++;
foundMatch = true;
break;
}
}
if (!foundMatch) {
return;
}
}
}
private boolean isMatchingIngredient(int gridSlot, int inputSlot) {
ItemStack inputStack = internalInventoryCrafting.tempStacks[inputSlot];
if (inputStack == null) {
return false;
} else if (StackHelper.isMatchingItem(craftingSlots.getStackInSlot(gridSlot), inputStack, true, false)) {
return true;
} else {
return StackHelper.isCraftingEquivalent(craftingSlots.oreIDs[gridSlot], inputStack);
}
}
private boolean hasIngredients() {
return currentRecipe != null && currentRecipe.matches(internalInventoryCrafting, worldObj);
}
private void craftItem() {
EntityPlayer internalPlayer = getInternalPlayer().get();
ItemStack recipeOutput = getRecipeOutput();
craftSlot.onPickupFromSlot(internalPlayer, recipeOutput);
ItemStack[] tempStorage = internalInventoryCrafting.tempStacks;
for (int i = 0; i < tempStorage.length; i++) {
if (tempStorage[i] != null && tempStorage[i].stackSize <= 0) {
tempStorage[i] = null;
}
inv.getItemStacks()[i] = tempStorage[i];
}
subtractEnergy(getRequiredEnergy());
List<ItemStack> outputs = Lists.newArrayList(recipeOutput.copy());
for (int i = 0; i < internalPlayer.inventory.mainInventory.length; i++) {
if (internalPlayer.inventory.mainInventory[i] != null) {
outputs.add(internalPlayer.inventory.mainInventory[i]);
internalPlayer.inventory.mainInventory[i] = null;
}
}
for (ItemStack output : outputs) {
output.stackSize -= Transactor.getTransactorFor(invOutput).add(output, ForgeDirection.UP, true).stackSize;
if (output.stackSize > 0) {
output.stackSize -= Utils.addToRandomInventoryAround(worldObj, xCoord, yCoord, zCoord, output);
}
if (output.stackSize > 0) {
InvUtils.dropItems(worldObj, output, xCoord, yCoord + 1, zCoord);
}
}
}
private void searchNeighborsForIngredients() {
if (cache == null) {
cache = TileBuffer.makeBuffer(worldObj, xCoord, yCoord, zCoord, false);
}
for (IInvSlot slot : InventoryIterator.getIterable(craftingSlots, ForgeDirection.UP)) {
ItemStack ingred = slot.getStackInSlot();
if (ingred == null) {
continue;
}
IStackFilter filter = new CraftingFilter(ingred);
if (InvUtils.countItems(invInput, ForgeDirection.UP, filter) < InvUtils.countItems(craftingSlots, ForgeDirection.UP, filter)) {
for (ForgeDirection side : SEARCH_SIDES) {
TileEntity tile = cache[side.ordinal()].getTile();
if (tile instanceof IInventory) {
IInventory inv = InvUtils.getInventory((IInventory) tile);
ItemStack result = InvUtils.moveOneItem(inv, side.getOpposite(), invInput, side, filter);
if (result != null) {
return;
}
}
}
}
}
}
public void updateCraftingMatrix(int slot, ItemStack stack) {
craftingSlots.setInventorySlotContents(slot, stack);
updateRecipe();
if (worldObj.isRemote) {
PacketSlotChange packet = new PacketSlotChange(PacketIds.ADVANCED_WORKBENCH_SETSLOT, xCoord, yCoord, zCoord, slot, stack);
BuildCraftSilicon.instance.sendToServer(packet);
}
}
private void updateRecipe() {
if (internalInventoryCrafting == null) {
return;
}
internalInventoryCrafting.recipeUpdate(true);
if (this.currentRecipe == null || !this.currentRecipe.matches(internalInventoryCrafting, worldObj)) {
currentRecipe = CraftingHelper.findMatchingRecipe(internalInventoryCrafting, worldObj);
}
internalInventoryCrafting.recipeUpdate(false);
markDirty();
}
private void updateRecipeOutputDisplay() {
if (internalInventoryCrafting == null || currentRecipe == null) {
craftResult.setInventorySlotContents(0, null);
return;
}
ItemStack resultStack = getRecipeOutput();
if (resultStack == null) {
internalInventoryCrafting.recipeUpdate(true);
resultStack = getRecipeOutput();
internalInventoryCrafting.recipeUpdate(false);
}
craftResult.setInventorySlotContents(0, resultStack);
markDirty();
}
private ItemStack getRecipeOutput() {
if (internalInventoryCrafting == null || currentRecipe == null) {
return null;
} else {
return currentRecipe.getCraftingResult(internalInventoryCrafting);
}
}
public IInventory getCraftingSlots() {
return craftingSlots;
}
public IInventory getOutputSlot() {
return craftResult;
}
@Override
public boolean canCraft() {
return craftable && !justCrafted && lastMode != IControllable.Mode.Off;
}
@Override
public boolean hasWork() {
return requiresLaserEnergy() && super.hasWork();
}
@Override
public int[] getAccessibleSlotsFromSide(int side) {
return SLOTS;
}
@Override
public boolean canInsertItem(int slot, ItemStack stack, int side) {
return isItemValidForSlot(slot, stack);
}
@Override
public boolean canExtractItem(int slot, ItemStack stack, int side) {
return slot >= 15;
}
@Override
public boolean isItemValidForSlot(int slot, ItemStack stack) {
return slot < 15;
}
@Override
public boolean hasCustomInventoryName() {
return false;
}
}