Island highlighting debug now works, albeit, not favorable.

Reworked DFS as well to suit a broader spectrum of needs.

Reworked other classes for easier use.
This commit is contained in:
Harrison Deng 2020-05-09 21:56:27 -05:00
parent 19f387def9
commit 4f18f25c01
15 changed files with 365 additions and 330 deletions

View File

@ -5,6 +5,7 @@ import java.util.LinkedList;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Particle; import org.bukkit.Particle;
import org.bukkit.World; import org.bukkit.World;
@ -12,16 +13,18 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import ca.recrown.islandsurvivalcraft.IslandSurvivalCraft; import ca.recrown.islandsurvivalcraft.IslandSurvivalCraft;
import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2;
import ca.recrown.islandsurvivalcraft.utilities.pathfinding.DepthFirstSearch;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMap;
import ca.recrown.islandsurvivalcraft.world.WorldInfo; import ca.recrown.islandsurvivalcraft.world.WorldInfo;
public class HighlightIslandCommand implements CommandRunnable { public class HighlightIslandCommand implements CommandRunnable {
private final int CHECK_RADIUS = 3; private final Particle.DustOptions OPTIONS = new Particle.DustOptions(Color.RED, 1f);
private final int PARTICLE_AMOUNT = 2; private final int PARTICLE_AMOUNT = 3;
private final Particle PARTICLE_TYPE = Particle.FIREWORKS_SPARK; private final double OFFSET_X = 0.0d, OFFSET_Y = 6.0d, OFFSET_Z = 0.0d;
private final double OFFSET_X = 0.0d, OFFSET_Y = 0.0d, OFFSET_Z = 0.0d; private final double EXTRA = 0d;
HashSet<Player> playersHighlighting = new HashSet<>(); HashSet<Player> playersHighlighting = new HashSet<>();
LinkedList<Player> waitingList = new LinkedList<>(); LinkedList<PlayerIslandHighlighter> waitingList = new LinkedList<>();
@Override @Override
public String getDescription() { public String getDescription() {
@ -42,7 +45,7 @@ public class HighlightIslandCommand implements CommandRunnable {
} }
if (args[0].toLowerCase().equals("start")) { if (args[0].toLowerCase().equals("start")) {
if (playersHighlighting.add(player)) { if (playersHighlighting.add(player)) {
waitingList.add(player); waitingList.add(new PlayerIslandHighlighter(player));
completableFuture.complete("You are now highlighting islands."); completableFuture.complete("You are now highlighting islands.");
} else { } else {
completableFuture.complete("You are already highlighting islands."); completableFuture.complete("You are already highlighting islands.");
@ -54,17 +57,19 @@ public class HighlightIslandCommand implements CommandRunnable {
completableFuture.complete("You weren't highlighting islands."); completableFuture.complete("You weren't highlighting islands.");
} }
} else { } else {
completableFuture.complete(String.format("Argument \"%d\" was not understood. Please refer to \"/IslandSurvivalCraft help\" for more info.", args[0])); completableFuture.complete(String.format("Argument \"%s\" was not understood. Please refer to \"/IslandSurvivalCraft help\" for more info.", args[0]));
} }
return completableFuture; return completableFuture;
} }
@Override @Override
public void initialize(IslandSurvivalCraft islandsurvivalcraft) { public void initialize(IslandSurvivalCraft islandsurvivalcraft) {
Bukkit.getScheduler().scheduleSyncRepeatingTask(islandsurvivalcraft, new Runnable() { Bukkit.getScheduler().scheduleSyncRepeatingTask(islandsurvivalcraft, new Runnable() {
@Override @Override
public void run() { public void run() {
Player player = waitingList.poll(); PlayerIslandHighlighter highlighter = waitingList.poll();
if (highlighter == null) return;
Player player = highlighter.player;
if (player == null || !player.isOnline() || !playersHighlighting.contains(player)) { if (player == null || !player.isOnline() || !playersHighlighting.contains(player)) {
playersHighlighting.remove(player); playersHighlighting.remove(player);
return; return;
@ -73,25 +78,74 @@ public class HighlightIslandCommand implements CommandRunnable {
WorldInfo worldInfo = islandsurvivalcraft.getWorldInfoManager().retrieve(world); WorldInfo worldInfo = islandsurvivalcraft.getWorldInfoManager().retrieve(world);
Location playerLocation = player.getLocation(); Location playerLocation = player.getLocation();
int playerX = playerLocation.getBlockX(), playerY = playerLocation.getBlockY(), playerZ = playerLocation.getBlockZ(); int playerX = playerLocation.getBlockX(), playerY = playerLocation.getBlockY(), playerZ = playerLocation.getBlockZ();
for (int x = 1; x < CHECK_RADIUS; x++) { IslandWorldMap map = worldInfo.getIslandMap();
for (int z = 1; z < CHECK_RADIUS; z++) {
if (worldInfo.getIslandMap().isIsland(playerX + x, playerZ + z)) { highlighter.update(new Point2(playerX, playerZ), map);
world.spawnParticle(PARTICLE_TYPE, x + playerX, playerY, z + playerZ, PARTICLE_AMOUNT, OFFSET_X, OFFSET_Y, OFFSET_Z);
} LinkedList<Point2> highlighted = highlighter.getHighlighted();
if (worldInfo.getIslandMap().isIsland(playerX - x, playerZ - z)) { for (Point2 point2 : highlighted) {
world.spawnParticle(PARTICLE_TYPE, playerX - x, playerY, playerZ - z, PARTICLE_AMOUNT, OFFSET_X, OFFSET_Y, OFFSET_Z); if (point2 != null) {
} spawnParticle(world, point2.x, playerY, point2.y);
} }
} }
waitingList.add(player); waitingList.add(highlighter);
} }
}, 0, 5); }, 0, 4);
} }
private void spawnParticle(World world, double x, double y, double z) {
world.spawnParticle(Particle.REDSTONE, x, y, z, PARTICLE_AMOUNT, OFFSET_X, OFFSET_Y, OFFSET_Z, EXTRA, OPTIONS, true);
}
@Override @Override
public String getDetailedDescription() { public String getDetailedDescription() {
return "A helpful debugging command to analyze what the plugin considers an island by displaying particle effects around the player that is a part of the island. The <start | stop> argument is required to dicate whether or not to start the highlighting or stop it."; return "A helpful debugging command to analyze what the plugin considers an island by displaying particle effects around the player that is a part of the island. The <start | stop> argument is required to dicate whether or not to start the highlighting or stop it.";
} }
private class PlayerIslandHighlighter {
public final int TRAIL_LENGTH = 64;
private final int MAX_SEARCH = 24;
private final int MAX_SEARCH_DIST = 8;
private final Player player;
private final LinkedList<Point2> highlighted = new LinkedList<>();
private DepthFirstSearch dfs;
public PlayerIslandHighlighter(Player player) {
this.player = player;
}
private void addHighlight(Point2 point) {
highlighted.add(point);
if (highlighted.size() > TRAIL_LENGTH) {
highlighted.poll();
}
}
public void update(Point2 playerLocation, IslandWorldMap islandWorldMap) {
if (dfs == null || dfs.step(islandWorldMap.islandValidator, (p) -> p.distance(playerLocation) > MAX_SEARCH_DIST)) {
dfs = new DepthFirstSearch(playerLocation, null, (p1, p2) -> {
double diff = islandWorldMap.getWorldValue(p1.x, p1.y) - islandWorldMap.getWorldValue(p2.x, p2.y);
if (diff == 0) {
return 0;
} else if (diff > 0) {
return 1;
} else {
return -1;
}
}, MAX_SEARCH);
}
Point2 currentPoint = dfs.getCurrentNode();
if (currentPoint != null && islandWorldMap.isEdgeOfIsland(currentPoint.x, currentPoint.y)) {
addHighlight(currentPoint);
}
}
/**
* @return the highlighted
*/
public LinkedList<Point2> getHighlighted() {
return highlighted;
}
}
} }

View File

@ -20,15 +20,7 @@ public class OceanBiomeInfo implements BiomeInfo {
public Biome getOcean() { public Biome getOcean() {
return ocean; return ocean;
} }
/**
* @return the transition ocean. Will return the normal biome if no transitional biome is designated.
*/
public Biome getTransitionOcean() {
if (transitionOcean == null) return getOcean();
return transitionOcean;
}
/** /**
* @return the deep version of this ocean. * @return the deep version of this ocean.
*/ */
@ -43,6 +35,7 @@ public class OceanBiomeInfo implements BiomeInfo {
@Override @Override
public Biome getTransitionBiome() { public Biome getTransitionBiome() {
return getTransitionBiome(); if (transitionOcean == null) return getOcean();
return transitionOcean;
} }
} }

View File

@ -12,6 +12,10 @@ public class Point2 {
this.hash = Objects.hash(x, y); this.hash = Objects.hash(x, y);
} }
public Point2(Point2 point) {
this(point.x, point.y);
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj instanceof Point2) { if (obj instanceof Point2) {

View File

@ -1,5 +1,7 @@
package ca.recrown.islandsurvivalcraft.utilities.pathfinding; package ca.recrown.islandsurvivalcraft.utilities.pathfinding;
import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2;
public interface CoordinateTargetValidatable { public interface CoordinateTargetValidatable {
/** /**
* Is the current coordinate the objective? * Is the current coordinate the objective?
@ -7,5 +9,5 @@ public interface CoordinateTargetValidatable {
* @param y the y of the coordinate. * @param y the y of the coordinate.
* @return true if this is the objective coordinate. * @return true if this is the objective coordinate.
*/ */
public boolean isCoordinateTarget(int x, int y); public boolean isCoordinateTarget(Point2 point);
} }

View File

@ -1,5 +1,7 @@
package ca.recrown.islandsurvivalcraft.utilities.pathfinding; package ca.recrown.islandsurvivalcraft.utilities.pathfinding;
import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2;
public interface CoordinateValidatable { public interface CoordinateValidatable {
public boolean validate(int x, int y); public boolean validate(Point2 point);
} }

View File

@ -1,67 +0,0 @@
package ca.recrown.islandsurvivalcraft.utilities.pathfinding;
import java.util.Objects;
import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2;
public class DFSNode extends Point2 implements Comparable<DFSNode> {
private DFSNode goal;
public DFSNode[] child;
private DFSNode parent;
public DFSNode(DFSNode parent, int x, int y, DFSNode goal) {
super(x, y);
child = new DFSNode[4];
this.parent = parent;
this.goal = goal;
}
public DFSNode(DFSNode parent, Point2 coords, DFSNode goal) {
this(parent, coords.x, coords.y, goal);
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public DFSNode getGoal() {
return goal;
}
@Override
public int compareTo(DFSNode o) {
if (goal == null) return 0;
return Math.round(distanceToGoal(goal) - o.distanceToGoal(goal));
}
public DFSNode getParent() {
return parent;
}
public float distanceToGoal(DFSNode goal) {
float distanceX = goal.x - this.x;
float distanceY = goal.y - this.y;
float distance = (float) Math.sqrt(distanceX * distanceX + distanceY * distanceY);
return distance;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof DFSNode) {
DFSNode comp = (DFSNode) obj;
if (comp.x == this.x && comp.y == this.y) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}

View File

@ -4,127 +4,108 @@ import java.util.HashSet;
import java.util.PriorityQueue; import java.util.PriorityQueue;
import java.util.Queue; import java.util.Queue;
import org.apache.commons.lang.NullArgumentException;
import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2; import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2;
public class DepthFirstSearch { public class DepthFirstSearch {
private Queue<DFSNode> queue; private final HashSet<Node> checkedNodes;
private CoordinateValidatable coordValidatable; private final NodeComparable comparable;
private DFSNode startNode; private final Queue<Node> queue;
private DFSNode endNode; private final Node startNode;
private HashSet<DFSNode> checkedNodes; private final Point2 endGoal;
private int maxNodesSearched = -1; private final int maxNodesSearched;
private DFSNode foundNode; private Node currentValidNode;
private Node results;
public DepthFirstSearch() { /**
* Instantiates a DFS search object.
* @param start The starting point for this search.
* @param endGoal The objective. May be null, but a target validator will then be required.
* @param customComparable The comparable to use to compare points. May be null, where then there will be no prioritization of direction.
* @param maxNodesSearched The maximum amount of allowed searched nodes. set to 0 for infinite.
*/
public DepthFirstSearch(Point2 start, Point2 endGoal, NodeComparable customComparable, int maxNodesSearched) {
if (start == null) throw new NullPointerException("Start point cannot be null.");
checkedNodes = new HashSet<>(); checkedNodes = new HashSet<>();
queue = new PriorityQueue<>(); queue = new PriorityQueue<>();
} this.comparable = customComparable;
this.endGoal = endGoal;
public DepthFirstSearch(int maxNodesSearched) { this.startNode = new Node(null, start, endGoal, customComparable);
this(); queue.add(startNode);
this.maxNodesSearched = maxNodesSearched; this.maxNodesSearched = maxNodesSearched;
} }
public DepthFirstSearch(int searchRadius, CoordinateValidatable validatable) { /**
this(searchRadius); * Instantiates a DFS search object.
setValidatable(validatable); * Where there is no comparable set and distance to goal will be used if there is one,
* and where there is no limit on amount of searched nodes.
* @param start The starting point of the search.
* @param endGoal The objective of the search, may be null.
*/
public DepthFirstSearch(Point2 start, Point2 endGoal) {
this(start, endGoal, null, 0);
} }
public DepthFirstSearch(CoordinateValidatable validatable) {
this();
setValidatable(validatable);
}
public void setValidatable(CoordinateValidatable validatable) { public Node getResult() {
this.coordValidatable = validatable; return results;
}
public void setStartPosition(int x, int y) {
startNode = new DFSNode(null, x, y, endNode);
}
public void setStartPosition(Point2 start) {
startNode = new DFSNode(null, start, endNode);
}
public void setEndPosition(int x, int y) {
endNode = new DFSNode(null, x, y, endNode);
}
public void setEndPosition(Point2 goal) {
endNode = new DFSNode(null, goal, endNode);
}
public DFSNode getFoundNode() {
return foundNode;
}
public void deleteTree() {
startNode = null;
endNode = null;
}
public boolean buildPathToEndNode() {
if (coordValidatable == null)
throw new IllegalStateException("Need to set a validator.");
if (startNode == null)
throw new IllegalStateException("Need to set the starting position.");
return findTarget(new CoordinateTargetValidatable() {
@Override
public boolean isCoordinateTarget(int x, int y) {
return endNode.getX() == x && endNode.getY() == y;
}
});
} }
/** /**
* Finds a coordinate determined by a target validator. If an end node is set, * @return the currentNode
* it will try going in that direction first.
*
* @param targetValidator
* @return If it succeeded in finding an end node.
*/ */
public boolean findTarget(CoordinateTargetValidatable targetValidator) { public Node getCurrentNode() {
if (coordValidatable == null) return currentValidNode;
throw new IllegalStateException("Need to set a validator."); }
if (targetValidator == null)
throw new NullArgumentException("targetValidator"); /**
if (startNode == null) * Makes a search step.
throw new IllegalStateException("Need to set the starting position."); * @param validity the validator.
* @param objective used to determine whether or not this DFS has founds its objective. Will use this over using the end goal.
* @return true if done, whether or not it found the objective is determined by the result.
*/
public boolean step(CoordinateValidatable validity, CoordinateTargetValidatable objective) {
if (validity == null) throw new NullPointerException("The validator cannot be null.");
Node node = queue.poll();
if (node == null) return true;
queue.clear(); if (checkedNodes.add(node) && validity.validate(node)) {
checkedNodes.clear(); currentValidNode = node;
if (objective != null) {
if (objective.isCoordinateTarget(currentValidNode)) {
DFSNode begin = startNode; results = currentValidNode;
if (!coordValidatable.validate(begin.x, begin.y)) return false; return true;
if (targetValidator.isCoordinateTarget(begin.x, begin.y)) return true; }
checkedNodes.add(begin); } else {
queue.add(begin); if (endGoal == null) throw new IllegalStateException("Cannot build path without a target or goal.");
while (!queue.isEmpty()) { if (endGoal.fastEquals(currentValidNode)) {
DFSNode n = queue.poll(); results = currentValidNode;
if (maxNodesSearched != -1 && checkedNodes.size() > maxNodesSearched) return false; return true;
n.child[0] = new DFSNode(n, n.getX() + 1, n.getY(), endNode);
n.child[1] = new DFSNode(n, n.getX() - 1, n.getY(), endNode);
n.child[2] = new DFSNode(n, n.getX(), n.getY() + 1, endNode);
n.child[3] = new DFSNode(n, n.getX(), n.getY() - 1, endNode);
for (int c = 0; c < n.child.length; c++) {
if (checkedNodes.add(n.child[c]) && coordValidatable.validate(n.child[c].x, n.child[c].y)) {
if (targetValidator.isCoordinateTarget(n.child[c].x, n.child[c].y)) {
foundNode = n.child[c];
return true;
}
queue.add(n.child[c]);
} }
} }
if (maxNodesSearched > 0 && getSearchedCount() >= maxNodesSearched) return true;
Node north = new Node(currentValidNode, currentValidNode.x, currentValidNode.y + 1, endGoal, comparable);
queue.add(north);
Node south = new Node(currentValidNode, currentValidNode.x, currentValidNode.y - 1, endGoal, comparable);
queue.add(south);
Node west = new Node(currentValidNode, currentValidNode.x - 1, currentValidNode.y, endGoal, comparable);
queue.add(west);
Node east = new Node(currentValidNode, currentValidNode.x + 1, currentValidNode.y, endGoal, comparable);
queue.add(east);
} }
return false; return false;
} }
/**
* Searches until complete. Completion is defined such that when a call to the step function does not return true.
* @param validator The validator to use to check if a node is considered allowed to be used.
* @param targetValidatable The validator to be used to determine whether or not the current node is the objective. May be null, however, the end goal must not be null in such a case.
* @return
*/
public boolean buildToGoal(CoordinateValidatable validator, CoordinateTargetValidatable targetValidatable) {
while (!step(validator, targetValidatable));
return results != null;
}
public int getSearchedCount() { public int getSearchedCount() {
return checkedNodes.size(); return checkedNodes.size();
} }

View File

@ -0,0 +1,61 @@
package ca.recrown.islandsurvivalcraft.utilities.pathfinding;
import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2;
public class Node extends Point2 implements Comparable<Point2> {
private final NodeComparable nodeComparable;
private final Point2 parent;
private final Point2 goal;
private Point2 child;
public Node(Point2 parent, int x, int y, Point2 goal, NodeComparable nodeComparable) {
super(x, y);
this.parent = parent;
this.goal = goal;
this.nodeComparable = nodeComparable;
}
public Node(Point2 parent, Point2 location, Point2 goal, NodeComparable nodeComparable) {
this(parent, location.x, location.y, goal, nodeComparable);
}
/**
* Sets the child of this node. Can only be set once.
* @param child the child to set.
*/
public void setChild(Point2 child) {
if (this.child != null) throw new IllegalStateException("Child has already been set.");
this.child = child;
}
@Override
public int compareTo(Point2 o) {
if (nodeComparable != null) return nodeComparable.compare(this, o);
if (goal == null) return 0;
double distDiff = distance(goal) - distance(o);
if (distDiff == 0) {
return 0;
} else if (distDiff < 0) {
return -1;
} else {
return 1;
}
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
/**
* @return the parent
*/
public Point2 getParent() {
return parent;
}
}

View File

@ -0,0 +1,7 @@
package ca.recrown.islandsurvivalcraft.utilities.pathfinding;
import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2;
public interface NodeComparable {
public int compare(Point2 current, Point2 other);
}

View File

@ -12,25 +12,22 @@ import ca.recrown.islandsurvivalcraft.utilities.pathfinding.CoordinateValidatabl
import ca.recrown.islandsurvivalcraft.utilities.pathfinding.DepthFirstSearch; import ca.recrown.islandsurvivalcraft.utilities.pathfinding.DepthFirstSearch;
public class IslandWorldMap { public class IslandWorldMap {
private final Cache<Point2, Double> blockValueCache; private final float TRANSITION_DEPTH_PORTION = 0.1f;
private final float DEEP_OCEAN_PORTION = 0.5f;
private final float HILL_PORTION = 0.4f;
private final float SHORE_PORTION = 0.095f;
private final Cache<Point2, Double> blockValueCache;
private final SimplexOctaveGenerator noiseGenerator; private final SimplexOctaveGenerator noiseGenerator;
public final islandValidator islandValidator = new islandValidator();
private final int NOISE_OCTAVES = 4; private final int NOISE_OCTAVES = 4;
private final float ISLAND_PERCENT = 0.36f; private final float ISLAND_PERCENT = 0.36f;
private final double NOISE_FREQ = 1.78D; private final double NOISE_FREQ = 1.78D;
private final double NOISE_AMP = 0.47D; private final double NOISE_AMP = 0.47D;
private final float SHALLOW_PORTION = 0.06f; private final float SHALLOW_PORTION = 0.06f;
private final double SCALE = 0.005D; private final double SCALE = 0.005D;
private final DepthFirstSearch dfs;
public IslandWorldMap(Random random) { public IslandWorldMap(Random random) {
dfs = new DepthFirstSearch(new CoordinateValidatable(){
@Override
public boolean validate(int x, int y) {
return isIsland(x, y);
}
});
this.noiseGenerator = new SimplexOctaveGenerator(random, NOISE_OCTAVES); this.noiseGenerator = new SimplexOctaveGenerator(random, NOISE_OCTAVES);
noiseGenerator.setScale(SCALE); noiseGenerator.setScale(SCALE);
this.blockValueCache = new Cache<>(131072); this.blockValueCache = new Cache<>(131072);
@ -57,10 +54,10 @@ public class IslandWorldMap {
*/ */
public boolean isEdgeOfIsland(int worldX, int worldZ) { public boolean isEdgeOfIsland(int worldX, int worldZ) {
return isIsland(worldX, worldZ) && return isIsland(worldX, worldZ) &&
!(isSameIsland(worldX, worldZ, worldX + 1, worldZ) && !(isIsland(worldX + 1, worldZ) &&
isSameIsland(worldX, worldZ, worldX - 1, worldZ) && isIsland(worldX - 1, worldZ) &&
isSameIsland(worldX, worldZ, worldX , worldZ + 1) && isIsland(worldX , worldZ + 1) &&
isSameIsland(worldX, worldZ, worldX, worldZ - 1)); isIsland(worldX, worldZ - 1));
} }
/** /**
@ -74,6 +71,22 @@ public class IslandWorldMap {
return isLand(worldX, worldZ) || isShallowPortion(worldX, worldZ); return isLand(worldX, worldZ) || isShallowPortion(worldX, worldZ);
} }
public boolean isHill(int worldX, int worldZ) {
return getWorldValue(worldX, worldZ) >= HILL_PORTION;
}
public boolean isShore(int worldX, int worldZ) {
return isLand(worldX, worldZ) && getWorldValue(worldX, worldZ) <= SHORE_PORTION;
}
public boolean isTransitional(int worldX, int worldZ) {
return !isIsland(worldX, worldZ) && getWorldValue(worldX, worldZ) >= -TRANSITION_DEPTH_PORTION;
}
public boolean isDeep(int worldX, int worldZ) {
return getWorldValue(worldX, worldZ) <= -DEEP_OCEAN_PORTION;
}
/** /**
* This block is considered a shallow portion of the island: * This block is considered a shallow portion of the island:
* The block height is less than the sea level, * The block height is less than the sea level,
@ -123,22 +136,16 @@ public class IslandWorldMap {
} }
/** /**
* Using DFS, determines if two given blocks are from the same island. * Determines of the two columns are part of the same island.
* Works by checking if a path can be drawn between these two. * If both points are connected consistently by island, than both are considered part of the same island.
* @param firstBlockX The x coord of the first block. * @param firstColumn The column of the first island.
* @param firstBlockZ The z coord of the first block. * @param secondColumn The column of the second island.
* @param secondBlockX The X coord of the first block. * @return If the two islands are connected.
* @param secondBlockZ The Z coord of the first block.
* @return true if the two blocks are on the same island, and false otherwise.
*/ */
public boolean isSameIsland(int firstBlockX, int firstBlockZ, int secondBlockX, int secondBlockZ) { public boolean isSameIsland(Point2 firstColumn, Point2 secondColumn) {
if (!isIsland(firstBlockX, firstBlockZ)) return false; DepthFirstSearch dfs = new DepthFirstSearch(firstColumn, secondColumn);
if (!isIsland(secondBlockX, secondBlockZ)) return false; boolean same = dfs.buildToGoal(islandValidator, null);
dfs.setStartPosition(firstBlockX, firstBlockZ); return same;
dfs.setEndPosition(secondBlockX, secondBlockZ);
boolean res = dfs.buildPathToEndNode();
dfs.deleteTree();
return res;
} }
/** /**
@ -150,22 +157,21 @@ public class IslandWorldMap {
*/ */
public Point2 findIslandOrigin(int worldX, int worldZ, CoordinateTargetValidatable coordinateTargetValidatable) { public Point2 findIslandOrigin(int worldX, int worldZ, CoordinateTargetValidatable coordinateTargetValidatable) {
if (!isIsland(worldX, worldZ)) throw new IllegalArgumentException("The given coordinates are not part is an island."); if (!isIsland(worldX, worldZ)) throw new IllegalArgumentException("The given coordinates are not part is an island.");
dfs.setStartPosition(worldX, worldZ);
final Point2 goal = new Point2(0, 0); final Point2 goal = new Point2(0, 0);
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(worldX, worldZ), goal);
Reference<Point2> closest = new Reference<>(); Reference<Point2> closest = new Reference<>();
dfs.findTarget(new CoordinateTargetValidatable(){ dfs.buildToGoal(islandValidator, new CoordinateTargetValidatable(){
Double closestDistance = null; Double closestDistance = null;
@Override @Override
public boolean isCoordinateTarget(int x, int y) { public boolean isCoordinateTarget(Point2 point) {
if (coordinateTargetValidatable != null && coordinateTargetValidatable.isCoordinateTarget(x, y)) { if (coordinateTargetValidatable != null && coordinateTargetValidatable.isCoordinateTarget(point)) {
closest.value = null; closest.value = null;
return true; return true;
} }
Point2 current = new Point2(x, y); double currentDistance = point.distance(goal);
double currentDistance = current.distance(goal);
if (closestDistance == null || (currentDistance) < closestDistance) { if (closestDistance == null || (currentDistance) < closestDistance) {
closestDistance = currentDistance; closestDistance = currentDistance;
closest.value = current; closest.value = point;
} }
return false; return false;
} }
@ -181,4 +187,13 @@ public class IslandWorldMap {
public int calculateIslandHash(int worldX, int worldZ) { public int calculateIslandHash(int worldX, int worldZ) {
return findIslandOrigin(worldX, worldZ).hashCode(); return findIslandOrigin(worldX, worldZ).hashCode();
} }
private class islandValidator implements CoordinateValidatable {
@Override
public boolean validate(Point2 point) {
return isIsland(point.x, point.y);
}
}
} }

View File

@ -36,8 +36,7 @@ public class UniqueBiomeGenerator implements BiomeGenerator {
Reference<BiomeInfo> currentIslandInfo = new Reference<>(); Reference<BiomeInfo> currentIslandInfo = new Reference<>();
Point2 islandOrigin = islandMap.findIslandOrigin(worldX, worldZ, new CoordinateTargetValidatable() { Point2 islandOrigin = islandMap.findIslandOrigin(worldX, worldZ, new CoordinateTargetValidatable() {
@Override @Override
public boolean isCoordinateTarget(int x, int y) { public boolean isCoordinateTarget(Point2 coord) {
Point2 coord = new Point2(x, y);
BiomeInfo info = biomeCache.get(coord); BiomeInfo info = biomeCache.get(coord);
if (info == null) return false; if (info == null) return false;
currentIslandInfo.value = info; currentIslandInfo.value = info;

View File

@ -30,10 +30,6 @@ import ca.recrown.islandsurvivalcraft.world.generation.shaders.WorldHeightShader
import ca.recrown.islandsurvivalcraft.world.generation.shaders.WorldLayerShader; import ca.recrown.islandsurvivalcraft.world.generation.shaders.WorldLayerShader;
public class IslandWorldChunkGenerator extends ChunkGenerator implements Listener { public class IslandWorldChunkGenerator extends ChunkGenerator implements Listener {
private final float TRANSITION_DEPTH_PORTION = 0.1f;
private final float DEEP_OCEAN_PORTION = 0.5f;
private final float HILL_BIOME_PORTION = 0.4f;
private final float SHORE_PORTION = 0.095f;
private final int BEDROCK_HEIGHT = 5; private final int BEDROCK_HEIGHT = 5;
private volatile GeneratorModes generatorType; private volatile GeneratorModes generatorType;
private volatile boolean initialized = false; private volatile boolean initialized = false;
@ -106,29 +102,22 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene
} }
if (localBiomeInfos[localX][localZ] == null) throw new IllegalStateException("Biome column produced was null."); if (localBiomeInfos[localX][localZ] == null) throw new IllegalStateException("Biome column produced was null.");
Biome currentBiome = null; Biome currentBiome = null;
if (islandMap.isIsland(worldX, worldZ)) {
LandBiomeInfo biomeInfo = (LandBiomeInfo) localBiomeInfos[localX][localZ]; BiomeInfo biomeInfo = localBiomeInfos[localX][localZ];
double islandVal = islandMap.getWorldValue(worldX, worldZ); if (islandMap.isHill(worldX, worldZ)) {
if (islandVal >= HILL_BIOME_PORTION) { currentBiome = ((LandBiomeInfo) biomeInfo).getHillBiome();
currentBiome = biomeInfo.getHillBiome(); } else if (islandMap.isShore(worldX, worldZ)) {
} else { currentBiome = ((LandBiomeInfo) biomeInfo).getShoreBiome();
if (islandVal <= SHORE_PORTION) { } else if (islandMap.isLand(worldX, worldZ)) {
currentBiome = biomeInfo.getShoreBiome(); currentBiome = biomeInfo.getMainBiome();
} else { } else if (islandMap.isTransitional(worldX, worldZ)) {
currentBiome = biomeInfo.getMainBiome(); currentBiome = biomeInfo.getTransitionBiome();
} } else if (islandMap.isDeep(worldX, worldZ)) {
} currentBiome = ((OceanBiomeInfo) biomeInfo).getDeepAlternative();
} else { } else {
OceanBiomeInfo biomeInfo = (OceanBiomeInfo) localBiomeInfos[localX][localZ]; currentBiome = biomeInfo.getMainBiome();
double oceanVal = islandMap.getWorldValue(worldX, worldZ);
if (oceanVal >= -TRANSITION_DEPTH_PORTION) {
currentBiome = biomeInfo.getTransitionOcean();
} else if (oceanVal >= -DEEP_OCEAN_PORTION) {
currentBiome = biomeInfo.getOcean();
} else {
currentBiome = biomeInfo.getDeepAlternative();
}
} }
for (int y = 0; y < worldInfo.getWorldHeight(); y++) { for (int y = 0; y < worldInfo.getWorldHeight(); y++) {
biomeGrid.setBiome(localX, y, localZ, currentBiome); biomeGrid.setBiome(localX, y, localZ, currentBiome);
} }

View File

@ -30,6 +30,7 @@ public class WorldHeightShader {
height = (int) Math.floor(calculateTerrainFactor(worldX, worldZ, seaLevel * 0.8d, 1.7d, 0.5d, 1d, 0.09d)); height = (int) Math.floor(calculateTerrainFactor(worldX, worldZ, seaLevel * 0.8d, 1.7d, 0.5d, 1d, 0.09d));
} else { } else {
height = getIslandBiomeHeight(worldX, worldZ, biomeInfo.getMainBiome(), 0d); height = getIslandBiomeHeight(worldX, worldZ, biomeInfo.getMainBiome(), 0d);
if (!mapper.isShore(worldX, worldZ)) height ++;
} }
height = Math.max(minimumHeight, height); height = Math.max(minimumHeight, height);

View File

@ -14,6 +14,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.TestInstance.Lifecycle;
import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2;
import ca.recrown.islandsurvivalcraft.utilities.pathfinding.CoordinateTargetValidatable; import ca.recrown.islandsurvivalcraft.utilities.pathfinding.CoordinateTargetValidatable;
import ca.recrown.islandsurvivalcraft.utilities.pathfinding.CoordinateValidatable; import ca.recrown.islandsurvivalcraft.utilities.pathfinding.CoordinateValidatable;
@ -31,26 +32,24 @@ public class DepthFirstSearchTest {
} }
@Override @Override
public boolean validate(int x, int y) { public boolean validate(Point2 point) {
try { try {
return map[y][x] >= 1; return map[point.y][point.x] >= 1;
} catch (IndexOutOfBoundsException e) { } catch (IndexOutOfBoundsException e) {
return false; return false;
} }
} }
@Override @Override
public boolean isCoordinateTarget(int x, int y) { public boolean isCoordinateTarget(Point2 point) {
try { try {
return map[y][x] == 2; return map[point.y][point.x] == 2;
} catch (IndexOutOfBoundsException e) { } catch (IndexOutOfBoundsException e) {
return false; return false;
} }
} }
} }
private DepthFirstSearch dfs;
private byte[][] mapA; private byte[][] mapA;
private byte[][] mapB; private byte[][] mapB;
private byte[][] mapC; private byte[][] mapC;
@ -62,7 +61,6 @@ public class DepthFirstSearchTest {
@BeforeAll @BeforeAll
public void setUp() throws IOException { public void setUp() throws IOException {
dfs = new DepthFirstSearch();
mapA = readMap("DFSTestMapSmallA.txt"); mapA = readMap("DFSTestMapSmallA.txt");
mapB = readMap("DFSTestMapSmallB.txt"); mapB = readMap("DFSTestMapSmallB.txt");
mapC = readMap("DFSTestMapSmallC.txt"); mapC = readMap("DFSTestMapSmallC.txt");
@ -100,130 +98,126 @@ public class DepthFirstSearchTest {
@Test @Test
public void testDFSBuildPathToEndNodeMapAValid() public void testDFSBuildPathToEndNodeMapAValid()
{ {
dfs.setValidatable(new Validator(mapA)); DepthFirstSearch dfs = new DepthFirstSearch(new Point2(1, 2), new Point2(1, 0));
dfs.setStartPosition(1, 2); dfs.buildToGoal(new Validator(mapA), null);
dfs.setEndPosition(1, 0); assertTrue(dfs.getResult() != null);
assertTrue(dfs.buildPathToEndNode());
} }
@Test @Test
public void testDFSBuildPathToEndNodeMapBValid() public void testDFSBuildPathToEndNodeMapBValid()
{ {
dfs.setValidatable(new Validator(mapB)); DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(3, 2));
dfs.setStartPosition(3, 0); dfs.buildToGoal(new Validator(mapB), null);
dfs.setEndPosition(3, 2); assertTrue(dfs.getResult() != null);
assertTrue(dfs.buildPathToEndNode());
} }
@Test @Test
public void testDFSBuildPathToEndNodeMapDValid() public void testDFSBuildPathToEndNodeMapDValid()
{ {
dfs.setValidatable(new Validator(mapD)); DepthFirstSearch dfs = new DepthFirstSearch(new Point2(0, 0), new Point2(0, 3));
dfs.setStartPosition(0, 0); dfs.buildToGoal(new Validator(mapD), null);
dfs.setEndPosition(0, 3); assertTrue(dfs.getResult() != null);
assertTrue(dfs.buildPathToEndNode());
} }
@Test @Test
public void testDFSBuildPathToEndNodeMapCInvalid() public void testDFSBuildPathToEndNodeMapCInvalid()
{ {
dfs.setValidatable(new Validator(mapC)); DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(3, 2));
dfs.setStartPosition(3, 0); dfs.buildToGoal(new Validator(mapC), null);
dfs.setEndPosition(3, 2); assertFalse(dfs.getResult() != null);
assertFalse(dfs.buildPathToEndNode());
} }
@Test @Test
public void testDFSBuildPathToEndNodeMapEInvalid() public void testDFSBuildPathToEndNodeMapEInvalid()
{ {
dfs.setValidatable(new Validator(mapE)); DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(3, 2));
dfs.setStartPosition(3, 0); dfs.buildToGoal(new Validator(mapE), null);
dfs.setEndPosition(3, 2); assertFalse(dfs.getResult() != null);
assertFalse(dfs.buildPathToEndNode());
} }
@Test @Test
public void testDFSFindEndNodeMapBValid() { public void testDFSFindEndNodeMapBValid() {
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(0, 0));
Validator validator = new Validator(mapB); Validator validator = new Validator(mapB);
dfs.setValidatable(validator); dfs.buildToGoal(validator, validator);
dfs.setStartPosition(3, 0); assertTrue(dfs.getResult() != null);
assertEquals(1, dfs.getResult().x);
assertTrue(dfs.findTarget(validator)); assertEquals(2, dfs.getResult().y);
assertEquals(1, dfs.getFoundNode().getX());
assertEquals(2, dfs.getFoundNode().getY());
} }
@Test @Test
public void testDFSFindEndNodeMapDValid() { public void testDFSFindEndNodeMapDValid() {
Validator validator = new Validator(mapD); Validator validator = new Validator(mapD);
dfs = new DepthFirstSearch(validator); DepthFirstSearch dfs = new DepthFirstSearch(new Point2(0, 0), new Point2(0, 0));
dfs.setValidatable(validator); dfs.buildToGoal(validator, validator);
dfs.setStartPosition(0, 0); assertTrue(dfs.getResult() != null);
assertEquals(0, dfs.getResult().x);
assertTrue(dfs.findTarget(validator)); assertEquals(3, dfs.getResult().y);
assertEquals(0, dfs.getFoundNode().getX());
assertEquals(3, dfs.getFoundNode().getY());
} }
@Test @Test
public void testDFSFindEndNodeMapHValid() { public void testDFSFindEndNodeMapHValid() {
Validator validator = new Validator(mapH); Validator validator = new Validator(mapH);
dfs.setValidatable(validator); DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(0, 0));
dfs.setStartPosition(3, 0); dfs.buildToGoal(validator, validator);
assertTrue(dfs.findTarget(validator)); assertTrue(dfs.getResult() != null);
assertEquals(95, dfs.getFoundNode().getX()); assertEquals(95, dfs.getResult().x);
assertEquals(49, dfs.getFoundNode().getY()); assertEquals(49, dfs.getResult().y);
} }
@Test @Test
public void testDFSFindEndNodeMapHInvalidLimited() { public void testDFSFindEndNodeMapHInvalidLimited() {
Validator validator = new Validator(mapH); Validator validator = new Validator(mapH);
DepthFirstSearch temporary = new DepthFirstSearch(128, validator); DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(0, 0), null, 128);
temporary.setStartPosition(3, 0); dfs.buildToGoal(validator, validator);
assertFalse(temporary.findTarget(validator)); assertFalse(dfs.getResult() != null);
} }
@Test @Test
public void testDFSFindEndNodeMapEInvalid() { public void testDFSFindEndNodeMapEInvalid() {
Validator validator = new Validator(mapE); Validator validator = new Validator(mapE);
dfs = new DepthFirstSearch(); DepthFirstSearch dfs = new DepthFirstSearch(new Point2(0, 0), new Point2(0, 0), null, 128);
dfs.setValidatable(validator); dfs.buildToGoal(validator, validator);
dfs.setStartPosition(0, 0); assertFalse(dfs.getResult() != null);
assertFalse(dfs.findTarget(validator));
} }
@Test @Test
public void testDFSFindEndNodeMapFValid() { public void testDFSFindEndNodeMapFValid() {
Validator validator = new Validator(mapF); Validator validator = new Validator(mapF);
dfs.setValidatable(validator); DepthFirstSearch dfs = new DepthFirstSearch(new Point2(0, 0), new Point2(0, 0), null, 1024);
dfs.setStartPosition(0, 0); dfs.buildToGoal(validator, validator);
assertTrue(dfs.getResult() != null);
assertTrue(dfs.findTarget(validator)); assertEquals(26, dfs.getResult().x);
assertEquals(26, dfs.getFoundNode().getX()); assertEquals(32, dfs.getResult().y);
assertEquals(32, dfs.getFoundNode().getY());
} }
@Test @Test
public void testDFSFindEndNodeMapGInvalid() { public void testDFSFindEndNodeMapGInvalid() {
Validator validator = new Validator(mapG); Validator validator = new Validator(mapG);
dfs.setValidatable(validator); DepthFirstSearch dfs = new DepthFirstSearch(new Point2(0, 0), new Point2(0, 0), null, 128);
dfs.setStartPosition(0, 0); dfs.buildToGoal(validator, validator);
assertFalse(dfs.getResult() != null);
assertFalse(dfs.findTarget(validator));
} }
@Test @Test
public void testDFSFindEndNodeMapHWithAssistValid() { public void testDFSFindEndNodeMapHWithAssistValid() {
Validator validator = new Validator(mapH); Validator validator = new Validator(mapH);
dfs.setValidatable(validator); DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(95, 49), null, 0);
dfs.setStartPosition(3, 0); dfs.buildToGoal(validator, validator);
dfs.setEndPosition(42, 84); assertTrue(dfs.getResult() != null);
assertEquals(95, dfs.getResult().x);
assertEquals(49, dfs.getResult().y);
}
assertTrue(dfs.findTarget(validator)); @Test
assertEquals(95, dfs.getFoundNode().getX()); public void testDFSFindEndNodeMapHWithoutAssistValid() {
assertEquals(49, dfs.getFoundNode().getY()); Validator validator = new Validator(mapH);
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(0, 0), null, 0);
dfs.buildToGoal(validator, validator);
assertTrue(dfs.getResult() != null);
assertEquals(95, dfs.getResult().x);
assertEquals(49, dfs.getResult().y);
} }
} }

View File

@ -62,7 +62,7 @@ public class IslandWorldMapTest {
Point2 origin = null; Point2 origin = null;
for (int i = 0; i < GRIDSIZE; i++) { for (int i = 0; i < GRIDSIZE; i++) {
if (origin != null) { if (origin != null) {
if (mapper.isSameIsland(origin.x, origin.y, i, 0)) { if (mapper.isSameIsland(origin, new Point2(i, 0))) {
assertEquals(origin, mapper.findIslandOrigin(i, 0), String.format("Looking at (%d, 0)", i)); assertEquals(origin, mapper.findIslandOrigin(i, 0), String.format("Looking at (%d, 0)", i));
} else { } else {
origin = null; origin = null;
@ -80,7 +80,7 @@ public class IslandWorldMapTest {
for (int y = 0; y < gridSize; y++) { for (int y = 0; y < gridSize; y++) {
for (int x = 0; x < gridSize; x++) { for (int x = 0; x < gridSize; x++) {
if (origin != null) { if (origin != null) {
if (mapper.isSameIsland(origin.x, origin.y, x, y)) { if (mapper.isSameIsland(origin, new Point2(x, y))) {
assertEquals(origin, mapper.findIslandOrigin(x, y), String.format("Looking at (%d, %d)", x, y)); assertEquals(origin, mapper.findIslandOrigin(x, y), String.format("Looking at (%d, %d)", x, y));
} else { } else {
origin = null; origin = null;