Improved and randomized pathfinder

Pathfinder now works better
--It levels of at the correct y level for pathfinding
--It pathfinder no longer in a perfect line but is randomized
--Pathfinder is now limited to search with a set range preventing cross
map interaction
This commit is contained in:
Robert Seifert 2013-05-23 02:56:49 -04:00
parent 05a4f5b418
commit 13699c24b3
3 changed files with 275 additions and 83 deletions

View file

@ -23,6 +23,7 @@ import net.minecraftforge.liquids.ILiquidTank;
import net.minecraftforge.liquids.ITankContainer; import net.minecraftforge.liquids.ITankContainer;
import net.minecraftforge.liquids.LiquidContainerRegistry; import net.minecraftforge.liquids.LiquidContainerRegistry;
import net.minecraftforge.liquids.LiquidStack; import net.minecraftforge.liquids.LiquidStack;
import universalelectricity.core.vector.Vector2;
import universalelectricity.core.vector.Vector3; import universalelectricity.core.vector.Vector3;
import universalelectricity.core.vector.VectorHelper; import universalelectricity.core.vector.VectorHelper;
@ -37,7 +38,16 @@ public class TileEntityDrain extends TileEntityFluidDevice implements ITankConta
private List<Vector3> targetSources = new ArrayList<Vector3>(); private List<Vector3> targetSources = new ArrayList<Vector3>();
private List<Vector3> updateQue = new ArrayList<Vector3>(); private List<Vector3> updateQue = new ArrayList<Vector3>();
private LiquidPathFinder pathFinder; private LiquidPathFinder pathLiquid;
public LiquidPathFinder getLiquidFinder()
{
if(pathLiquid == null)
{
pathLiquid = new LiquidPathFinder(this.worldObj, false, 1000, 100);
}
return pathLiquid;
}
@Override @Override
public String getMeterReading(EntityPlayer user, ForgeDirection side) public String getMeterReading(EntityPlayer user, ForgeDirection side)
@ -49,7 +59,6 @@ public class TileEntityDrain extends TileEntityFluidDevice implements ITankConta
public void invalidate() public void invalidate()
{ {
super.invalidate(); super.invalidate();
pathFinder = new LiquidPathFinder(this.worldObj, false, TileEntityDrain.MAX_WORLD_EDITS_PER_PROCESS * 2);
} }
public boolean canDrainSources() public boolean canDrainSources()
@ -99,13 +108,17 @@ public class TileEntityDrain extends TileEntityFluidDevice implements ITankConta
{ {
break; break;
} }
if (request.getKey() instanceof ITankContainer) if (request.getKey() instanceof ITankContainer)
{ {
ITankContainer tank = (ITankContainer) request.getKey(); ITankContainer tank = (ITankContainer) request.getKey();
Iterator<Vector3> it = this.targetSources.iterator();
while (it.hasNext()) Vector3[] sortedList = this.sortedDrainList();
for (int i = 0; sortedList != null && i < sortedList.length; i++)
{ {
Vector3 loc = it.next(); Vector3 loc = sortedList[i];
if (this.currentWorldEdits >= MAX_WORLD_EDITS_PER_PROCESS) if (this.currentWorldEdits >= MAX_WORLD_EDITS_PER_PROCESS)
{ {
break; break;
@ -142,8 +155,6 @@ public class TileEntityDrain extends TileEntityFluidDevice implements ITankConta
/* REMOVE BLOCK */ /* REMOVE BLOCK */
loc.setBlock(this.worldObj, 0, 0, 2); loc.setBlock(this.worldObj, 0, 0, 2);
this.currentWorldEdits++; this.currentWorldEdits++;
it.remove();
} }
} }
} }
@ -159,15 +170,12 @@ public class TileEntityDrain extends TileEntityFluidDevice implements ITankConta
*/ */
public void getNextFluidBlock() public void getNextFluidBlock()
{ {
if (pathFinder == null)
{ getLiquidFinder().reset();
pathFinder = new LiquidPathFinder(this.worldObj, false, 1000); getLiquidFinder().init(new Vector3(this.xCoord + this.getFacing().offsetX, this.yCoord + this.getFacing().offsetY, this.zCoord + this.getFacing().offsetZ));
}
pathFinder.reset();
pathFinder.init(new Vector3(this.xCoord + this.getFacing().offsetX, this.yCoord + this.getFacing().offsetY, this.zCoord + this.getFacing().offsetZ));
// System.out.println("Nodes:" + pathFinder.nodes.size() + "Results:" + // System.out.println("Nodes:" + pathFinder.nodes.size() + "Results:" +
// pathFinder.results.size()); // pathFinder.results.size());
for (Vector3 vec : pathFinder.nodes) for (Vector3 vec : getLiquidFinder().nodes)
{ {
this.addVectorToQue(vec); this.addVectorToQue(vec);
} }
@ -211,73 +219,95 @@ public class TileEntityDrain extends TileEntityFluidDevice implements ITankConta
requests.remove(); requests.remove();
} }
} }
/* CLEANUP TARGET LIST AND REMOVE INVALID SOURCES */
Iterator<Vector3> targetIt = this.targetSources.iterator(); }
while (targetIt.hasNext())
{ public Vector3[] sortedDrainList()
Vector3 vec = targetIt.next(); {
if (!FluidHelper.isSourceBlock(this.worldObj, vec))
{
targetIt.remove();
}
}
/* SORT RESULTS TO PUT THE HiGHEST AND FURTHEST AT THE TOP */
try try
{ {
List<Vector3> updatedList = new ArrayList<Vector3>(); /* CLEANUP TARGET LIST AND REMOVE INVALID SOURCES */
updatedList.addAll(this.targetSources); Iterator<Vector3> targetIt = this.targetSources.iterator();
while (targetIt.hasNext())
for (int i = 0; i < updatedList.size(); i++)
{ {
Vector3 vec = updatedList.get(i).clone(); Vector3 vec = targetIt.next();
if (i + 1 < updatedList.size()) if (!FluidHelper.isSourceBlock(this.worldObj, vec))
{ {
for (int b = i + 1; b < updatedList.size(); b++) targetIt.remove();
}
}
Vector3[] sortedList = new Vector3[this.targetSources.size()];
for (int b = 0; b < this.targetSources.size(); b++)
{
sortedList[b] = this.targetSources.get(b);
}
/* SORT RESULTS TO PUT THE HiGHEST AND FURTHEST AT THE TOP */
Vector2 machine = new Vector3(this).toVector2();
for (int i = 0; i < sortedList.length; i++)
{
Vector3 vec = sortedList[i].clone();
Vector2 first = vec.toVector2();
if (i + 1 < sortedList.length)
{
Vector3 highest = vec;
int b = 0;
for (b = i + 1; b < sortedList.length; b++)
{ {
Vector3 checkVec = updatedList.get(b).clone(); Vector3 checkVec = sortedList[b].clone();
if (checkVec != null) if (checkVec != null)
{ {
if (checkVec.distanceTo(new Vector3(this)) > vec.distanceTo(new Vector3(this))) Vector2 second = checkVec.toVector2();
if (second.distanceTo(machine) > vec.toVector2().distanceTo(machine))
{ {
updatedList.set(i, checkVec); highest = checkVec.clone();
updatedList.set(b, vec);
break;
} }
} }
} }
} if (b < sortedList.length)
}
for (int i = 0; i < updatedList.size(); i++)
{
Vector3 vec = updatedList.get(i).clone();
if (i + 1 < updatedList.size())
{
for (int b = i + 1; b < updatedList.size(); b++)
{ {
Vector3 checkVec = updatedList.get(b).clone(); sortedList[i] = vec;
if (checkVec != null) sortedList[b] = highest;
{
if (checkVec.intY() > vec.intY())
{
updatedList.set(i, checkVec);
updatedList.set(b, vec);
break;
}
}
} }
} }
} }
this.targetSources.clear(); for (int i = 0; i < sortedList.length; i++)
this.targetSources.addAll(updatedList); {
updatedList.clear(); Vector3 vec = sortedList[i].clone();
if (i + 1 < sortedList.length)
{
Vector3 highest = vec;
int b = 0;
for (b = i + 1; b < sortedList.length; b++)
{
Vector3 checkVec = sortedList[b].clone();
if (checkVec != null)
{
if (checkVec.intY() > highest.intY())
{
highest = checkVec.clone();
}
}
}
if (b < sortedList.length)
{
sortedList[i] = vec;
sortedList[b] = highest;
}
}
}
return sortedList;
} }
catch (Exception e) catch (Exception e)
{ {
System.out.println("FluidMech: Error sorting fill collection \n"); System.out.println("FluidMech: Critical Error Processing Drain List");
e.printStackTrace(); e.printStackTrace();
} }
return null;
} }
@Override @Override
@ -319,16 +349,15 @@ public class TileEntityDrain extends TileEntityFluidDevice implements ITankConta
int blocks = (resource.amount / LiquidContainerRegistry.BUCKET_VOLUME); int blocks = (resource.amount / LiquidContainerRegistry.BUCKET_VOLUME);
/* FIND ALL VALID BLOCKS ON LEVEL OR BELLOW */ /* FIND ALL VALID BLOCKS ON LEVEL OR BELLOW */
LiquidPathFinder pathFinder = new LiquidPathFinder(this.worldObj, true, this.MAX_WORLD_EDITS_PER_PROCESS * 2);
final Vector3 faceVec = new Vector3(this.xCoord + this.getFacing().offsetX, this.yCoord + this.getFacing().offsetY, this.zCoord + this.getFacing().offsetZ); final Vector3 faceVec = new Vector3(this.xCoord + this.getFacing().offsetX, this.yCoord + this.getFacing().offsetY, this.zCoord + this.getFacing().offsetZ);
pathFinder.init(faceVec); getLiquidFinder().init(faceVec);
/* SORT RESULTS TO PUT THE LOWEST AND CLOSEST AT THE TOP */ /* SORT RESULTS TO PUT THE LOWEST AND CLOSEST AT THE TOP */
try try
{ {
if (pathFinder.results.size() > 1) if (getLiquidFinder().results.size() > 1)
{ {
Collections.sort(pathFinder.results, new Comparator() Collections.sort(getLiquidFinder().results, new Comparator()
{ {
@Override @Override
public int compare(Object o1, Object o2) public int compare(Object o1, Object o2)
@ -362,7 +391,7 @@ public class TileEntityDrain extends TileEntityFluidDevice implements ITankConta
} }
/* START FILLING IN OR CHECKING IF CAN FILL AREA */ /* START FILLING IN OR CHECKING IF CAN FILL AREA */
int fillable = 0; int fillable = 0;
for (Vector3 loc : pathFinder.results) for (Vector3 loc : getLiquidFinder().results)
{ {
if (blocks <= 0) if (blocks <= 0)
{ {
@ -387,7 +416,7 @@ public class TileEntityDrain extends TileEntityFluidDevice implements ITankConta
} }
for (Vector3 loc : pathFinder.results) for (Vector3 loc : getLiquidFinder().results)
{ {
if (blocks <= 0) if (blocks <= 0)
{ {

View file

@ -3,11 +3,14 @@ package fluidmech.common.pump.path;
import hydraulic.helpers.FluidHelper; import hydraulic.helpers.FluidHelper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Random;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.common.ForgeDirection; import net.minecraftforge.common.ForgeDirection;
import universalelectricity.core.vector.Vector2;
import universalelectricity.core.vector.Vector3; import universalelectricity.core.vector.Vector3;
/** /**
@ -24,9 +27,14 @@ public class LiquidPathFinder
private boolean fill; /* ARE WE FILLING THE PATH OR DRAINING THE PATH */ private boolean fill; /* ARE WE FILLING THE PATH OR DRAINING THE PATH */
private ForgeDirection priority; /* BASED ON fill -- WHICH DIRECTION WILL THE PATH GO FIRST */ private ForgeDirection priority; /* BASED ON fill -- WHICH DIRECTION WILL THE PATH GO FIRST */
private int resultLimit = 2000; private int resultLimit = 2000;
private Vector2 Start;
private double range;
private Random random = new Random();
List<ForgeDirection> bn = new ArrayList<ForgeDirection>();
public LiquidPathFinder(final World world, final boolean fill, final int resultLimit) public LiquidPathFinder(final World world, final boolean fill, final int resultLimit, final double range)
{ {
this.range = range;
this.world = world; this.world = world;
this.fill = fill; this.fill = fill;
if (fill) if (fill)
@ -39,6 +47,10 @@ public class LiquidPathFinder
} }
this.resultLimit = resultLimit; this.resultLimit = resultLimit;
this.reset(); this.reset();
bn.add(ForgeDirection.EAST);
bn.add(ForgeDirection.WEST);
bn.add(ForgeDirection.NORTH);
bn.add(ForgeDirection.SOUTH);
} }
/** /**
@ -73,28 +85,25 @@ public class LiquidPathFinder
return false; return false;
} }
vec = node.clone().modifyPositionFromSide(this.priority); if (find(this.priority, node.clone()))
if (this.isValidNode(vec) & !this.nodes.contains(vec))
{ {
if (this.findNodes(vec)) return true;
}
Collections.shuffle(bn);
Collections.shuffle(bn);
for (ForgeDirection direction : bn)
{
if (find(direction, vec))
{ {
return true; return true;
} }
} }
for (ForgeDirection direction : ForgeDirection.VALID_DIRECTIONS) if (find(this.priority.getOpposite(), node.clone()))
{ {
if (direction != this.priority) return true;
{
vec = node.clone().modifyPositionFromSide(direction);
if (this.isValidNode(vec) & !this.nodes.contains(vec))
{
if (this.findNodes(vec))
{
return true;
}
}
}
} }
} }
catch (Exception e) catch (Exception e)
@ -105,6 +114,20 @@ public class LiquidPathFinder
return false; return false;
} }
public boolean find(ForgeDirection direction, Vector3 vec)
{
vec = vec.clone().modifyPositionFromSide(direction);
double distance = vec.toVector2().distanceTo(this.Start);
if (distance <= this.range && this.isValidNode(vec) & !this.nodes.contains(vec))
{
if (this.findNodes(vec))
{
return true;
}
}
return false;
}
public boolean isValidNode(Vector3 pos) public boolean isValidNode(Vector3 pos)
{ {
int blockID = pos.getBlockID(world); int blockID = pos.getBlockID(world);
@ -130,8 +153,9 @@ public class LiquidPathFinder
/** /**
* Called to execute the pathfinding operation. * Called to execute the pathfinding operation.
*/ */
public LiquidPathFinder init(Vector3 startNode) public LiquidPathFinder init(final Vector3 startNode)
{ {
this.Start = startNode.toVector2();
this.findNodes(startNode); this.findNodes(startNode);
if (this.fill && this.isValidNode(startNode)) if (this.fill && this.isValidNode(startNode))
{ {

View file

@ -0,0 +1,139 @@
package fluidmech.common.pump.path;
import hydraulic.helpers.FluidHelper;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.common.ForgeDirection;
import universalelectricity.core.vector.Vector3;
/**
* A simpler pathfinder based on Calclavia's PathFinder from UE api
*/
public class LiquidPathFinder2D
{
private World world; /* MC WORLD */
public List<Vector3> nodes = new ArrayList<Vector3>(); /*
* LOCATIONs THE PATH FINDER HAS GONE
* OVER
*/
public List<Vector3> results = new ArrayList<Vector3>();/* LOCATIONS THAT ARE VALID RESULTS */
private boolean fill; /* ARE WE FILLING THE PATH OR DRAINING THE PATH */
private ForgeDirection priority; /* BASED ON fill -- WHICH DIRECTION WILL THE PATH GO FIRST */
private int resultLimit = 2000;
public LiquidPathFinder2D(final World world, final int resultLimit)
{
this.world = world;
this.fill = fill;
if (fill)
{
priority = ForgeDirection.DOWN;
}
else
{
priority = ForgeDirection.UP;
}
this.resultLimit = resultLimit;
this.reset();
}
/**
* @return True on success finding, false on failure.
*/
public boolean findNodes(final Vector3 node)
{
try
{
Vector3 vec = node.clone();
this.nodes.add(node);
Chunk chunk = this.world.getChunkFromBlockCoords(vec.intX(), vec.intZ());
if (chunk == null || !chunk.isChunkLoaded)
{
return true;
}
int id = node.getBlockID(world);
int meta = node.getBlockID(world);
if (this.fill && (id == 0 || (FluidHelper.getLiquidFromBlockId(id) != null && meta != 0)))
{
this.results.add(node);
}
else if (!this.fill && FluidHelper.isSourceBlock(world, node))
{
this.results.add(node);
}
if (this.isDone(node))
{
return false;
}
for (ForgeDirection direction : ForgeDirection.VALID_DIRECTIONS)
{
if (direction != ForgeDirection.DOWN && direction != ForgeDirection.UP)
{
vec = node.clone().modifyPositionFromSide(direction);
if (this.isValidNode(vec) & !this.nodes.contains(vec))
{
if (this.findNodes(vec))
{
return true;
}
}
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
return false;
}
public boolean isValidNode(Vector3 pos)
{
int blockID = pos.getBlockID(world);
if (!this.fill)
{
return FluidHelper.getLiquidFromBlockId(pos.getBlockID(world)) != null;
}
else
{
return FluidHelper.getLiquidFromBlockId(pos.getBlockID(world)) != null || (blockID == 0 && FluidHelper.getConnectedSources(world, pos) > 0);
}
}
public boolean isDone(Vector3 vec)
{
if (this.results.size() >= this.resultLimit || this.nodes.size() >= 4000)
{
return true;
}
return false;
}
/**
* Called to execute the pathfinding operation.
*/
public LiquidPathFinder2D init(Vector3 startNode)
{
this.findNodes(startNode);
if (this.fill && this.isValidNode(startNode))
{
}
return this;
}
public LiquidPathFinder2D reset()
{
this.nodes.clear();
this.results.clear();
return this;
}
}