Made command system and DFS usage more intuitive.

This commit is contained in:
Harrison Deng 2020-05-11 12:48:16 -05:00
parent 73adefdc7f
commit 19552f1a2b
11 changed files with 107 additions and 120 deletions

View File

@ -2,13 +2,11 @@ package ca.recrown.islandsurvivalcraft.interaction.commands;
import java.util.Arrays;
import java.util.HashSet;
import java.util.concurrent.CompletableFuture;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import ca.recrown.islandsurvivalcraft.IslandSurvivalCraft;
@ -31,15 +29,8 @@ public class CommandProcessor implements CommandExecutor {
}
if (initializedCommands.add(currentCommand)) currentCommand.initialize(islandSurvivalCraft);
String[] commandArguments = Arrays.copyOfRange(args, 1, args.length);
CompletableFuture<String> results = currentCommand.invoke(sender, commandArguments);
results.thenAccept(s -> {
if (sender instanceof Player) {
Player player = (Player) sender;
if (!player.isOnline()) return;
}
sender.sendMessage(s != null ? s : "");
});
boolean success = currentCommand.invoke(sender, commandArguments);
if (!success) sender.sendMessage(String.format("The command arguments were incorrect. Please refer to \"/IslandSurvivalCraft help %s\" for help.", currentCommand.toString()));
return true;
}
}

View File

@ -2,8 +2,6 @@ package ca.recrown.islandsurvivalcraft.interaction.commands;
import ca.recrown.islandsurvivalcraft.interaction.commands.runnables.HighlightIslandCommand;
import java.util.concurrent.CompletableFuture;
import org.bukkit.command.CommandSender;
import ca.recrown.islandsurvivalcraft.IslandSurvivalCraft;
@ -28,7 +26,7 @@ public enum Commands implements CommandRunnable {
}
@Override
public CompletableFuture<String> invoke(CommandSender sender, String[] args) {
public boolean invoke(CommandSender sender, String[] args) {
return commandRunnable.invoke(sender, args);
}

View File

@ -1,7 +1,5 @@
package ca.recrown.islandsurvivalcraft.interaction.commands.runnables;
import java.util.concurrent.CompletableFuture;
import org.bukkit.command.CommandSender;
import ca.recrown.islandsurvivalcraft.IslandSurvivalCraft;
@ -22,9 +20,9 @@ public interface CommandRunnable {
* Should check for correct arguments.
* @param sender The sender of this command.
* @param args The arguments given for this command. May be length of 0, in which case there were no arguments.
* @return The completable future for the command. The completable future should ultimately return a message in the form of the string letting the user know the the completed execution, or a failure message.
* @return True if the execution of this command was a success, and false to display the help message for this command.
*/
public CompletableFuture<String> invoke(CommandSender sender, String[] args);
public boolean invoke(CommandSender sender, String[] args);
/**
* Initializes the command. Is run the first time this command is run.

View File

@ -1,7 +1,5 @@
package ca.recrown.islandsurvivalcraft.interaction.commands.runnables;
import java.util.concurrent.CompletableFuture;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
@ -16,17 +14,16 @@ public class HelpCommand implements CommandRunnable {
}
@Override
public CompletableFuture<String> invoke(CommandSender sender, String[] args) {
CompletableFuture<String> completableFuture = new CompletableFuture<>();
public boolean invoke(CommandSender sender, String[] args) {
if (args == null || args.length == 0) {
completableFuture.complete(helpMessage);
sender.sendMessage(helpMessage);
return true;
} else {
Commands command = null;
try {
command = Commands.valueOf(args[0].toUpperCase());
} catch (IllegalArgumentException e) {
completableFuture.complete(ChatColor.LIGHT_PURPLE + String.format("The request %s does not exist. Please type \"/IslandSurvivalCraft help\" for more info.", args[0]));
return completableFuture;
return false;
}
StringBuilder sb = new StringBuilder();
sb.append(ChatColor.YELLOW + args[0]);
@ -34,16 +31,16 @@ public class HelpCommand implements CommandRunnable {
sb.append(command.getDescription());
sb.append("\n" + ChatColor.WHITE);
sb.append(command.getDetailedDescription());
completableFuture.complete(sb.toString().trim());
sender.sendMessage(sb.toString().trim());
}
return completableFuture;
return true;
}
@Override
public void initialize(IslandSurvivalCraft islandSurvivalCraft) {
StringBuilder stringBuilder = new StringBuilder();
Commands[] commands = Commands.values();
stringBuilder.append(ChatColor.YELLOW + "Commands:\n");
stringBuilder.append(ChatColor.YELLOW + "IslandSurvivalCraft Commands:\n");
for (int i = 0; i < commands.length; i++) {
stringBuilder.append(ChatColor.GREEN + commands[i].toString());
stringBuilder.append(": " + ChatColor.WHITE);

View File

@ -1,5 +1,6 @@
package ca.recrown.islandsurvivalcraft.interaction.commands.runnables;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.concurrent.CompletableFuture;
@ -32,34 +33,32 @@ public class HighlightIslandCommand implements CommandRunnable {
}
@Override
public CompletableFuture<String> invoke(CommandSender sender, String[] args) {
CompletableFuture<String> completableFuture = new CompletableFuture<>();
public boolean invoke(CommandSender sender, String[] args) {
if (!(sender instanceof Player)) {
completableFuture.complete("This command can only be run as a player!");
return completableFuture;
sender.sendMessage("This command can only be run as a player!");
return false;
}
Player player = (Player) sender;
if (args[0].isEmpty()) {
completableFuture.complete("Arguments required. Please refer to \"/IslandSurvivalCraft help\" for more info.");
return completableFuture;
return false;
}
if (args[0].toLowerCase().equals("start")) {
if (playersHighlighting.add(player)) {
waitingList.add(new PlayerIslandHighlighter(player));
completableFuture.complete("You are now highlighting islands.");
sender.sendMessage("You are now highlighting islands.");
} else {
completableFuture.complete("You are already highlighting islands.");
sender.sendMessage("You are already highlighting islands.");
}
} else if (args[0].toLowerCase().equals("stop")) {
if (playersHighlighting.remove(player)) {
completableFuture.complete("You are no longer highlighting islands.");
sender.sendMessage("You are no longer highlighting islands.");
} else {
completableFuture.complete("You weren't highlighting islands.");
sender.sendMessage("You weren't highlighting islands.");
}
} else {
completableFuture.complete(String.format("Argument \"%s\" was not understood. Please refer to \"/IslandSurvivalCraft help\" for more info.", args[0]));
return false;
}
return completableFuture;
return true;
}
@Override
@ -80,16 +79,7 @@ public class HighlightIslandCommand implements CommandRunnable {
int playerX = playerLocation.getBlockX(), playerY = playerLocation.getBlockY(), playerZ = playerLocation.getBlockZ();
IslandWorldMap map = worldInfo.getIslandMap();
for (int i = 0; i < 8; i++) {
highlighter.update(new Point2(playerX, playerZ), map);
}
LinkedList<Point2> highlighted = highlighter.getHighlighted();
for (Point2 point2 : highlighted) {
if (point2 != null) {
spawnParticle(world, point2.x, playerY, point2.y);
}
}
//TODO SPAWN HIGHLIGHTING PARTICLES.
waitingList.add(highlighter);
}
}, 0, 8);
@ -106,26 +96,19 @@ public class HighlightIslandCommand implements CommandRunnable {
}
private class PlayerIslandHighlighter {
public final int TRAIL_LENGTH = 64;
private final int MAX_SEARCH = 20;
private final int MAX_SEARCH_DIST = 16;
private final int MAX_SEARCH = 0;
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)) {
public CompletableFuture<ArrayList<Point2>> update(Point2 playerLocation, IslandWorldMap islandWorldMap, IslandSurvivalCraft isc) {
CompletableFuture<ArrayList<Point2>> completableFuture = new CompletableFuture<>();
Bukkit.getScheduler().runTaskAsynchronously(isc, () -> {
ArrayList<Point2> border = new ArrayList<>(64);
while (dfs == null || dfs.step(islandWorldMap.islandValidator, (p) -> false)) {
dfs = new DepthFirstSearch(playerLocation, null, (p1, p2) -> {
double diff = islandWorldMap.getWorldValue(p1.x, p1.y) - islandWorldMap.getWorldValue(p2.x, p2.y);
if (diff == 0) {
@ -136,18 +119,15 @@ public class HighlightIslandCommand implements CommandRunnable {
return -1;
}
}, MAX_SEARCH);
}
Point2 currentPoint = dfs.getCurrentNode();
if (currentPoint != null && islandWorldMap.isEdgeOfIsland(currentPoint.x, currentPoint.y)) {
addHighlight(currentPoint);
border.add(currentPoint);
}
}
completableFuture.complete(border);
});
/**
* @return the highlighted
*/
public LinkedList<Point2> getHighlighted() {
return highlighted;
return completableFuture;
}
}
}

View File

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

View File

@ -63,7 +63,7 @@ public class DepthFirstSearch {
* @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) {
public boolean step(CoordinateValidatable validity, CoordinateValidatable objective) {
if (validity == null) throw new NullPointerException("The validator cannot be null.");
Node node = queue.poll();
if (node == null) return true;
@ -71,7 +71,7 @@ public class DepthFirstSearch {
if (checkedNodes.add(node) && validity.validate(node)) {
currentValidNode = node;
if (objective != null) {
if (objective.isCoordinateTarget(currentValidNode)) {
if (objective.validate(currentValidNode)) {
results = currentValidNode;
return true;
}
@ -101,7 +101,7 @@ public class DepthFirstSearch {
* @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) {
public boolean buildToGoal(CoordinateValidatable validator, CoordinateValidatable targetValidatable) {
while (!step(validator, targetValidatable));
return results != null;
}

View File

@ -0,0 +1,20 @@
package ca.recrown.islandsurvivalcraft.utilities.pathfinding;
import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2;
public class LinkedCoordinateValidator implements CoordinateValidatable {
private final CoordinateValidatable[] CoordinateValidatables;
public LinkedCoordinateValidator(CoordinateValidatable...coordinateValidatables) {
this.CoordinateValidatables = coordinateValidatables;
}
@Override
public boolean validate(Point2 point) {
for (int i = 0; i < CoordinateValidatables.length; i++) {
if (CoordinateValidatables[i].validate(point)) return true;
}
return false;
}
}

View File

@ -7,7 +7,6 @@ import org.bukkit.util.noise.SimplexOctaveGenerator;
import ca.recrown.islandsurvivalcraft.utilities.caching.Cache;
import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2;
import ca.recrown.islandsurvivalcraft.utilities.datatypes.Reference;
import ca.recrown.islandsurvivalcraft.utilities.pathfinding.CoordinateTargetValidatable;
import ca.recrown.islandsurvivalcraft.utilities.pathfinding.CoordinateValidatable;
import ca.recrown.islandsurvivalcraft.utilities.pathfinding.DepthFirstSearch;
@ -152,19 +151,19 @@ public class IslandWorldMap {
* Find the island's origin block. Will call the coordinate target validatable to end early if nessecary.
* @param worldX The x coordinate of the island block in question.
* @param worldZ The y coordinate of the island block in question.
* @param coordinateTargetValidatable The coordinate target validator to use. Optional and is null.
* @param targetValidator The coordinate target validator to use. Optional and is null.
* @return The island origin point.
*/
public Point2 findIslandOrigin(int worldX, int worldZ, CoordinateTargetValidatable coordinateTargetValidatable) {
public Point2 findIslandOrigin(int worldX, int worldZ, CoordinateValidatable targetValidator) {
if (!isIsland(worldX, worldZ)) throw new IllegalArgumentException("The given coordinates are not part is an island.");
final Point2 goal = new Point2(0, 0);
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(worldX, worldZ), goal);
Reference<Point2> closest = new Reference<>();
dfs.buildToGoal(islandValidator, new CoordinateTargetValidatable(){
dfs.buildToGoal(islandValidator, new CoordinateValidatable() {
Double closestDistance = null;
@Override
public boolean isCoordinateTarget(Point2 point) {
if (coordinateTargetValidatable != null && coordinateTargetValidatable.isCoordinateTarget(point)) {
public boolean validate(Point2 point) {
if (targetValidator != null && targetValidator.validate(point)) {
closest.value = null;
return true;
}

View File

@ -7,7 +7,7 @@ import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2;
import ca.recrown.islandsurvivalcraft.utilities.datatypes.Reference;
import ca.recrown.islandsurvivalcraft.utilities.floodfill.Floodable;
import ca.recrown.islandsurvivalcraft.utilities.floodfill.Flooder;
import ca.recrown.islandsurvivalcraft.utilities.pathfinding.CoordinateTargetValidatable;
import ca.recrown.islandsurvivalcraft.utilities.pathfinding.CoordinateValidatable;
import ca.recrown.islandsurvivalcraft.world.BiomeMap;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMap;
import ca.recrown.islandsurvivalcraft.world.TemperatureMap;
@ -34,9 +34,9 @@ public class UniqueBiomeGenerator implements BiomeGenerator {
}
Reference<BiomeInfo> currentIslandInfo = new Reference<>();
Point2 islandOrigin = islandMap.findIslandOrigin(worldX, worldZ, new CoordinateTargetValidatable() {
Point2 islandOrigin = islandMap.findIslandOrigin(worldX, worldZ, new CoordinateValidatable() {
@Override
public boolean isCoordinateTarget(Point2 coord) {
public boolean validate(Point2 coord) {
BiomeInfo info = biomeCache.get(coord);
if (info == null) return false;
currentIslandInfo.value = info;

View File

@ -15,7 +15,6 @@ import org.junit.jupiter.api.TestInstance;
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.CoordinateValidatable;
/**
@ -24,7 +23,7 @@ import ca.recrown.islandsurvivalcraft.utilities.pathfinding.CoordinateValidatabl
@TestInstance(Lifecycle.PER_CLASS)
public class DepthFirstSearchTest {
private class Validator implements CoordinateValidatable, CoordinateTargetValidatable {
private class Validator implements CoordinateValidatable {
private byte[][] map;
public Validator(byte[][] map) {
@ -39,15 +38,24 @@ public class DepthFirstSearchTest {
return false;
}
}
}
private class TargetValidator implements CoordinateValidatable {
private byte[][] map;
public TargetValidator(byte[][] map) {
this.map = map;
}
@Override
public boolean isCoordinateTarget(Point2 point) {
public boolean validate(Point2 point) {
try {
return map[point.y][point.x] == 2;
} catch (IndexOutOfBoundsException e) {
return false;
}
}
}
private byte[][] mapA;
@ -139,7 +147,8 @@ public class DepthFirstSearchTest {
public void testDFSFindEndNodeMapBValid() {
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(0, 0));
Validator validator = new Validator(mapB);
dfs.buildToGoal(validator, validator);
TargetValidator targetValidator = new TargetValidator(mapB);
dfs.buildToGoal(validator, targetValidator);
assertTrue(dfs.getResult() != null);
assertEquals(1, dfs.getResult().x);
assertEquals(2, dfs.getResult().y);
@ -148,8 +157,9 @@ public class DepthFirstSearchTest {
@Test
public void testDFSFindEndNodeMapDValid() {
Validator validator = new Validator(mapD);
TargetValidator targetValidator = new TargetValidator(mapD);
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(0, 0), new Point2(0, 0));
dfs.buildToGoal(validator, validator);
dfs.buildToGoal(validator, targetValidator);
assertTrue(dfs.getResult() != null);
assertEquals(0, dfs.getResult().x);
assertEquals(3, dfs.getResult().y);
@ -158,8 +168,9 @@ public class DepthFirstSearchTest {
@Test
public void testDFSFindEndNodeMapHValid() {
Validator validator = new Validator(mapH);
TargetValidator targetValidator = new TargetValidator(mapH);
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(0, 0));
dfs.buildToGoal(validator, validator);
dfs.buildToGoal(validator, targetValidator);
assertTrue(dfs.getResult() != null);
assertEquals(95, dfs.getResult().x);
@ -169,8 +180,9 @@ public class DepthFirstSearchTest {
@Test
public void testDFSFindEndNodeMapHInvalidLimited() {
Validator validator = new Validator(mapH);
TargetValidator targetValidator = new TargetValidator(mapH);
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(0, 0), null, 128);
dfs.buildToGoal(validator, validator);
dfs.buildToGoal(validator, targetValidator);
assertFalse(dfs.getResult() != null);
}
@ -178,16 +190,18 @@ public class DepthFirstSearchTest {
@Test
public void testDFSFindEndNodeMapEInvalid() {
Validator validator = new Validator(mapE);
TargetValidator targetValidator = new TargetValidator(mapE);
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(0, 0), new Point2(0, 0), null, 128);
dfs.buildToGoal(validator, validator);
dfs.buildToGoal(validator, targetValidator);
assertFalse(dfs.getResult() != null);
}
@Test
public void testDFSFindEndNodeMapFValid() {
Validator validator = new Validator(mapF);
TargetValidator targetValidator = new TargetValidator(mapF);
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(0, 0), new Point2(0, 0), null, 1024);
dfs.buildToGoal(validator, validator);
dfs.buildToGoal(validator, targetValidator);
assertTrue(dfs.getResult() != null);
assertEquals(26, dfs.getResult().x);
assertEquals(32, dfs.getResult().y);
@ -196,16 +210,18 @@ public class DepthFirstSearchTest {
@Test
public void testDFSFindEndNodeMapGInvalid() {
Validator validator = new Validator(mapG);
TargetValidator targetValidator = new TargetValidator(mapG);
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(0, 0), new Point2(0, 0), null, 128);
dfs.buildToGoal(validator, validator);
dfs.buildToGoal(validator, targetValidator);
assertFalse(dfs.getResult() != null);
}
@Test
public void testDFSFindEndNodeMapHWithAssistValid() {
Validator validator = new Validator(mapH);
TargetValidator targetValidator = new TargetValidator(mapH);
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(95, 49), null, 0);
dfs.buildToGoal(validator, validator);
dfs.buildToGoal(validator, targetValidator);
assertTrue(dfs.getResult() != null);
assertEquals(95, dfs.getResult().x);
assertEquals(49, dfs.getResult().y);
@ -214,8 +230,9 @@ public class DepthFirstSearchTest {
@Test
public void testDFSFindEndNodeMapHWithoutAssistValid() {
Validator validator = new Validator(mapH);
TargetValidator targetValidator = new TargetValidator(mapH);
DepthFirstSearch dfs = new DepthFirstSearch(new Point2(3, 0), new Point2(0, 0), null, 0);
dfs.buildToGoal(validator, validator);
dfs.buildToGoal(validator, targetValidator);
assertTrue(dfs.getResult() != null);
assertEquals(95, dfs.getResult().x);
assertEquals(49, dfs.getResult().y);