/** * Copyright (c) 2011-2015, SpaceToad and the BuildCraft Team * http://www.mod-buildcraft.com * <p/> * 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.transport; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import org.apache.logging.log4j.Level; import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; 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.Constants; import net.minecraftforge.common.util.ForgeDirection; import buildcraft.BuildCraftTransport; import buildcraft.api.core.BCLog; import buildcraft.api.core.Position; import buildcraft.api.tiles.IDebuggable; import buildcraft.api.transport.IPipeTile; import buildcraft.core.DefaultProps; import buildcraft.core.lib.inventory.Transactor; import buildcraft.core.lib.utils.BlockUtils; import buildcraft.core.lib.utils.MathUtils; import buildcraft.transport.network.PacketPipeTransportItemStackRequest; import buildcraft.transport.network.PacketPipeTransportTraveler; import buildcraft.transport.pipes.events.PipeEventItem; import buildcraft.transport.utils.TransportUtils; public class PipeTransportItems extends PipeTransport implements IDebuggable { public static final int MAX_PIPE_STACKS = 64; public static final int MAX_PIPE_ITEMS = 1024; public boolean allowBouncing = false; public final TravelerSet items = new TravelerSet(this); @Override public IPipeTile.PipeType getPipeType() { return IPipeTile.PipeType.ITEM; } public void readjustSpeed(TravelingItem item) { PipeEventItem.AdjustSpeed event = new PipeEventItem.AdjustSpeed(container.pipe, item); container.pipe.eventBus.handleEvent(PipeEventItem.AdjustSpeed.class, event); if (!event.handled) { defaultReadjustSpeed(item, event.slowdownAmount); } } protected void defaultReadjustSpeed(TravelingItem item, float slowdownAmount) { float speed = item.getSpeed(); if (speed > TransportConstants.PIPE_MAX_SPEED) { speed = TransportConstants.PIPE_MAX_SPEED; } if (speed > TransportConstants.PIPE_MIN_SPEED) { speed -= slowdownAmount; } if (speed < TransportConstants.PIPE_MIN_SPEED) { speed = TransportConstants.PIPE_MIN_SPEED; } item.setSpeed(speed); } private void readjustPosition(TravelingItem item) { double x = MathUtils.clamp(item.xCoord, container.xCoord + 0.01, container.xCoord + 0.99); double y = MathUtils.clamp(item.yCoord, container.yCoord + 0.01, container.yCoord + 0.99); double z = MathUtils.clamp(item.zCoord, container.zCoord + 0.01, container.zCoord + 0.99); if (item.input != ForgeDirection.UP && item.input != ForgeDirection.DOWN) { y = container.yCoord + TransportUtils.getPipeFloorOf(item.getItemStack()); } item.setPosition(x, y, z); } public void injectItem(TravelingItem item, ForgeDirection inputOrientation) { if (item.isCorrupted()) { // Safe guard - if for any reason the item is corrupted at this // stage, avoid adding it to the pipe to avoid further exceptions. return; } item.reset(); item.input = inputOrientation; readjustSpeed(item); readjustPosition(item); PipeEventItem.Entered event = new PipeEventItem.Entered(container.pipe, item); container.pipe.eventBus.handleEvent(PipeEventItem.Entered.class, event); if (event.cancelled) { return; } if (!container.getWorldObj().isRemote) { item.output = resolveDestination(item); } items.add(item); if (!container.getWorldObj().isRemote) { sendTravelerPacket(item, false); int itemStackCount = getNumberOfStacks(); if (itemStackCount >= (MAX_PIPE_STACKS / 2)) { groupEntities(); itemStackCount = getNumberOfStacks(); } if (itemStackCount > MAX_PIPE_STACKS) { BCLog.logger.log(Level.WARN, String.format("Pipe exploded at %d,%d,%d because it had too many stacks: %d", container.xCoord, container.yCoord, container.zCoord, items.size())); destroyPipe(); return; } int numItems = getNumberOfItems(); if (numItems > MAX_PIPE_ITEMS) { BCLog.logger.log(Level.WARN, String.format("Pipe exploded at %d,%d,%d because it had too many items: %d", container.xCoord, container.yCoord, container.zCoord, numItems)); destroyPipe(); } } } private void destroyPipe() { BlockUtils.explodeBlock(container.getWorldObj(), container.xCoord, container.yCoord, container.zCoord); container.getWorldObj().setBlockToAir(container.xCoord, container.yCoord, container.zCoord); } /** * Bounces the item back into the pipe without changing the items map. * * @param item */ protected void reverseItem(TravelingItem item) { if (item.isCorrupted()) { // Safe guard - if for any reason the item is corrupted at this // stage, avoid adding it to the pipe to avoid further exceptions. return; } item.toCenter = true; item.input = item.output.getOpposite(); readjustSpeed(item); readjustPosition(item); PipeEventItem.Entered event = new PipeEventItem.Entered(container.pipe, item); container.pipe.eventBus.handleEvent(PipeEventItem.Entered.class, event); if (event.cancelled) { return; } if (!container.getWorldObj().isRemote) { item.output = resolveDestination(item); } items.unscheduleRemoval(item); if (!container.getWorldObj().isRemote) { sendTravelerPacket(item, true); } } public ForgeDirection resolveDestination(TravelingItem data) { List<ForgeDirection> validDestinations = getPossibleMovements(data); if (validDestinations.isEmpty()) { return ForgeDirection.UNKNOWN; } return validDestinations.get(0); } /** * Returns a list of all possible movements, that is to say adjacent * implementers of IPipeEntry or TileEntityChest. */ public List<ForgeDirection> getPossibleMovements(TravelingItem item) { LinkedList<ForgeDirection> result = new LinkedList<ForgeDirection>(); item.blacklist.add(item.input.getOpposite()); EnumSet<ForgeDirection> sides = EnumSet.complementOf(item.blacklist); sides.remove(ForgeDirection.UNKNOWN); for (ForgeDirection o : sides) { if (container.pipe.outputOpen(o) && canReceivePipeObjects(o, item)) { result.add(o); } } PipeEventItem.FindDest event = new PipeEventItem.FindDest(container.pipe, item, result); container.pipe.eventBus.handleEvent(PipeEventItem.FindDest.class, event); if (allowBouncing && result.isEmpty()) { if (canReceivePipeObjects(item.input.getOpposite(), item)) { result.add(item.input.getOpposite()); } } if (event.shuffle) { Collections.shuffle(result); } return result; } private boolean canReceivePipeObjects(ForgeDirection o, TravelingItem item) { TileEntity entity = container.getTile(o); if (!container.isPipeConnected(o)) { return false; } if (entity instanceof IPipeTile) { Pipe<?> pipe = (Pipe<?>) ((IPipeTile) entity).getPipe(); if (pipe == null || pipe.transport == null) { return false; } //return !pipe.pipe.isClosed() && pipe.pipe.transport instanceof PipeTransportItems; return pipe.inputOpen(o.getOpposite()) && pipe.transport instanceof PipeTransportItems; } else if (entity instanceof IInventory && item.getInsertionHandler().canInsertItem(item, (IInventory) entity)) { if (Transactor.getTransactorFor(entity).add(item.getItemStack(), o.getOpposite(), false).stackSize > 0) { return true; } } return false; } @Override public void updateEntity() { moveSolids(); } private void moveSolids() { items.flush(); items.iterating = true; for (TravelingItem item : items) { if (item.getContainer() != this.container) { items.scheduleRemoval(item); continue; } switch (item.toCenter ? item.input : item.output) { case DOWN: item.movePosition(0, -item.getSpeed(), 0); break; case UP: item.movePosition(0, item.getSpeed(), 0); break; case WEST: item.movePosition(-item.getSpeed(), 0, 0); break; case EAST: item.movePosition(item.getSpeed(), 0, 0); break; case NORTH: item.movePosition(0, 0, -item.getSpeed()); break; case SOUTH: item.movePosition(0, 0, item.getSpeed()); break; default: break; } if ((item.toCenter && middleReached(item)) || outOfBounds(item)) { if (item.isCorrupted()) { items.remove(item); continue; } item.toCenter = false; // Reajusting to the middle item.setPosition(container.xCoord + 0.5, container.yCoord + TransportUtils.getPipeFloorOf(item.getItemStack()), container.zCoord + 0.5); if (item.output == ForgeDirection.UNKNOWN) { if (items.scheduleRemoval(item)) { dropItem(item); } } else { PipeEventItem.ReachedCenter event = new PipeEventItem.ReachedCenter(container.pipe, item); container.pipe.eventBus.handleEvent(PipeEventItem.ReachedCenter.class, event); } } else if (!item.toCenter && endReached(item)) { if (item.isCorrupted()) { items.remove(item); continue; } if (item.output == ForgeDirection.UNKNOWN) { // TODO: Figure out why this is actually happening. items.scheduleRemoval(item); BCLog.logger.warn("Glitched item [Output direction UNKNOWN] removed from world @ " + container.x() + ", " + container.y() + ", " + container.z() + "!"); continue; } TileEntity tile = container.getTile(item.output, true); PipeEventItem.ReachedEnd event = new PipeEventItem.ReachedEnd(container.pipe, item, tile); container.pipe.eventBus.handleEvent(PipeEventItem.ReachedEnd.class, event); boolean handleItem = !event.handled; // If the item has not been scheduled to removal by the hook if (handleItem && items.scheduleRemoval(item)) { handleTileReached(item, tile); } } } items.iterating = false; items.flush(); } private boolean passToNextPipe(TravelingItem item, TileEntity tile) { if (tile instanceof IPipeTile) { Pipe<?> pipe = (Pipe<?>) ((IPipeTile) tile).getPipe(); if (BlockGenericPipe.isValid(pipe) && pipe.transport instanceof PipeTransportItems) { ((PipeTransportItems) pipe.transport).injectItem(item, item.output); return true; } } return false; } private void handleTileReached(TravelingItem item, TileEntity tile) { if (passToNextPipe(item, tile)) { // NOOP } else if (tile instanceof IInventory) { if (!container.getWorldObj().isRemote) { if (item.getInsertionHandler().canInsertItem(item, (IInventory) tile)) { ItemStack added = Transactor.getTransactorFor(tile).add(item.getItemStack(), item.output.getOpposite(), true); item.getItemStack().stackSize -= added.stackSize; } if (item.getItemStack().stackSize > 0) { reverseItem(item); } } } else { dropItem(item); } } private void dropItem(TravelingItem item) { if (container.getWorldObj().isRemote) { return; } PipeEventItem.DropItem event = new PipeEventItem.DropItem(container.pipe, item, item.toEntityItem()); container.pipe.eventBus.handleEvent(PipeEventItem.DropItem.class, event); if (event.entity == null) { return; } final EntityItem entity = event.entity; ForgeDirection direction = item.input; entity.setPosition(entity.posX + direction.offsetX * 0.5d, entity.posY + direction.offsetY * 0.5d, entity.posZ + direction.offsetZ * 0.5d); entity.motionX = direction.offsetX * item.speed * 5 + getWorld().rand.nextGaussian() * 0.1d; entity.motionY = direction.offsetY * item.speed * 5 + getWorld().rand.nextGaussian() * 0.1d; entity.motionZ = direction.offsetZ * item.speed * 5 + getWorld().rand.nextGaussian() * 0.1d; container.getWorldObj().spawnEntityInWorld(entity); } protected boolean middleReached(TravelingItem item) { float middleLimit = item.getSpeed() * 1.01F; return Math.abs(container.xCoord + 0.5 - item.xCoord) < middleLimit && Math.abs(container.yCoord + TransportUtils.getPipeFloorOf(item.getItemStack()) - item.yCoord) < middleLimit && Math .abs(container.zCoord + 0.5 - item.zCoord) < middleLimit; } protected boolean endReached(TravelingItem item) { return item.xCoord > container.xCoord + 1 || item.xCoord < container.xCoord || item.yCoord > container.yCoord + 1 || item.yCoord < container.yCoord || item.zCoord > container.zCoord + 1 || item.zCoord < container.zCoord; } protected boolean outOfBounds(TravelingItem item) { return item.xCoord > container.xCoord + 2 || item.xCoord < container.xCoord - 1 || item.yCoord > container.yCoord + 2 || item.yCoord < container.yCoord - 1 || item.zCoord > container.zCoord + 2 || item.zCoord < container.zCoord - 1; } public Position getPosition() { return new Position(container.xCoord, container.yCoord, container.zCoord); } @Override public void readFromNBT(NBTTagCompound nbt) { super.readFromNBT(nbt); NBTTagList nbttaglist = nbt.getTagList("travelingEntities", Constants.NBT.TAG_COMPOUND); for (int j = 0; j < nbttaglist.tagCount(); ++j) { try { NBTTagCompound dataTag = nbttaglist.getCompoundTagAt(j); TravelingItem item = TravelingItem.make(dataTag); if (item.isCorrupted()) { continue; } items.scheduleLoad(item); } catch (Throwable t) { // It may be the case that entities cannot be reloaded between // two versions - ignore these errors. } } } @Override public void writeToNBT(NBTTagCompound nbt) { super.writeToNBT(nbt); NBTTagList nbttaglist = new NBTTagList(); for (TravelingItem item : items) { NBTTagCompound dataTag = new NBTTagCompound(); nbttaglist.appendTag(dataTag); item.writeToNBT(dataTag); } nbt.setTag("travelingEntities", nbttaglist); } protected void doWork() { } /** * Handles a packet describing a stack of items inside a pipe. * * @param packet */ public void handleTravelerPacket(PacketPipeTransportTraveler packet) { TravelingItem item = TravelingItem.clientCache.get(packet.getTravelingEntityId()); if (item == null) { item = TravelingItem.make(packet.getTravelingEntityId()); } if (item.getContainer() != container) { items.add(item); } if (packet.forceStackRefresh() || item.getItemStack() == null) { BuildCraftTransport.instance.sendToServer(new PacketPipeTransportItemStackRequest(packet.getTravelingEntityId())); } item.setPosition(packet.getItemX(), packet.getItemY(), packet.getItemZ()); item.setSpeed(packet.getSpeed()); item.toCenter = true; item.input = packet.getInputOrientation(); item.output = packet.getOutputOrientation(); item.color = packet.getColor(); } private void sendTravelerPacket(TravelingItem data, boolean forceStackRefresh) { PacketPipeTransportTraveler packet = new PacketPipeTransportTraveler(data, forceStackRefresh); BuildCraftTransport.instance.sendToPlayers(packet, container.getWorldObj(), container.xCoord, container.yCoord, container.zCoord, DefaultProps.PIPE_CONTENTS_RENDER_DIST); } public int getNumberOfStacks() { int num = 0; for (TravelingItem item : items) { if (!item.ignoreWeight()) { num++; } } return num; } public int getNumberOfItems() { int num = 0; for (TravelingItem item : items) { if (!item.ignoreWeight() && item.getItemStack() != null) { num += item.getItemStack().stackSize; } } return num; } protected void neighborChange() { } @Override public boolean canPipeConnect(TileEntity tile, ForgeDirection side) { if (tile instanceof IPipeTile) { Pipe<?> pipe2 = (Pipe<?>) ((IPipeTile) tile).getPipe(); if (BlockGenericPipe.isValid(pipe2) && !(pipe2.transport instanceof PipeTransportItems)) { return false; } } if (tile instanceof ISidedInventory) { int[] slots = ((ISidedInventory) tile).getAccessibleSlotsFromSide(side.getOpposite().ordinal()); return slots != null && slots.length > 0; } return tile instanceof IPipeTile || (tile instanceof IInventory && ((IInventory) tile).getSizeInventory() > 0); } /** * Group all items that are similar, that is to say same dmg, same id, same * nbt and no contribution controlling them */ public void groupEntities() { for (TravelingItem item : items) { if (item.isCorrupted()) { continue; } for (TravelingItem otherItem : items) { if (item.tryMergeInto(otherItem)) { break; } } } } @Override public void dropContents() { groupEntities(); for (TravelingItem item : items) { if (!item.isCorrupted()) { container.pipe.dropItem(item.getItemStack()); } } items.clear(); } public List<ItemStack> getDroppedItems() { groupEntities(); ArrayList<ItemStack> itemsDropped = new ArrayList<ItemStack>(items.size()); for (TravelingItem item : items) { if (!item.isCorrupted()) { itemsDropped.add(item.getItemStack()); } } return itemsDropped; } @Override public boolean delveIntoUnloadedChunks() { return true; } @Override public void getDebugInfo(List<String> info, ForgeDirection side, ItemStack debugger, EntityPlayer player) { info.add("PipeTransportItems"); info.add("- Items: " + getNumberOfStacks() + "/" + MAX_PIPE_STACKS + " (" + getNumberOfItems() + "/" + MAX_PIPE_ITEMS + ")"); } }