Island info data structure added.

In addition: changed the world info management to use world name as key.
This commit is contained in:
Harrison Deng 2020-05-11 17:46:30 -05:00
parent 44518270e1
commit bd7f591f36
13 changed files with 356 additions and 79 deletions

View File

@ -44,7 +44,7 @@ public class IslandSurvivalCraft extends JavaPlugin implements Listener {
} catch (NullPointerException | IllegalArgumentException e) {
gID = GeneratorModes.UNIQUE;
}
return worldInfoManager.getChunkGenerator(getServer().getWorld(worldName), gID);
return worldInfoManager.getChunkGenerator(worldName, gID);
}
/**

View File

@ -5,12 +5,12 @@ import ca.recrown.islandsurvivalcraft.interaction.commands.runnables.HighlightIs
import org.bukkit.command.CommandSender;
import ca.recrown.islandsurvivalcraft.IslandSurvivalCraft;
import ca.recrown.islandsurvivalcraft.interaction.commands.runnables.CommandRunnable;
import ca.recrown.islandsurvivalcraft.interaction.commands.runnables.HelpCommand;
import ca.recrown.islandsurvivalcraft.interaction.commands.runnables.*;
public enum Commands implements CommandRunnable {
HIGHLIGHT(new HighlightIslandCommand()),
HELP(new HelpCommand()),
VALUE(new ValueRunnable()),
;
private final CommandRunnable commandRunnable;

View File

@ -2,7 +2,9 @@ package ca.recrown.islandsurvivalcraft.interaction.commands.runnables;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import org.bukkit.Bukkit;
@ -16,12 +18,12 @@ import org.bukkit.entity.Player;
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.Information.IslandInformation;
public class HighlightIslandCommand implements CommandRunnable {
private final Particle.DustOptions OPTIONS = new Particle.DustOptions(Color.RED, 1f);
private final int PARTICLE_AMOUNT = 3;
private final int PARTICLE_AMOUNT = 1;
private final double OFFSET_X = 0.0d, OFFSET_Y = 6.0d, OFFSET_Z = 0.0d;
private final double EXTRA = 0d;
HashSet<Player> playersHighlighting = new HashSet<>();
@ -74,20 +76,28 @@ public class HighlightIslandCommand implements CommandRunnable {
return;
}
World world = player.getWorld();
WorldInfo worldInfo = islandsurvivalcraft.getWorldInfoManager().retrieve(world);
WorldInfo worldInfo = islandsurvivalcraft.getWorldInfoManager().retrieve(world.getName());
Location playerLocation = player.getLocation();
int playerX = playerLocation.getBlockX(), playerY = playerLocation.getBlockY(), playerZ = playerLocation.getBlockZ();
IslandWorldMap map = worldInfo.getIslandMap();
//TODO SPAWN HIGHLIGHTING PARTICLES.
Point2 playerCoords = new Point2(playerX, playerZ);
IslandInformation islandInfo = worldInfo.getIslandInfoManager().getIslandInformation(playerCoords);
if (islandInfo != null) {
Set<Point2> islandBorder = islandInfo.getEdgeCoordinates();
Iterator<Point2> islandborderIterable = islandBorder.iterator();
for (int i = 0; i < islandBorder.size(); i+= 2) {
Point2 current = islandborderIterable.next();
spawnParticle(world, current.x, playerY, current.y);
islandborderIterable.next();
}
}
waitingList.add(highlighter);
}
}, 0, 8);
}, 0, 10);
}
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);
world.spawnParticle(Particle.REDSTONE, x, y, z, PARTICLE_AMOUNT, OFFSET_X, OFFSET_Y, OFFSET_Z, EXTRA, OPTIONS, false);
}
@Override
@ -97,6 +107,7 @@ public class HighlightIslandCommand implements CommandRunnable {
private class PlayerIslandHighlighter {
private final int MAX_SEARCH = 0;
private IslandInformation lastIsland;
private final Player player;
private DepthFirstSearch dfs;
@ -104,30 +115,12 @@ public class HighlightIslandCommand implements CommandRunnable {
this.player = player;
}
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) {
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)) {
border.add(currentPoint);
}
}
completableFuture.complete(border);
});
return completableFuture;
public boolean hasUpdated(IslandInformation islandInformation) {
if (lastIsland == null || !(lastIsland.hashCode() == islandInformation.hashCode() && lastIsland.equals(islandInformation))) {
lastIsland = islandInformation;
return true;
}
return false;
}
}
}

View File

@ -0,0 +1,64 @@
package ca.recrown.islandsurvivalcraft.interaction.commands.runnables;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import ca.recrown.islandsurvivalcraft.IslandSurvivalCraft;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMap;
public class ValueRunnable implements CommandRunnable {
private IslandSurvivalCraft isc;
@Override
public String getDescription() {
return "Gives the world value this plugin is using.";
}
@Override
public String getDetailedDescription() {
return "Player only command. Usage: \"IslandSurvivalCraft value [x] [z]\". Gives the current world value at the given x and z coordinates. If the two coordinates are not provided, player location is used.";
}
@Override
public boolean invoke(CommandSender sender, String[] args) {
if (!(sender instanceof Player)) return false;
Player p = (Player) sender;
int worldX = 0, worldZ = 0;
if (args.length > 0) {
if (args.length != 2) return false;
try {
worldX = Integer.valueOf(args[0]);
worldZ = Integer.valueOf(args[1]);
} catch (NumberFormatException e) {
return false;
}
} else {
worldX = p.getLocation().getBlockX();
worldZ = p.getLocation().getBlockZ();
}
IslandWorldMap map = isc.getWorldInfoManager().retrieve(p.getWorld().getName()).getIslandMap();
p.sendMessage(String.format("world: %s, World value (%d, %d): %s",
p.getWorld().getName(),
worldX, worldZ,
map.getWorldValue(worldX, worldZ)));
p.sendMessage(String.format("Terrain: \nIsland: %b, Shallow: %b, Hill: %b, Land: %b, Shore: %b, Edge: %b, Transitional: %b, Deep: %b",
map.isIsland(worldX, worldZ),
map.isShallowPortion(worldX, worldZ),
map.isHill(worldX, worldZ),
map.isLand(worldX, worldZ),
map.isShore(worldX, worldZ),
map.isEdgeOfIsland(worldX, worldZ),
map.isTransitional(worldX, worldZ),
map.isDeep(worldX, worldZ)));
return true;
}
@Override
public void initialize(IslandSurvivalCraft islandSurvivalCraft) {
isc = islandSurvivalCraft;
}
}

View File

@ -42,4 +42,8 @@ public class Point2 {
public String toString() {
return String.format("(%d, %d)", x, y);
}
public Point2 shift(int x, int y) {
return new Point2(this.x + x, this.y + y);
}
}

View File

@ -0,0 +1,89 @@
package ca.recrown.islandsurvivalcraft.world.Information;
import java.util.Collections;
import java.util.Set;
import java.util.UUID;
import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2;
public class IslandInformation {
private final Set<Point2> chunkSpan;
private final Set<Point2> islandCoordinates;
private final Set<Point2> edgeCoordinates;
private final Point2 islandOrigin;
private UUID ownerUUID;
public IslandInformation(Point2 islandOrigin, Set<Point2> islandCoordinates, Set<Point2> edgeCoordinates, Set<Point2> chunksUsed) {
this.islandCoordinates = Collections.unmodifiableSet(islandCoordinates);
this.chunkSpan = Collections.unmodifiableSet(chunksUsed);
this.edgeCoordinates = Collections.unmodifiableSet(edgeCoordinates);
this.islandOrigin = islandOrigin;
}
/**
* @return a set that represents all coordinates within this island.
*/
public Set<Point2> getIslandCoordinates() {
return islandCoordinates;
}
/**
* @return a set of chunks that this island spans.
*/
public Set<Point2> getChunkSpan() {
return chunkSpan;
}
/**
* @return a set of points that represent all the edges of this island.
*/
public Set<Point2> getEdgeCoordinates() {
return edgeCoordinates;
}
/**
* @param ownerUUID the UUID of the player owner of this island. May be null.
*/
public void setOwnerUUID(UUID ownerUUID) {
this.ownerUUID = ownerUUID;
}
/**
* @return the islandOrigin
*/
public Point2 getIslandOrigin() {
return islandOrigin;
}
/**
* @return the UUID of the player owner of this island. May be null.
*/
public UUID getOwnerUUID() {
return ownerUUID;
}
public boolean isWithinIsland(Point2 coordinates) {
return islandCoordinates.contains(coordinates);
}
public boolean isEdgeOfIsland(Point2 coordinates) {
return edgeCoordinates.contains(coordinates);
}
public boolean isIslandInChunk(Point2 chunkCoords) {
return chunkSpan.contains(chunkCoords);
}
@Override
public int hashCode() {
return islandOrigin.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof IslandInformation) {
return islandOrigin.equals(((IslandInformation) obj).getIslandOrigin());
}
return false;
}
}

View File

@ -0,0 +1,39 @@
package ca.recrown.islandsurvivalcraft.world.Information;
import java.util.HashSet;
import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2;
public class IslandInformationBuilder {
private final HashSet<Point2> islandCoordinates = new HashSet<>();
private final HashSet<Point2> edgeCoordinates = new HashSet<>();
private final HashSet<Point2> chunksUsed = new HashSet<>();
private boolean built = false;
public boolean addCoordinate(Point2 coordinate, boolean isEdge) {
if (coordinate == null) throw new NullPointerException("Coordinate cannot be null!");
if (built) throw new IllegalStateException("Island information has already been built!");
if (isEdge) edgeCoordinates.add(coordinate);
return islandCoordinates.add(coordinate);
}
public boolean addChunk(Point2 chunkCoordinate) {
if (chunkCoordinate == null) throw new NullPointerException("Chunk coordinate cannot be null!");
if (built) throw new IllegalStateException("Island information has already been built!");
return chunksUsed.add(chunkCoordinate);
}
public IslandInformation build(Point2 islandOrigin) {
if (islandOrigin == null) throw new NullPointerException("IslandOrigin cannot be null.");
if (built) throw new IllegalStateException("This island informationh as already been built!");
built = true;
return new IslandInformation(islandOrigin, islandCoordinates, edgeCoordinates, chunksUsed);
}
/**
* @return the chunksUsed
*/
public HashSet<Point2> getChunksUsed() {
return chunksUsed;
}
}

View File

@ -0,0 +1,74 @@
package ca.recrown.islandsurvivalcraft.world.Information;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;
import ca.recrown.islandsurvivalcraft.utilities.GeneralUtilities;
import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMap;
public class IslandInformationManager {
private final IslandWorldMap islandMap;
private final ConcurrentHashMap<Point2, HashSet<IslandInformation>> islandSets = new ConcurrentHashMap<>();
public IslandInformationManager(IslandWorldMap islandWorldMap) {
this.islandMap = islandWorldMap;
}
public void loadChunkIslandInformation(Point2 chunkCoords) {
Point2[] surrounding = new Point2[4];
surrounding[0] = chunkCoords.shift(1, 0);
surrounding[1] = chunkCoords.shift(-1, 0);
surrounding[2] = chunkCoords.shift(0, -1);
surrounding[3] = chunkCoords.shift(0, 1);
HashSet<Point2> checkedCoordinates = new HashSet<>();
for (Point2 chunk : surrounding) {
HashSet<IslandInformation> chunkIslandSet = getChunkIslandInformationSet(chunk);
for (IslandInformation islandInformation : chunkIslandSet) {
if (islandInformation.isIslandInChunk(chunkCoords)) {
checkedCoordinates.addAll(islandInformation.getIslandCoordinates());
}
}
}
for (int x = 0; x < GeneralUtilities.CHUNK_SIZE; x++) {
for (int z = 0; z < GeneralUtilities.CHUNK_SIZE; z++) {
int worldX = chunkCoords.x * GeneralUtilities.CHUNK_SIZE + x;
int worldZ = chunkCoords.y * GeneralUtilities.CHUNK_SIZE + z;
if (islandMap.isIsland(worldX, worldZ)) {
IslandInformationBuilder infoBuilder = new IslandInformationBuilder();
Point2 origin = islandMap.findIslandOrigin(worldX, worldZ, (p) -> {
if (!checkedCoordinates.add(p)) return true;
infoBuilder.addCoordinate(p, islandMap.isEdgeOfIsland(p.x, p.y));
infoBuilder.addChunk(GeneralUtilities.worldToChunkCoordinates(p));
return false;
});
if (origin != null) {
IslandInformation islandInfo = infoBuilder.build(origin);
for (Point2 chunk : infoBuilder.getChunksUsed()) {
getChunkIslandInformationSet(chunk).add(islandInfo);
}
}
}
}
}
}
public void unloadChunkIslandInformation(Point2 chunkCoords) {
islandSets.remove(chunkCoords);
}
public HashSet<IslandInformation> getChunkIslandInformationSet(Point2 chunkCoords) {
if (!islandSets.containsKey(chunkCoords)) islandSets.put(chunkCoords, new HashSet<>());
return islandSets.get(chunkCoords);
}
public IslandInformation getIslandInformation(Point2 coords) {
HashSet<IslandInformation> chunkIslandSet = getChunkIslandInformationSet(GeneralUtilities.worldToChunkCoordinates(coords));
for (IslandInformation islandInformation : chunkIslandSet) {
if (islandInformation.isWithinIsland(coords)) return islandInformation;
}
return null;
}
}

View File

@ -11,6 +11,7 @@ import ca.recrown.islandsurvivalcraft.utilities.pathfinding.CoordinateValidatabl
import ca.recrown.islandsurvivalcraft.utilities.pathfinding.DepthFirstSearch;
public class IslandWorldMap {
private final float SHALLOW_PORTION = 0.06f;
private final float TRANSITION_DEPTH_PORTION = 0.1f;
private final float DEEP_OCEAN_PORTION = 0.5f;
private final float HILL_PORTION = 0.4f;
@ -23,7 +24,6 @@ public class IslandWorldMap {
private final float ISLAND_PERCENT = 0.36f;
private final double NOISE_FREQ = 1.78D;
private final double NOISE_AMP = 0.47D;
private final float SHALLOW_PORTION = 0.06f;
private final double SCALE = 0.005D;
public IslandWorldMap(Random random) {
@ -70,6 +70,13 @@ public class IslandWorldMap {
return isLand(worldX, worldZ) || isShallowPortion(worldX, worldZ);
}
/**
* Checks coordinates for if this location is a hill.
* It is considered a hill when its world value is greater than the HILL_PORTION.
* @param worldX
* @param worldZ
* @return
*/
public boolean isHill(int worldX, int worldZ) {
return getWorldValue(worldX, worldZ) >= HILL_PORTION;
}
@ -152,7 +159,7 @@ public class IslandWorldMap {
* @param worldX The x coordinate of the island block in question.
* @param worldZ The y coordinate of the island block in question.
* @param targetValidator The coordinate target validator to use. Optional and is null.
* @return The island origin point.
* @return The island origin point. Will be null if the passed target target validator stops search prematurely.
*/
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.");

View File

@ -4,10 +4,12 @@ import java.util.Random;
import org.bukkit.World;
import ca.recrown.islandsurvivalcraft.world.Information.IslandInformationManager;
import ca.recrown.islandsurvivalcraft.world.generation.chunks.IslandWorldChunkGenerator;
public class WorldInfo {
private final WorldInfoManager manager;
private volatile IslandInformationManager islandInfoManager;
private volatile Random random;
private volatile int seaLevel;
private volatile int worldHeight;
@ -26,14 +28,12 @@ public class WorldInfo {
* @param world The world this object is describing. May be null to create a
* non-initialized info object.
*/
public WorldInfo(WorldInfoManager manager, World world) {
public WorldInfo(WorldInfoManager manager) {
this.manager = manager;
if (world != null) {
initialize(world, null);
}
this.generator = new IslandWorldChunkGenerator(this);
}
public void initialize(World world, IslandWorldChunkGenerator chunkGenerator) {
public void initialize(World world) {
if (initialized)
throw new IllegalStateException("This world information object has already been initialized.");
this.initialized = true;
@ -42,15 +42,9 @@ public class WorldInfo {
this.biomeMap = new BiomeMap(random);
this.tempMap = new TemperatureMap(random);
this.islandMap = new IslandWorldMap(random);
this.islandInfoManager = new IslandInformationManager(islandMap);
this.worldHeight = world.getMaxHeight();
this.seaLevel = world.getSeaLevel();
if (chunkGenerator != null) {
this.generator = chunkGenerator;
this.generator.initialize();
this.manager.register(world, this);
} else {
generator = new IslandWorldChunkGenerator(this);
}
}
/**
@ -60,6 +54,13 @@ public class WorldInfo {
return initialized;
}
/**
* @return the islandInfoManager
*/
public IslandInformationManager getIslandInfoManager() {
return islandInfoManager;
}
/**
* @return the biomeMap
*/

View File

@ -1,18 +1,25 @@
package ca.recrown.islandsurvivalcraft.world;
import java.security.InvalidParameterException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.NullArgumentException;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.event.world.ChunkUnloadEvent;
import org.bukkit.generator.ChunkGenerator;
import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2;
import ca.recrown.islandsurvivalcraft.world.generation.GeneratorModes;
import ca.recrown.islandsurvivalcraft.world.generation.chunks.IslandWorldChunkGenerator;
public class WorldInfoManager implements Listener {
public final ConcurrentHashMap<UUID, WorldInfo> worldInformation = new ConcurrentHashMap<>();
public final ConcurrentHashMap<String, WorldInfo> worldInformation = new ConcurrentHashMap<>();
/**
* Return the world info requested for the given world.
@ -20,13 +27,19 @@ public class WorldInfoManager implements Listener {
* @param world The world the associated info is to be requested for.
* @return The world info.
*/
public WorldInfo retrieve(World world) {
if (world == null) throw new NullArgumentException("world");
return worldInformation.computeIfAbsent(world.getUID(), (worldInfo) -> {
return new WorldInfo(this, world);
public WorldInfo retrieve(String worldName) {
if (worldName == null) throw new NullArgumentException("world");
return worldInformation.computeIfAbsent(worldName, (name) -> {
return new WorldInfo(this);
});
}
public ChunkGenerator getChunkGenerator(String worldName, GeneratorModes mode) {
IslandWorldChunkGenerator islandWorldChunkGenerator = retrieve(worldName).getGenerator();
if (!islandWorldChunkGenerator.isInitialized()) islandWorldChunkGenerator.setGeneratorType(mode);
return islandWorldChunkGenerator;
}
/**
* Resets the manager deleting all currently loaded world info from memory.
*/
@ -34,29 +47,21 @@ public class WorldInfoManager implements Listener {
worldInformation.clear();
}
/**
* Returns a chunk generator.
* @param world The world the chunk generator is for. If it does not yet exist, it will be created.
* @return The chunk generator associated with the given world.
*/
public ChunkGenerator getChunkGenerator(World world, GeneratorModes mode) {
WorldInfo worldInfo = null;
if (world != null) {
worldInfo = retrieve(world);
if (!worldInfo.getGenerator().isInitialized()) {
worldInfo.getGenerator().setGeneratorType(mode);
worldInfo.getGenerator().initialize();
}
return worldInfo.getGenerator();
} else {
worldInfo = new WorldInfo(this, null);
IslandWorldChunkGenerator generator = new IslandWorldChunkGenerator(worldInfo);
generator.setGeneratorType(mode);
return generator;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onChunkLoad(ChunkLoadEvent event) {
Chunk chunk = event.getChunk();
if (!worldInformation.containsKey(event.getWorld().getName())) return;
WorldInfo worldInfo = retrieve(event.getWorld().getName());
if (!worldInfo.isInitialized()) worldInfo.initialize(event.getWorld());
worldInfo.getIslandInfoManager().loadChunkIslandInformation(new Point2(chunk.getX(), chunk.getZ()));
}
protected void register(World world, WorldInfo worldInfo) {
worldInformation.put(world.getUID(), worldInfo);
@EventHandler(priority = EventPriority.MONITOR)
public void onChunkUnload(ChunkUnloadEvent event) {
Chunk chunk = event.getChunk();
if (!worldInformation.containsKey(event.getWorld().getName())) return;
WorldInfo worldInfo = retrieve(event.getWorld().getName());
if (!worldInfo.isInitialized()) worldInfo.initialize(event.getWorld());
worldInfo.getIslandInfoManager().unloadChunkIslandInformation(new Point2(chunk.getX(), chunk.getZ()));
}
}

View File

@ -74,8 +74,9 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene
@Override
public ChunkData generateChunkData(World world, Random random, int chunkX, int chunkZ, BiomeGrid biomeGrid) {
if (!worldInfo.isInitialized()) {
worldInfo.initialize(world, this);
worldInfo.initialize(world);
}
if (!isInitialized()) initialize();
if (!initialized) throw new IllegalStateException("This generator has not been initialized.");
Future<Boolean> preLoader = exAlpha.submit(() -> {
for (int x = GeneralUtilities.CHUNK_SIZE - 1; x >= 0; x--) {
@ -108,6 +109,8 @@ public class IslandWorldChunkGenerator extends ChunkGenerator implements Listene
currentBiome = ((LandBiomeInfo) biomeInfo).getHillBiome();
} else if (islandMap.isShore(worldX, worldZ)) {
currentBiome = ((LandBiomeInfo) biomeInfo).getShoreBiome();
} else if (islandMap.isShallowPortion(worldX, worldZ)) {
currentBiome = ((LandBiomeInfo) biomeInfo).getShoreBiome();
} else if (islandMap.isLand(worldX, worldZ)) {
currentBiome = biomeInfo.getMainBiome();
} else if (islandMap.isTransitional(worldX, worldZ)) {

View File

@ -1,4 +1,2 @@
write-Output "Deleting previous world if there was one..."
remove-Item -Recurse world* -Force -ErrorAction Ignore
write-Output "Attempting to copy plugin jar to plugins folder..."
copy-Item -Path "..\target\IslandSurvivalCraft*.jar" -Destination "plugins\IslandSurvivalCraft.jar"