package mekanism.common.content.transporter; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import mekanism.api.Coord4D; import mekanism.common.InventoryNetwork; import mekanism.common.InventoryNetwork.AcceptorData; import mekanism.common.base.ILogisticalTransporter; import mekanism.common.base.ITransporterTile; import mekanism.common.content.transporter.PathfinderCache.PathData; import mekanism.common.content.transporter.TransporterPathfinder.Pathfinder.DestChecker; import mekanism.common.content.transporter.TransporterStack.Path; import mekanism.common.tile.TileEntityLogisticalSorter; import mekanism.common.util.InventoryUtils; import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; import net.minecraft.world.World; import net.minecraftforge.common.util.ForgeDirection; public final class TransporterPathfinder { public static class IdlePath { public World worldObj; public Coord4D start; public TransporterStack transportStack; public IdlePath(World world, Coord4D obj, TransporterStack stack) { worldObj = world; start = obj; transportStack = stack; } public Destination find() { ArrayList<Coord4D> ret = new ArrayList<Coord4D>(); ret.add(start); if(transportStack.idleDir == ForgeDirection.UNKNOWN) { ForgeDirection newSide = findSide(); if(newSide == null) { return null; } transportStack.idleDir = newSide; loopSide(ret, newSide); return new Destination(ret, true, null, 0).setPathType(Path.NONE); } else { TileEntity tile = start.getFromSide(transportStack.idleDir).getTileEntity(worldObj); if(transportStack.canInsertToTransporter(tile, transportStack.idleDir)) { loopSide(ret, transportStack.idleDir); return new Destination(ret, true, null, 0).setPathType(Path.NONE); } else { Destination newPath = TransporterPathfinder.getNewBasePath((ILogisticalTransporter)start.getTileEntity(worldObj), transportStack, 0); if(newPath != null && TransporterManager.didEmit(transportStack.itemStack, newPath.rejected)) { transportStack.idleDir = ForgeDirection.UNKNOWN; newPath.setPathType(Path.DEST); return newPath; } else { ForgeDirection newSide = findSide(); if(newSide == null) { return null; } transportStack.idleDir = newSide; loopSide(ret, newSide); return new Destination(ret, true, null, 0).setPathType(Path.NONE); } } } } private void loopSide(List<Coord4D> list, ForgeDirection side) { int count = 1; while(true) { Coord4D coord = start.getFromSide(side, count); if(transportStack.canInsertToTransporter(coord.getTileEntity(worldObj), side)) { list.add(coord); count++; } else { break; } } } private ForgeDirection findSide() { if(transportStack.idleDir == ForgeDirection.UNKNOWN) { for(ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) { TileEntity tile = start.getFromSide(side).getTileEntity(worldObj); if(transportStack.canInsertToTransporter(tile, side)) { return side; } } } else { for(ForgeDirection side : EnumSet.complementOf(EnumSet.of(ForgeDirection.UNKNOWN, transportStack.idleDir.getOpposite()))) { TileEntity tile = start.getFromSide(side).getTileEntity(worldObj); if(transportStack.canInsertToTransporter(tile, side)) { return side; } } TileEntity tile = start.getFromSide(transportStack.idleDir.getOpposite()).getTileEntity(worldObj); if(transportStack.canInsertToTransporter(tile, transportStack.idleDir.getOpposite())) { return transportStack.idleDir.getOpposite(); } } return null; } } public static class Destination implements Comparable<Destination> { public List<Coord4D> path; public Path pathType; public ItemStack rejected; public double score; public Destination(List<Coord4D> list, boolean inv, ItemStack rejects, double gScore) { path = new ArrayList<>(list); if(inv) { Collections.reverse(path); } rejected = rejects; score = gScore; } public Destination setPathType(Path type) { pathType = type; return this; } public Destination calculateScore(World world) { score = 0; for(Coord4D location : path) { TileEntity tile = location.getTileEntity(world); if(tile instanceof ITransporterTile) { score += ((ITransporterTile)tile).getTransmitter().getCost(); } } return this; } @Override public int hashCode() { int code = 1; code = 31 * code + path.hashCode(); return code; } @Override public boolean equals(Object dest) { return dest instanceof Destination && ((Destination)dest).path.equals(path); } @Override public int compareTo(Destination dest) { if(score < dest.score) { return -1; } else if(score > dest.score) { return 1; } else { return path.size() - dest.path.size(); } } } public static List<Destination> getPaths(ILogisticalTransporter start, TransporterStack stack, int min) { InventoryNetwork network = start.getTransmitterNetwork(); List<AcceptorData> acceptors = network.calculateAcceptors(stack.itemStack, stack.color); List<Destination> paths = new ArrayList<Destination>(); for(AcceptorData entry : acceptors) { DestChecker checker = new DestChecker() { @Override public boolean isValid(TransporterStack stack, int dir, TileEntity tile) { return InventoryUtils.canInsert(tile, stack.color, stack.itemStack, dir, false); } }; Destination d = getPath(checker, entry.sides, start, entry.location, stack, entry.rejected, min); if(d != null) { paths.add(d); } } Collections.sort(paths); return paths; } public static Destination getPath(DestChecker checker, EnumSet<ForgeDirection> sides, ILogisticalTransporter start, Coord4D dest, TransporterStack stack, ItemStack rejects, int min) { List<Coord4D> test = PathfinderCache.getCache(start.coord(), dest, sides); if(test != null) { return new Destination(test, false, rejects, 0).calculateScore(start.world()); } Pathfinder p = new Pathfinder(checker, start.world(), dest, start.coord(), stack); if(p.getPath().size() >= 2) { if(TransporterManager.getToUse(stack.itemStack, rejects).stackSize >= min) { PathfinderCache.cachedPaths.put(new PathData(start.coord(), dest, p.side), p.getPath()); return new Destination(p.getPath(), false, rejects, p.finalScore); } } return null; } public static Destination getNewBasePath(ILogisticalTransporter start, TransporterStack stack, int min) { List<Destination> paths = getPaths(start, stack, min); if(paths.isEmpty()) { return null; } return paths.get(0); } public static Destination getNewRRPath(ILogisticalTransporter start, TransporterStack stack, TileEntityLogisticalSorter outputter, int min) { List<Destination> paths = getPaths(start, stack, min); Map<Coord4D, Destination> destPaths = new HashMap<Coord4D, Destination>(); for(Destination d : paths) { if(destPaths.get(d.path.get(0)) == null || destPaths.get(d.path.get(0)).path.size() < d.path.size()) { destPaths.put(d.path.get(0), d); } } List<Destination> dests = new ArrayList<Destination>(); dests.addAll(destPaths.values()); Collections.sort(dests); Destination closest = null; if(!dests.isEmpty()) { if(outputter.rrIndex <= dests.size()-1) { closest = dests.get(outputter.rrIndex); if(outputter.rrIndex == dests.size()-1) { outputter.rrIndex = 0; } else if(outputter.rrIndex < dests.size()-1) { outputter.rrIndex++; } } else { closest = dests.get(dests.size()-1); outputter.rrIndex = 0; } } if(closest == null) { return null; } return closest; } public static class Pathfinder { public final Set<Coord4D> openSet, closedSet; public final HashMap<Coord4D, Coord4D> navMap; public final HashMap<Coord4D, Double> gScore, fScore; public final Coord4D start; public final Coord4D finalNode; public final TransporterStack transportStack; public final DestChecker destChecker; public double finalScore; public ForgeDirection side; public ArrayList<Coord4D> results; private World worldObj; public Pathfinder(DestChecker checker, World world, Coord4D finishObj, Coord4D startObj, TransporterStack stack) { destChecker = checker; worldObj = world; finalNode = finishObj; start = startObj; transportStack = stack; openSet = new HashSet<Coord4D>(); closedSet = new HashSet<Coord4D>(); navMap = new HashMap<Coord4D, Coord4D>(); gScore = new HashMap<Coord4D, Double>(); fScore = new HashMap<Coord4D, Double>(); results = new ArrayList<Coord4D>(); find(start); } public boolean find(Coord4D start) { openSet.add(start); gScore.put(start, 0D); fScore.put(start, gScore.get(start) + getEstimate(start, finalNode)); int blockCount = 0; for(int i = 0; i < 6; i++) { ForgeDirection direction = ForgeDirection.getOrientation(i); Coord4D neighbor = start.getFromSide(direction); if(!transportStack.canInsertToTransporter(neighbor.getTileEntity(worldObj), direction) && (!neighbor.equals(finalNode) || !destChecker.isValid(transportStack, i, neighbor.getTileEntity(worldObj)))) { blockCount++; } } if(blockCount >= 6) { return false; } double maxSearchDistance = start.distanceTo(finalNode) * 2; while(!openSet.isEmpty()) { Coord4D currentNode = null; double lowestFScore = 0; for(Coord4D node : openSet) { if(currentNode == null || fScore.get(node) < lowestFScore) { currentNode = node; lowestFScore = fScore.get(node); } } if(currentNode == null && start.distanceTo(currentNode) > maxSearchDistance) { break; } openSet.remove(currentNode); closedSet.add(currentNode); for(ForgeDirection direction : ForgeDirection.VALID_DIRECTIONS) { Coord4D neighbor = currentNode.getFromSide(direction); if(transportStack.canInsertToTransporter(neighbor.getTileEntity(worldObj), direction)) { TileEntity tile = neighbor.getTileEntity(worldObj); double tentativeG = gScore.get(currentNode) + ((ITransporterTile)tile).getTransmitter().getCost(); if(closedSet.contains(neighbor)) { if(tentativeG >= gScore.get(neighbor)) { continue; } } if(!openSet.contains(neighbor) || tentativeG < gScore.get(neighbor)) { navMap.put(neighbor, currentNode); gScore.put(neighbor, tentativeG); fScore.put(neighbor, gScore.get(neighbor) + getEstimate(neighbor, finalNode)); openSet.add(neighbor); } } else if(neighbor.equals(finalNode) && destChecker.isValid(transportStack, direction.ordinal(), neighbor.getTileEntity(worldObj))) { side = direction; results = reconstructPath(navMap, currentNode); return true; } } } return false; } private ArrayList<Coord4D> reconstructPath(HashMap<Coord4D, Coord4D> naviMap, Coord4D currentNode) { ArrayList<Coord4D> path = new ArrayList<Coord4D>(); path.add(currentNode); if(naviMap.containsKey(currentNode)) { path.addAll(reconstructPath(naviMap, naviMap.get(currentNode))); } finalScore = gScore.get(currentNode) + currentNode.distanceTo(finalNode); return path; } public ArrayList<Coord4D> getPath() { ArrayList<Coord4D> path = new ArrayList<Coord4D>(); path.add(finalNode); path.addAll(results); return path; } private double getEstimate(Coord4D start, Coord4D target2) { return start.distanceTo(target2); } public static class DestChecker { public boolean isValid(TransporterStack stack, int side, TileEntity tile) { return false; } } } public static List<Coord4D> getIdlePath(ILogisticalTransporter start, TransporterStack stack) { if(stack.homeLocation != null) { DestChecker checker = new DestChecker() { @Override public boolean isValid(TransporterStack stack, int side, TileEntity tile) { return InventoryUtils.canInsert(tile, stack.color, stack.itemStack, side, true); } }; Pathfinder p = new Pathfinder(checker, start.world(), stack.homeLocation, start.coord(), stack); List<Coord4D> path = p.getPath(); if(path.size() >= 2) { stack.pathType = Path.HOME; return path; } else { stack.homeLocation = null; } } IdlePath d = new IdlePath(start.world(), start.coord(), stack); Destination dest = d.find(); if(dest == null) { return null; } stack.pathType = dest.pathType; return dest.path; } }