package pneumaticCraft.common.tileentity; import java.util.List; import net.minecraft.inventory.IInventory; import net.minecraft.inventory.ISidedInventory; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.tileentity.TileEntity; import net.minecraftforge.common.util.ForgeDirection; import pneumaticCraft.api.recipe.AssemblyRecipe; import pneumaticCraft.common.network.DescSynced; import pneumaticCraft.common.network.LazySynced; import pneumaticCraft.common.util.IOHelper; import pneumaticCraft.common.util.PneumaticCraftUtils; import pneumaticCraft.lib.TileEntityConstants; public class TileEntityAssemblyIOUnit extends TileEntityAssemblyRobot{ @DescSynced public boolean shouldClawClose; @DescSynced @LazySynced public float clawProgress; public float oldClawProgress; @DescSynced public ItemStack[] inventory = new ItemStack[1]; private List<AssemblyRecipe> recipeList; private ItemStack searchedItemStack; private byte state = 0; private byte tickCounter = 0; private boolean hasSwitchedThisTick; private final static byte SLEEP_TICKS = 50; private final static byte STATE_IDLE = 0; private final static byte STATE_SEARCH_SRC = 1; private final static byte STATE_CLOSECLAW_AFTER_PICKUP = 5; private final static byte STATE_RESET_CLOSECLAW_AFTER_PICKUP = 20; private final static byte STATE_RESET_GOTO_IDLE = 26; private final static byte STATE_MAX = 127; @Override public void updateEntity(){ super.updateEntity(); hasSwitchedThisTick = false; if(worldObj.isRemote) { if(!isClawDone()) moveClaw(); } else { slowMode = false; switch(state){ case STATE_IDLE: break; case STATE_SEARCH_SRC: if(findPickupLocation()) state++; break; // rise to the right height for target location case 2: // for pickup case 7: // for drop-off case 22: // for reset if(hoverOverTarget()) state++; break; // turn and move to target case 3: // for pickup case 8: // for drop-off case 23: // for reset slowMode = true; if(gotoTarget()) state++; break; case 4: // pickup item - need to pick up before closeClaw; claw needs to know item size to 'grab' it! if(getItemFromCurrentDirection()) state++; break; case STATE_CLOSECLAW_AFTER_PICKUP: case STATE_RESET_CLOSECLAW_AFTER_PICKUP: if(closeClaw()) state++; break; case 6: case 21: if(findDropOffLocation()) state++; break; case 9: case 24: if(openClaw()) state++; break; case 10: // drop off item case 25: if(putItemToCurrentDirection()) state++; break; case 11: case STATE_RESET_GOTO_IDLE: if(gotoIdlePos()) state = 0; case STATE_MAX: // this will be set if we encounter an unknown state; prevents log-spam that would result from default-case break; default: System.out.printf("unexpected state: %d%n", state); state = STATE_MAX; break; } } } @Override public boolean reset(){ if(state >= STATE_RESET_CLOSECLAW_AFTER_PICKUP) return false; else if(inventory[0] != null) { state = STATE_RESET_CLOSECLAW_AFTER_PICKUP; return false; } else if(state == STATE_IDLE) { return true; } else { state = STATE_RESET_GOTO_IDLE; return isIdle(); } } /** * @return true if the controller should use air and display 'running' */ public boolean pickupItem(List<AssemblyRecipe> list){ recipeList = list; if(state == STATE_IDLE) state++; return state > STATE_IDLE && !isSleeping() // will not use air while waiting for item/inventory to be available && state < STATE_MAX; } private boolean gotoIdlePos(){ gotoHomePosition(); return isDoneInternal(); } private boolean findPickupLocation(){ if(shouldSleep()) return false; ForgeDirection[] inventoryDir = null; if(isImportUnit()) { searchedItemStack = null; if(recipeList != null) { for(AssemblyRecipe recipe : recipeList) { inventoryDir = getInventoryDirectionForItem(recipe.getInput()); if(inventoryDir != null) { searchedItemStack = recipe.getInput(); break; } } } } else { inventoryDir = getPlatformDirection(); } targetDirection = inventoryDir; if(targetDirection == null) { sleepBeforeNextSearch(); return false; } else return true; } private boolean isSleeping(){ return tickCounter > 0; } private boolean shouldSleep(){ if(tickCounter > 0 && tickCounter++ < SLEEP_TICKS) { return true; } else { tickCounter = 0; return false; } } private void sleepBeforeNextSearch(){ tickCounter = 1; } private boolean findDropOffLocation(){ if(shouldSleep()) return false; ForgeDirection[] inventoryDir = null; if(isImportUnit()) { inventoryDir = getPlatformDirection(); } else { inventoryDir = getExportLocationForItem(inventory[0]); } targetDirection = inventoryDir; if(targetDirection == null) { sleepBeforeNextSearch(); return false; } else return true; } private boolean getItemFromCurrentDirection(){ TileEntity tile = getTileEntityForCurrentDirection(); boolean extracted = false; /* * we must not .reset here because we might inadvertently change this.state right before this.state++ * if((tile == null) || !(tile instanceof IInventory)) // TE / inventory is gone reset(); */ if(isImportUnit()) { if(searchedItemStack == null) { // we don't know what we're supposed to pick up reset(); } else if(tile instanceof IInventory) { IInventory inv = (IInventory)tile; int oldStackSize = inventory[0] == null ? 0 : inventory[0].stackSize; for(int i = 0; i < inv.getSizeInventory(); i++) { if(inv.getStackInSlot(i) != null) { if(inventory[0] == null) { if(inv.getStackInSlot(i).isItemEqual(searchedItemStack)) { inventory[0] = inv.decrStackSize(i, 1); } } else if(inv.getStackInSlot(i).isItemEqual(inventory[0])) { inv.decrStackSize(i, 1); inventory[0].stackSize++; } extracted = (inventory[0] == null ? 0 : inventory[0].stackSize) == searchedItemStack.stackSize; // we might need to pickup more than 1 item if(extracted) break; } } if(oldStackSize == (inventory[0] == null ? 0 : inventory[0].stackSize)) // nothing picked up, search for different inventory state = STATE_SEARCH_SRC; } else state = STATE_SEARCH_SRC; // inventory gone } else { if(tile instanceof TileEntityAssemblyPlatform) { TileEntityAssemblyPlatform plat = (TileEntityAssemblyPlatform)tile; if(plat.openClaw()) { inventory[0] = plat.getHeldStack(); plat.setHeldStack(null); extracted = inventory[0] != null; if(!extracted) // something went wrong - either the platform is gone altogether, or the item is not there anymore state = STATE_SEARCH_SRC; } } } return extracted; } private boolean putItemToCurrentDirection(){ if(isImportUnit()) { TileEntity tile = getTileEntityForCurrentDirection(); if(tile instanceof TileEntityAssemblyPlatform) { TileEntityAssemblyPlatform plat = (TileEntityAssemblyPlatform)tile; if(inventory[0] == null) return plat.closeClaw(); if(plat.isIdle()) { plat.setHeldStack(inventory[0]); inventory[0] = null; return plat.closeClaw(); } } else repeatDropOffSearch(); // platform gone; close claw and search new drop-off-location } else { IInventory inv = getInventoryForCurrentDirection(); if(inv == null) repeatDropOffSearch(); // inventory gone; close claw and search new drop-off-location else { int startSize = inventory[0].stackSize; for(int i = 0; i < 6; i++) { inventory[0] = PneumaticCraftUtils.exportStackToInventory(inv, inventory[0], ForgeDirection.getOrientation(i)); if(inventory[0] == null) break; } if(inventory[0] == null || startSize != inventory[0].stackSize) sendDescriptionPacket(); // TODO - is this still needed? Shouldn't @DescSynced on inventory take care of this? if(inventory[0] != null && startSize == inventory[0].stackSize) repeatDropOffSearch(); // target-inventory full or unavailable } return inventory[0] == null; } return false; } private void repeatDropOffSearch(){ state = state >= STATE_RESET_CLOSECLAW_AFTER_PICKUP ? STATE_RESET_CLOSECLAW_AFTER_PICKUP : STATE_CLOSECLAW_AFTER_PICKUP; } private boolean closeClaw(){ shouldClawClose = true; return moveClaw(); } private boolean openClaw(){ shouldClawClose = false; return moveClaw(); } private boolean moveClaw(){ oldClawProgress = clawProgress; if(!shouldClawClose && clawProgress > 0F) { clawProgress = Math.max(clawProgress - TileEntityConstants.ASSEMBLY_IO_UNIT_CLAW_SPEED * speed, 0); } else if(shouldClawClose && clawProgress < 1F) { clawProgress = Math.min(clawProgress + TileEntityConstants.ASSEMBLY_IO_UNIT_CLAW_SPEED * speed, 1); } return isClawDone(); } private boolean isClawDone(){ // need to make sure that clawProgress and oldClawProgress are the same, or we will get rendering artifacts return clawProgress == oldClawProgress && clawProgress == (shouldClawClose ? 1F : 0F); } private boolean isImportUnit(){ return getBlockMetadata() == 0; } public IInventory getInventoryForCurrentDirection(){ TileEntity te = getTileEntityForCurrentDirection(); if(te instanceof IInventory) return (IInventory)te; return null; } public boolean switchMode(){ if(state <= STATE_SEARCH_SRC) { if(!hasSwitchedThisTick) { worldObj.setBlockMetadataWithNotify(xCoord, yCoord, zCoord, 1 - getBlockMetadata(), 3); hasSwitchedThisTick = true; } return true; } else { return false; } //PacketDispatcher.sendPacketToAllPlayers(getDescriptionPacket()); } @Override public void gotoHomePosition(){ super.gotoHomePosition(); if(isClawDone()) openClaw(); } @Override public boolean isIdle(){ return state == STATE_IDLE; } private boolean isDoneInternal(){ return super.isDoneMoving(); /* if(super.isDone()) { boolean searchDone = feedPlatformStep != 4 || searchedItemStack != null && inventory[0] != null && searchedItemStack.isItemEqual(inventory[0]) && inventory[0].stackSize == searchedItemStack.stackSize; return clawProgress == (shouldClawClose ? 1F : 0F) && searchDone; } else { return false; } */ } public ForgeDirection[] getInventoryDirectionForItem(ItemStack searchedItem){ if(searchedItem != null && (inventory[0] == null || inventory[0].isItemEqual(searchedItem))) { for(ForgeDirection dir : ForgeDirection.VALID_DIRECTIONS) { if(dir != ForgeDirection.UP && dir != ForgeDirection.DOWN) { TileEntity te = worldObj.getTileEntity(xCoord + dir.offsetX, yCoord, zCoord + dir.offsetZ); if(te instanceof IInventory) { if(IOHelper.extract(te, ForgeDirection.UP, searchedItem, true, true) != null) return new ForgeDirection[]{dir, ForgeDirection.UNKNOWN}; } } } if(canMoveToDiagonalNeighbours()) { for(ForgeDirection secDir : new ForgeDirection[]{ForgeDirection.WEST, ForgeDirection.EAST}) { for(ForgeDirection primDir : new ForgeDirection[]{ForgeDirection.NORTH, ForgeDirection.SOUTH}) { TileEntity te = worldObj.getTileEntity(xCoord + primDir.offsetX + secDir.offsetX, yCoord, zCoord + primDir.offsetZ + secDir.offsetZ); if(te instanceof IInventory) { if(IOHelper.extract(te, ForgeDirection.UP, searchedItem, true, true) != null) return new ForgeDirection[]{primDir, secDir}; } } } } } return null; } /** * Returns the inventory the IOUnit can point to for the given item. This should only be invoked when you're sure that there is * an accessible inventory with the item surrounding this TE. * @param searchedItem * @return */ public IInventory getInventory(ItemStack searchedItem){ ForgeDirection[] inventoryDir = getInventoryDirectionForItem(searchedItem); return (IInventory)worldObj.getTileEntity(xCoord + inventoryDir[0].offsetX + inventoryDir[1].offsetX, yCoord, zCoord + inventoryDir[0].offsetZ + inventoryDir[1].offsetZ); } public ForgeDirection[] getExportLocationForItem(ItemStack exportedItem){ if(exportedItem != null) { for(ForgeDirection dir : ForgeDirection.VALID_DIRECTIONS) { if(dir != ForgeDirection.UP && dir != ForgeDirection.DOWN) { TileEntity te = worldObj.getTileEntity(xCoord + dir.offsetX, yCoord, zCoord + dir.offsetZ); if(te instanceof IInventory) { int slot = getInventoryPlaceLocation(exportedItem, (IInventory)te); if(slot >= 0) return new ForgeDirection[]{dir, ForgeDirection.UNKNOWN}; } } } if(canMoveToDiagonalNeighbours()) { for(ForgeDirection secDir : new ForgeDirection[]{ForgeDirection.WEST, ForgeDirection.EAST}) { for(ForgeDirection primDir : new ForgeDirection[]{ForgeDirection.NORTH, ForgeDirection.SOUTH}) { TileEntity te = worldObj.getTileEntity(xCoord + primDir.offsetX + secDir.offsetX, yCoord, zCoord + primDir.offsetZ + secDir.offsetZ); if(te instanceof IInventory) { int slot = getInventoryPlaceLocation(exportedItem, (IInventory)te); if(slot >= 0) return new ForgeDirection[]{primDir, secDir}; } } } } } return null; } /** * * @param exported * @param inventory where the item is being tried to be placed in the top (respects ISidedInventory) * @return returns -1 when the item can't be placed / accessed */ public static int getInventoryPlaceLocation(ItemStack exportedItem, IInventory inventory){ if(inventory instanceof ISidedInventory) { int[] slotsInTop = ((ISidedInventory)inventory).getAccessibleSlotsFromSide(ForgeDirection.UP.ordinal()); for(int slot : slotsInTop) { if(inventory.isItemValidForSlot(slot, exportedItem)) { ItemStack stack = inventory.getStackInSlot(slot); if(stack == null || stack.isItemEqual(exportedItem) && stack.getMaxStackSize() > stack.stackSize) return slot; } } } else { for(int slot = 0; slot < inventory.getSizeInventory(); slot++) { if(inventory.isItemValidForSlot(slot, exportedItem)) { ItemStack stack = inventory.getStackInSlot(slot); if(stack == null || stack.isItemEqual(exportedItem) && stack.getMaxStackSize() > stack.stackSize) return slot; } } } return -1; } /** * Returns the inventory the IOUnit can point to export the given item. This should only be invoked when you're sure that there is * an accessible inventory with the item surrounding this TE. * @param exportedItem * @return */ public IInventory getExportInventory(ItemStack exportedItem){ ForgeDirection[] inventoryDir = getExportLocationForItem(exportedItem); return (IInventory)worldObj.getTileEntity(xCoord + inventoryDir[0].offsetX + inventoryDir[1].offsetX, yCoord, zCoord + inventoryDir[0].offsetZ + inventoryDir[1].offsetZ); } @Override public void readFromNBT(NBTTagCompound tag){ super.readFromNBT(tag); clawProgress = tag.getFloat("clawProgress"); shouldClawClose = tag.getBoolean("clawClosing"); state = tag.getByte("state"); // Read in the ItemStacks in the inventory from NBT NBTTagList tagList = tag.getTagList("Items", 10); inventory = new ItemStack[1]; for(int i = 0; i < tagList.tagCount(); ++i) { NBTTagCompound tagCompound = tagList.getCompoundTagAt(i); byte slot = tagCompound.getByte("Slot"); if(slot >= 0 && slot < inventory.length) { inventory[slot] = ItemStack.loadItemStackFromNBT(tagCompound); } } } @Override public void writeToNBT(NBTTagCompound tag){ super.writeToNBT(tag); tag.setFloat("clawProgress", clawProgress); tag.setBoolean("clawClosing", shouldClawClose); tag.setByte("state", state); // Write the ItemStacks in the inventory to NBT NBTTagList tagList = new NBTTagList(); for(int currentIndex = 0; currentIndex < inventory.length; ++currentIndex) { if(inventory[currentIndex] != null) { NBTTagCompound tagCompound = new NBTTagCompound(); tagCompound.setByte("Slot", (byte)currentIndex); inventory[currentIndex].writeToNBT(tagCompound); tagList.appendTag(tagCompound); } } tag.setTag("Items", tagList); } @Override public boolean canMoveToDiagonalNeighbours(){ return true; } }