diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitPlotGenerator.java b/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitPlotGenerator.java index 1559d32f3..7bb690dba 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitPlotGenerator.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitPlotGenerator.java @@ -22,20 +22,30 @@ import com.plotsquared.bukkit.queue.GenChunk; import com.plotsquared.bukkit.util.BukkitUtil; import com.plotsquared.bukkit.util.BukkitWorld; import com.plotsquared.core.PlotSquared; +import com.plotsquared.core.generator.ClassicPlotWorld; import com.plotsquared.core.generator.GeneratorWrapper; import com.plotsquared.core.generator.IndependentPlotGenerator; import com.plotsquared.core.generator.SingleWorldGenerator; import com.plotsquared.core.location.ChunkWrapper; +import com.plotsquared.core.location.UncheckedWorldLocation; import com.plotsquared.core.plot.PlotArea; import com.plotsquared.core.plot.world.PlotAreaManager; import com.plotsquared.core.queue.ZeroedDelegateScopedQueueCoordinator; import com.plotsquared.core.util.ChunkManager; +import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.math.BlockVector2; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bukkit.HeightMap; import org.bukkit.World; import org.bukkit.block.Biome; +import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.BlockPopulator; import org.bukkit.generator.ChunkGenerator; +import org.bukkit.generator.WorldInfo; import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -44,6 +54,8 @@ import java.util.Set; public class BukkitPlotGenerator extends ChunkGenerator implements GeneratorWrapper { + private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + BukkitPlotGenerator.class.getSimpleName()); + @SuppressWarnings("unused") public final boolean PAPER_ASYNC_SAFE = true; @@ -52,9 +64,15 @@ public class BukkitPlotGenerator extends ChunkGenerator implements GeneratorWrap private final ChunkGenerator platformGenerator; private final boolean full; private final String levelName; + private final boolean useNewGenerationMethods; + private final BiomeProvider biomeProvider; private List populators; private boolean loaded = false; + private PlotArea lastPlotArea; + private int lastChunkX = Integer.MIN_VALUE; + private int lastChunkZ = Integer.MIN_VALUE; + public BukkitPlotGenerator( final @NonNull String name, final @NonNull IndependentPlotGenerator generator, @@ -72,18 +90,23 @@ public class BukkitPlotGenerator extends ChunkGenerator implements GeneratorWrap this.populators.add(new LegacyBlockStatePopulator(this.plotGenerator)); } this.full = true; + this.useNewGenerationMethods = PlotSquared.platform().serverVersion()[1] >= 17; + this.biomeProvider = new BukkitPlotBiomeProvider(); } public BukkitPlotGenerator(final String world, final ChunkGenerator cg, final @NonNull PlotAreaManager plotAreaManager) { if (cg instanceof BukkitPlotGenerator) { - throw new IllegalArgumentException("ChunkGenerator: " + cg.getClass().getName() - + " is already a BukkitPlotGenerator!"); + throw new IllegalArgumentException("ChunkGenerator: " + cg + .getClass() + .getName() + " is already a BukkitPlotGenerator!"); } this.plotAreaManager = plotAreaManager; this.levelName = world; this.full = false; this.platformGenerator = cg; this.plotGenerator = new DelegatePlotGenerator(cg, world); + this.useNewGenerationMethods = PlotSquared.platform().serverVersion()[1] >= 19; + this.biomeProvider = null; } @Override @@ -111,7 +134,7 @@ public class BukkitPlotGenerator extends ChunkGenerator implements GeneratorWrap try { checkLoaded(world); } catch (Exception e) { - e.printStackTrace(); + LOGGER.error("Error attempting to load world into PlotSquared.", e); } ArrayList toAdd = new ArrayList<>(); List existing = world.getPopulators(); @@ -128,6 +151,7 @@ public class BukkitPlotGenerator extends ChunkGenerator implements GeneratorWrap return toAdd; } + // Extracted to synchronized method for thread-safety, preventing multiple internal world load calls private synchronized void checkLoaded(@NonNull World world) { // Do not attempt to load configurations until WorldEdit has a platform ready. if (!PlotSquared.get().isWeInitialised()) { @@ -153,7 +177,7 @@ public class BukkitPlotGenerator extends ChunkGenerator implements GeneratorWrap } } - @SuppressWarnings("deprecation") + @SuppressWarnings("deprecation") // Kept for compatibility with <=1.17.1 private void setSpawnLimits(@NonNull World world, int limit) { world.setAmbientSpawnLimit(limit); world.setAnimalSpawnLimit(limit); @@ -162,10 +186,112 @@ public class BukkitPlotGenerator extends ChunkGenerator implements GeneratorWrap } @Override - public @NonNull ChunkData generateChunkData( - @NonNull World world, @NonNull Random random, int x, int z, - @NonNull BiomeGrid biome + public void generateNoise( + @NotNull final WorldInfo worldInfo, + @NotNull final Random random, + final int chunkX, + final int chunkZ, + @NotNull final ChunkData chunkData ) { + if (this.platformGenerator != this) { + this.platformGenerator.generateNoise(worldInfo, random, chunkX, chunkZ, chunkData); + return; + } + int minY = chunkData.getMinHeight(); + int maxY = chunkData.getMaxHeight(); + GenChunk result = new GenChunk(minY, maxY); + // Set the chunk location + result.setChunk(new ChunkWrapper(worldInfo.getName(), chunkX, chunkZ)); + // Set the result data + result.setChunkData(chunkData); + result.result = null; + + // Catch any exceptions (as exceptions usually thrown) + try { + generate(BlockVector2.at(chunkX, chunkZ), worldInfo.getName(), result, false); + } catch (Throwable e) { + LOGGER.error("Error attempting to generate chunk.", e); + } + } + + @Override + public void generateSurface( + @NotNull final WorldInfo worldInfo, + @NotNull final Random random, + final int chunkX, + final int chunkZ, + @NotNull final ChunkData chunkData + ) { + if (platformGenerator != this) { + platformGenerator.generateSurface(worldInfo, random, chunkX, chunkZ, chunkData); + } + } + + @Override + public void generateBedrock( + @NotNull final WorldInfo worldInfo, + @NotNull final Random random, + final int chunkX, + final int chunkZ, + @NotNull final ChunkData chunkData + ) { + if (platformGenerator != this) { + platformGenerator.generateBedrock(worldInfo, random, chunkX, chunkZ, chunkData); + } + } + + @Override + public void generateCaves( + @NotNull final WorldInfo worldInfo, + @NotNull final Random random, + final int chunkX, + final int chunkZ, + @NotNull final ChunkData chunkData + ) { + if (platformGenerator != this) { + platformGenerator.generateCaves(worldInfo, random, chunkX, chunkZ, chunkData); + } + } + + @Override + public @Nullable BiomeProvider getDefaultBiomeProvider(@NotNull final WorldInfo worldInfo) { + if (platformGenerator != this) { + return platformGenerator.getDefaultBiomeProvider(worldInfo); + } + return biomeProvider; + } + + @Override + public int getBaseHeight( + @NotNull final WorldInfo worldInfo, + @NotNull final Random random, + final int x, + final int z, + @NotNull final HeightMap heightMap + ) { + PlotArea area = getPlotArea(worldInfo.getName(), x, z); + if (area instanceof ClassicPlotWorld cpw) { + // Default to plot height being the heighest point before decoration (i.e. roads, walls etc.) + return cpw.PLOT_HEIGHT; + } + return super.getBaseHeight(worldInfo, random, x, z, heightMap); + } + + @SuppressWarnings("deprecation") // The entire method is deprecated, but kept for compatibility with <=1.16.2 + @Override + @Deprecated(since = "TODO") + public @NonNull ChunkData generateChunkData( + @NonNull World world, @NonNull Random random, int x, int z, @NonNull BiomeGrid biome + ) { + if (useNewGenerationMethods) { + if (this.platformGenerator != this) { + return this.platformGenerator.generateChunkData(world, random, x, z, biome); + } else { + // Return super as it will throw an exception caught by the server that will mean this method is no longer used. + return super.generateChunkData(world, random, x, z, biome); + } + } + int minY = BukkitWorld.getMinWorldHeight(world); int maxY = BukkitWorld.getMaxWorldHeight(world); GenChunk result = new GenChunk(minY, maxY); @@ -175,7 +301,6 @@ public class BukkitPlotGenerator extends ChunkGenerator implements GeneratorWrap for (int chunkZ = 0; chunkZ < 16; chunkZ++) { for (int y = minY; y < maxY; y++) { biome.setBiome(chunkX, y, chunkZ, Biome.PLAINS); - } } } @@ -195,35 +320,32 @@ public class BukkitPlotGenerator extends ChunkGenerator implements GeneratorWrap if (this.platformGenerator != this) { return this.platformGenerator.generateChunkData(world, random, x, z, biome); } else { - generate(BlockVector2.at(x, z), world, result); + generate(BlockVector2.at(x, z), world.getName(), result, true); } } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Error attempting to load world into PlotSquared.", e); } // Return the result data return result.getChunkData(); } - private void generate(BlockVector2 loc, World world, ZeroedDelegateScopedQueueCoordinator result) { + private void generate(BlockVector2 loc, String world, ZeroedDelegateScopedQueueCoordinator result, boolean biomes) { // Load if improperly loaded if (!this.loaded) { - checkLoaded(world); + synchronized (this) { + PlotSquared.get().loadWorld(world, this); + } } // Process the chunk if (ChunkManager.preProcessChunk(loc, result)) { return; } - PlotArea area = this.plotAreaManager.getPlotArea(world.getName(), null); - if (area == null && (area = this.plotAreaManager.getPlotArea(this.levelName, null)) == null) { - throw new IllegalStateException( - "Cannot regenerate chunk that does not belong to a plot area." + " Location: " + loc - + ", world: " + world); - } + PlotArea area = getPlotArea(world, loc.getX(), loc.getZ()); try { - this.plotGenerator.generateChunk(result, area); + this.plotGenerator.generateChunk(result, area, biomes); } catch (Throwable e) { // Recover from generator error - e.printStackTrace(); + LOGGER.error("Error attempting to generate chunk.", e); } ChunkManager.postProcessChunk(loc, result); } @@ -277,4 +399,51 @@ public class BukkitPlotGenerator extends ChunkGenerator implements GeneratorWrap return this.levelName; } + private synchronized PlotArea getPlotArea(String name, int chunkX, int chunkZ) { + // Load if improperly loaded + if (!this.loaded) { + PlotSquared.get().loadWorld(name, this); + // Do not set loaded to true as we want to ensure spawn limits are set when "loading" is actually able to be + // completed properly. + } + if (lastPlotArea != null && name.equals(this.levelName) && chunkX == lastChunkX && chunkZ == lastChunkZ) { + return lastPlotArea; + } + PlotArea area = UncheckedWorldLocation.at(name, chunkX << 4, 0, chunkZ << 4).getPlotArea(); + if (area == null) { + throw new IllegalStateException(String.format("Cannot generate chunk that does not belong to a plot area. World: %s", + name + )); + } + this.lastChunkX = chunkX; + this.lastChunkZ = chunkZ; + return this.lastPlotArea = area; + } + + /** + * Biome provider should never need to be accessed outside of this class. + */ + private final class BukkitPlotBiomeProvider extends BiomeProvider { + + private static final List BIOMES; + + static { + ArrayList biomes = new ArrayList<>(List.of(Biome.values())); + biomes.remove(Biome.CUSTOM); + BIOMES = List.copyOf(biomes); + } + + @Override + public @NotNull Biome getBiome(@NotNull final WorldInfo worldInfo, final int x, final int y, final int z) { + PlotArea area = getPlotArea(worldInfo.getName(), x >> 4, z >> 4); + return BukkitAdapter.adapt(plotGenerator.getBiome(area, x, y, z)); + } + + @Override + public @NotNull List getBiomes(@NotNull final WorldInfo worldInfo) { + return BIOMES; // Allow all biomes + } + + } + } diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/generator/DelegatePlotGenerator.java b/Bukkit/src/main/java/com/plotsquared/bukkit/generator/DelegatePlotGenerator.java index fb303ced5..d55d5fd6c 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/generator/DelegatePlotGenerator.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/generator/DelegatePlotGenerator.java @@ -27,6 +27,7 @@ import com.plotsquared.core.plot.PlotId; import com.plotsquared.core.queue.ZeroedDelegateScopedQueueCoordinator; import com.plotsquared.core.util.MathMan; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.world.biome.BiomeType; import org.bukkit.World; import org.bukkit.block.Biome; import org.bukkit.generator.BlockPopulator; @@ -49,6 +50,11 @@ final class DelegatePlotGenerator extends IndependentPlotGenerator { public void initialize(PlotArea area) { } + @Override + public BiomeType getBiome(final PlotArea settings, final int x, final int y, final int z) { + return null; + } + @Override public String getName() { return this.chunkGenerator.getClass().getName(); @@ -60,7 +66,7 @@ final class DelegatePlotGenerator extends IndependentPlotGenerator { } @Override - public void generateChunk(final ZeroedDelegateScopedQueueCoordinator result, PlotArea settings) { + public void generateChunk(final ZeroedDelegateScopedQueueCoordinator result, PlotArea settings, boolean biomes) { World world = BukkitUtil.getWorld(this.world); Location min = result.getMin(); int chunkX = min.getX() >> 4; diff --git a/Core/src/main/java/com/plotsquared/core/PlotSquared.java b/Core/src/main/java/com/plotsquared/core/PlotSquared.java index 9b8d2fd02..b717a470d 100644 --- a/Core/src/main/java/com/plotsquared/core/PlotSquared.java +++ b/Core/src/main/java/com/plotsquared/core/PlotSquared.java @@ -786,7 +786,9 @@ public class PlotSquared { if (world.equals("CheckingPlotSquaredGenerator")) { return; } - this.getPlotAreaManager().addWorld(world); + if (!this.getPlotAreaManager().addWorld(world)) { + return; + } Set worlds; if (this.worldConfiguration.contains("worlds")) { worlds = this.worldConfiguration.getConfigurationSection("worlds").getKeys(false); diff --git a/Core/src/main/java/com/plotsquared/core/generator/AugmentedUtils.java b/Core/src/main/java/com/plotsquared/core/generator/AugmentedUtils.java index c5eb5d3b9..d59ff596b 100644 --- a/Core/src/main/java/com/plotsquared/core/generator/AugmentedUtils.java +++ b/Core/src/main/java/com/plotsquared/core/generator/AugmentedUtils.java @@ -167,7 +167,7 @@ public class AugmentedUtils { Location.at(world, blockX, area.getMinGenHeight(), blockZ), Location.at(world, blockX + 15, area.getMaxGenHeight(), blockZ + 15) ); - generator.generateChunk(scoped, area); + generator.generateChunk(scoped, area, true); generator.populateChunk(scoped, area); } if (enqueue) { diff --git a/Core/src/main/java/com/plotsquared/core/generator/HybridGen.java b/Core/src/main/java/com/plotsquared/core/generator/HybridGen.java index a5a902c9d..2ac431480 100644 --- a/Core/src/main/java/com/plotsquared/core/generator/HybridGen.java +++ b/Core/src/main/java/com/plotsquared/core/generator/HybridGen.java @@ -42,6 +42,8 @@ import com.sk89q.worldedit.world.block.BlockTypes; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.EnumSet; + public class HybridGen extends IndependentPlotGenerator { private static final CuboidRegion CHUNK = new CuboidRegion(BlockVector3.ZERO, BlockVector3.at(15, 396, 15)); @@ -64,11 +66,11 @@ public class HybridGen extends IndependentPlotGenerator { short relativeZ, int x, int z, - boolean isRoad, - boolean isPopulating + EnumSet features ) { int minY; // Math.min(world.PLOT_HEIGHT, world.ROAD_HEIGHT); - if ((isRoad && Settings.Schematics.PASTE_ROAD_ON_TOP) || (!isRoad && Settings.Schematics.PASTE_ON_TOP)) { + if ((features.contains(SchematicFeature.ROAD) && Settings.Schematics.PASTE_ROAD_ON_TOP) + || (!features.contains(SchematicFeature.ROAD) && Settings.Schematics.PASTE_ON_TOP)) { minY = world.SCHEM_Y; } else { minY = world.getMinBuildHeight(); @@ -77,12 +79,15 @@ public class HybridGen extends IndependentPlotGenerator { if (blocks != null) { for (int y = 0; y < blocks.length; y++) { if (blocks[y] != null) { - if (!isPopulating || blocks[y].hasNbtData()) { + if (!features.contains(SchematicFeature.POPULATING) || blocks[y].hasNbtData()) { result.setBlock(x, minY + y, z, blocks[y]); } } } } + if (!features.contains(SchematicFeature.BIOMES)) { + return; + } BiomeType biome = world.G_SCH_B.get(MathMan.pair(relativeX, relativeZ)); if (biome != null) { result.setBiome(x, z, biome); @@ -90,13 +95,15 @@ public class HybridGen extends IndependentPlotGenerator { } @Override - public void generateChunk(@NonNull ZeroedDelegateScopedQueueCoordinator result, @NonNull PlotArea settings) { + public void generateChunk(@NonNull ZeroedDelegateScopedQueueCoordinator result, @NonNull PlotArea settings, boolean biomes) { Preconditions.checkNotNull(result, "result cannot be null"); Preconditions.checkNotNull(settings, "settings cannot be null"); HybridPlotWorld hybridPlotWorld = (HybridPlotWorld) settings; // Biome - result.fillBiome(hybridPlotWorld.getPlotBiome()); + if (biomes) { + result.fillBiome(hybridPlotWorld.getPlotBiome()); + } // Bedrock if (hybridPlotWorld.PLOT_BEDROCK) { for (short x = 0; x < 16; x++) { @@ -105,26 +112,25 @@ public class HybridGen extends IndependentPlotGenerator { } } } + EnumSet roadFeatures = EnumSet.of(SchematicFeature.ROAD); + EnumSet plotFeatures = EnumSet.noneOf(SchematicFeature.class); + if (biomes) { + roadFeatures.add(SchematicFeature.BIOMES); + plotFeatures.add(SchematicFeature.BIOMES); + } + // Coords Location min = result.getMin(); int bx = min.getX() - hybridPlotWorld.ROAD_OFFSET_X; int bz = min.getZ() - hybridPlotWorld.ROAD_OFFSET_Z; + // The relative X-coordinate (within the plot) of the minimum X coordinate // contained in the scoped queue - short relativeOffsetX; - if (bx < 0) { - relativeOffsetX = (short) (hybridPlotWorld.SIZE + (bx % hybridPlotWorld.SIZE)); - } else { - relativeOffsetX = (short) (bx % hybridPlotWorld.SIZE); - } + short relativeOffsetX = (short) Math.floorMod(bx, hybridPlotWorld.SIZE); // The relative Z-coordinate (within the plot) of the minimum Z coordinate // contained in the scoped queue - short relativeOffsetZ; - if (bz < 0) { - relativeOffsetZ = (short) (hybridPlotWorld.SIZE + (bz % hybridPlotWorld.SIZE)); - } else { - relativeOffsetZ = (short) (bz % hybridPlotWorld.SIZE); - } + short relativeOffsetZ = (short) Math.floorMod(bz, hybridPlotWorld.SIZE); + // The X-coordinate of a given X coordinate, relative to the // plot (Counting from the corner with the least positive // coordinates) @@ -171,7 +177,7 @@ public class HybridGen extends IndependentPlotGenerator { result.setBlock(x, y, z, hybridPlotWorld.ROAD_BLOCK.toPattern()); } if (hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) { - placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, false); + placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, roadFeatures); } } } else if (insideWallX[x]) { @@ -182,7 +188,7 @@ public class HybridGen extends IndependentPlotGenerator { result.setBlock(x, y, z, hybridPlotWorld.ROAD_BLOCK.toPattern()); } if (hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) { - placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, false); + placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, roadFeatures); } } else { // wall @@ -194,7 +200,7 @@ public class HybridGen extends IndependentPlotGenerator { result.setBlock(x, hybridPlotWorld.WALL_HEIGHT + 1, z, hybridPlotWorld.WALL_BLOCK.toPattern()); } } else { - placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, false); + placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, roadFeatures); } } } @@ -206,7 +212,7 @@ public class HybridGen extends IndependentPlotGenerator { result.setBlock(x, y, z, hybridPlotWorld.ROAD_BLOCK.toPattern()); } if (hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) { - placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, false); + placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, roadFeatures); } } else if (insideWallZ[z]) { // wall @@ -218,7 +224,7 @@ public class HybridGen extends IndependentPlotGenerator { result.setBlock(x, hybridPlotWorld.WALL_HEIGHT + 1, z, hybridPlotWorld.WALL_BLOCK.toPattern()); } } else { - placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, false); + placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, roadFeatures); } } else { // plot @@ -227,7 +233,7 @@ public class HybridGen extends IndependentPlotGenerator { } result.setBlock(x, hybridPlotWorld.PLOT_HEIGHT, z, hybridPlotWorld.TOP_BLOCK.toPattern()); if (hybridPlotWorld.PLOT_SCHEMATIC) { - placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, false, false); + placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, plotFeatures); } } } @@ -236,31 +242,26 @@ public class HybridGen extends IndependentPlotGenerator { } @Override - public boolean populateChunk(final ZeroedDelegateScopedQueueCoordinator result, final PlotArea settings) { + public void populateChunk(final ZeroedDelegateScopedQueueCoordinator result, final PlotArea settings) { HybridPlotWorld hybridPlotWorld = (HybridPlotWorld) settings; if (!hybridPlotWorld.populationNeeded()) { - return false; + return; } + EnumSet roadFeatures = EnumSet.of(SchematicFeature.POPULATING, SchematicFeature.ROAD); + EnumSet plotFeatures = EnumSet.of(SchematicFeature.POPULATING); + // Coords Location min = result.getMin(); int bx = min.getX() - hybridPlotWorld.ROAD_OFFSET_X; int bz = min.getZ() - hybridPlotWorld.ROAD_OFFSET_Z; + // The relative X-coordinate (within the plot) of the minimum X coordinate // contained in the scoped queue - short relativeOffsetX; - if (bx < 0) { - relativeOffsetX = (short) (hybridPlotWorld.SIZE + (bx % hybridPlotWorld.SIZE)); - } else { - relativeOffsetX = (short) (bx % hybridPlotWorld.SIZE); - } + short relativeOffsetX = (short) Math.floorMod(bx, hybridPlotWorld.SIZE); // The relative Z-coordinate (within the plot) of the minimum Z coordinate // contained in the scoped queue - short relativeOffsetZ; - if (bz < 0) { - relativeOffsetZ = (short) (hybridPlotWorld.SIZE + (bz % hybridPlotWorld.SIZE)); - } else { - relativeOffsetZ = (short) (bz % hybridPlotWorld.SIZE); - } + short relativeOffsetZ = (short) Math.floorMod(bz, hybridPlotWorld.SIZE); + boolean allRoad = true; boolean overlap = false; @@ -313,17 +314,17 @@ public class HybridGen extends IndependentPlotGenerator { if (insideRoadX[x] || insideWallX[x]) { if (hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) { for (short z = 0; z < 16; z++) { - placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, true); + placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, roadFeatures); } } } else { for (short z = 0; z < 16; z++) { if (insideRoadZ[z] || insideWallZ[z]) { if (hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) { - placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, true); + placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, roadFeatures); } } else if (hybridPlotWorld.PLOT_SCHEMATIC) { - placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, false, true); + placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, plotFeatures); } } } @@ -364,7 +365,7 @@ public class HybridGen extends IndependentPlotGenerator { } } } - return true; + return; } @Override @@ -377,6 +378,27 @@ public class HybridGen extends IndependentPlotGenerator { // All initialization is done in the PlotArea class } + @Override + public BiomeType getBiome(final PlotArea settings, final int worldX, final int worldY, final int worldZ) { + HybridPlotWorld hybridPlotWorld = (HybridPlotWorld) settings; + if (!hybridPlotWorld.PLOT_SCHEMATIC && !hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) { + return hybridPlotWorld.getPlotBiome(); + } + int relativeX = worldX; + int relativeZ = worldZ; + if (hybridPlotWorld.ROAD_OFFSET_X != 0) { + relativeX -= hybridPlotWorld.ROAD_OFFSET_X; + } + if (hybridPlotWorld.ROAD_OFFSET_Z != 0) { + relativeZ -= hybridPlotWorld.ROAD_OFFSET_Z; + } + int size = hybridPlotWorld.PLOT_WIDTH + hybridPlotWorld.ROAD_WIDTH; + relativeX = Math.floorMod(relativeX, size); + relativeZ = Math.floorMod(relativeZ, size); + BiomeType biome = hybridPlotWorld.G_SCH_B.get(MathMan.pair((short) relativeX, (short) relativeZ)); + return biome == null ? hybridPlotWorld.getPlotBiome() : biome; + } + /** * Wrapper to allow a WorldEdit {@link Entity} to effectively have a mutable location as the location in its NBT should be changed * when set to the world. @@ -431,4 +453,10 @@ public class HybridGen extends IndependentPlotGenerator { } + private enum SchematicFeature { + BIOMES, + ROAD, + POPULATING + } + } diff --git a/Core/src/main/java/com/plotsquared/core/generator/HybridUtils.java b/Core/src/main/java/com/plotsquared/core/generator/HybridUtils.java index c9d8745cc..86b691360 100644 --- a/Core/src/main/java/com/plotsquared/core/generator/HybridUtils.java +++ b/Core/src/main/java/com/plotsquared/core/generator/HybridUtils.java @@ -163,7 +163,7 @@ public class HybridUtils { int relChunkZ = chunkPos.getZ() - cbz; oldBlockQueue.setOffsetX(relChunkX << 4); oldBlockQueue.setOffsetZ(relChunkZ << 4); - hpw.getGenerator().generateChunk(oldBlockQueue, hpw); + hpw.getGenerator().generateChunk(oldBlockQueue, hpw, false); }); final BlockState[][][] oldBlocks = oldBlockQueue.getBlockStates(); diff --git a/Core/src/main/java/com/plotsquared/core/generator/IndependentPlotGenerator.java b/Core/src/main/java/com/plotsquared/core/generator/IndependentPlotGenerator.java index 04137b797..b2d4d2a05 100644 --- a/Core/src/main/java/com/plotsquared/core/generator/IndependentPlotGenerator.java +++ b/Core/src/main/java/com/plotsquared/core/generator/IndependentPlotGenerator.java @@ -23,6 +23,7 @@ import com.plotsquared.core.plot.PlotArea; import com.plotsquared.core.plot.PlotId; import com.plotsquared.core.queue.ZeroedDelegateScopedQueueCoordinator; import com.plotsquared.core.setup.PlotAreaBuilder; +import com.sk89q.worldedit.world.biome.BiomeType; import org.checkerframework.checker.nullness.qual.NonNull; /** @@ -42,14 +43,21 @@ public abstract class IndependentPlotGenerator { /** * Generate chunk block data * - * @param result queue + * @param result Queue to write to * @param settings PlotArea (settings) + * @param biomes If biomes should be generated + * @since TODO */ - public abstract void generateChunk(ZeroedDelegateScopedQueueCoordinator result, PlotArea settings); + public abstract void generateChunk(ZeroedDelegateScopedQueueCoordinator result, PlotArea settings, boolean biomes); - public boolean populateChunk(ZeroedDelegateScopedQueueCoordinator result, PlotArea setting) { - return false; - } + /** + * Populate a chunk-queue with tile entities, entities, etc. + * + * @param result Queue to write to + * @param setting PlotArea (settings) + * @since TODO + */ + public void populateChunk(ZeroedDelegateScopedQueueCoordinator result, PlotArea setting) {} /** * Return a new PlotArea object. @@ -91,6 +99,18 @@ public abstract class IndependentPlotGenerator { return (GeneratorWrapper) PlotSquared.platform().wrapPlotGenerator(world, this); } + /** + * Get the biome to be generated at a specific point + * + * @param settings PlotArea settings to provide biome + * @param x World x position + * @param y World y position + * @param z World z position + * @return Biome type to be generated + * @since TODO + */ + public abstract BiomeType getBiome(PlotArea settings, int x, int y, int z); + @Override public String toString() { return getName(); diff --git a/Core/src/main/java/com/plotsquared/core/generator/SingleWorldGenerator.java b/Core/src/main/java/com/plotsquared/core/generator/SingleWorldGenerator.java index 0409417ce..e5eaa1a63 100644 --- a/Core/src/main/java/com/plotsquared/core/generator/SingleWorldGenerator.java +++ b/Core/src/main/java/com/plotsquared/core/generator/SingleWorldGenerator.java @@ -26,7 +26,9 @@ import com.plotsquared.core.plot.world.PlotAreaManager; import com.plotsquared.core.plot.world.SinglePlotArea; import com.plotsquared.core.plot.world.SinglePlotAreaManager; import com.plotsquared.core.queue.ZeroedDelegateScopedQueueCoordinator; +import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeTypes; +import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockTypes; import org.checkerframework.checker.nullness.qual.NonNull; @@ -38,6 +40,9 @@ public class SingleWorldGenerator extends IndependentPlotGenerator { private static final Location dirt2 = Location.at("", 15, 2, 15); private static final Location grass1 = Location.at("", 0, 3, 0); private static final Location grass2 = Location.at("", 15, 3, 15); + private static final BlockState BEDROCK = BlockTypes.BEDROCK.getDefaultState(); + private static final BlockState DIRT = BlockTypes.DIRT.getDefaultState(); + private static final BlockState GRASS_BLOCK = BlockTypes.GRASS_BLOCK.getDefaultState(); private final PlotAreaManager plotAreaManager; @@ -52,19 +57,21 @@ public class SingleWorldGenerator extends IndependentPlotGenerator { } @Override - public void generateChunk(ZeroedDelegateScopedQueueCoordinator result, PlotArea settings) { + public void generateChunk(ZeroedDelegateScopedQueueCoordinator result, PlotArea settings, boolean biomes) { SinglePlotArea area = (SinglePlotArea) settings; if (area.VOID) { Location min = result.getMin(); if (min.getX() == 0 && min.getZ() == 0) { - result.setBlock(0, 0, 0, BlockTypes.BEDROCK.getDefaultState()); + result.setBlock(0, 0, 0, BEDROCK); } } else { - result.setCuboid(bedrock1, bedrock2, BlockTypes.BEDROCK.getDefaultState()); - result.setCuboid(dirt1, dirt2, BlockTypes.DIRT.getDefaultState()); - result.setCuboid(grass1, grass2, BlockTypes.GRASS_BLOCK.getDefaultState()); + result.setCuboid(bedrock1, bedrock2, BEDROCK); + result.setCuboid(dirt1, dirt2, DIRT); + result.setCuboid(grass1, grass2, GRASS_BLOCK); + } + if (biomes) { + result.fillBiome(BiomeTypes.PLAINS); } - result.fillBiome(BiomeTypes.PLAINS); } @Override @@ -76,4 +83,9 @@ public class SingleWorldGenerator extends IndependentPlotGenerator { public void initialize(PlotArea area) { } + @Override + public BiomeType getBiome(final PlotArea settings, final int x, final int y, final int z) { + return BiomeTypes.PLAINS; + } + } diff --git a/Core/src/main/java/com/plotsquared/core/plot/BlockBucket.java b/Core/src/main/java/com/plotsquared/core/plot/BlockBucket.java index 857cb7cd3..864e5a007 100644 --- a/Core/src/main/java/com/plotsquared/core/plot/BlockBucket.java +++ b/Core/src/main/java/com/plotsquared/core/plot/BlockBucket.java @@ -110,54 +110,62 @@ public final class BlockBucket implements ConfigurationSerializable { if (isCompiled()) { return; } - this.compiled = true; - String string = this.input.toString(); - if (string.isEmpty()) { - this.single = null; - this.pattern = null; - return; - } - // Convert legacy format - boolean legacy = false; - String[] blocksStr = string.split(",(?![^\\(\\[]*[\\]\\)])"); - if (blocksStr.length == 1) { - try { - Matcher matcher = regex.matcher(string); + // Synchronized as BlockBuckets may require compilation asynchronously due to async chunk generation on Paper servers + synchronized (this) { + if (isCompiled()) { + return; + } + String string = this.input.toString(); + if (string.isEmpty()) { + this.single = null; + this.pattern = null; + this.compiled = true; + return; + } + // Convert legacy format + boolean legacy = false; + String[] blocksStr = string.split(",(?![^\\(\\[]*[\\]\\)])"); + if (blocksStr.length == 1) { + try { + Matcher matcher = regex.matcher(string); + if (matcher.find()) { + String chanceStr = matcher.group("chance"); + String block = matcher.group("block"); + //noinspection PointlessNullCheck + if (chanceStr != null && block != null && !MathMan.isInteger(block) && MathMan + .isInteger(chanceStr)) { + String namespace = matcher.group("namespace"); + string = (namespace == null ? "" : namespace + ":") + block; + } + } + this.single = BlockUtil.get(string); + this.pattern = new BlockPattern(single); + this.compiled = true; + return; + } catch (Exception ignore) { + } + } + for (int i = 0; i < blocksStr.length; i++) { + String entry = blocksStr[i]; + Matcher matcher = regex.matcher(entry); if (matcher.find()) { String chanceStr = matcher.group("chance"); - String block = matcher.group("block"); //noinspection PointlessNullCheck - if (chanceStr != null && block != null && !MathMan.isInteger(block) && MathMan - .isInteger(chanceStr)) { - String namespace = matcher.group("namespace"); - string = (namespace == null ? "" : namespace + ":") + block; + if (chanceStr != null && MathMan.isInteger(chanceStr)) { + String[] parts = entry.split(":"); + parts = Arrays.copyOf(parts, parts.length - 1); + entry = chanceStr + "%" + StringMan.join(parts, ":"); + blocksStr[i] = entry; + legacy = true; } } - this.single = BlockUtil.get(string); - this.pattern = new BlockPattern(single); - return; - } catch (Exception ignore) { } - } - for (int i = 0; i < blocksStr.length; i++) { - String entry = blocksStr[i]; - Matcher matcher = regex.matcher(entry); - if (matcher.find()) { - String chanceStr = matcher.group("chance"); - //noinspection PointlessNullCheck - if (chanceStr != null && MathMan.isInteger(chanceStr)) { - String[] parts = entry.split(":"); - parts = Arrays.copyOf(parts, parts.length - 1); - entry = chanceStr + "%" + StringMan.join(parts, ":"); - blocksStr[i] = entry; - legacy = true; - } + if (legacy) { + string = StringMan.join(blocksStr, ","); } + pattern = PatternUtil.parse(null, string); + this.compiled = true; } - if (legacy) { - string = StringMan.join(blocksStr, ","); - } - pattern = PatternUtil.parse(null, string); } public boolean isCompiled() { diff --git a/Core/src/main/java/com/plotsquared/core/plot/world/DefaultPlotAreaManager.java b/Core/src/main/java/com/plotsquared/core/plot/world/DefaultPlotAreaManager.java index 258adfd19..e51e01eb3 100644 --- a/Core/src/main/java/com/plotsquared/core/plot/world/DefaultPlotAreaManager.java +++ b/Core/src/main/java/com/plotsquared/core/plot/world/DefaultPlotAreaManager.java @@ -137,15 +137,16 @@ public class DefaultPlotAreaManager implements PlotAreaManager { } @Override - public void addWorld(final @NonNull String worldName) { + public boolean addWorld(final @NonNull String worldName) { PlotWorld world = this.plotWorlds.get(worldName); if (world != null) { - return; + return false; } // Create a new empty world. When a new area is added // the world will be re-recreated with the correct type world = new StandardPlotWorld(worldName, null); this.plotWorlds.put(worldName, world); + return true; } @Override diff --git a/Core/src/main/java/com/plotsquared/core/plot/world/PlotAreaManager.java b/Core/src/main/java/com/plotsquared/core/plot/world/PlotAreaManager.java index fcbac1f9e..b265e9394 100644 --- a/Core/src/main/java/com/plotsquared/core/plot/world/PlotAreaManager.java +++ b/Core/src/main/java/com/plotsquared/core/plot/world/PlotAreaManager.java @@ -110,8 +110,10 @@ public interface PlotAreaManager { * Add a world * * @param worldName Name of the world to add + * @return {@code true} if successful, {@code false} if world already existed + * @since TODO */ - void addWorld(@NonNull String worldName); + boolean addWorld(@NonNull String worldName); /** * Remove a world diff --git a/Core/src/main/java/com/plotsquared/core/plot/world/SinglePlotAreaManager.java b/Core/src/main/java/com/plotsquared/core/plot/world/SinglePlotAreaManager.java index 858b1d507..b856997a5 100644 --- a/Core/src/main/java/com/plotsquared/core/plot/world/SinglePlotAreaManager.java +++ b/Core/src/main/java/com/plotsquared/core/plot/world/SinglePlotAreaManager.java @@ -172,8 +172,8 @@ public class SinglePlotAreaManager extends DefaultPlotAreaManager { } @Override - public void addWorld(final @NonNull String worldName) { - super.addWorld(worldName); + public boolean addWorld(final @NonNull String worldName) { + return super.addWorld(worldName); } @Override diff --git a/Core/src/main/java/com/plotsquared/core/util/ChunkManager.java b/Core/src/main/java/com/plotsquared/core/util/ChunkManager.java index a3a479c34..76ba793d1 100644 --- a/Core/src/main/java/com/plotsquared/core/util/ChunkManager.java +++ b/Core/src/main/java/com/plotsquared/core/util/ChunkManager.java @@ -38,7 +38,6 @@ public abstract class ChunkManager { /** * @since TODO */ - @Deprecated(forRemoval = true, since = "6.9.0") public static void setChunkInPlotArea( RunnableVal force, RunnableVal add,