diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
deleted file mode 100644
index bbbc60f0..00000000
--- a/.gitlab-ci.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-image: maven:latest
-
-cache:
- paths:
- - .m2/repository/
- - target/
-
-build:
- stage: build
- script:
- - mvn compile
-
-test:
- stage: test
- script:
- - mvn test
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 84322726..4916424b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
4.0.0
com.songoda
skyblock
- 2.3.19
+ 2.3.21
jar
UTF-8
diff --git a/src/main/java/com/songoda/skyblock/blockscanner/BlockScanner.java b/src/main/java/com/songoda/skyblock/blockscanner/BlockScanner.java
index 57956cfc..92c4fdf1 100644
--- a/src/main/java/com/songoda/skyblock/blockscanner/BlockScanner.java
+++ b/src/main/java/com/songoda/skyblock/blockscanner/BlockScanner.java
@@ -7,6 +7,7 @@ import com.songoda.skyblock.SkyBlock;
import com.songoda.skyblock.island.Island;
import com.songoda.skyblock.island.IslandEnvironment;
import com.songoda.skyblock.world.WorldManager;
+import io.papermc.lib.PaperLib;
import org.bukkit.Bukkit;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Location;
@@ -15,26 +16,33 @@ import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.scheduler.BukkitRunnable;
-import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
public final class BlockScanner extends BukkitRunnable {
private static final Method ID_FIELD;
+ private static final int MAX_CHUNKS_PER_ITERATION = 2;
+ private static final int MAX_EMPTY_ITERATIONS = 20;
static {
Method temp = null;
try {
temp = ChunkSnapshot.class.getMethod("getBlockTypeId", int.class, int.class, int.class);
- } catch (NoSuchMethodException ignored) {}
+ } catch (NoSuchMethodException ignored) {
+ }
ID_FIELD = temp;
}
@@ -57,9 +65,9 @@ public final class BlockScanner extends BukkitRunnable {
private final int threadCount;
private final Queue blocks;
private final ScannerTasks tasks;
-
+
private final Island island;
-
+
private final boolean ignoreLiquids;
private final boolean ignoreAir;
@@ -84,35 +92,35 @@ public final class BlockScanner extends BukkitRunnable {
for (Entry> entry : snapshots.entrySet()) {
final List> parts = Lists.partition(entry.getValue(), 16);
-
+
threadCount += parts.size();
World world = entry.getKey();
final String env;
switch (world.getEnvironment()) {
- case NETHER:
- env = "Nether";
- break;
- case THE_END:
- env = "End";
- break;
- default:
- env = "Normal";
- break;
+ case NETHER:
+ env = "Nether";
+ break;
+ case THE_END:
+ env = "End";
+ break;
+ default:
+ env = "Normal";
+ break;
}
final ConfigurationSection liquidSection = config.getConfigurationSection("Island.World." + env + ".Liquid");
int startY;
- if(ignoreY){
+ if (ignoreY) {
startY = 255;
} else {
startY = !ignoreLiquidsY && liquidSection.getBoolean("Enable") && !config.getBoolean("Island.Levelling.ScanLiquid") ? liquidSection.getInt("Height") + 1 : 0;
}
for (List sub : parts) {
- queueWork(world, startY, sub);
+ queueWork(world, startY, sub);
}
}
@@ -121,63 +129,166 @@ public final class BlockScanner extends BukkitRunnable {
private void queueWork(World world, int scanY, List subList) {
WorldManager worldManager = SkyBlock.getInstance().getWorldManager();
-
+
+ // The chunks that couldn't be taken snapshot async
+ List pendingChunks = new ArrayList<>();
+
+ // The chunks that are ready to be processed asynchronously
+ List readyChunks = new ArrayList<>();
+
+ // This lock will help to make the bukkit task wait after all the chunks that could be processed async are processed
+ Lock lock = new ReentrantLock();
+
+ // This is the actual object that we will use to wait
+ Condition emptyCondition = lock.newCondition();
+
Bukkit.getServer().getScheduler().runTaskAsynchronously(SkyBlock.getInstance(), () -> {
+ // We need to hold the lock on the thread calling the await
+ lock.lock();
+
LocationBounds bounds = null;
- if(island != null) {
+ if (island != null) {
Location islandLocation = island.getLocation(worldManager.getIslandWorld(world), IslandEnvironment.Island);
-
+
Location minLocation = new Location(world, islandLocation.getBlockX() - island.getRadius(), 0, islandLocation.getBlockZ() - island.getRadius());
Location maxLocation = new Location(world, islandLocation.getBlockX() + island.getRadius(), world.getMaxHeight(), islandLocation.getBlockZ() + island.getRadius());
-
+
int minX = Math.min(maxLocation.getBlockX(), minLocation.getBlockX());
int minZ = Math.min(maxLocation.getBlockZ(), minLocation.getBlockZ());
-
+
int maxX = Math.max(maxLocation.getBlockX(), minLocation.getBlockX());
int maxZ = Math.max(maxLocation.getBlockZ(), minLocation.getBlockZ());
-
+
bounds = new LocationBounds(minX, minZ, maxX, maxZ);
}
- for (CachedChunk shot : subList) {
- final int cX = shot.getX() << 4;
- final int cZ = shot.getZ() << 4;
-
- int initX = 0;
- int initZ = 0;
- int lastX = 15;
- int lastZ = 15;
-
- if(bounds != null) {
- initX = Math.max(cX, bounds.getMinX())&0x000F;
- initZ = Math.max(cZ, bounds.getMinZ())&0x000F;
-
- lastX = Math.min(cX | 15, bounds.getMaxX()-1)&0x000F;
- lastZ = Math.min(cZ | 15, bounds.getMaxZ()-1)&0x000F;
- }
-
- for (int x = initX; x <= lastX; x++) {
- for (int z = initZ; z <= lastZ; z++) {
- for (int y = scanY; y < world.getMaxHeight(); y++) {
- final CompatibleMaterial type = CompatibleMaterial.getBlockMaterial(
- ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)
- ? shot.getSnapshot().getBlockType(x, y, z) :
- MaterialIDHelper.getLegacyMaterial(getBlockTypeID(shot, x, y, z)));
-
- if(type == null){
- continue;
- } else if(type.equals(CompatibleMaterial.AIR) && ignoreAir){
- continue;
- } else if(type.equals(CompatibleMaterial.WATER) && ignoreLiquids){
- continue;
- }
- blocks.add(new BlockInfo(world, x + (cX), y, z + (cZ)));
- }
- }
+ for (CachedChunk shot : subList) {
+ if (!shot.isSnapshotAvailable() && !areAsyncChunksAvailable()) {
+ pendingChunks.add(shot);
+
+ continue;
}
+
+ processCachedChunk(world, scanY, shot, bounds);
}
+
+ // Don't wait for the condition if the async chunks are available, since it would never be signalled
+ if (areAsyncChunksAvailable()) {
+ increment();
+
+ lock.unlock();
+ return;
+ }
+
+ try {
+ emptyCondition.await();
+ } catch (InterruptedException e) {
+ // Pass the interruption
+ Thread.currentThread().interrupt();
+ }
+
+ // process the pending chunks
+ for (CachedChunk shot : readyChunks) {
+ processCachedChunk(world, scanY, shot, bounds);
+ }
+
+ lock.unlock();
increment();
});
+
+ if (!areAsyncChunksAvailable()) {
+ startChunkSnapshotTask(pendingChunks, readyChunks, emptyCondition, lock);
+ }
+ }
+
+ private boolean areAsyncChunksAvailable() {
+ return PaperLib.isVersion(9) && PaperLib.isPaper();
+ }
+
+ private void startChunkSnapshotTask(List pendingChunks, List readyChunks, Condition emptyCondition, Lock lock) {
+ new BukkitRunnable() {
+ // The number of iterations with the pendingChunks list empty
+ private int emptyIterations = 0;
+
+ @Override
+ public void run() {
+ lock.lock();
+ int updatedChunks = 0;
+
+ Iterator chunkIterator = pendingChunks.iterator();
+
+ try {
+ while (chunkIterator.hasNext()) {
+ CachedChunk pendingChunk = chunkIterator.next();
+
+ if (updatedChunks >= MAX_CHUNKS_PER_ITERATION) {
+ break;
+ }
+
+ // take the snapshot
+ pendingChunk.takeSnapshot();
+
+ chunkIterator.remove();
+ readyChunks.add(pendingChunk);
+
+ updatedChunks++;
+ }
+
+ if (pendingChunks.isEmpty()) {
+ if (emptyIterations >= MAX_EMPTY_ITERATIONS) {
+ // Send the signal to unlock the async thread and continue with the processing
+ emptyCondition.signalAll();
+ this.cancel();
+
+ return;
+ }
+
+ emptyIterations++;
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+ }.runTaskTimer(SkyBlock.getInstance(), 1, 1);
+ }
+
+ private void processCachedChunk(World world, int scanY, CachedChunk shot, LocationBounds bounds) {
+ final int cX = shot.getX() << 4;
+ final int cZ = shot.getZ() << 4;
+
+ int initX = 0;
+ int initZ = 0;
+ int lastX = 15;
+ int lastZ = 15;
+
+ if (bounds != null) {
+ initX = Math.max(cX, bounds.getMinX()) & 0x000F;
+ initZ = Math.max(cZ, bounds.getMinZ()) & 0x000F;
+
+ lastX = Math.min(cX | 15, bounds.getMaxX() - 1) & 0x000F;
+ lastZ = Math.min(cZ | 15, bounds.getMaxZ() - 1) & 0x000F;
+ }
+
+ for (int x = initX; x <= lastX; x++) {
+ for (int z = initZ; z <= lastZ; z++) {
+ for (int y = scanY; y < world.getMaxHeight(); y++) {
+ final CompatibleMaterial type = CompatibleMaterial.getBlockMaterial(
+ ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)
+ ? shot.getSnapshot().getBlockType(x, y, z) :
+ MaterialIDHelper.getLegacyMaterial(getBlockTypeID(shot, x, y, z)));
+
+ if (type == null) {
+ continue;
+ } else if (type.equals(CompatibleMaterial.AIR) && ignoreAir) {
+ continue;
+ } else if (type.equals(CompatibleMaterial.WATER) && ignoreLiquids) {
+ continue;
+ }
+
+ blocks.add(new BlockInfo(world, x + (cX), y, z + (cZ)));
+ }
+ }
+ }
}
private synchronized int increment() {
diff --git a/src/main/java/com/songoda/skyblock/blockscanner/CachedChunk.java b/src/main/java/com/songoda/skyblock/blockscanner/CachedChunk.java
index 17864e92..b49d0091 100644
--- a/src/main/java/com/songoda/skyblock/blockscanner/CachedChunk.java
+++ b/src/main/java/com/songoda/skyblock/blockscanner/CachedChunk.java
@@ -49,6 +49,10 @@ public class CachedChunk {
return PaperLib.getChunkAtAsync(world, this.x, this.z);
}
+ public boolean isSnapshotAvailable() {
+ return latestSnapshot != null;
+ }
+
public ChunkSnapshot getSnapshot() {
if (latestSnapshot == null)
return takeSnapshot();
diff --git a/src/main/java/com/songoda/skyblock/command/commands/island/UpgradeCommand.java b/src/main/java/com/songoda/skyblock/command/commands/island/UpgradeCommand.java
index a90df98c..aecd3043 100644
--- a/src/main/java/com/songoda/skyblock/command/commands/island/UpgradeCommand.java
+++ b/src/main/java/com/songoda/skyblock/command/commands/island/UpgradeCommand.java
@@ -29,7 +29,7 @@ public class UpgradeCommand extends SubCommand {
configLoad.getString("Command.Island.Upgrade.Owner.Message"));
soundManager.playSound(player, CompatibleSound.BLOCK_ANVIL_LAND.getSound(), 1.0F, 1.0F);
} else {
- if (!economy.isEnabled()) {
+ if (economy == null || !economy.isEnabled()) {
messageManager.sendMessage(player, configLoad.getString("Command.Island.Upgrade.Disabled.Message"));
soundManager.playSound(player, CompatibleSound.BLOCK_ANVIL_LAND.getSound(), 1.0F, 1.0F);
return;
diff --git a/src/main/java/com/songoda/skyblock/island/IslandStatus.java b/src/main/java/com/songoda/skyblock/island/IslandStatus.java
index 682e9495..97bd7893 100644
--- a/src/main/java/com/songoda/skyblock/island/IslandStatus.java
+++ b/src/main/java/com/songoda/skyblock/island/IslandStatus.java
@@ -1,11 +1,16 @@
package com.songoda.skyblock.island;
+import java.util.Arrays;
+
public enum IslandStatus {
OPEN,
CLOSED,
WHITELISTED;
public static IslandStatus getEnum(String value) {
- return valueOf(value.toUpperCase());
+ return Arrays.stream(values())
+ .filter(status -> value.toUpperCase().equals(status.name()))
+ .findFirst()
+ .orElse(OPEN);
}
}
diff --git a/src/main/java/com/songoda/skyblock/levelling/QueuedIslandScan.java b/src/main/java/com/songoda/skyblock/levelling/QueuedIslandScan.java
index 77f4793d..312d4371 100644
--- a/src/main/java/com/songoda/skyblock/levelling/QueuedIslandScan.java
+++ b/src/main/java/com/songoda/skyblock/levelling/QueuedIslandScan.java
@@ -12,7 +12,6 @@ import org.bukkit.Bukkit;
import org.bukkit.configuration.Configuration;
import org.bukkit.entity.Player;
-import java.io.File;
import java.text.NumberFormat;
import java.util.*;
@@ -55,7 +54,7 @@ public class QueuedIslandScan {
update();
if (toScan.isEmpty()) {
- finalize();
+ finalizeScan();
return false;
}
IslandWorld world = toScan.poll();
@@ -63,8 +62,7 @@ public class QueuedIslandScan {
return true;
}
- public void finalize() {
-
+ public void finalizeScan() {
final Map materials = new HashMap<>(amounts.size());
for (Map.Entry entry : amounts.entrySet()) {
diff --git a/src/main/java/com/songoda/skyblock/utils/structure/SchematicUtil.java b/src/main/java/com/songoda/skyblock/utils/structure/SchematicUtil.java
index 697668a4..a74cb0b8 100644
--- a/src/main/java/com/songoda/skyblock/utils/structure/SchematicUtil.java
+++ b/src/main/java/com/songoda/skyblock/utils/structure/SchematicUtil.java
@@ -4,6 +4,7 @@ import com.songoda.skyblock.SkyBlock;
import com.songoda.skyblock.utils.version.NMSUtil;
import org.bukkit.Bukkit;
import org.bukkit.World;
+import org.bukkit.plugin.PluginManager;
import java.io.File;
import java.io.FileInputStream;
@@ -14,7 +15,8 @@ import java.lang.reflect.Method;
public class SchematicUtil {
public static Float[] pasteSchematic(File schematicFile, org.bukkit.Location location) {
- if (!Bukkit.getPluginManager().isPluginEnabled("WorldEdit"))
+ PluginManager pluginManager = Bukkit.getPluginManager();
+ if (!pluginManager.isPluginEnabled("WorldEdit") && !pluginManager.isPluginEnabled("AsyncWorldEdit") && !pluginManager.isPluginEnabled("FastAsyncWorldEdit"))
throw new IllegalStateException("Tried to generate an island using a schematic file without WorldEdit installed!");
Runnable pasteTask = () -> {
diff --git a/src/main/java/com/songoda/skyblock/world/WorldManager.java b/src/main/java/com/songoda/skyblock/world/WorldManager.java
index abb93f75..98451d38 100644
--- a/src/main/java/com/songoda/skyblock/world/WorldManager.java
+++ b/src/main/java/com/songoda/skyblock/world/WorldManager.java
@@ -47,9 +47,9 @@ public class WorldManager {
String netherWorldGeneratorName = configLoad.getString("Island.World.End.CustomWorldGenerator");
String endWorldGeneratorName = configLoad.getString("Island.World.End.CustomWorldGenerator");
- normalWorldWorldGenerator = getWorldGenerator(normalWorldName, normalWorldGeneratorName);
- netherWorldWorldGenerator = getWorldGenerator(netherWorldName, netherWorldGeneratorName);
- endWorldWorldGenerator = getWorldGenerator(endWorldName, endWorldGeneratorName);
+ normalWorldWorldGenerator = getWorldGenerator(normalWorldName, normalWorldGeneratorName, IslandWorld.Normal);
+ netherWorldWorldGenerator = getWorldGenerator(netherWorldName, netherWorldGeneratorName, IslandWorld.Nether);
+ endWorldWorldGenerator = getWorldGenerator(endWorldName, endWorldGeneratorName, IslandWorld.End);
normalWorld = Bukkit.getServer().getWorld(normalWorldName);
netherWorld = Bukkit.getServer().getWorld(netherWorldName);
@@ -138,9 +138,9 @@ public class WorldManager {
return location;
}
- private ChunkGenerator getWorldGenerator(String mapName, String worldGeneratorName) {
+ private ChunkGenerator getWorldGenerator(String mapName, String worldGeneratorName, IslandWorld islandWorld) {
if (worldGeneratorName == null || worldGeneratorName == "default" || worldGeneratorName.length() == 0) {
- return new VoidGenerator();
+ return new VoidGenerator(islandWorld);
}
ChunkGenerator customWorldGenerator = WorldCreator.getGeneratorForName(mapName, worldGeneratorName, null);
@@ -149,7 +149,7 @@ public class WorldManager {
return customWorldGenerator;
}
- return new VoidGenerator();
+ return new VoidGenerator(islandWorld);
}
public ChunkGenerator getWorldGeneratorForMapName(String mapName) {
@@ -159,6 +159,6 @@ public class WorldManager {
if (endWorld != null && endWorld.getName().equals(mapName)) return endWorldWorldGenerator;
- return new VoidGenerator();
+ return new VoidGenerator(IslandWorld.Normal);
}
}
diff --git a/src/main/java/com/songoda/skyblock/world/generator/VoidGenerator.java b/src/main/java/com/songoda/skyblock/world/generator/VoidGenerator.java
index e35500ff..1a392224 100644
--- a/src/main/java/com/songoda/skyblock/world/generator/VoidGenerator.java
+++ b/src/main/java/com/songoda/skyblock/world/generator/VoidGenerator.java
@@ -21,6 +21,11 @@ import java.util.Random;
public class VoidGenerator extends ChunkGenerator {
+ private final IslandWorld islandWorld;
+ public VoidGenerator(IslandWorld islandWorld) {
+ this.islandWorld = islandWorld;
+ }
+
@Override
public @Nonnull ChunkData generateChunkData(@Nonnull World world, @Nonnull Random random, int chunkX, int chunkZ, @Nonnull BiomeGrid biomeGrid) {
final ChunkData chunkData = createChunkData(world);
@@ -28,12 +33,15 @@ public class VoidGenerator extends ChunkGenerator {
final SkyBlock plugin = SkyBlock.getInstance();
final Configuration configLoad = plugin.getConfiguration();
final ConfigurationSection worldSection = configLoad.getConfigurationSection("Island.World");
-
+
Biome biome;
switch (world.getEnvironment()) {
case NORMAL:
- biome = CompatibleBiome.valueOf(configLoad.getString("Island.Biome.Default.Type", "PLAINS").toUpperCase()).getBiome();
+ biome = Arrays.stream(CompatibleBiome.values())
+ .filter(compatibleBiome -> compatibleBiome.getBiome().name().equals(configLoad.getString("Island.Biome.Default.Type", "PLAINS").toUpperCase()))
+ .findFirst()
+ .orElse(CompatibleBiome.PLAINS).getBiome();
break;
case NETHER:
biome = CompatibleBiome.NETHER_WASTES.getBiome();
@@ -50,26 +58,19 @@ public class VoidGenerator extends ChunkGenerator {
} else {
setChunkBiome2D(biome, biomeGrid);
}
-
- for (IslandWorld worldList : IslandWorld.values()) {
- if (world.getEnvironment() == World.Environment.NETHER
- || world.getEnvironment() == World.Environment.NORMAL
- || world.getEnvironment() == World.Environment.THE_END) {
- ConfigurationSection section = worldSection.getConfigurationSection(worldList.name());
+ ConfigurationSection section = worldSection.getConfigurationSection(islandWorld.name());
- if (section.getBoolean("Liquid.Enable")) {
- if (section.getBoolean("Liquid.Lava")) {
- setBlock(chunkData, CompatibleMaterial.LAVA.getBlockMaterial(), section.getInt("Liquid.Height"));
- } else {
- setBlock(chunkData, CompatibleMaterial.WATER.getBlockMaterial(), section.getInt("Liquid.Height"));
- }
- }
-
- break;
+ if (section.getBoolean("Liquid.Enable")) {
+ if (section.getBoolean("Liquid.Lava")) {
+ setBlock(chunkData, CompatibleMaterial.LAVA.getBlockMaterial(), section.getInt("Liquid.Height"));
+ } else {
+ setBlock(chunkData, CompatibleMaterial.WATER.getBlockMaterial(), section.getInt("Liquid.Height"));
}
}
+
+
return chunkData;
}
@@ -96,7 +97,7 @@ public class VoidGenerator extends ChunkGenerator {
}
}
}
-
+
// Do not use - Too laggy
private void setChunkBiome3D(Biome biome, BiomeGrid grid, World world) {
for(int x = 0; x < 16; x++){
@@ -107,7 +108,7 @@ public class VoidGenerator extends ChunkGenerator {
}
}
}
-
+
private void setChunkBiome2D(Biome biome, BiomeGrid grid) {
for(int x = 0; x < 16; x++){
for(int z = 0; z < 16; z++){