Merge branch 'development'

This commit is contained in:
Fernando Pettinelli 2020-12-23 13:14:17 -03:00
commit ab89b312fd
10 changed files with 212 additions and 107 deletions

View File

@ -1,16 +0,0 @@
image: maven:latest
cache:
paths:
- .m2/repository/
- target/
build:
stage: build
script:
- mvn compile
test:
stage: test
script:
- mvn test

View File

@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.songoda</groupId>
<artifactId>skyblock</artifactId>
<version>2.3.19</version>
<version>2.3.21</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

View File

@ -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<BlockInfo> 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<World, List<CachedChunk>> entry : snapshots.entrySet()) {
final List<List<CachedChunk>> 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<CachedChunk> 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<CachedChunk> subList) {
WorldManager worldManager = SkyBlock.getInstance().getWorldManager();
// The chunks that couldn't be taken snapshot async
List<CachedChunk> pendingChunks = new ArrayList<>();
// The chunks that are ready to be processed asynchronously
List<CachedChunk> 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<CachedChunk> pendingChunks, List<CachedChunk> 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<CachedChunk> 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() {

View File

@ -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();

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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<String, Long> materials = new HashMap<>(amounts.size());
for (Map.Entry<CompatibleMaterial, BlockAmount> entry : amounts.entrySet()) {

View File

@ -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 = () -> {

View File

@ -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);
}
}

View File

@ -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++){