Now using brand new grate algorithm

This commit is contained in:
Calclavia 2014-02-23 13:39:44 +08:00
parent ff84c867b0
commit d6d028df70
3 changed files with 338 additions and 614 deletions

View file

@ -1,23 +0,0 @@
package resonantinduction.mechanical.fluid.transport;
import java.util.List;
import net.minecraftforge.fluids.IFluidHandler;
import universalelectricity.api.vector.Vector3;
import calclavia.lib.prefab.tile.IRotatable;
/**
* Interface to make or use the TileEntityDrain. This is mostly a dummy interface to help the
* construction pump use the TileEntity as the center of drain location
*
* The use of ITankContainer is optional but is need for the drain to be added to a Fluid Network
* Same goes for IRotatable but make sure to return direction as the direction the drain faces
*/
public interface IDrain extends IFluidHandler, IRotatable
{
/** Gets the list of fillable blocks */
public List<Vector3> getFillList();
/** Gets the list of drainable blocks */
public List<Vector3> getDrainList();
}

View file

@ -1,361 +0,0 @@
package resonantinduction.mechanical.fluid.transport;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.common.ForgeDirection;
import universalelectricity.api.vector.Vector3;
import calclavia.lib.utility.FluidUtility;
/**
* A simpler path Finder used to find drainable or fillable tiles
*
* @author DarkGuardsman
*/
public class LiquidPathFinder
{
/** Curent world this pathfinder will operate in */
private World world;
/** List of all nodes traveled by the path finder */
public Set<Vector3> nodeList = new HashSet<Vector3>();
/** List of all nodes that match the search parms */
public Set<Vector3> results = new HashSet<Vector3>();
public final List<Vector3> sortedResults = new ArrayList<Vector3>();
/** Are we looking for liquid fillable blocks */
private boolean fill = false;
/** priority search direction either up or down only */
private ForgeDirection priority;
/** Limit on the searched nodes per run */
private int resultLimit = 200;
private int resultsFound = 0;
private int resultRun = resultLimit;
private int runs = 0;
/** Start location of the pathfinder used for range calculations */
private Vector3 start;
/** Range to limit the search to */
private double range;
/** List of forgeDirection to use that are shuffled to prevent strait lines */
List<ForgeDirection> shuffledDirections = new ArrayList<ForgeDirection>();
public LiquidPathFinder(final World world, final int resultLimit, final double range)
{
this.range = range;
this.world = world;
if (fill)
{
priority = ForgeDirection.DOWN;
}
else
{
priority = ForgeDirection.UP;
}
this.resultLimit = resultLimit;
this.reset();
shuffledDirections.add(ForgeDirection.EAST);
shuffledDirections.add(ForgeDirection.WEST);
shuffledDirections.add(ForgeDirection.NORTH);
shuffledDirections.add(ForgeDirection.SOUTH);
}
public void addNode(Vector3 vec)
{
if (!this.nodeList.contains(vec))
{
this.nodeList.add(vec);
}
}
public void addResult(Vector3 vec)
{
if (!this.results.contains(vec))
{
this.resultsFound++;
this.results.add(vec);
}
}
/**
* Searches for nodes attached to the given node
*
* @return True on success finding, false on failure.
*/
public boolean findNodes(Vector3 node)
{
if (node == null)
{
return false;
}
this.addNode(node);
if (this.isValidResult(node))
{
this.addResult(node);
}
if (this.isDone(node.clone()))
{
return false;
}
if (find(this.priority, node.clone()))
{
return true;
}
Collections.shuffle(shuffledDirections);
Collections.shuffle(shuffledDirections);
for (ForgeDirection direction : shuffledDirections)
{
if (find(direction, node.clone()))
{
return true;
}
}
if (find(this.priority.getOpposite(), node.clone()))
{
return true;
}
return false;
}
/**
* Find all node attached to the origin mode in the given direction
*
* Note: Calls findNode if the next code is valid
*/
public boolean find(ForgeDirection direction, Vector3 origin)
{
this.runs++;
Vector3 vec = origin.clone().translate(direction);
double distance = vec.toVector2().distance(this.start.toVector2());
if (distance <= this.range && isValidNode(vec))
{
if (this.fill && FluidUtility.drainBlock(world, vec, false) != null || FluidUtility.isFillableFluid(world, vec))
{
for (ForgeDirection dir : ForgeDirection.VALID_DIRECTIONS)
{
Vector3 veb = vec.clone().translate(dir);
if (FluidUtility.isFillableBlock(world, veb))
{
this.addNode(veb);
if (this.isValidResult(veb))
{
this.addResult(veb);
}
}
}
}
if (this.findNodes(vec))
{
return true;
}
}
return false;
}
/** Checks to see if this node is valid to path find threw */
public boolean isValidNode(Vector3 pos)
{
if (pos == null)
{
return false;
}
/* Check if the chunk is loaded to prevent action outside of the loaded area */
Chunk chunk = this.world.getChunkFromBlockCoords(pos.intX(), pos.intZ());
if (chunk == null || !chunk.isChunkLoaded)
{
return false;
}
return FluidUtility.drainBlock(world, pos, false) != null || FluidUtility.isFillableFluid(world, pos);
}
public boolean isValidResult(Vector3 node)
{
if (this.fill)
{
return FluidUtility.isFillableBlock(world, node) || FluidUtility.isFillableFluid(world, node);
}
else
{
return FluidUtility.drainBlock(world, node, false) != null;
}
}
/** Checks to see if we are done pathfinding */
public boolean isDone(Vector3 vec)
{
if (this.runs > 1000)
{
return true;
}
if (this.resultsFound >= this.resultRun)
if (this.results.size() >= this.resultLimit || this.nodeList.size() >= 10000)
{
return true;
}
return false;
}
/** Called to execute the pathfinding operation. */
public LiquidPathFinder start(final Vector3 startNode, int runCount, final boolean fill)
{
this.start = startNode;
this.fill = fill;
this.runs = 0;
this.resultsFound = 0;
this.resultRun = runCount;
this.find(ForgeDirection.UNKNOWN, startNode);
this.refresh();
this.sortBlockList(start, results, fill);
return this;
}
public LiquidPathFinder reset()
{
this.nodeList.clear();
this.results.clear();
return this;
}
public LiquidPathFinder refresh()
{
Iterator<Vector3> it = this.nodeList.iterator();
while (it.hasNext())
{
Vector3 vec = it.next();
if (!this.isValidNode(vec))
{
it.remove();
}
if (this.isValidResult(vec))
{
this.addResult(vec);
}
}
it = this.results.iterator();
while (it.hasNext())
{
Vector3 vec = it.next();
if (!this.isValidResult(vec))
{
it.remove();
}
if (this.isValidNode(vec))
{
this.addNode(vec);
}
}
return this;
}
/**
* Used to sort a list of vector3 locations using the vector3's distance from one point and
* elevation in the y axis
*
* Sorting is ordered from the highest-furthest block and consequently will have its adjacent
* blocks as the "next blocks" to make sure the fluid can easily remove infinite fluids like
* water.
*
* @param start - start location to measure distance from
* @param results2 - list of vectors to sort
* @param prioritizeHighest - sort highest y value to the top.
*
* Note: Height takes priority over distance.
*/
public void sortBlockList(final Vector3 start, final Set<Vector3> set, final boolean prioritizeHighest)
{
try
{
sortedResults.clear();
sortedResults.addAll(set);
// The highest and furthest fluid block for drain. Closest fluid block for fill.
Vector3 bestFluid = null;
if (fill)
{
bestFluid = start;
}
else
{
for (Vector3 checkPos : set)
{
if (bestFluid == null)
{
bestFluid = checkPos;
}
/**
* We're draining, so we want the highest furthest block first.
*/
if (prioritizeHighest)
{
if (checkPos.y > bestFluid.y)
{
bestFluid = checkPos;
}
else if (checkPos.y == bestFluid.y && start.distance(checkPos) > start.distance(bestFluid))
{
bestFluid = checkPos;
}
}
else
{
/**
* We're filling, so prioritize the lowest block first.
*/
if (checkPos.y < bestFluid.y)
{
bestFluid = checkPos;
}
else if (start.distance(checkPos) > start.distance(bestFluid))
{
bestFluid = checkPos;
}
}
}
}
final Vector3 optimalFluid = bestFluid;
Collections.sort(sortedResults, new Comparator<Vector3>()
{
@Override
public int compare(Vector3 vecA, Vector3 vecB)
{
if (vecA.equals(vecB))
return 0;
return vecA.distance(optimalFluid) < vecB.distance(optimalFluid) ? -1 : 1;
}
});
}
catch (Exception e)
{
e.printStackTrace();
}
}
public LiquidPathFinder setWorld(World world2)
{
if (world2 != this.world)
{
this.reset();
this.world = world2;
}
return this;
}
}

View file

@ -1,62 +1,23 @@
package resonantinduction.mechanical.fluid.transport;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.PriorityQueue;
import net.minecraftforge.common.ForgeDirection;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidContainerRegistry;
import net.minecraftforge.fluids.FluidRegistry;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidTank;
import net.minecraftforge.fluids.FluidTankInfo;
import net.minecraftforge.fluids.IFluidHandler;
import universalelectricity.api.net.IUpdate;
import universalelectricity.api.vector.Vector3;
import universalelectricity.core.net.NetworkTickHandler;
import calclavia.lib.prefab.tile.TileAdvanced;
import calclavia.lib.utility.FluidUtility;
public class TileGrate extends TileAdvanced implements IFluidHandler, IDrain
public class TileGrate extends TileAdvanced implements IFluidHandler
{
public static final int MAX_FLUID_MODIFY_RATE = 50;
private long lastUseTime = 0;
private int currentWorldEdits = 0;
private LiquidPathFinder pathDrain;
private LiquidPathFinder pathFill;
public LiquidPathFinder getFillFinder()
{
if (pathFill == null)
{
pathFill = new LiquidPathFinder(this.worldObj, 100, 100);
}
return pathFill;
}
@Override
public List<Vector3> getFillList()
{
return this.getFillFinder().refresh().sortedResults;
}
public LiquidPathFinder getDrainFinder()
{
if (pathDrain == null)
{
pathDrain = new LiquidPathFinder(this.worldObj, 1000, 100);
}
return pathDrain;
}
@Override
public List<Vector3> getDrainList()
{
return getDrainFinder().refresh().sortedResults;
}
private GratePathfinder gratePath;
@Override
public boolean canUpdate()
@ -64,36 +25,10 @@ public class TileGrate extends TileAdvanced implements IFluidHandler, IDrain
return false;
}
public void update()
{
if (System.currentTimeMillis() - lastUseTime > 1000)
{
currentWorldEdits = 0;
lastUseTime = System.currentTimeMillis();
}
}
/**
* Updates the pathfinding operation.
*/
public void doPathfinding()
{
if (this.getDrainFinder().results.size() < TileGrate.MAX_FLUID_MODIFY_RATE + 10)
{
this.getDrainFinder().refresh().start(new Vector3(this).translate(this.getDirection()), TileGrate.MAX_FLUID_MODIFY_RATE, false);
}
if (this.getFillFinder().results.size() < TileGrate.MAX_FLUID_MODIFY_RATE + 10)
{
this.getFillFinder().refresh().start(new Vector3(this).translate(this.getDirection()), TileGrate.MAX_FLUID_MODIFY_RATE, true);
getFillFinder().results.add(new Vector3(this).translate(this.getDirection()));
}
}
@Override
public ForgeDirection getDirection()
{
return ForgeDirection.getOrientation(worldObj.getBlockMetadata(xCoord, yCoord, zCoord));
return ForgeDirection.getOrientation(getBlockMetadata());
}
@Override
@ -102,96 +37,36 @@ public class TileGrate extends TileAdvanced implements IFluidHandler, IDrain
this.worldObj.setBlockMetadataWithNotify(xCoord, yCoord, zCoord, direction.ordinal(), 3);
}
@Override
public FluidTankInfo[] getTankInfo(ForgeDirection from)
{
return null;
}
@Override
public boolean canFill(ForgeDirection from, Fluid fluid)
{
return getDirection() != from;
}
@Override
public boolean canDrain(ForgeDirection from, Fluid fluid)
{
return getDirection() != from;
}
@Override
public int fill(ForgeDirection from, FluidStack resource, boolean doFill)
{
update();
if (resource == null)
if (resource != null && resource.amount > 0)
{
return 0;
}
if (currentWorldEdits < MAX_FLUID_MODIFY_RATE)
{
int remainingVolume = resource.amount;
/* ID LIQUID BLOCK AND SET VARS FOR BLOCK PLACEMENT */
if (resource == null || resource.amount < FluidContainerRegistry.BUCKET_VOLUME)
if (gratePath == null)
{
return 0;
gratePath = new GratePathfinder(true);
gratePath.startFill(new Vector3(this), resource.fluidID);
}
List<Vector3> fluids = new ArrayList<Vector3>();
List<Vector3> blocks = new ArrayList<Vector3>();
List<Vector3> filled = new ArrayList<Vector3>();
if (getFillList() == null || getFillList().size() == 0)
{
doPathfinding();
}
/* Sort results out into two groups and clear the rest out of the result list */
Iterator<Vector3> it = getFillList().iterator();
while (it.hasNext())
{
Vector3 vec = it.next();
if (FluidUtility.isFillableFluid(worldObj, vec) && !fluids.contains(vec) && !blocks.contains(vec))
{
fluids.add(vec);
}
else if (FluidUtility.isFillableBlock(worldObj, vec) && !blocks.contains(vec) && !fluids.contains(vec))
{
blocks.add(vec);
}
else
{
it.remove();
}
}
/* Fill non-full fluids first */
for (Vector3 loc : fluids)
{
if (remainingVolume <= 0)
{
break;
}
if (FluidUtility.isFillableFluid(worldObj, loc))
{
remainingVolume -= FluidUtility.fillBlock(worldObj, loc, FluidUtility.getStack(resource, remainingVolume), doFill);
if (doFill)
{
filled.add(loc);
currentWorldEdits++;
}
}
}
/* Fill air or replaceable blocks after non-full fluids */
for (Vector3 loc : blocks)
{
if (remainingVolume <= 0)
{
break;
}
if (FluidUtility.isFillableBlock(worldObj, loc))
{
remainingVolume -= FluidUtility.fillBlock(worldObj, loc, FluidUtility.getStack(resource, remainingVolume), doFill);
if (doFill)
{
filled.add(loc);
currentWorldEdits++;
}
}
}
this.getDrainFinder().results.removeAll(filled);
return Math.max(resource.amount - remainingVolume, 0);
return gratePath.tryFill(resource.amount, 2000);
}
return 0;
@ -208,103 +83,336 @@ public class TileGrate extends TileAdvanced implements IFluidHandler, IDrain
@Override
public FluidStack drain(ForgeDirection from, int maxDrain, boolean doDrain)
{
update();
FluidStack resultStack = null;
if (getDrainList() == null || getDrainList().size() == 0)
if (maxDrain > 0)
{
doPathfinding();
if (gratePath == null)
{
gratePath = new GratePathfinder(false);
if (!gratePath.startDrain(new Vector3(this)))
{
resetPath();
}
}
if (gratePath != null && gratePath.tryPopulateDrainMap(500))
{
return gratePath.tryDrain(maxDrain, doDrain);
}
}
List<Vector3> drainList = getDrainList();
return null;
}
if (drainList != null && drainList.size() > 0)
/**
* Pathfinding operations
*
* @author Calclavia
*/
public void resetPath()
{
this.gratePath = null;
}
public class GratePathfinder
{
/**
* The starting vector for our grate.
*/
Vector3 start;
/**
* All the back trace blocks tracing back to the original.
*/
HashMap<Vector3, Vector3> navigationMap = new HashMap<Vector3, Vector3>();
/**
* The nodes we're currently working on.
*/
PriorityQueue<ComparableVector> workingNodes;
/**
* The list of blocks to drain.
*/
PriorityQueue<ComparableVector> drainNodes = new PriorityQueue<ComparableVector>(1024, Collections.reverseOrder());
/**
* The type of fluid we're dealing with. When draining, this will be the type of fluid being
* drained.
*/
public Fluid fluidType;
public GratePathfinder(boolean checkVertical)
{
Iterator<Vector3> iterator = drainList.iterator();
while (iterator.hasNext())
if (checkVertical)
{
if (currentWorldEdits >= MAX_FLUID_MODIFY_RATE)
this.workingNodes = new PriorityQueue<ComparableVector>();
}
else
{
this.workingNodes = new PriorityQueue<ComparableVector>(1024, new Comparator()
{
break;
}
final Vector3 drainLocation = iterator.next();
FluidStack drainStack = FluidUtility.drainBlock(worldObj, drainLocation, false, 3);
if (resultStack == null)
{
drainStack = FluidUtility.drainBlock(worldObj, drainLocation, doDrain, 2);
resultStack = drainStack;
}
else if (resultStack.equals(drainStack))
{
drainStack = FluidUtility.drainBlock(worldObj, drainLocation, doDrain, 2);
resultStack.amount += drainStack.amount;
}
if (doDrain)
{
/**
* Add a delayed notify event to prevent infinite fluids from reconstructing
* quickly.
*/
NetworkTickHandler.addNetwork(new IUpdate()
@Override
public int compare(Object a, Object b)
{
int wait = 20;
TileGrate.ComparableVector wa = (TileGrate.ComparableVector) a;
TileGrate.ComparableVector wb = (TileGrate.ComparableVector) b;
@Override
public void update()
{
if (--wait <= 0)
{
worldObj.notifyBlocksOfNeighborChange(drainLocation.intX(), drainLocation.intY(), drainLocation.intZ(), worldObj.getBlockId(drainLocation.intX(), drainLocation.intY(), drainLocation.intZ()), 20);
}
}
return wa.iterations - wb.iterations;
}
});
}
}
@Override
public boolean canUpdate()
{
return true;
}
/**
* Traces back to the start position to see if this fluid position is connected with the
* starting position through fluid mediums.
*/
public boolean isConnected(Vector3 check)
{
if (check.equals(this.start))
return true;
do
{
check = this.navigationMap.get(check);
@Override
public boolean continueUpdate()
{
return wait > 0;
}
});
}
if (check == null)
return false;
currentWorldEdits++;
iterator.remove();
if (check.equals(this.start))
return true;
}
while (FluidUtility.getFluidFromBlock(TileGrate.this.worldObj, check) != null && fluidType.getID() == FluidUtility.getFluidFromBlock(TileGrate.this.worldObj, check).getID());
if (resultStack != null && resultStack.amount >= maxDrain)
return false;
}
public void startFill(Vector3 start, int fluidID)
{
this.fluidType = FluidRegistry.getFluid(fluidID);
this.start = start;
for (int i = 0; i < 6; i++)
{
ForgeDirection dir = ForgeDirection.getOrientation(i);
if (dir == TileGrate.this.getDirection())
{
break;
Vector3 check = start.clone().translate(dir);
this.navigationMap.put(check, start);
this.workingNodes.add(new TileGrate.ComparableVector(check, 0));
}
}
}
return resultStack;
/**
* Tries to fill.
*
* @param amount
* @param tries
* @return Amount filled.
*/
public int tryFill(int amount, int tries)
{
for (int i = 0; i < tries; i++)
{
ComparableVector next = this.workingNodes.poll();
if (next == null)
{
TileGrate.this.resetPath();
return 0;
}
if (!isConnected(next.position))
{
TileGrate.this.resetPath();
return 0;
}
int filled = FluidUtility.fillBlock(TileGrate.this.worldObj, next.position, new FluidStack(fluidType, amount), true);
amount -= filled;
if (filled > 0)
{
addNextFill(next);
return filled;
}
}
return 0;
}
/**
* Adds new nodes into the map.
*
* @param next
*/
public void addNextFill(ComparableVector next)
{
for (int i = 0; i < 6; i++)
{
Vector3 newPosition = next.position.clone().translate(ForgeDirection.getOrientation(i));
if (!this.navigationMap.containsKey(newPosition))
{
this.navigationMap.put(newPosition, next.position);
this.workingNodes.add(new ComparableVector(newPosition, next.iterations + 1));
}
}
}
public boolean startDrain(Vector3 start)
{
fluidType = null;
this.start = start;
for (int i = 0; i < 6; i++)
{
ForgeDirection dir = ForgeDirection.getOrientation(i);
if (dir == TileGrate.this.getDirection())
{
Vector3 check = start.clone().translate(dir);
this.navigationMap.put(check, start);
this.workingNodes.add(new ComparableVector(check, 0));
fluidType = FluidUtility.getFluidFromBlock(TileGrate.this.worldObj, check);
}
}
return fluidType != null;
}
/**
* Creates a map of all the fluids.
*
* @param tries
* @return
*/
public boolean tryPopulateDrainMap(int tries)
{
if (drainNodes.size() >= Integer.MAX_VALUE)
{
return true;
}
for (int i = 0; i < tries; i++)
{
ComparableVector check = workingNodes.poll();
if (check == null)
return true;
Fluid checkFluid = FluidUtility.getFluidFromBlock(TileGrate.this.worldObj, check.position);
if (checkFluid != null && fluidType.getID() == checkFluid.getID())
{
addNextDrain(check);
int checkAmount = FluidUtility.getFluidAmountFromBlock(TileGrate.this.worldObj, check.position);
if (checkAmount > 0)
this.drainNodes.add(check);
}
}
return false;
}
public void addNextDrain(ComparableVector next)
{
for (int i = 0; i < 6; i++)
{
Vector3 check = next.position.clone().translate(ForgeDirection.getOrientation(i));
Fluid checkFluid = FluidUtility.getFluidFromBlock(TileGrate.this.worldObj, check);
if (checkFluid != null && fluidType.getID() == checkFluid.getID())
{
if (!navigationMap.containsKey(check))
{
navigationMap.put(check, next.position);
workingNodes.add(new TileGrate.ComparableVector(check, next.iterations + 1));
}
}
}
}
/**
* Tries to drain a specific amount of fluid.
*
* @return - The amount drained.
*/
public FluidStack tryDrain(int amount, boolean doDrain)
{
int tryAmount = 0;
while (!drainNodes.isEmpty())
{
ComparableVector fluidCoord = drainNodes.peek();
if (!isConnected(fluidCoord.position))
{
TileGrate.this.resetPath();
return new FluidStack(fluidType, tryAmount);
}
if (!this.fluidType.equals(FluidUtility.getFluidFromBlock(TileGrate.this.worldObj, fluidCoord.position)))
{
this.drainNodes.poll();
}
else
{
int amount1 = FluidUtility.getFluidAmountFromBlock(TileGrate.this.worldObj, fluidCoord.position);
if (amount1 == 0)
{
this.drainNodes.poll();
}
else
{
if (tryAmount + amount1 <= amount)
{
tryAmount += amount1;
fluidCoord.position.setBlock(TileGrate.this.worldObj, 0);
this.drainNodes.poll();
if (tryAmount == amount)
return new FluidStack(fluidType, amount);
}
FluidStack fluidStack = FluidUtility.drainBlock(TileGrate.this.worldObj, fluidCoord.position, doDrain, 3);
if (fluidStack != null && fluidStack.amount > 0)
{
return new FluidStack(fluidType, fluidStack.amount);
}
}
}
}
TileGrate.this.resetPath();
return null;
}
}
@Override
public FluidTankInfo[] getTankInfo(ForgeDirection from)
public static class ComparableVector implements Comparable
{
return new FluidTankInfo[] { new FluidTank(this.getDrainFinder().results.size() * FluidContainerRegistry.BUCKET_VOLUME).getInfo() };
}
public Vector3 position;
public int iterations;
@Override
public boolean canFill(ForgeDirection from, Fluid fluid)
{
return getDirection() != from;
}
public ComparableVector(Vector3 position, int iterations)
{
this.position = position;
this.iterations = iterations;
}
@Override
public boolean canDrain(ForgeDirection from, Fluid fluid)
{
return getDirection() != from;
public int compareTo(Object obj)
{
ComparableVector wr = (ComparableVector) obj;
if (this.position.y == wr.position.y)
return this.iterations - wr.iterations;
return this.position.intY() - wr.position.intY();
}
}
}