package mekanism.common.multiblock; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import mekanism.api.Coord4D; import mekanism.common.tile.TileEntityMultiblock; import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; public abstract class UpdateProtocol<T extends SynchronizedData<T>> { /** The multiblock nodes that have already been iterated over. */ public Set<Coord4D> iteratedNodes = new HashSet<Coord4D>(); public Set<Coord4D> innerNodes = new HashSet<Coord4D>(); /** The structures found, all connected by some nodes to the pointer. */ public T structureFound = null; /** The original block the calculation is getting run from. */ public TileEntityMultiblock<T> pointer; public UpdateProtocol(TileEntityMultiblock<T> tileEntity) { pointer = tileEntity; } /** * Recursively loops through each node connected to the given TileEntity. * @param tile - the TileEntity to loop over */ public void loopThrough(Coord4D coord) { World worldObj = pointer.getWorld(); int origX = coord.xCoord, origY = coord.yCoord, origZ = coord.zCoord; boolean isCorner = true; boolean isHollow = true; boolean rightBlocks = true; boolean rightFrame = true; Set<Coord4D> locations = new HashSet<Coord4D>(); int xmin = 0, xmax = 0, ymin = 0, ymax = 0, zmin = 0, zmax = 0; int x = 0, y = 0, z = 0; int volume = 0; if((isViableNode(origX + 1, origY, origZ) && isViableNode(origX - 1, origY, origZ)) || (isViableNode(origX, origY + 1, origZ) && isViableNode(origX, origY - 1, origZ)) || (isViableNode(origX, origY, origZ + 1) && isViableNode(origX, origY, origZ - 1))) { isCorner = false; } if(isCorner) { if(isViableNode(origX+1, origY, origZ)) { xmin = 0; while(isViableNode(origX+x+1, origY, origZ)) { x++; } xmax = x; } else { xmax = 0; while(isViableNode(origX+x-1, origY, origZ)) { x--; } xmin = x; } if(isViableNode(origX, origY+1, origZ)) { ymin = 0; while(isViableNode(origX, origY+y+1, origZ)) { y++; } ymax = y; } else { ymax = 0; while(isViableNode(origX, origY+y-1 ,origZ)) { y--; } ymin = y; } if(isViableNode(origX, origY, origZ+1)) { zmin = 0; while(isViableNode(origX, origY, origZ+z+1)) { z++; } zmax = z; } else { zmax = 0; while(isViableNode(origX, origY, origZ+z-1)) { z--; } zmin = z; } for(x = xmin; x <= xmax; x++) { for(y = ymin; y <= ymax; y++) { for(z = zmin; z <= zmax; z++) { if(x == xmin || x == xmax || y == ymin || y == ymax || z == zmin || z == zmax) { if(!isViableNode(origX+x, origY+y, origZ+z)) { rightBlocks = false; break; } else if(isFrame(coord.translate(x, y, z), origX+xmin, origX+xmax, origY+ymin, origY+ymax, origZ+zmin, origZ+zmax) && !isValidFrame(origX+x, origY+y, origZ+z)) { rightFrame = false; break; } else { locations.add(coord.translate(x, y, z)); } } else { if(!isValidInnerNode(origX+x, origY+y, origZ+z)) { isHollow = false; break; } else { if(!isAir(origX+x, origY+y, origZ+z)) { innerNodes.add(new Coord4D(origX+x, origY+y, origZ+z, pointer.getWorld().provider.getDimension())); } } volume++; } } if(!isHollow || !rightBlocks || !rightFrame) { break; } } if(!isHollow || !rightBlocks || !rightFrame) { break; } } } volume += locations.size(); if(Math.abs(xmax-xmin)+1 <= 18 && Math.abs(ymax-ymin)+1 <= 18 && Math.abs(zmax-zmin)+1 <= 18) { if(rightBlocks && rightFrame && isHollow && isCorner) { T structure = getNewStructure(); structure.locations = locations; structure.volLength = Math.abs(xmax-xmin)+1; structure.volHeight = Math.abs(ymax-ymin)+1; structure.volWidth = Math.abs(zmax-zmin)+1; structure.volume = structure.volLength*structure.volHeight*structure.volWidth; structure.renderLocation = coord.translate(0, 1, 0); structure.minLocation = coord.translate(xmin, ymin, zmin); structure.maxLocation = coord.translate(xmax, ymax, zmax); if(structure.volLength >= 3 && structure.volHeight >= 3 && structure.volWidth >= 3) { onStructureCreated(structure, origX, origY, origZ, xmin, xmax, ymin, ymax, zmin, zmax); if(structure.locations.contains(Coord4D.get(pointer)) && isCorrectCorner(coord, origX+xmin, origY+ymin, origZ+zmin)) { if(canForm(structure)) { structureFound = structure; return; } } } } } innerNodes.clear(); iteratedNodes.add(coord); if(iteratedNodes.size() > 2048) { return; } for(EnumFacing side : EnumFacing.VALUES) { Coord4D sideCoord = coord.offset(side); if(isViableNode(sideCoord.getPos())) { if(!iteratedNodes.contains(sideCoord)) { loopThrough(sideCoord); } } } } protected boolean canForm(T structure) { return true; } public EnumFacing getSide(Coord4D obj, int xmin, int xmax, int ymin, int ymax, int zmin, int zmax) { if(obj.xCoord == xmin) { return EnumFacing.WEST; } else if(obj.xCoord == xmax) { return EnumFacing.EAST; } else if(obj.yCoord == ymin) { return EnumFacing.DOWN; } else if(obj.yCoord == ymax) { return EnumFacing.UP; } else if(obj.zCoord == zmin) { return EnumFacing.NORTH; } else if(obj.zCoord == zmax) { return EnumFacing.SOUTH; } return null; } /** * Whether or not the block at the specified location is an air block. * @param x - x coordinate * @param y - y coordinate * @param z - z coordinate * @return */ protected boolean isAir(int x, int y, int z) { return pointer.getWorld().isAirBlock(new BlockPos(x, y, z)); } protected boolean isValidInnerNode(int x, int y, int z) { return isAir(x, y, z); } /** * Whether or not the block at the specified location is a viable node for a multiblock structure. * @param x - x coordinate * @param y - y coordinate * @param z - z coordinate * @return */ public boolean isViableNode(int x, int y, int z) { TileEntity tile = new Coord4D(x, y, z, pointer.getWorld().provider.getDimension()).getTileEntity(pointer.getWorld()); if(tile instanceof IStructuralMultiblock) { if(((IStructuralMultiblock)tile).canInterface(pointer)) { return true; } } if(MultiblockManager.areEqual(tile, pointer)) { return true; } return false; } /** * Whether or not the block at the specified location is a viable node for a multiblock structure. * @param pos - coordinates * @return */ public boolean isViableNode(BlockPos pos) { TileEntity tile = new Coord4D(pos, pointer.getWorld()).getTileEntity(pointer.getWorld()); if(tile == null || !tile.hasWorldObj() || tile.isInvalid()) { return false; } if(tile instanceof IStructuralMultiblock) { if(((IStructuralMultiblock)tile).canInterface(pointer)) { return true; } } if(MultiblockManager.areEqual(tile, pointer)) { return true; } return false; } /** * If the block at the specified location is on the minimum of all angles of this multiblock structure, and the one to use for the * actual calculation. * @param obj - location to check * @param xmin - minimum x value * @param ymin - minimum y value * @param zmin - minimum z value * @return */ private boolean isCorrectCorner(Coord4D obj, int xmin, int ymin, int zmin) { if(obj.xCoord == xmin && obj.yCoord == ymin && obj.zCoord == zmin) { return true; } return false; } /** * Whether or not the block at the specified location is considered a frame on the multiblock structure. * @param obj - location to check * @param xmin - minimum x value * @param xmax - maximum x value * @param ymin - minimum y value * @param ymax - maximum y value * @param zmin - minimum z value * @param zmax - maximum z value * @return */ private boolean isFrame(Coord4D obj, int xmin, int xmax, int ymin, int ymax, int zmin, int zmax) { if(obj.xCoord == xmin && obj.yCoord == ymin) return true; if(obj.xCoord == xmax && obj.yCoord == ymin) return true; if(obj.xCoord == xmin && obj.yCoord == ymax) return true; if(obj.xCoord == xmax && obj.yCoord == ymax) return true; if(obj.xCoord == xmin && obj.zCoord == zmin) return true; if(obj.xCoord == xmax && obj.zCoord == zmin) return true; if(obj.xCoord == xmin && obj.zCoord == zmax) return true; if(obj.xCoord == xmax && obj.zCoord == zmax) return true; if(obj.yCoord == ymin && obj.zCoord == zmin) return true; if(obj.yCoord == ymax && obj.zCoord == zmin) return true; if(obj.yCoord == ymin && obj.zCoord == zmax) return true; if(obj.yCoord == ymax && obj.zCoord == zmax) return true; return false; } /** * Whether or not the block at the specified location serves as a frame for a multiblock structure. * @param x - x coordinate * @param y - y coordinate * @param z - z coordinate * @return */ protected abstract boolean isValidFrame(int x, int y, int z); protected abstract MultiblockCache<T> getNewCache(); protected abstract T getNewStructure(); protected abstract MultiblockManager<T> getManager(); protected abstract void mergeCaches(List<ItemStack> rejectedItems, MultiblockCache<T> cache, MultiblockCache<T> merge); protected void onFormed() { for(Coord4D coord : structureFound.internalLocations) { TileEntity tile = coord.getTileEntity(pointer.getWorld()); if(tile instanceof TileEntityInternalMultiblock) { ((TileEntityInternalMultiblock)tile).setMultiblock(structureFound.inventoryID); } } } protected void onStructureCreated(T structure, int origX, int origY, int origZ, int xmin, int xmax, int ymin, int ymax, int zmin, int zmax) {} public void onStructureDestroyed(T structure) { for(Coord4D coord : structure.internalLocations) { TileEntity tile = coord.getTileEntity(pointer.getWorld()); if(tile instanceof TileEntityInternalMultiblock) { ((TileEntityInternalMultiblock)tile).setMultiblock(null); } } } public void killInnerNode(Coord4D coord) { TileEntity tile = coord.getTileEntity(pointer.getWorld()); if(tile instanceof TileEntityInternalMultiblock) { ((TileEntityInternalMultiblock)tile).setMultiblock(null); } } /** * Runs the protocol and updates all nodes that make a part of the multiblock. */ public void doUpdate() { loopThrough(Coord4D.get(pointer)); if(structureFound != null) { for(Coord4D coord : iteratedNodes) { if(!structureFound.locations.contains(coord)) { for(Coord4D newCoord : iteratedNodes) { TileEntity tile = newCoord.getTileEntity(pointer.getWorld()); if(tile instanceof TileEntityMultiblock) { ((TileEntityMultiblock)tile).structure = null; } else if(tile instanceof IStructuralMultiblock) { ((IStructuralMultiblock)tile).setController(null); } } for(Coord4D newCoord : innerNodes) { killInnerNode(newCoord); } return; } } List<String> idsFound = new ArrayList<String>(); String idToUse = null; for(Coord4D obj : structureFound.locations) { TileEntity tileEntity = obj.getTileEntity(pointer.getWorld()); if(tileEntity instanceof TileEntityMultiblock && ((TileEntityMultiblock)tileEntity).cachedID != null) { idsFound.add(((TileEntityMultiblock)tileEntity).cachedID); } } MultiblockCache<T> cache = getNewCache(); List<ItemStack> rejectedItems = new ArrayList<ItemStack>(); if(!idsFound.isEmpty()) { for(String id : idsFound) { if(getManager().inventories.get(id) != null) { if(cache == null) { cache = getManager().pullInventory(pointer.getWorld(), id); } else { mergeCaches(rejectedItems, cache, getManager().pullInventory(pointer.getWorld(), id)); } idToUse = id; } } } else { idToUse = getManager().getUniqueInventoryID(); } //TODO someday: drop all items in rejectedItems //TODO seriously this needs to happen soon //TODO perhaps drop from pointer? cache.apply((T)structureFound); structureFound.inventoryID = idToUse; onFormed(); List<IStructuralMultiblock> structures = new ArrayList<IStructuralMultiblock>(); Coord4D toUse = null; for(Coord4D obj : structureFound.locations) { TileEntity tileEntity = obj.getTileEntity(pointer.getWorld()); if(tileEntity instanceof TileEntityMultiblock) { ((TileEntityMultiblock<T>)tileEntity).structure = structureFound; if(toUse == null) { toUse = obj; } } else if(tileEntity instanceof IStructuralMultiblock) { structures.add((IStructuralMultiblock)tileEntity); } } //Remove all structural multiblocks from locations, set controllers for(IStructuralMultiblock node : structures) { node.setController(toUse); structureFound.locations.remove(Coord4D.get((TileEntity)node)); } } else { for(Coord4D coord : iteratedNodes) { TileEntity tile = coord.getTileEntity(pointer.getWorld()); if(tile instanceof TileEntityMultiblock) { TileEntityMultiblock<T> tileEntity = (TileEntityMultiblock<T>)tile; if(tileEntity.structure != null && !tileEntity.structure.destroyed) { onStructureDestroyed(tileEntity.structure); tileEntity.structure.destroyed = true; } tileEntity.structure = null; } else if(tile instanceof IStructuralMultiblock) { ((IStructuralMultiblock)tile).setController(null); } } for(Coord4D coord : innerNodes) { killInnerNode(coord); } } } public class NodeCounter { public Set<Coord4D> iterated = new HashSet<Coord4D>(); public NodeChecker checker; public NodeCounter(NodeChecker c) { checker = c; } public void loop(Coord4D pos) { iterated.add(pos); if(!checker.shouldContinue(iterated.size())) { return; } for(EnumFacing side : EnumFacing.VALUES) { Coord4D coord = pos.offset(side); if(!iterated.contains(coord) && checker.isValid(coord)) { loop(coord); } } } public int calculate(Coord4D coord) { if(!checker.isValid(coord)) { return 0; } loop(coord); return iterated.size(); } } public static abstract class NodeChecker { public abstract boolean isValid(final Coord4D coord); public boolean shouldContinue(int iterated) { return true; } } }