From 77f41e2a95ad1ea8fc14f3aff1a4c3ceaacf0d0c Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Mon, 14 Jul 2025 04:17:26 +0200 Subject: [PATCH 01/60] move world generation to SkeletonWorldGenerator and copy skeleton data in PlotWorldGenerator --- pom.xml | 5 + .../plot/generator/AbstractPlotGenerator.java | 1 + .../{ => world}/PlotWorldGenerator.java | 94 +++++++++---------- .../world/SkeletonWorldGenerator.java | 83 ++++++++++++++++ 4 files changed, 133 insertions(+), 50 deletions(-) rename src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/{ => world}/PlotWorldGenerator.java (64%) create mode 100644 src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java diff --git a/pom.xml b/pom.xml index 77e855ea..9fcb2ca6 100644 --- a/pom.xml +++ b/pom.xml @@ -172,6 +172,11 @@ 1.5 provided + + com.github.Querz + NBT + 6.1 + diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java index f85c53ac..27c7b9d8 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java @@ -28,6 +28,7 @@ import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.generator.world.PlotWorldGenerator; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.core.system.plot.world.CityPlotWorld; diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/PlotWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java similarity index 64% rename from src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/PlotWorldGenerator.java rename to src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java index 2482bb4a..f7926991 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/PlotWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java @@ -22,7 +22,7 @@ * SOFTWARE. */ -package com.alpsbte.plotsystem.core.system.plot.generator; +package com.alpsbte.plotsystem.core.system.plot.generator.world; import com.alpsbte.plotsystem.PlotSystem; import com.alpsbte.plotsystem.utils.DependencyManager; @@ -37,25 +37,28 @@ import com.sk89q.worldguard.protection.managers.storage.StorageException; import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; import com.sk89q.worldguard.protection.regions.RegionContainer; +import net.querz.nbt.io.NBTUtil; +import net.querz.nbt.io.NamedTag; +import net.querz.nbt.tag.CompoundTag; +import org.apache.commons.io.FileUtils; import org.bukkit.*; -import org.bukkit.generator.ChunkGenerator; -import javax.annotation.Nonnull; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Objects; -import java.util.Random; import static net.kyori.adventure.text.Component.text; public class PlotWorldGenerator { private final MVWorldManager worldManager = DependencyManager.getMultiverseCore().getMVWorldManager(); - private WorldCreator worldCreator; - private final String worldName; private static final World.Environment environment = World.Environment.NORMAL; private static final WorldType worldType = WorldType.FLAT; - private static final String generatorSettings = "{\"features\": false,\"layers\": [{\"block\": \"air\", \"height\": 1}],\"biome\":\"plains\"}"; - public PlotWorldGenerator(String worldName) throws Exception { + private World world = null; + + public PlotWorldGenerator(String worldName) throws IOException, StorageException { this.worldName = worldName; generateWorld(); createMultiverseWorld(); @@ -63,44 +66,44 @@ public PlotWorldGenerator(String worldName) throws Exception { createGlobalProtection(); } - protected void generateWorld() { - worldCreator = new WorldCreator(worldName); - worldCreator.environment(environment); - worldCreator.type(worldType); - worldCreator.generator(new EmptyChunkGenerator()); - worldCreator.generatorSettings(generatorSettings); - worldCreator.createWorld(); + protected void generateWorld() throws IOException { + // copy skeleton world with correct world name + Path skeletonPath = Bukkit.getWorldContainer().toPath().resolve("Skeleton"); + Path worldPath = Bukkit.getWorldContainer().toPath().resolve(worldName); + FileUtils.copyDirectory(skeletonPath.toFile(), worldPath.toFile()); + + // rename world name in level.dat + Path levelDat = worldPath.resolve("level.dat"); + NamedTag level = NBTUtil.read(levelDat.toFile()); + CompoundTag tag = (CompoundTag) level.getTag(); + tag.remove("LevelName"); + tag.putString("LevelName", worldName); + level.setTag(tag); + NBTUtil.write(level, levelDat.toFile()); + + // rename world in paper-world.yml + Path paperWorld = worldPath.resolve("paper-world.yml"); + String paperWorldContents = Files.readString(paperWorld); + String updatedContents = paperWorldContents + .replaceAll(SkeletonWorldGenerator.WORLD_NAME, worldName) + .replaceAll(SkeletonWorldGenerator.WORLD_NAME.toLowerCase(), worldName.toLowerCase()); + Files.writeString(paperWorld, updatedContents); + + // load world + this.world = Bukkit.getWorld(worldName); } - protected void createMultiverseWorld() throws Exception { - // Check if world creator is configured and add new world to multiverse world manager - if (worldCreator != null) { - if (!worldManager.isMVWorld(worldName)) - worldManager.addWorld(worldName, environment, null, worldType, false, - "VoidGen:{\"caves\":false,\"decoration\":false,\"mobs\":false,\"structures\":false}", false); - } else { - throw new Exception("World Creator is not configured"); - } + protected void createMultiverseWorld() { + assert this.world != null; + if (worldManager.isMVWorld(worldName)) return; + + worldManager.addWorld(worldName, environment, null, worldType, false, + "VoidGen:{\"caves\":false,\"decoration\":false,\"mobs\":false,\"structures\":false}", false); } protected void configureWorld() { - World bukkitWorld = Bukkit.getWorld(worldName); - MultiverseWorld mvWorld = worldManager.getMVWorld(bukkitWorld); - - // Set world time to midday - assert bukkitWorld != null; - bukkitWorld.setTime(6000); - - // Set Bukkit world game rules - bukkitWorld.setGameRule(GameRule.RANDOM_TICK_SPEED, 0); - bukkitWorld.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false); - bukkitWorld.setGameRule(GameRule.DO_FIRE_TICK, false); - bukkitWorld.setGameRule(GameRule.DO_WEATHER_CYCLE, false); - bukkitWorld.setGameRule(GameRule.KEEP_INVENTORY, true); - bukkitWorld.setGameRule(GameRule.DO_MOB_SPAWNING, false); - bukkitWorld.setGameRule(GameRule.ANNOUNCE_ADVANCEMENTS, false); - bukkitWorld.setGameRule(GameRule.DO_TILE_DROPS, false); - bukkitWorld.setGameRule(GameRule.DO_MOB_LOOT, false); + assert this.world != null; + MultiverseWorld mvWorld = worldManager.getMVWorld(this.world); // Configure multiverse world mvWorld.setAllowFlight(true); @@ -136,15 +139,6 @@ protected void createGlobalProtection() throws StorageException { regionManager.saveChanges(); } else PlotSystem.getPlugin().getComponentLogger().warn(text("Region Manager is null!")); } - - public static class EmptyChunkGenerator extends ChunkGenerator { - @Override - @Nonnull - public ChunkData generateChunkData(@Nonnull World world, @Nonnull Random random, int x, int z, @Nonnull BiomeGrid biome) { - return createChunkData(world); - } - } - } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java new file mode 100644 index 00000000..43454daa --- /dev/null +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java @@ -0,0 +1,83 @@ +/* + * The MIT License (MIT) + * + * Copyright © 2021-2025, Alps BTE + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.alpsbte.plotsystem.core.system.plot.generator.world; + +import net.kyori.adventure.util.TriState; +import org.bukkit.*; +import org.bukkit.generator.ChunkGenerator; + +import javax.annotation.Nonnull; +import java.util.Random; + +public class SkeletonWorldGenerator { + public static final String WORLD_NAME = "Skeleton"; + private static final World.Environment ENVIRONMENT = World.Environment.NORMAL; + private static final WorldType WORLD_TYPE = WorldType.FLAT; + private static final String GENERATOR_SETTINGS = "{\"features\": false,\"layers\": [{\"block\": \"air\", \"height\": 1}],\"biome\":\"plains\"}"; + + private World world; + + public SkeletonWorldGenerator() { + generateWorld(); + configureWorld(); + } + + protected void generateWorld() { + WorldCreator worldCreator = new WorldCreator(WORLD_NAME) + .environment(ENVIRONMENT) + .type(WORLD_TYPE) + .generator(new SkeletonWorldGenerator.EmptyChunkGenerator()) + .generatorSettings(GENERATOR_SETTINGS) + .keepSpawnLoaded(TriState.FALSE); + this.world = worldCreator.createWorld(); + } + + protected void configureWorld() { + World bukkitWorld = this.world; + assert bukkitWorld != null; + + // Set game rules + bukkitWorld.setGameRule(GameRule.RANDOM_TICK_SPEED, 0); + bukkitWorld.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false); + bukkitWorld.setGameRule(GameRule.DO_FIRE_TICK, false); + bukkitWorld.setGameRule(GameRule.DO_WEATHER_CYCLE, false); + bukkitWorld.setGameRule(GameRule.KEEP_INVENTORY, true); + bukkitWorld.setGameRule(GameRule.DO_MOB_SPAWNING, false); + bukkitWorld.setGameRule(GameRule.ANNOUNCE_ADVANCEMENTS, false); + bukkitWorld.setGameRule(GameRule.DO_TILE_DROPS, false); + bukkitWorld.setGameRule(GameRule.SPAWN_CHUNK_RADIUS, 0); + + // Set time to noon + bukkitWorld.setTime(6000); + } + + public static class EmptyChunkGenerator extends ChunkGenerator { + @Override + @Nonnull + public ChunkData generateChunkData(@Nonnull World world, @Nonnull Random random, int x, int z, @Nonnull BiomeGrid biome) { + return createChunkData(world); + } + } +} From 92632068de619f798407aa0f5eea24b4c79e7a56 Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Mon, 14 Jul 2025 13:18:59 +0200 Subject: [PATCH 02/60] generate skeleton on startup if not exists. --- src/main/java/com/alpsbte/plotsystem/PlotSystem.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java index cacd4c3b..08c18085 100644 --- a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java +++ b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java @@ -36,6 +36,7 @@ import com.alpsbte.plotsystem.core.holograms.HologramRegister; import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.generator.world.SkeletonWorldGenerator; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.core.system.tutorial.AbstractTutorial; import com.alpsbte.plotsystem.core.system.tutorial.BeginnerTutorial; @@ -145,6 +146,13 @@ public void onEnable() { Bukkit.getScheduler().runTaskTimerAsynchronously(FancyNpcsPlugin.get().getPlugin(), new TutorialNPCTurnTracker(), 0, 1L); } + // Generate Skeleton World + if (Bukkit.getWorld("Skeleton") == null) { + getComponentLogger().info("No skeleton world found!"); + getComponentLogger().info("Generating skeleton world..."); + new SkeletonWorldGenerator(); + } + pluginEnabled = true; getComponentLogger().info(text("Enabled Plot-System plugin.", DARK_GREEN)); getComponentLogger().info(text("------------------------------------------------------", GOLD)); From 573884f618ad6c004ac573de4079740cca425ceb Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Mon, 14 Jul 2025 13:19:46 +0200 Subject: [PATCH 03/60] save skeleton to generate level.dat and remove uid.dat for multiverse loading --- .../system/plot/generator/world/PlotWorldGenerator.java | 3 +++ .../system/plot/generator/world/SkeletonWorldGenerator.java | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java index f7926991..cd91466a 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java @@ -72,6 +72,9 @@ protected void generateWorld() throws IOException { Path worldPath = Bukkit.getWorldContainer().toPath().resolve(worldName); FileUtils.copyDirectory(skeletonPath.toFile(), worldPath.toFile()); + // delete uid.dat + Files.delete(worldPath.resolve("uid.dat")); + // rename world name in level.dat Path levelDat = worldPath.resolve("level.dat"); NamedTag level = NBTUtil.read(levelDat.toFile()); diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java index 43454daa..7ced0a65 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java @@ -42,6 +42,7 @@ public class SkeletonWorldGenerator { public SkeletonWorldGenerator() { generateWorld(); configureWorld(); + saveWorld(); } protected void generateWorld() { @@ -73,6 +74,11 @@ protected void configureWorld() { bukkitWorld.setTime(6000); } + protected void saveWorld() { + assert this.world != null; + this.world.save(); + } + public static class EmptyChunkGenerator extends ChunkGenerator { @Override @Nonnull From d0b817396d71345f08554168e913e2f2d5281873 Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Sun, 20 Jul 2025 21:24:13 +0200 Subject: [PATCH 04/60] fix async issues and cleanup generation related classes --- .../plotsystem/core/system/plot/Plot.java | 11 +- .../plot/generator/AbstractPlotGenerator.java | 53 ++++--- .../plot/generator/DefaultPlotGenerator.java | 37 +++-- .../generator/world/PlotWorldGenerator.java | 23 +-- .../core/system/plot/world/CityPlotWorld.java | 65 ++++----- .../core/system/plot/world/PlotWorld.java | 138 +++++++++--------- 6 files changed, 170 insertions(+), 157 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/Plot.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/Plot.java index 387ebd5a..e67948d0 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/Plot.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/Plot.java @@ -30,7 +30,6 @@ import com.alpsbte.plotsystem.core.system.CityProject; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.core.system.plot.world.CityPlotWorld; -import com.alpsbte.plotsystem.core.system.plot.world.OnePlotWorld; import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; import com.alpsbte.plotsystem.core.system.review.PlotReview; import com.alpsbte.plotsystem.utils.enums.PlotDifficulty; @@ -151,14 +150,8 @@ public boolean setPlotType(PlotType type) { @SuppressWarnings("unchecked") @Override - public T getWorld() { - if (getVersion() <= 2 || getPlotType().hasOnePlotPerWorld()) { - if (onePlotWorld == null) onePlotWorld = new OnePlotWorld(this); - return (T) onePlotWorld; - } else { - if (cityPlotWorld == null) cityPlotWorld = new CityPlotWorld(this); - return (T) cityPlotWorld; - } + public PlotWorld getWorld() { + return PlotWorld.getByType(getPlotType(), this); } @Override diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java index 27c7b9d8..1a69faa8 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java @@ -31,8 +31,6 @@ import com.alpsbte.plotsystem.core.system.plot.generator.world.PlotWorldGenerator; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; -import com.alpsbte.plotsystem.core.system.plot.world.CityPlotWorld; -import com.alpsbte.plotsystem.core.system.plot.world.OnePlotWorld; import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; import com.alpsbte.plotsystem.utils.DependencyManager; import com.alpsbte.plotsystem.utils.Utils; @@ -69,6 +67,7 @@ import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; import com.sk89q.worldguard.protection.regions.ProtectedRegion; import com.sk89q.worldguard.protection.regions.RegionContainer; +import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -78,6 +77,7 @@ import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.concurrent.CompletableFuture; import static net.kyori.adventure.text.Component.text; @@ -87,7 +87,6 @@ public abstract class AbstractPlotGenerator { protected final PlotWorld world; protected final double plotVersion; protected final PlotType plotType; - /** * Generates a new plot in the plot world * @@ -106,7 +105,7 @@ protected AbstractPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder bui * @param plotType - type of the plot */ protected AbstractPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder builder, @NotNull PlotType plotType) { - this(plot, builder, plotType, plot.getVersion() <= 2 || plotType.hasOnePlotPerWorld() ? new OnePlotWorld(plot) : new CityPlotWorld((Plot) plot)); + this(plot, builder, plotType, PlotWorld.getByType(plotType, (Plot) plot)); } /** @@ -123,24 +122,42 @@ private AbstractPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder build this.plotVersion = plot.getVersion(); this.plotType = plotType; - if (init()) { - Exception exception = null; + PlotSystem.getPlugin().getComponentLogger().info("plotType: " + plotType.getId()); + + if (!init()) return; + boolean generateWorld = plotType.hasOnePlotPerWorld() || !world.isWorldGenerated(); + boolean isWorldLoaded = world.isWorldLoaded(); + + CompletableFuture.runAsync(() -> { + final Exception[] exception = new Exception[]{null}; try { - if (plotType.hasOnePlotPerWorld() || !world.isWorldGenerated()) { + if (generateWorld) { new PlotWorldGenerator(world.getWorldName()); - } else if (!world.isWorldLoaded() && !world.loadWorld()) throw new Exception("Could not load world"); - generateOutlines(); - createPlotProtection(); + } else if (!isWorldLoaded) { + final boolean[] successful = new boolean[1]; + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> successful[0] = world.loadWorld()); + if (!successful[0]) throw new Exception("Could not load world"); + } + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + try { + createPlotProtection(); + generateOutlines(); + } catch (StorageException | IOException e) { + exception[0] = e; + } + }); } catch (Exception ex) { - exception = ex; + exception[0] = ex; } - this.onComplete(exception != null, false); - - if (exception != null) { - PlotUtils.Actions.abandonPlot(plot); - onException(exception); - } - } + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + this.onComplete(exception[0] != null, false); + + if (exception[0] != null) { + PlotUtils.Actions.abandonPlot(plot); + onException(exception[0]); + } + }); + }); } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java index 6ada921c..002f0680 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java @@ -70,32 +70,31 @@ public DefaultPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder builder @Override protected boolean init() { - if (getBuilder().getFreeSlot() != null) { - if (DefaultPlotGenerator.playerPlotGenerationHistory.containsKey(getBuilder().getUUID())) { - if (DefaultPlotGenerator.playerPlotGenerationHistory.get(getBuilder().getUUID()).isBefore(LocalDateTime.now().minusSeconds(10))) { - DefaultPlotGenerator.playerPlotGenerationHistory.remove(getBuilder().getUUID()); - } else { - getBuilder().getPlayer().sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(getBuilder().getPlayer(), LangPaths.Message.Error.PLEASE_WAIT))); - getBuilder().getPlayer().playSound(getBuilder().getPlayer().getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); - return false; - } - } - - DefaultPlotGenerator.playerPlotGenerationHistory.put(getBuilder().getUUID(), LocalDateTime.now()); - getBuilder().getPlayer().sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(getBuilder().getPlayer(), LangPaths.Message.Info.CREATING_PLOT))); - getBuilder().getPlayer().playSound(getBuilder().getPlayer().getLocation(), Utils.SoundUtils.CREATE_PLOT_SOUND, 1, 1); - return true; - } else { + if (getBuilder().getFreeSlot() == null) { getBuilder().getPlayer().sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(getBuilder().getPlayer(), LangPaths.Message.Error.ALL_SLOTS_OCCUPIED))); getBuilder().getPlayer().playSound(getBuilder().getPlayer().getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); + return false; } - return false; + + if (DefaultPlotGenerator.playerPlotGenerationHistory.containsKey(getBuilder().getUUID())) { + if (!DefaultPlotGenerator.playerPlotGenerationHistory.get(getBuilder().getUUID()).isBefore(LocalDateTime.now().minusSeconds(10))) { + getBuilder().getPlayer().sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(getBuilder().getPlayer(), LangPaths.Message.Error.PLEASE_WAIT))); + getBuilder().getPlayer().playSound(getBuilder().getPlayer().getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); + return false; + } + DefaultPlotGenerator.playerPlotGenerationHistory.remove(getBuilder().getUUID()); + } + + DefaultPlotGenerator.playerPlotGenerationHistory.put(getBuilder().getUUID(), LocalDateTime.now()); + getBuilder().getPlayer().sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(getBuilder().getPlayer(), LangPaths.Message.Info.CREATING_PLOT))); + getBuilder().getPlayer().playSound(getBuilder().getPlayer().getLocation(), Utils.SoundUtils.CREATE_PLOT_SOUND, 1, 1); + return true; } @Override protected void generateOutlines() throws IOException, WorldEditException { - if (plot instanceof Plot) { - byte[] completedSchematic = ((Plot) plot).getCompletedSchematic(); + if (plot instanceof Plot p) { + byte[] completedSchematic = p.getCompletedSchematic(); if (completedSchematic != null) { Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(world.getBukkitWorld()), BlockTypes.AIR); pasteSchematic(airMask, completedSchematic, world, true); diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java index cd91466a..60532ddf 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java @@ -58,12 +58,21 @@ public class PlotWorldGenerator { private World world = null; - public PlotWorldGenerator(String worldName) throws IOException, StorageException { + public PlotWorldGenerator(String worldName) throws Exception { this.worldName = worldName; generateWorld(); - createMultiverseWorld(); - configureWorld(); - createGlobalProtection(); + + final Exception[] exception = {null}; + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + try { + createMultiverseWorld(); + configureWorld(); + createGlobalProtection(); + } catch (Exception e) { + exception[0] = e; + } + }); + if (exception[0] != null) throw exception[0]; } protected void generateWorld() throws IOException { @@ -91,20 +100,16 @@ protected void generateWorld() throws IOException { .replaceAll(SkeletonWorldGenerator.WORLD_NAME, worldName) .replaceAll(SkeletonWorldGenerator.WORLD_NAME.toLowerCase(), worldName.toLowerCase()); Files.writeString(paperWorld, updatedContents); - - // load world - this.world = Bukkit.getWorld(worldName); } protected void createMultiverseWorld() { - assert this.world != null; if (worldManager.isMVWorld(worldName)) return; - worldManager.addWorld(worldName, environment, null, worldType, false, "VoidGen:{\"caves\":false,\"decoration\":false,\"mobs\":false,\"structures\":false}", false); } protected void configureWorld() { + this.world = Bukkit.getWorld(worldName); assert this.world != null; MultiverseWorld mvWorld = worldManager.getMVWorld(this.world); diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java index b3152dc0..9d2d0599 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java @@ -48,25 +48,23 @@ public CityPlotWorld(@NotNull Plot plot) { @Override public boolean teleportPlayer(@NotNull Player player) { - if (super.teleportPlayer(player)) { - player.playSound(player.getLocation(), Utils.SoundUtils.TELEPORT_SOUND, 1, 1); - player.setAllowFlight(true); - player.setFlying(true); + if (!super.teleportPlayer(player)) return false; - if (getPlot() != null) { - player.sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(player, LangPaths.Message.Info.TELEPORTING_PLOT, String.valueOf(getPlot().getID())))); + player.playSound(player.getLocation(), Utils.SoundUtils.TELEPORT_SOUND, 1, 1); + player.setAllowFlight(true); + player.setFlying(true); - Utils.updatePlayerInventorySlots(player); - PlotUtils.ChatFormatting.sendLinkMessages(getPlot(), player); + if (getPlot() == null) return true; - if (getPlot().getPlotOwner().getUUID().equals(player.getUniqueId())) { - getPlot().setLastActivity(false); - } - } + player.sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(player, LangPaths.Message.Info.TELEPORTING_PLOT, String.valueOf(getPlot().getID())))); - return true; - } - return false; + Utils.updatePlayerInventorySlots(player); + PlotUtils.ChatFormatting.sendLinkMessages(getPlot(), player); + + if (!getPlot().getPlotOwner().getUUID().equals(player.getUniqueId())) return true; + getPlot().setLastActivity(false); + + return true; } @Override @@ -100,25 +98,24 @@ public int getWorldHeight() throws IOException { try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(inputStream)) { clipboard = reader.read(); } - if (clipboard != null) { - int plotHeight = clipboard.getMinimumPoint().y(); - - /// Minimum building height for a plot (this should be configurable depending on minecraft build limit) - /// This is in the case that a plot is created at y level 300 where the max build limit is 318, - /// so you don't want builder to only be able to build for 18 blocks - int minBuildingHeight = 128; - - /// Negative y level of the current minecraft version (1.21) - /// Additional ground layer the plot use to save as schematic need to be included for plot's y-level - int groundLayer = 64; - - // Plots created outside of vanilla build limit or the build-able height is too small - if (plotHeight + groundLayer < MIN_WORLD_HEIGHT + groundLayer - || plotHeight + groundLayer + minBuildingHeight > MAX_WORLD_HEIGHT + groundLayer) - return 0; // throw new IOException("Plot height is out of range."); - return plotHeight; - } - throw new IOException("A Plot's Outline schematic fails to load, cannot get clipboard."); + if (clipboard == null) throw new IOException("A Plot's Outline schematic fails to load, cannot get clipboard."); + + int plotHeight = clipboard.getMinimumPoint().y(); + + /// Minimum building height for a plot (this should be configurable depending on minecraft build limit) + /// This is in the case that a plot is created at y level 300 where the max build limit is 318, + /// so you don't want builder to only be able to build for 18 blocks + int minBuildingHeight = 128; + + /// Negative y level of the current minecraft version (1.21) + /// Additional ground layer the plot use to save as schematic need to be included for plot's y-level + int groundLayer = 64; + + // Plots created outside of vanilla build limit or the build-able height is too small + if (plotHeight + groundLayer < MIN_WORLD_HEIGHT + groundLayer + || plotHeight + groundLayer + minBuildingHeight > MAX_WORLD_HEIGHT + groundLayer) + return 0; // throw new IOException("Plot height is out of range."); + return plotHeight; } /** diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java index ce0ccadb..261d8ae6 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java @@ -28,10 +28,13 @@ import com.alpsbte.plotsystem.PlotSystem; import com.alpsbte.plotsystem.core.database.DataProvider; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; +import com.alpsbte.plotsystem.core.system.plot.Plot; import com.alpsbte.plotsystem.core.system.plot.TutorialPlot; import com.alpsbte.plotsystem.core.system.plot.generator.AbstractPlotGenerator; +import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.utils.DependencyManager; import com.alpsbte.plotsystem.utils.Utils; +import com.alpsbte.plotsystem.utils.io.ConfigPaths; import com.onarandombox.MultiverseCore.MultiverseCore; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.extent.clipboard.Clipboard; @@ -82,48 +85,48 @@ public boolean regenWorld(@NotNull Class ge @Override public boolean deleteWorld() { - if (isWorldGenerated() && loadWorld()) { - if (mvCore.getMVWorldManager().deleteWorld(getWorldName(), true, true) && mvCore.saveWorldConfig()) { - try { - File multiverseInventoriesConfig = new File(DependencyManager.getMultiverseInventoriesConfigPath(getWorldName())); - File worldGuardConfig = new File(DependencyManager.getWorldGuardConfigPath(getWorldName())); - if (multiverseInventoriesConfig.exists()) FileUtils.deleteDirectory(multiverseInventoriesConfig); - if (worldGuardConfig.exists()) FileUtils.deleteDirectory(worldGuardConfig); - } catch (IOException ex) { - PlotSystem.getPlugin().getComponentLogger().warn(text("Could not delete config files for world " + getWorldName() + "!")); - return false; - } - return true; - } else PlotSystem.getPlugin().getComponentLogger().warn(text("Could not delete world " + getWorldName() + "!")); + if (!isWorldGenerated() || !loadWorld()) return false; + if (!mvCore.getMVWorldManager().deleteWorld(getWorldName(), true, true) || !mvCore.saveWorldConfig()) { + PlotSystem.getPlugin().getComponentLogger().warn(text("Could not delete world " + getWorldName() + "!")); + return false; } - return false; + try { + File multiverseInventoriesConfig = new File(DependencyManager.getMultiverseInventoriesConfigPath(getWorldName())); + File worldGuardConfig = new File(DependencyManager.getWorldGuardConfigPath(getWorldName())); + if (multiverseInventoriesConfig.exists()) FileUtils.deleteDirectory(multiverseInventoriesConfig); + if (worldGuardConfig.exists()) FileUtils.deleteDirectory(worldGuardConfig); + } catch (IOException ex) { + PlotSystem.getPlugin().getComponentLogger().warn(text("Could not delete config files for world " + getWorldName() + "!")); + return false; + } + return true; } @Override public boolean loadWorld() { - if (isWorldGenerated()) { - if (isWorldLoaded()) { - return true; - } else return mvCore.getMVWorldManager().loadWorld(getWorldName()) || isWorldLoaded(); - } else PlotSystem.getPlugin().getComponentLogger().warn(text("Could not load world " + worldName + " because it is not generated!")); - return false; + if (!isWorldGenerated()) { + PlotSystem.getPlugin().getComponentLogger().warn(text("Could not load world " + worldName + " because it is not generated!")); + return false; + } + + return isWorldLoaded() || mvCore.getMVWorldManager().loadWorld(getWorldName()); } @Override public boolean unloadWorld(boolean movePlayers) { - if (isWorldGenerated()) { - if (isWorldLoaded()) { - if (movePlayers && !getBukkitWorld().getPlayers().isEmpty()) { - for (Player player : getBukkitWorld().getPlayers()) { - player.teleport(Utils.getSpawnLocation()); - } - } - - return Bukkit.unloadWorld(getBukkitWorld(), true); + if (!isWorldGenerated()) { + PlotSystem.getPlugin().getComponentLogger().warn(text("Could not unload world " + worldName + " because it is not generated!")); + return false; + } + if (!isWorldLoaded()) return true; + + if (movePlayers && !getBukkitWorld().getPlayers().isEmpty()) { + for (Player player : getBukkitWorld().getPlayers()) { + player.teleport(Utils.getSpawnLocation()); } - return true; - } else PlotSystem.getPlugin().getComponentLogger().warn(text("Could not unload world " + worldName + " because it is not generated!")); - return false; + } + + return Bukkit.unloadWorld(getBukkitWorld(), true); } @Override @@ -131,25 +134,21 @@ public boolean teleportPlayer(@NotNull Player player) { if (loadWorld() && plot != null) { player.teleport(getSpawnPoint(plot instanceof TutorialPlot ? null : plot.getCenter())); return true; - } else PlotSystem.getPlugin().getComponentLogger().warn(text("Could not teleport player " + player.getName() + " to world " + worldName + "!")); + } + PlotSystem.getPlugin().getComponentLogger().warn(text("Could not teleport player " + player.getName() + " to world " + worldName + "!")); return false; } @Override public Location getSpawnPoint(BlockVector3 plotVector) { - if (isWorldGenerated() && loadWorld()) { - Location spawnLocation; - if (plotVector == null) { - spawnLocation = getBukkitWorld().getSpawnLocation(); - } else { - spawnLocation = new Location(getBukkitWorld(), plotVector.x(), plotVector.y(), plotVector.z()); - } + if (!isWorldGenerated() || !loadWorld()) return null; + Location spawnLocation = plotVector == null + ? getBukkitWorld().getSpawnLocation() + : new Location(getBukkitWorld(), plotVector.x(), plotVector.y(), plotVector.z()); - // Set spawn point 1 block above the highest block at the spawn location - spawnLocation.setY(getBukkitWorld().getHighestBlockYAt((int) spawnLocation.getX(), (int) spawnLocation.getZ()) + 1d); - return spawnLocation; - } - return null; + // Set spawn point 1 block above the highest block at the spawn location + spawnLocation.setY(getBukkitWorld().getHighestBlockYAt((int) spawnLocation.getX(), (int) spawnLocation.getZ()) + 1d); + return spawnLocation; } @Override @@ -159,17 +158,15 @@ public int getPlotHeight() throws IOException { @Override public int getPlotHeightCentered() throws IOException { - if (plot != null) { - Clipboard clipboard; - ByteArrayInputStream inputStream = new ByteArrayInputStream(plot.getInitialSchematicBytes()); - try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(inputStream)) { - clipboard = reader.read(); - } - if (clipboard != null) { - return (int) clipboard.getRegion().getCenter().y() - clipboard.getMinimumPoint().y(); - } + if (plot == null) return 0; + + Clipboard clipboard; + ByteArrayInputStream inputStream = new ByteArrayInputStream(plot.getInitialSchematicBytes()); + try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(inputStream)) { + clipboard = reader.read(); } - return 0; + if (clipboard == null) return 0; + return (int) clipboard.getRegion().getCenter().y() - clipboard.getMinimumPoint().y(); } @Override @@ -208,13 +205,13 @@ public boolean isWorldGenerated() { } private @Nullable ProtectedRegion getRegion(String regionName) { + if (!loadWorld()) return null; + RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); - if (loadWorld()) { - RegionManager regionManager = container.get(BukkitAdapter.adapt(getBukkitWorld())); - if (regionManager != null) { - return regionManager.getRegion(regionName); - } else PlotSystem.getPlugin().getComponentLogger().warn(text("Region manager is null!")); - } + RegionManager regionManager = container.get(BukkitAdapter.adapt(getBukkitWorld())); + if (regionManager != null) return regionManager.getRegion(regionName); + + PlotSystem.getPlugin().getComponentLogger().warn(text("Region manager is null!")); return null; } @@ -247,12 +244,17 @@ public static boolean isCityPlotWorld(@NotNull String worldName) { * @return - plot world */ public static @Nullable PlotWorld getPlotWorldByName(String worldName) { - if (isOnePlotWorld(worldName) || isCityPlotWorld(worldName)) { - Integer id = AlpsUtils.tryParseInt(worldName.substring(2)); - if (id == null) return new PlotWorld(worldName, null); - AbstractPlot plot = worldName.toLowerCase().startsWith("t-") ? DataProvider.TUTORIAL_PLOT.getById(id).orElse(null) : DataProvider.PLOT.getPlotById(id); - return plot == null ? null : plot.getWorld(); - } - return null; + if (!isOnePlotWorld(worldName) && !isCityPlotWorld(worldName)) return null; + + Integer id = AlpsUtils.tryParseInt(worldName.substring(2)); + if (id == null) return new PlotWorld(worldName, null); + + AbstractPlot plot = worldName.toLowerCase().startsWith("t-") ? DataProvider.TUTORIAL_PLOT.getById(id).orElse(null) : DataProvider.PLOT.getPlotById(id); + return plot == null ? null : plot.getWorld(); + } + + public static PlotWorld getByType(PlotType type, Plot plot) { + boolean disableCIM = PlotSystem.getPlugin().getConfig().getBoolean(ConfigPaths.DISABLE_CITY_INSPIRATION_MODE); + return disableCIM || type.hasOnePlotPerWorld() ? new OnePlotWorld(plot) : new CityPlotWorld(plot); } } From 16f0b39a6cb506d08788aa494a7d93a01af93a4e Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Mon, 4 Aug 2025 00:18:57 +0200 Subject: [PATCH 05/60] add util methods for passing up exceptions in bukkit scheduler --- .../com/alpsbte/plotsystem/utils/Utils.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/main/java/com/alpsbte/plotsystem/utils/Utils.java b/src/main/java/com/alpsbte/plotsystem/utils/Utils.java index 1bd10227..d1cbef3c 100644 --- a/src/main/java/com/alpsbte/plotsystem/utils/Utils.java +++ b/src/main/java/com/alpsbte/plotsystem/utils/Utils.java @@ -62,6 +62,9 @@ import java.util.Objects; import java.util.Random; import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import static com.alpsbte.plotsystem.core.system.tutorial.utils.TutorialUtils.TEXT_HIGHLIGHT_END; import static com.alpsbte.plotsystem.core.system.tutorial.utils.TutorialUtils.TEXT_HIGHLIGHT_START; @@ -118,6 +121,34 @@ public static ItemStack getConfiguredItem(@NotNull String material, Object custo return builder.build(); } + public static CompletableFuture runSync(Callable task) { + CompletableFuture future = new CompletableFuture<>(); + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + try { + var result = task.call(); + future.complete(result); + } catch (Exception e) { + future.completeExceptionally(e); + } + }); + + return future; + } + + public static CompletableFuture supplySync(Callable task) { + CompletableFuture future = new CompletableFuture<>(); + Executor executor = Bukkit.getScheduler().getMainThreadExecutor(PlotSystem.getPlugin()); + executor.execute(() -> { + try { + var result = task.call(); + future.complete(result); + } catch (Exception e) { + future.completeExceptionally(e); + } + }); + return future; + } + public static class SoundUtils { private SoundUtils() {} From 779b31a31692075d3f9752a72cf6a203769498af Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Thu, 7 Aug 2025 02:53:52 +0200 Subject: [PATCH 06/60] - pass schematic bytes into generateOutlines method of PlotGenerators - measure old generator performance - add new AbstractPlotLoader, DefaultPlotLoader and PlotHandler --- .../plotsystem/core/EventListener.java | 2 +- .../core/menus/companion/CityProjectMenu.java | 57 ++-- .../core/system/plot/PlotHandler.java | 93 ++++++ .../plot/generator/AbstractPlotGenerator.java | 24 +- .../plot/generator/DefaultPlotGenerator.java | 12 +- .../plot/generator/TutorialPlotGenerator.java | 2 +- .../generator/loader/AbstractPlotLoader.java | 270 ++++++++++++++++++ .../generator/loader/DefaultPlotLoader.java | 82 ++++++ .../generator/world/PlotWorldGenerator.java | 34 +-- .../core/system/plot/utils/PlotUtils.java | 6 +- 10 files changed, 531 insertions(+), 51 deletions(-) create mode 100644 src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java create mode 100644 src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java create mode 100644 src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java diff --git a/src/main/java/com/alpsbte/plotsystem/core/EventListener.java b/src/main/java/com/alpsbte/plotsystem/core/EventListener.java index 1f1d5b15..1397769c 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/EventListener.java +++ b/src/main/java/com/alpsbte/plotsystem/core/EventListener.java @@ -183,7 +183,7 @@ public void onInventoryClickEvent(@NotNull InventoryClickEvent event) { } @EventHandler - public void onlPlayerItemDropEvent(@NotNull PlayerDropItemEvent event) { + public void onPlayerItemDropEvent(@NotNull PlayerDropItemEvent event) { if (event.getItemDrop().getItemStack().equals(CompanionMenu.getMenuItem(event.getPlayer())) || event.getItemDrop().getItemStack().equals(ReviewMenu.getMenuItem(event.getPlayer()))) { event.setCancelled(true); diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java index 0bccffc8..0a6930a3 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java +++ b/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java @@ -33,15 +33,19 @@ import com.alpsbte.plotsystem.core.system.CityProject; import com.alpsbte.plotsystem.core.system.Country; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.core.system.plot.generator.DefaultPlotGenerator; +import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.PlotDifficulty; import com.alpsbte.plotsystem.utils.enums.Status; import com.alpsbte.plotsystem.utils.io.ConfigPaths; +import com.alpsbte.plotsystem.utils.io.ConfigUtil; import com.alpsbte.plotsystem.utils.io.LangPaths; import com.alpsbte.plotsystem.utils.io.LangUtil; import com.alpsbte.plotsystem.utils.items.MenuItems; import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.Player; import org.ipvp.canvas.mask.BinaryMask; @@ -50,6 +54,7 @@ import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class CityProjectMenu extends AbstractPaginatedMenu { @@ -126,6 +131,34 @@ protected void setItemClickEventsAsync() { CompanionMenu.clickEventTutorialItem(getMenu()); } + public void handleCityProjectClick(Player player, CityProject city) { + Builder builder = Builder.byUUID(player.getUniqueId()); + + PlotDifficulty plotDifficultyForCity; + try { + plotDifficultyForCity = selectedPlotDifficulty != null ? selectedPlotDifficulty : Plot.getPlotDifficultyForBuilder(city, builder).get(); + } catch (ExecutionException | InterruptedException ex) { + sqlError(player, ex); + return; + } + + List unclaimedPlots = DataProvider.PLOT.getPlots(city, plotDifficultyForCity, Status.unclaimed); + if (unclaimedPlots.isEmpty()) { + player.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(player, LangPaths.Message.Error.NO_PLOTS_LEFT))); + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> player.playSound(player.getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1)); + return; + } + + Plot plot = unclaimedPlots.get(Utils.getRandom().nextInt(unclaimedPlots.size())); + + PlotType type = builder.getPlotType(); + if (type.equals(PlotType.CITY_INSPIRATION_MODE) && ConfigUtil.getInstance().configs[0].getBoolean(ConfigPaths.DISABLE_CITY_INSPIRATION_MODE)) + type = PlotType.LOCAL_INSPIRATION_MODE; + + boolean successful = PlotHandler.assignPlot(builder, plot); + if (successful) PlotHandler.generatePlot(builder, plot, type); + } + public static boolean generateRandomPlot(Player player, @NotNull List items, PlotDifficulty selectedPlotDifficulty) { PlotDifficulty difficulty = selectedPlotDifficulty; if (items.isEmpty()) { @@ -205,29 +238,9 @@ protected void setPaginatedItemClickEventsAsync(List source) { clickPlayer.playSound(clickPlayer.getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); return; } - clickPlayer.closeInventory(); - Builder builder = Builder.byUUID(clickPlayer.getUniqueId()); - - try { - PlotDifficulty plotDifficultyForCity = selectedPlotDifficulty != null ? selectedPlotDifficulty : Plot.getPlotDifficultyForBuilder(city, builder).get(); - List unclaimedPlots = DataProvider.PLOT.getPlots(city, plotDifficultyForCity, Status.unclaimed); - if (unclaimedPlots.isEmpty()) { - clickPlayer.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(clickPlayer, LangPaths.Message.Error.NO_PLOTS_LEFT))); - clickPlayer.playSound(clickPlayer.getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); - return; - } - - if (selectedPlotDifficulty != null && PlotSystem.getPlugin().getConfig().getBoolean(ConfigPaths.ENABLE_SCORE_REQUIREMENT) && !DataProvider.DIFFICULTY.builderMeetsRequirements(builder, selectedPlotDifficulty)) { - clickPlayer.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(clickPlayer, LangPaths.Message.Error.PLAYER_NEEDS_HIGHER_SCORE))); - clickPlayer.playSound(clickPlayer.getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); - return; - } - - new DefaultPlotGenerator(city, plotDifficultyForCity, builder); - } catch (ExecutionException | InterruptedException ex) { - sqlError(clickPlayer, ex); - } + + CompletableFuture.runAsync(() -> handleCityProjectClick(clickPlayer, city)); }); slot++; } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java new file mode 100644 index 00000000..88734796 --- /dev/null +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java @@ -0,0 +1,93 @@ +/* + * The MIT License (MIT) + * + * Copyright © 2021-2025, Alps BTE + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.alpsbte.plotsystem.core.system.plot; + +import com.alpsbte.plotsystem.PlotSystem; +import com.alpsbte.plotsystem.core.database.DataProvider; +import com.alpsbte.plotsystem.core.system.Builder; +import com.alpsbte.plotsystem.core.system.plot.generator.loader.DefaultPlotLoader; +import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; +import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; +import com.alpsbte.plotsystem.utils.Utils; +import com.alpsbte.plotsystem.utils.enums.Slot; +import com.alpsbte.plotsystem.utils.enums.Status; +import com.alpsbte.plotsystem.utils.io.ConfigPaths; +import com.alpsbte.plotsystem.utils.io.LangPaths; +import com.alpsbte.plotsystem.utils.io.LangUtil; +import org.bukkit.SoundCategory; +import org.bukkit.entity.Player; + +import java.time.LocalDateTime; +import java.util.*; + +public class PlotHandler { + private PlotHandler() {} + + private static final Map playerPlotGenerationHistory = new HashMap<>(); + + public static boolean assignPlot(Builder builder, Plot plot) { + Player player = builder.getPlayer(); + + // Score Requirement met? + if (PlotSystem.getPlugin().getConfig().getBoolean(ConfigPaths.ENABLE_SCORE_REQUIREMENT) && !DataProvider.DIFFICULTY.builderMeetsRequirements(builder, plot.getDifficulty())) { + player.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(player, LangPaths.Message.Error.PLAYER_NEEDS_HIGHER_SCORE))); + player.playSound(player.getLocation(), Utils.SoundUtils.ERROR_SOUND, SoundCategory.MASTER, 1, 1, 0); + return false; + } + + // Slot available? + Slot freeSlot = builder.getFreeSlot(); + if (freeSlot == null) { + player.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(player, LangPaths.Message.Error.ALL_SLOTS_OCCUPIED))); + player.playSound(player.getLocation(), Utils.SoundUtils.ERROR_SOUND, SoundCategory.MASTER, 1, 1, 0); + return false; + } + + // Assign + if (!builder.setSlot(builder.getFreeSlot(), plot.getID())) return false; + if (!plot.setStatus(Status.unfinished)) return false; + return plot.setPlotOwner(builder); + } + + public static void generatePlot(Builder builder, Plot plot, PlotType type) { + Player player = builder.getPlayer(); + + // Cooldown + if (playerPlotGenerationHistory.containsKey(builder.getUUID())) { + if (!playerPlotGenerationHistory.get(builder.getUUID()).isBefore(LocalDateTime.now().minusSeconds(10))) { + player.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(player, LangPaths.Message.Error.PLEASE_WAIT))); + player.playSound(player.getLocation(), Utils.SoundUtils.ERROR_SOUND, SoundCategory.MASTER, 1, 1, 0); + return; + } + playerPlotGenerationHistory.remove(builder.getUUID()); + } + playerPlotGenerationHistory.put(builder.getUUID(), LocalDateTime.now()); + + player.sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(player, LangPaths.Message.Info.CREATING_PLOT))); + player.playSound(player.getLocation(), Utils.SoundUtils.CREATE_PLOT_SOUND, SoundCategory.MASTER, 1, 1, 0); + + new DefaultPlotLoader(plot, builder, type, PlotWorld.getByType(type, plot)); + } +} diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java index 1a69faa8..b4f6a94d 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java @@ -124,28 +124,43 @@ private AbstractPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder build PlotSystem.getPlugin().getComponentLogger().info("plotType: " + plotType.getId()); + long initStartTime = System.nanoTime(); if (!init()) return; + PlotSystem.getPlugin().getComponentLogger().info("(APG) Init time: {}ms", (System.nanoTime() - initStartTime) / 1_000_000); + boolean generateWorld = plotType.hasOnePlotPerWorld() || !world.isWorldGenerated(); boolean isWorldLoaded = world.isWorldLoaded(); CompletableFuture.runAsync(() -> { final Exception[] exception = new Exception[]{null}; try { + // Generate and/or load world if (generateWorld) { + long worldGenerationStartTime = System.nanoTime(); new PlotWorldGenerator(world.getWorldName()); + PlotSystem.getPlugin().getComponentLogger().info("(APG) Total PlotWorldGenerator time: {}ms", (System.nanoTime() - worldGenerationStartTime) / 1_000_000); } else if (!isWorldLoaded) { final boolean[] successful = new boolean[1]; Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> successful[0] = world.loadWorld()); if (!successful[0]) throw new Exception("Could not load world"); } + + long schemFetchTime = System.nanoTime(); + // Fetch initial plot schematic + byte[] initialSchematic = plot.getInitialSchematicBytes(); + PlotSystem.getPlugin().getComponentLogger().info("(APG) schem fetching time: {}ms", (System.nanoTime() - schemFetchTime) / 1_000_000); + + long generateOutlinesTime = System.nanoTime(); Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { try { createPlotProtection(); - generateOutlines(); + generateOutlines(initialSchematic); } catch (StorageException | IOException e) { exception[0] = e; } }); + PlotSystem.getPlugin().getComponentLogger().info("(APG) generate outlines time (main thread): {}ms", (System.nanoTime() - generateOutlinesTime) / 1_000_000); + } catch (Exception ex) { exception[0] = ex; } @@ -157,6 +172,7 @@ private AbstractPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder build onException(exception[0]); } }); + PlotSystem.getPlugin().getComponentLogger().info("(APG) total time: {}ms", (System.nanoTime() - initStartTime) / 1_000_000); }); } @@ -172,12 +188,12 @@ private AbstractPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder build /** * Generates plot schematic and outlines */ - protected void generateOutlines() throws IOException { + protected void generateOutlines(byte[] initialSchematic) throws IOException { if (plotVersion >= 3 && plotType.hasEnvironment()) { - pasteSchematic(null, plot.getInitialSchematicBytes(), world, false); + pasteSchematic(null, initialSchematic, world, false); } else { Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(world.getBukkitWorld()), BlockTypes.AIR); - pasteSchematic(airMask, PlotUtils.getOutlinesSchematicBytes(plot, world.getBukkitWorld()), world, true); + pasteSchematic(airMask, PlotUtils.getOutlinesSchematicBytes(plot, initialSchematic, world.getBukkitWorld()), world, true); } } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java index 002f0680..0dcd7ae5 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java @@ -92,14 +92,16 @@ protected boolean init() { } @Override - protected void generateOutlines() throws IOException, WorldEditException { - if (plot instanceof Plot p) { + protected void generateOutlines(byte[] schematic) throws IOException, WorldEditException { + long startTime = System.nanoTime(); + if (!(plot instanceof Plot p)) super.generateOutlines(schematic); + else { byte[] completedSchematic = p.getCompletedSchematic(); if (completedSchematic != null) { Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(world.getBukkitWorld()), BlockTypes.AIR); pasteSchematic(airMask, completedSchematic, world, true); - } else super.generateOutlines(); - } else super.generateOutlines(); + } else super.generateOutlines(schematic); + } // If the player is playing in his own world, then additionally generate the plot in the city world if (PlotWorld.isOnePlotWorld(world.getWorldName()) && plotVersion >= 3 && plot.getStatus() != Status.completed) { @@ -124,6 +126,8 @@ protected void onException(Throwable ex) { } }; } + + PlotSystem.getPlugin().getComponentLogger().info("(DPG) Generate Outlines time: {}ms", (System.nanoTime() - startTime) / 1_000_000); } @Override diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/TutorialPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/TutorialPlotGenerator.java index 1b3028e2..de105ac2 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/TutorialPlotGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/TutorialPlotGenerator.java @@ -60,7 +60,7 @@ protected boolean init() { public void generateOutlines(int schematicId) throws IOException, WorldEditException { ((TutorialPlot) plot).setTutorialSchematic(schematicId); - generateOutlines(); + generateOutlines(getPlot().getInitialSchematicBytes()); } @Override diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java new file mode 100644 index 00000000..d239e980 --- /dev/null +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java @@ -0,0 +1,270 @@ +/* + * The MIT License (MIT) + * + * Copyright © 2021-2025, Alps BTE + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.alpsbte.plotsystem.core.system.plot.generator.loader; + +import com.alpsbte.plotsystem.PlotSystem; +import com.alpsbte.plotsystem.core.system.Builder; +import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; +import com.alpsbte.plotsystem.core.system.plot.generator.world.PlotWorldGenerator; +import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; +import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; +import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; +import com.alpsbte.plotsystem.utils.DependencyManager; +import com.alpsbte.plotsystem.utils.Utils; +import com.alpsbte.plotsystem.utils.io.ConfigPaths; +import com.alpsbte.plotsystem.utils.io.ConfigUtil; +import com.alpsbte.plotsystem.utils.io.LangPaths; +import com.alpsbte.plotsystem.utils.io.LangUtil; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.BukkitWorld; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; +import com.sk89q.worldedit.function.mask.BlockTypeMask; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.mask.RegionMask; +import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.Vector2; +import com.sk89q.worldedit.regions.CylinderRegion; +import com.sk89q.worldedit.regions.Polygonal2DRegion; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.session.ClipboardHolder; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.RegionGroup; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.RegionContainer; +import org.bukkit.configuration.file.FileConfiguration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; + +import static net.kyori.adventure.text.Component.text; + +public abstract class AbstractPlotLoader { + protected final AbstractPlot plot; + protected final PlotType plotType; + protected final PlotWorld plotWorld; + + protected final Builder builder; + + protected byte[] schematicBytes = null; + + protected AbstractPlotLoader(@NotNull AbstractPlot plot, Builder builder, PlotType plotType, PlotWorld plotWorld) { + this.plot = plot; + this.plotType = plotType; + this.plotWorld = plotWorld; + this.builder = builder; + + PlotSystem.getPlugin().getComponentLogger().info("Loading plot #{}...", plot.getID()); + PlotSystem.getPlugin().getComponentLogger().info("Plot Type: {}", plot.getPlotType().name()); + + boolean successful = true; + try { + generateWorld(); + loadWorld(); + fetchSchematicData(); + createPlotProtection(); + generateStructure(); + } catch (Exception e) { + successful = false; + onException(e); + } + + if (successful) onCompletion(); + } + + protected void generateWorld() throws Exception { + boolean generateWorld = plotType.hasOnePlotPerWorld() || !Utils.supplySync(plotWorld::isWorldGenerated).get(); + if (!generateWorld) return; + + new PlotWorldGenerator(plotWorld.getWorldName()); + } + + protected void loadWorld() throws Exception { + Utils.runSync(() -> { + if (plotWorld.isWorldLoaded()) return null; + + boolean successful = plotWorld.loadWorld(); + if (!successful) throw new Exception("Could not load world!"); + return null; + }).get(); + } + + protected void fetchSchematicData() { + this.schematicBytes = plot.getInitialSchematicBytes(); + } + + protected void createPlotProtection() throws Exception { + Utils.runSync(() -> { + RegionContainer regionContainer = WorldGuard.getInstance().getPlatform().getRegionContainer(); + RegionManager regionManager = regionContainer.get(BukkitAdapter.adapt(plotWorld.getBukkitWorld())); + if (regionManager == null) { + PlotSystem.getPlugin().getComponentLogger().warn(text("Region Manager is null!")); + return null; + } + + // Create build region for plot from the outline of the plot + ProtectedRegion protectedBuildRegion = new ProtectedPolygonalRegion(plotWorld.getRegionName(), plot.getOutline(), PlotWorld.MIN_WORLD_HEIGHT, PlotWorld.MAX_WORLD_HEIGHT); + protectedBuildRegion.setPriority(100); + + // Create protected plot region for plot + ProtectedRegion protectedRegion = getProtectedRegion(); + + // Add plot owner + DefaultDomain owner = protectedBuildRegion.getOwners(); + owner.addPlayer(builder.getUUID()); + protectedBuildRegion.setOwners(owner); + + // Set protected build region permissions + setBuildRegionPermissions(protectedBuildRegion); + + // Set protected region permissions + setRegionPermissions(protectedRegion); + + // Add regions and save changes + if (regionManager.hasRegion(plotWorld.getRegionName())) regionManager.removeRegion(plotWorld.getRegionName()); + if (regionManager.hasRegion(plotWorld.getRegionName() + "-1")) regionManager.removeRegion(plotWorld.getRegionName() + "-1"); + regionManager.addRegion(protectedBuildRegion); + regionManager.addRegion(protectedRegion); + regionManager.saveChanges(); + return null; + }).get(); + } + + private @NotNull ProtectedRegion getProtectedRegion() { + World weWorld = new BukkitWorld(plotWorld.getBukkitWorld()); + CylinderRegion cylinderRegion = new CylinderRegion(weWorld, plot.getCenter(), Vector2.at(PlotWorld.PLOT_SIZE, PlotWorld.PLOT_SIZE), PlotWorld.MIN_WORLD_HEIGHT, PlotWorld.MAX_WORLD_HEIGHT); + ProtectedRegion protectedRegion = new ProtectedPolygonalRegion(plotWorld.getRegionName() + "-1", cylinderRegion.polygonize(-1), PlotWorld.MIN_WORLD_HEIGHT, PlotWorld.MAX_WORLD_HEIGHT); + protectedRegion.setPriority(50); + return protectedRegion; + } + + /** + * Sets the permissions for the plot build region only + * + * @param region build region + */ + protected void setBuildRegionPermissions(@NotNull ProtectedRegion region) { + region.setFlag(Flags.BUILD, StateFlag.State.ALLOW); + region.setFlag(Flags.BUILD.getRegionGroupFlag(), RegionGroup.OWNERS); + if (DependencyManager.isWorldGuardExtraFlagsEnabled()) + region.setFlag(new StateFlag("worldedit", true, RegionGroup.OWNERS), StateFlag.State.ALLOW); + } + + /** + * Sets the permissions for the whole plot region + * + * @param region plot region + */ + protected void setRegionPermissions(@NotNull ProtectedRegion region) { + region.setFlag(Flags.ENTRY, StateFlag.State.ALLOW); + region.setFlag(Flags.ENTRY.getRegionGroupFlag(), RegionGroup.ALL); + + FileConfiguration config = ConfigUtil.getInstance().configs[1]; + region.setFlag(Flags.BLOCKED_CMDS, new HashSet<>(getBlockedCommands(config))); + region.setFlag(Flags.BLOCKED_CMDS.getRegionGroupFlag(), RegionGroup.ALL); + } + + /** + * Reads the blocked commands for the plot region from the config + * + * @param config commands.yml config + * @return list of blocked commands + */ + protected List getBlockedCommands(@NotNull FileConfiguration config) { + List blockedCommands = config.getStringList(ConfigPaths.BLOCKED_COMMANDS_BUILDERS); + blockedCommands.removeIf(c -> c.equals("/cmd1")); + return blockedCommands; + } + + protected void generateStructure() throws Exception { + Utils.runSync(() -> { + if (plotType.hasEnvironment()) { + pasteSchematic(null, this.schematicBytes, this.plotWorld, false); + } else { + Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(this.plotWorld.getBukkitWorld()), BlockTypes.AIR); + pasteSchematic(airMask, PlotUtils.getOutlinesSchematicBytes(plot, this.schematicBytes, this.plotWorld.getBukkitWorld()), this.plotWorld, true); + } + return null; + }).get(); + } + + public static void pasteSchematic(@Nullable Mask pasteMask, byte[] schematicFile, @NotNull PlotWorld world, boolean clearArea) throws IOException { + // load world if not loaded already + if (!world.loadWorld()) return; + World weWorld = new BukkitWorld(world.getBukkitWorld()); + + // set outline region with air + if (clearArea) { + try (EditSession editSession = WorldEdit.getInstance().newEditSession(BukkitAdapter.adapt(world.getBukkitWorld()))) { + Polygonal2DRegion polyRegion = new Polygonal2DRegion(weWorld, world.getPlot().getOutline(), 0, PlotWorld.MAX_WORLD_HEIGHT); + editSession.setMask(new RegionMask(polyRegion)); + editSession.setBlocks((Region) polyRegion, Objects.requireNonNull(BlockTypes.AIR).getDefaultState()); + } + } + + // load schematic + Clipboard clipboard; + ByteArrayInputStream inputStream = new ByteArrayInputStream(schematicFile); + try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(inputStream)) { + clipboard = reader.read(); + } + + // paste schematic + try (EditSession editSession = WorldEdit.getInstance().newEditSession(BukkitAdapter.adapt(world.getBukkitWorld()))) { + if (pasteMask != null) editSession.setMask(pasteMask); + Operation clipboardHolder = new ClipboardHolder(clipboard) + .createPaste(editSession) + .to(BlockVector3.at(world.getPlot().getCenter().x(), world.getPlotHeight(), world.getPlot().getCenter().z())) + .build(); + Operations.complete(clipboardHolder); + } + } + + protected void onException(Exception e) { + // TODO: abandon plot + + PlotSystem.getPlugin().getComponentLogger().error(text("An error occurred while generating plot!"), e); + builder.getPlayer().sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(builder.getPlayer(), LangPaths.Message.Error.ERROR_OCCURRED))); + builder.getPlayer().playSound(builder.getPlayer().getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); + } + + protected abstract void onCompletion(); +} diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java new file mode 100644 index 00000000..4e581177 --- /dev/null +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java @@ -0,0 +1,82 @@ +/* + * The MIT License (MIT) + * + * Copyright © 2021-2025, Alps BTE + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.alpsbte.plotsystem.core.system.plot.generator.loader; + +import com.alpsbte.plotsystem.PlotSystem; +import com.alpsbte.plotsystem.core.system.Builder; +import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; +import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; +import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; +import com.alpsbte.plotsystem.utils.Utils; +import com.alpsbte.plotsystem.utils.enums.Status; +import com.alpsbte.plotsystem.utils.io.LangPaths; +import com.alpsbte.plotsystem.utils.io.LangUtil; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.function.mask.BlockTypeMask; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.world.block.BlockTypes; +import org.bukkit.Bukkit; +import org.jetbrains.annotations.NotNull; + +public class DefaultPlotLoader extends AbstractPlotLoader { + public DefaultPlotLoader(@NotNull AbstractPlot plot, Builder builder, PlotType plotType, PlotWorld plotWorld) { + super(plot, builder, plotType, plotWorld); + } + + @Override + protected void generateStructure() throws Exception { + if (!(plot instanceof Plot p)) super.generateStructure(); + else { + byte[] completedSchematic = p.getCompletedSchematic(); + if (completedSchematic != null) { + Utils.runSync(() -> { + Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(plotWorld.getBukkitWorld()), BlockTypes.AIR); + pasteSchematic(airMask, completedSchematic, plotWorld, true); + return null; + }).get(); + } else super.generateStructure(); + } + + copyToCityWorld(); + } + + @Override + protected void onCompletion() { + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + plot.getWorld().teleportPlayer(builder.getPlayer()); + LangUtil.getInstance().broadcast(LangPaths.Message.Info.CREATED_NEW_PLOT, builder.getName()); + }); + } + + protected void copyToCityWorld() { + if (plot.getStatus() == Status.completed) return; + if (!PlotWorld.isOnePlotWorld(plotWorld.getWorldName())) return; + + // If the player is playing in his own world, then additionally generate the plot in the city world + // TODO: generate plot on city world + // TODO: unload CIM world when done + } +} diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java index 60532ddf..80b39e34 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java @@ -26,6 +26,7 @@ import com.alpsbte.plotsystem.PlotSystem; import com.alpsbte.plotsystem.utils.DependencyManager; +import com.alpsbte.plotsystem.utils.Utils; import com.onarandombox.MultiverseCore.api.MVWorldManager; import com.onarandombox.MultiverseCore.api.MultiverseWorld; import com.sk89q.worldedit.bukkit.BukkitAdapter; @@ -56,23 +57,24 @@ public class PlotWorldGenerator { private static final World.Environment environment = World.Environment.NORMAL; private static final WorldType worldType = WorldType.FLAT; - private World world = null; - public PlotWorldGenerator(String worldName) throws Exception { + long startTime = System.nanoTime(); this.worldName = worldName; + + // Async Part generateWorld(); - final Exception[] exception = {null}; - Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - try { - createMultiverseWorld(); - configureWorld(); - createGlobalProtection(); - } catch (Exception e) { - exception[0] = e; - } - }); - if (exception[0] != null) throw exception[0]; + // Sync Part + long mainThreadStart = System.nanoTime(); + Utils.runSync(() -> { + createMultiverseWorld(); + configureWorld(); + createGlobalProtection(); + return null; + }).get(); + + PlotSystem.getPlugin().getComponentLogger().info("(PWG) Total time to generate world: {}ms", (System.nanoTime() - startTime) / 1_000_000); + PlotSystem.getPlugin().getComponentLogger().info("(PWG) Total time on main thread: {}ms", (System.nanoTime() - mainThreadStart) / 1_000_000); } protected void generateWorld() throws IOException { @@ -109,9 +111,9 @@ protected void createMultiverseWorld() { } protected void configureWorld() { - this.world = Bukkit.getWorld(worldName); - assert this.world != null; - MultiverseWorld mvWorld = worldManager.getMVWorld(this.world); + World world = Bukkit.getWorld(worldName); + assert world != null; + MultiverseWorld mvWorld = worldManager.getMVWorld(world); // Configure multiverse world mvWorld.setAllowFlight(true); diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java index 6838ce05..5c52ee29 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java @@ -176,9 +176,9 @@ public static boolean isPlotWorld(World world) { return DependencyManager.getMultiverseCore().getMVWorldManager().isMVWorld(world) && (PlotWorld.isOnePlotWorld(world.getName()) || PlotWorld.isCityPlotWorld(world.getName())); } - public static byte @Nullable [] getOutlinesSchematicBytes(@NotNull AbstractPlot plot, World world) throws IOException { + public static byte @Nullable [] getOutlinesSchematicBytes(@NotNull AbstractPlot plot, byte[] initialSchematic, World world) throws IOException { Clipboard clipboard; - ByteArrayInputStream inputStream = new ByteArrayInputStream(plot.getInitialSchematicBytes()); + ByteArrayInputStream inputStream = new ByteArrayInputStream(initialSchematic); try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(inputStream)) { clipboard = reader.read(); } @@ -397,7 +397,7 @@ public static boolean abandonPlot(@NotNull AbstractPlot plot) { if (regionManager.hasRegion(world.getRegionName())) regionManager.removeRegion(world.getRegionName()); if (regionManager.hasRegion(world.getRegionName() + "-1")) regionManager.removeRegion(world.getRegionName() + "-1"); - AbstractPlotGenerator.pasteSchematic(null, getOutlinesSchematicBytes(plot, world.getBukkitWorld()), world, true); + AbstractPlotGenerator.pasteSchematic(null, getOutlinesSchematicBytes(plot, plot.getInitialSchematicBytes(), world.getBukkitWorld()), world, true); } else PlotSystem.getPlugin().getComponentLogger().warn(text("Region Manager is null!")); playersToTeleport.forEach(p -> p.teleport(Utils.getSpawnLocation())); From fb9fd471c044f9cffa4f05c19d0c70191072c81a Mon Sep 17 00:00:00 2001 From: Zoriot Date: Mon, 25 Aug 2025 03:49:03 +0200 Subject: [PATCH 07/60] =?UTF-8?q?feat(gen):=20=E2=9C=A8=20update=20emptych?= =?UTF-8?q?unkgenerator=20and=20provide=20it=20outside=20of=20plugin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/alpsbte/plotsystem/PlotSystem.java | 6 ++++++ .../generator/world/SkeletonWorldGenerator.java | 14 +++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java index 08c18085..19fe3581 100644 --- a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java +++ b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java @@ -53,6 +53,7 @@ import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.generator.ChunkGenerator; import org.bukkit.plugin.java.JavaPlugin; import org.ipvp.canvas.MenuFunctionListener; import org.jetbrains.annotations.NotNull; @@ -222,4 +223,9 @@ public void initDatabase() throws IOException, SQLException, ClassNotFoundExcept s.execute(initScript); } } + + @Override + public ChunkGenerator getDefaultWorldGenerator(@NotNull String worldName, String id) { + return new SkeletonWorldGenerator.EmptyChunkGenerator(); + } } \ No newline at end of file diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java index 7ced0a65..acf97e31 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java @@ -25,12 +25,12 @@ package com.alpsbte.plotsystem.core.system.plot.generator.world; import net.kyori.adventure.util.TriState; -import org.bukkit.*; +import org.bukkit.GameRule; +import org.bukkit.World; +import org.bukkit.WorldCreator; +import org.bukkit.WorldType; import org.bukkit.generator.ChunkGenerator; -import javax.annotation.Nonnull; -import java.util.Random; - public class SkeletonWorldGenerator { public static final String WORLD_NAME = "Skeleton"; private static final World.Environment ENVIRONMENT = World.Environment.NORMAL; @@ -80,10 +80,6 @@ protected void saveWorld() { } public static class EmptyChunkGenerator extends ChunkGenerator { - @Override - @Nonnull - public ChunkData generateChunkData(@Nonnull World world, @Nonnull Random random, int x, int z, @Nonnull BiomeGrid biome) { - return createChunkData(world); - } + // It should just do nothing } } From 198e0140f105f16c3b98de2c5a97ba7f9bcea9ef Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Sat, 1 Nov 2025 17:29:19 +0100 Subject: [PATCH 08/60] Implement plot abandon functionality in the PlotHandler and rework Plot Worlds to handle world specific abandon logic --- .../com/alpsbte/plotsystem/PlotSystem.java | 5 +- .../commands/admin/CMD_DeletePlot.java | 11 +- .../commands/plot/CMD_Plot_Abandon.java | 8 +- .../commands/plot/CMD_Plot_Submit.java | 3 +- .../commands/plot/CMD_Plot_UndoSubmit.java | 3 +- .../menus/review/ReviewPlotTogglesMenu.java | 3 +- .../core/system/plot/PlotHandler.java | 108 +++++++++++ .../plot/generator/AbstractPlotGenerator.java | 3 +- .../core/system/plot/utils/PlotUtils.java | 182 ++---------------- .../core/system/plot/world/CityPlotWorld.java | 71 +++++-- .../core/system/plot/world/IWorld.java | 18 ++ .../core/system/plot/world/OnePlotWorld.java | 60 +++--- .../core/system/plot/world/PlotWorld.java | 19 +- 13 files changed, 270 insertions(+), 224 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java index 19fe3581..3b105e5e 100644 --- a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java +++ b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java @@ -36,6 +36,7 @@ import com.alpsbte.plotsystem.core.holograms.HologramRegister; import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.core.system.plot.generator.world.SkeletonWorldGenerator; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.core.system.tutorial.AbstractTutorial; @@ -137,10 +138,12 @@ public void onEnable() { DecentHologramDisplay.registerPlugin(this); HologramRegister.init(); - PlotUtils.checkPlotsForLastActivity(); Utils.ChatUtils.checkForChatInputExpiry(); PlotUtils.Effects.startTimer(); + // Start task that checks for and abandons inactive plots every hour + Bukkit.getScheduler().runTaskTimerAsynchronously(PlotSystem.getPlugin(), PlotHandler::abandonInactivePlots, 0L, 20 * 60 * 60L); + // Register tutorials if (getConfig().getBoolean(ConfigPaths.TUTORIAL_ENABLE)) { AbstractTutorial.registerTutorials(Collections.singletonList(BeginnerTutorial.class)); diff --git a/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_DeletePlot.java b/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_DeletePlot.java index 530b72f5..02bb7005 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_DeletePlot.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_DeletePlot.java @@ -29,6 +29,7 @@ import com.alpsbte.plotsystem.commands.BaseCommand; import com.alpsbte.plotsystem.core.database.DataProvider; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.utils.Utils; import org.bukkit.Bukkit; @@ -62,10 +63,12 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N sender.sendMessage(Utils.ChatUtils.getInfoFormat("Deleting plot...")); Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - if (PlotUtils.Actions.deletePlot(plot)) { - sender.sendMessage(Utils.ChatUtils.getInfoFormat("Successfully deleted plot with the ID §6#" + plotID + "§a!")); - if (getPlayer(sender) != null) getPlayer(sender).playSound(getPlayer(sender).getLocation(), Utils.SoundUtils.DONE_SOUND, 1f, 1f); - } else sender.sendMessage(Utils.ChatUtils.getAlertFormat("An unexpected error has occurred!")); + if (!PlotHandler.deletePlot(plot)) { + sender.sendMessage(Utils.ChatUtils.getAlertFormat("An unexpected error has occurred!")); + return; + } + sender.sendMessage(Utils.ChatUtils.getInfoFormat("Successfully deleted plot with the ID §6#" + plotID + "§a!")); + if (getPlayer(sender) != null) getPlayer(sender).playSound(getPlayer(sender).getLocation(), Utils.SoundUtils.DONE_SOUND, 1f, 1f); }); }); return true; diff --git a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Abandon.java b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Abandon.java index 1cb48d46..dba38cb4 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Abandon.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Abandon.java @@ -32,6 +32,7 @@ import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.Status; @@ -91,10 +92,9 @@ public void onCommand(CommandSender sender, String[] args) { } Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - if (PlotUtils.Actions.abandonPlot(plot)) { - sender.sendMessage(Utils.ChatUtils.getInfoFormat(langUtil.get(sender, LangPaths.Message.Info.ABANDONED_PLOT, plot.getID() + ""))); - player.playSound(player.getLocation(), Utils.SoundUtils.ABANDON_PLOT_SOUND, 1, 1); - } + if (!PlotHandler.abandonPlot(plot)) return; + sender.sendMessage(Utils.ChatUtils.getInfoFormat(langUtil.get(sender, LangPaths.Message.Info.ABANDONED_PLOT, plot.getID() + ""))); + player.playSound(player.getLocation(), Utils.SoundUtils.ABANDON_PLOT_SOUND, 1, 1); }); }); } diff --git a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Submit.java b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Submit.java index e93a3284..74dacc99 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Submit.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Submit.java @@ -32,6 +32,7 @@ import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.Status; @@ -99,7 +100,7 @@ public void onCommand(CommandSender sender, String[] args) { Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - PlotUtils.Actions.submitPlot(plot); + PlotHandler.submitPlot(plot); if (plotMembers.isEmpty()) { // Plot was made alone langUtil.broadcast(LangPaths.Message.Info.FINISHED_PLOT, String.valueOf(plot.getID()), plot.getPlotOwner().getName()); diff --git a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_UndoSubmit.java b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_UndoSubmit.java index b4a6bebc..8d907992 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_UndoSubmit.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_UndoSubmit.java @@ -32,6 +32,7 @@ import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.Status; @@ -93,7 +94,7 @@ public void onCommand(CommandSender sender, String[] args) { } Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - PlotUtils.Actions.undoSubmit(plot); + PlotHandler.undoSubmit(plot); sender.sendMessage(Utils.ChatUtils.getInfoFormat(langUtil.get(sender, LangPaths.Message.Info.UNDID_SUBMISSION, plot.getID() + ""))); player.playSound(player.getLocation(), Utils.SoundUtils.FINISH_PLOT_SOUND, 1, 1); diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java index 38a8ea22..cad95ae1 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java +++ b/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java @@ -31,6 +31,7 @@ import com.alpsbte.plotsystem.core.menus.AbstractMenu; import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.core.system.review.PlotReview; import com.alpsbte.plotsystem.core.system.review.ReviewRating; @@ -156,7 +157,7 @@ private void submitReview() { if(!acceptPlot(review.getScore(), review.getSplitScore())) return; } else { reviewerConfirmationMessage = Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Message.Info.PLOT_REJECTED, Integer.toString(plot.getID()), getParticipantsString())); - PlotUtils.Actions.undoSubmit(plot); + PlotHandler.undoSubmit(plot); } Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java index 88734796..8621fead 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java @@ -29,6 +29,9 @@ import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.generator.loader.DefaultPlotLoader; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; +import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; +import com.alpsbte.plotsystem.core.system.plot.world.CityPlotWorld; +import com.alpsbte.plotsystem.core.system.plot.world.OnePlotWorld; import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.Slot; @@ -36,11 +39,18 @@ import com.alpsbte.plotsystem.utils.io.ConfigPaths; import com.alpsbte.plotsystem.utils.io.LangPaths; import com.alpsbte.plotsystem.utils.io.LangUtil; +import org.bukkit.Bukkit; import org.bukkit.SoundCategory; +import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; +import java.util.concurrent.CompletableFuture; + +import static net.kyori.adventure.text.Component.text; public class PlotHandler { private PlotHandler() {} @@ -90,4 +100,102 @@ public static void generatePlot(Builder builder, Plot plot, PlotType type) { new DefaultPlotLoader(plot, builder, type, PlotWorld.getByType(type, plot)); } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + public static boolean abandonPlot(AbstractPlot plot) { + boolean successfullyAbandoned = plot.getWorld().onAbandon(); + if (!successfullyAbandoned) { + PlotSystem.getPlugin().getComponentLogger().error(text("Failed to abandon plot with the ID " + plot.getID() + "!")); + return false; + } + + CompletableFuture.runAsync(() -> { + if (plot.getPlotType() == PlotType.TUTORIAL) return; + Plot dPlot = (Plot) plot; + boolean successful; + successful = DataProvider.REVIEW.removeAllReviewsOfPlot(dPlot.getID()); + + for (Builder builder : dPlot.getPlotMembers()) { + if (!successful) break; + successful = dPlot.removePlotMember(builder); + } + + if (successful && plot.getPlotOwner() != null) { + PlotUtils.Cache.clearCache(plot.getPlotOwner().getUUID()); + successful = plot.getPlotOwner().setSlot(plot.getPlotOwner().getSlot(dPlot), -1); + } + + if (successful) { + successful = dPlot.setPlotOwner(null) + && dPlot.setLastActivity(true) + && dPlot.setStatus(Status.unclaimed) + && dPlot.setPlotType(PlotType.LOCAL_INSPIRATION_MODE); + } + + successful = successful && DataProvider.PLOT.setCompletedSchematic(plot.getID(), null); + if (!successful) PlotSystem.getPlugin().getComponentLogger().error(text("Failed to abandon plot with the ID " + plot.getID() + "!")); + }); + return true; + } + + public static boolean deletePlot(Plot plot) { + if (!abandonPlot(plot)) { + PlotSystem.getPlugin().getComponentLogger().warn(text("Failed to delete plot with the ID " + plot.getID() + "!")); + return false; + } + CompletableFuture.runAsync(() -> { + if (DataProvider.PLOT.deletePlot(plot.getID())) return; + PlotSystem.getPlugin().getComponentLogger().warn(text("Failed to delete plot with the ID " + plot.getID() + " from the database!")); + }); + return true; + } + + public static void abandonInactivePlots() { + FileConfiguration config = PlotSystem.getPlugin().getConfig(); + long inactivityIntervalDays = config.getLong(ConfigPaths.INACTIVITY_INTERVAL); + long rejectedInactivityIntervalDays = (config.getLong(ConfigPaths.REJECTED_INACTIVITY_INTERVAL) != -1) ? config.getLong(ConfigPaths.REJECTED_INACTIVITY_INTERVAL) : inactivityIntervalDays; + if (inactivityIntervalDays == -2 && rejectedInactivityIntervalDays == -2) return; + + for (Plot plot : DataProvider.PLOT.getPlots(Status.unfinished)) { + LocalDate lastActivity = plot.getLastActivity(); + long interval = plot.isRejected() ? rejectedInactivityIntervalDays : inactivityIntervalDays; + if (interval == -2 || lastActivity == null || lastActivity.plusDays(interval).isAfter(LocalDate.now())) continue; + + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + if (!abandonPlot(plot)) { + PlotSystem.getPlugin().getComponentLogger().warn(text("An error occurred while abandoning plot #" + plot.getID() + " due to inactivity!")); + return; + } + PlotSystem.getPlugin().getComponentLogger().info(text("Abandoned plot #" + plot.getID() + " due to inactivity!")); + }); + } + } + + public static void submitPlot(@NotNull Plot plot) { + plot.setStatus(Status.unreviewed); + + if (plot.getWorld().isWorldLoaded()) { + for (Player player : plot.getWorld() instanceof OnePlotWorld ? plot.getWorld().getBukkitWorld().getPlayers() : ((CityPlotWorld) plot.getWorld()).getPlayersOnPlot(plot)) { + player.teleport(Utils.getSpawnLocation()); + } + } + + plot.getPermissions().removeBuilderPerms(plot.getPlotOwner().getUUID()).save(); + if (!plot.getPlotMembers().isEmpty()) { + for (Builder builder : plot.getPlotMembers()) { + plot.getPermissions().removeBuilderPerms(builder.getUUID()); + } + } + } + + public static void undoSubmit(@NotNull Plot plot) { + plot.setStatus(Status.unfinished); + + plot.getPermissions().addBuilderPerms(plot.getPlotOwner().getUUID()).save(); + if (!plot.getPlotMembers().isEmpty()) { + for (Builder builder : plot.getPlotMembers()) { + plot.getPermissions().addBuilderPerms(builder.getUUID()); + } + } + } } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java index b4f6a94d..a38f242b 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java @@ -28,6 +28,7 @@ import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.core.system.plot.generator.world.PlotWorldGenerator; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; @@ -168,7 +169,7 @@ private AbstractPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder build this.onComplete(exception[0] != null, false); if (exception[0] != null) { - PlotUtils.Actions.abandonPlot(plot); + PlotHandler.abandonPlot(plot); onException(exception[0]); } }); diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java index edffdb36..a53a0247 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java @@ -30,10 +30,8 @@ import com.alpsbte.plotsystem.core.system.CityProject; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; -import com.alpsbte.plotsystem.core.system.plot.TutorialPlot; import com.alpsbte.plotsystem.core.system.plot.generator.AbstractPlotGenerator; import com.alpsbte.plotsystem.core.system.plot.world.CityPlotWorld; -import com.alpsbte.plotsystem.core.system.plot.world.OnePlotWorld; import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; import com.alpsbte.plotsystem.core.system.review.PlotReview; import com.alpsbte.plotsystem.core.system.review.ReviewNotification; @@ -62,17 +60,13 @@ import com.sk89q.worldedit.math.transform.AffineTransform; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Polygonal2DRegion; -import com.sk89q.worldguard.WorldGuard; -import com.sk89q.worldguard.protection.managers.RegionManager; import com.sk89q.worldguard.protection.regions.ProtectedRegion; -import com.sk89q.worldguard.protection.regions.RegionContainer; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Particle; import org.bukkit.World; -import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitTask; import org.jetbrains.annotations.NotNull; @@ -86,8 +80,6 @@ import java.net.URISyntaxException; import java.nio.file.Paths; import java.text.DecimalFormat; -import java.time.LocalDate; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -161,9 +153,10 @@ public static AbstractPlot getCurrentPlot(@NotNull Builder builder, Status... st } public static boolean isPlayerOnPlot(@NotNull AbstractPlot plot, Player player) { - if (plot.getWorld().isWorldLoaded() && plot.getWorld().getBukkitWorld().getPlayers().contains(player)) { + PlotWorld world = plot.getWorld(); + if (world.isWorldLoaded() && world.getBukkitWorld().getPlayers().contains(player)) { Location playerLoc = player.getLocation(); - ProtectedRegion protectedRegion = plot.getWorld().getProtectedRegion(); + ProtectedRegion protectedRegion = world.getProtectedRegion(); return protectedRegion == null || protectedRegion.contains(Vector3.toBlockPoint(playerLoc.getX(), playerLoc.getY(), playerLoc.getZ())); } return false; @@ -302,39 +295,17 @@ public static boolean savePlotAsSchematic(@NotNull Plot plot) throws IOException }; // Return coordinates if they are in the schematic plot region - ProtectedRegion protectedPlotRegion = plot.getWorld().getProtectedRegion() != null - ? plot.getWorld().getProtectedRegion() - : plot.getWorld().getProtectedBuildRegion(); - if (protectedPlotRegion.contains(BlockVector3.at((int) plotCoords[0], plot.getWorld().getPlotHeightCentered(), (int) plotCoords[1]))) { + PlotWorld world = plot.getWorld(); + ProtectedRegion protectedPlotRegion = world.getProtectedRegion() != null + ? world.getProtectedRegion() + : world.getProtectedBuildRegion(); + if (protectedPlotRegion.contains(BlockVector3.at((int) plotCoords[0], world.getPlotHeightCentered(), (int) plotCoords[1]))) { return CompletableFuture.completedFuture(plotCoords); } return null; } - public static void checkPlotsForLastActivity() { - Bukkit.getScheduler().runTaskTimerAsynchronously(PlotSystem.getPlugin(), () -> { - List plots = DataProvider.PLOT.getPlots(Status.unfinished); - FileConfiguration config = PlotSystem.getPlugin().getConfig(); - long inactivityIntervalDays = config.getLong(ConfigPaths.INACTIVITY_INTERVAL); - long rejectedInactivityIntervalDays = (config.getLong(ConfigPaths.REJECTED_INACTIVITY_INTERVAL) != -1) ? config.getLong(ConfigPaths.REJECTED_INACTIVITY_INTERVAL) : inactivityIntervalDays; - if (inactivityIntervalDays == -2 && rejectedInactivityIntervalDays == -2) return; - for (Plot plot : plots) { - LocalDate lastActivity = plot.getLastActivity(); - long interval = plot.isRejected() ? rejectedInactivityIntervalDays : inactivityIntervalDays; - if (interval == -2 || lastActivity == null || lastActivity.plusDays(interval).isAfter(LocalDate.now())) continue; - - Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - if (Actions.abandonPlot(plot)) { - PlotSystem.getPlugin().getComponentLogger().info(text("Abandoned plot #" + plot.getID() + " due to inactivity!")); - } else { - PlotSystem.getPlugin().getComponentLogger().warn(text("An error occurred while abandoning plot #" + plot.getID() + " due to inactivity!")); - } - }); - } - }, 0L, 20 * 60 * 60L); // Check every hour - } - public static void informPlayerAboutUnfinishedPlots(@NotNull Player player, Builder builder) { try { List plots = Cache.getCachedInProgressPlots(builder); @@ -354,119 +325,6 @@ public static void startUnfinishedPlotReminderTimer(Player player) { 20L * 60 * interval)); } - public static final class Actions { - private Actions() {} - - public static void submitPlot(@NotNull Plot plot) { - plot.setStatus(Status.unreviewed); - - if (plot.getWorld().isWorldLoaded()) { - for (Player player : plot.getWorld() instanceof OnePlotWorld ? plot.getWorld().getBukkitWorld().getPlayers() : ((CityPlotWorld) plot.getWorld()).getPlayersOnPlot()) { - player.teleport(Utils.getSpawnLocation()); - } - } - - plot.getPermissions().removeBuilderPerms(plot.getPlotOwner().getUUID()).save(); - if (!plot.getPlotMembers().isEmpty()) { - for (Builder builder : plot.getPlotMembers()) { - plot.getPermissions().removeBuilderPerms(builder.getUUID()); - } - } - } - - public static void undoSubmit(@NotNull Plot plot) { - plot.setStatus(Status.unfinished); - - plot.getPermissions().addBuilderPerms(plot.getPlotOwner().getUUID()).save(); - if (!plot.getPlotMembers().isEmpty()) { - for (Builder builder : plot.getPlotMembers()) { - plot.getPermissions().addBuilderPerms(builder.getUUID()); - } - } - } - - public static boolean abandonPlot(@NotNull AbstractPlot plot) { - try { - if (plot.getWorld() instanceof OnePlotWorld) { - if (plot.getWorld().isWorldGenerated()) { - if (plot.getWorld().isWorldLoaded()) { - for (Player player : plot.getWorld().getBukkitWorld().getPlayers()) { - player.teleport(Utils.getSpawnLocation()); - } - } - if (!plot.getWorld().deleteWorld()) PlotSystem.getPlugin().getComponentLogger().warn(text("Could not delete plot world " + plot.getWorld().getWorldName() + "!")); - } - } else if (!(plot instanceof TutorialPlot)) { - RegionContainer regionContainer = WorldGuard.getInstance().getPlatform().getRegionContainer(); - - if (plot.getWorld().loadWorld()) { - CityPlotWorld world = plot.getWorld(); - List playersToTeleport = new ArrayList<>(world.getPlayersOnPlot()); - - RegionManager regionManager = regionContainer.get(BukkitAdapter.adapt(world.getBukkitWorld())); - if (regionManager != null) { - for (Builder builder : ((Plot) plot).getPlotMembers()) { - ((Plot) plot).removePlotMember(builder); - } - - if (regionManager.hasRegion(world.getRegionName())) regionManager.removeRegion(world.getRegionName()); - if (regionManager.hasRegion(world.getRegionName() + "-1")) regionManager.removeRegion(world.getRegionName() + "-1"); - - AbstractPlotGenerator.pasteSchematic(null, getOutlinesSchematicBytes(plot, plot.getInitialSchematicBytes(), world.getBukkitWorld()), world, true); - } else PlotSystem.getPlugin().getComponentLogger().warn(text("Region Manager is null!")); - - playersToTeleport.forEach(p -> p.teleport(Utils.getSpawnLocation())); - if (plot.getWorld().isWorldLoaded()) plot.getWorld().unloadWorld(false); - } - } - } catch (IOException | WorldEditException ex) { - PlotSystem.getPlugin().getComponentLogger().error(text("Failed to abandon plot with the ID " + plot.getID() + "!"), ex); - return false; - } - - CompletableFuture.runAsync(() -> { - if (plot.getPlotType() == PlotType.TUTORIAL) return; - Plot dPlot = (Plot) plot; - boolean successful; - successful = DataProvider.REVIEW.removeAllReviewsOfPlot(dPlot.getID()); - - for (Builder builder : dPlot.getPlotMembers()) { - if (!successful) break; - successful = dPlot.removePlotMember(builder); - } - - if (successful && plot.getPlotOwner() != null) { - Cache.clearCache(plot.getPlotOwner().getUUID()); - successful = plot.getPlotOwner().setSlot(plot.getPlotOwner().getSlot(dPlot), -1); - } - - if (successful) { - successful = dPlot.setPlotOwner(null) - && dPlot.setLastActivity(true) - && dPlot.setStatus(Status.unclaimed) - && dPlot.setPlotType(PlotType.LOCAL_INSPIRATION_MODE); - } - - successful = successful && DataProvider.PLOT.setCompletedSchematic(plot.getID(), null); - - if (!successful) PlotSystem.getPlugin().getComponentLogger().error(text("Failed to abandon plot with the ID " + plot.getID() + "!")); - }); - return true; - } - - public static boolean deletePlot(Plot plot) { - if (abandonPlot(plot)) { - CompletableFuture.runAsync(() -> { - if (DataProvider.PLOT.deletePlot(plot.getID())) return; - PlotSystem.getPlugin().getComponentLogger().warn(text("Failed to abandon plot with the ID " + plot.getID() + "!")); - }); - return true; - } - PlotSystem.getPlugin().getComponentLogger().warn(text("Failed to abandon plot with the ID " + plot.getID() + "!")); - return false; - } - } - public static final class Cache { private Cache() {} @@ -548,19 +406,19 @@ public static void showOutlines() { List points = plot.getBlockOutline(); - for (BlockVector2 point : points) - if (point.distanceSq(playerPos2D) < 50 * 50) { - if (!particleAPIEnabled) { - player.spawnParticle(Particle.FLAME, point.x(), player.getLocation().getY() + 1, point.z(), 1, 0.0, 0.0, 0.0, 0); - } else { - Location loc = new Location(player.getWorld(), point.x(), player.getLocation().getY() + 1, point.z()); - // create a particle packet - Object packet = particles.FLAME().packet(true, loc); - - // send this packet to player - particles.sendPacket(player, packet); - } + for (BlockVector2 point : points) { + if (point.distanceSq(playerPos2D) >= 50 * 50) continue; + if (!particleAPIEnabled) { + player.spawnParticle(Particle.FLAME, point.x(), player.getLocation().getY() + 1, point.z(), 1, 0.0, 0.0, 0.0, 0); + continue; } + Location loc = new Location(player.getWorld(), point.x(), player.getLocation().getY() + 1, point.z()); + // create a particle packet + Object packet = particles.FLAME().packet(true, loc); + + // send this packet to player + particles.sendPacket(player, packet); + } } } } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java index 9d2d0599..8f061aa8 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java @@ -24,15 +24,22 @@ package com.alpsbte.plotsystem.core.system.plot.world; +import com.alpsbte.plotsystem.PlotSystem; +import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.generator.loader.AbstractPlotLoader; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.io.LangPaths; import com.alpsbte.plotsystem.utils.io.LangUtil; import com.google.common.annotations.Beta; +import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.regions.RegionContainer; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -41,6 +48,8 @@ import java.util.ArrayList; import java.util.List; +import static net.kyori.adventure.text.Component.text; + public class CityPlotWorld extends PlotWorld { public CityPlotWorld(@NotNull Plot plot) { super("C-" + plot.getCityProject().getID(), plot); @@ -54,29 +63,29 @@ public boolean teleportPlayer(@NotNull Player player) { player.setAllowFlight(true); player.setFlying(true); - if (getPlot() == null) return true; + if (plot == null) return true; - player.sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(player, LangPaths.Message.Info.TELEPORTING_PLOT, String.valueOf(getPlot().getID())))); + player.sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(player, LangPaths.Message.Info.TELEPORTING_PLOT, String.valueOf(plot.getID())))); Utils.updatePlayerInventorySlots(player); - PlotUtils.ChatFormatting.sendLinkMessages(getPlot(), player); + PlotUtils.ChatFormatting.sendLinkMessages(plot, player); - if (!getPlot().getPlotOwner().getUUID().equals(player.getUniqueId())) return true; - getPlot().setLastActivity(false); + if (!plot.getPlotOwner().getUUID().equals(player.getUniqueId())) return true; + plot.setLastActivity(false); return true; } @Override public String getRegionName() { - return super.getRegionName() + "-" + getPlot().getID(); + return super.getRegionName() + "-" + plot.getID(); } @Beta @Override public int getPlotHeight() throws IOException { - return getPlot().getVersion() >= 3 ? MIN_WORLD_HEIGHT + getWorldHeight() : getPlotHeightCentered(); + return plot.getVersion() >= 3 ? MIN_WORLD_HEIGHT + getWorldHeight() : getPlotHeightCentered(); } @Beta @@ -94,7 +103,7 @@ public int getPlotHeightCentered() throws IOException { @Beta public int getWorldHeight() throws IOException { Clipboard clipboard; - ByteArrayInputStream inputStream = new ByteArrayInputStream(getPlot().getInitialSchematicBytes()); + ByteArrayInputStream inputStream = new ByteArrayInputStream(plot.getInitialSchematicBytes()); try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(inputStream)) { clipboard = reader.read(); } @@ -123,14 +132,48 @@ public int getWorldHeight() throws IOException { * * @return a list of players located on the plot */ - public List getPlayersOnPlot() { + public List getPlayersOnPlot(AbstractPlot plot) { List players = new ArrayList<>(); - if (getPlot() != null && getPlot().getWorld().isWorldLoaded() && !getPlot().getWorld().getBukkitWorld().getPlayers().isEmpty()) { - for (Player player : getPlot().getWorld().getBukkitWorld().getPlayers()) { - if (PlotUtils.isPlayerOnPlot(getPlot(), player)) players.add(player); - } - return players; + if (plot == null || !plot.getWorld().isWorldLoaded() || plot.getWorld().getBukkitWorld().getPlayers().isEmpty()) return players; + + for (Player player : plot.getWorld().getBukkitWorld().getPlayers()) { + if (PlotUtils.isPlayerOnPlot(plot, player)) players.add(player); } return players; } + + @Override + public boolean onAbandon() { + RegionContainer regionContainer = WorldGuard.getInstance().getPlatform().getRegionContainer(); + if (!loadWorld()) { + PlotSystem.getPlugin().getComponentLogger().warn(text("Could not load world!")); + return false; + } + + RegionManager regionManager = regionContainer.get(BukkitAdapter.adapt(getBukkitWorld())); + if (regionManager == null) { + PlotSystem.getPlugin().getComponentLogger().warn(text("Region Manager is null!")); + return false; + } + + for (Builder builder : ((Plot) plot).getPlotMembers()) { + ((Plot) plot).removePlotMember(builder); + } + + if (regionManager.hasRegion(getRegionName())) regionManager.removeRegion(getRegionName()); + if (regionManager.hasRegion(getRegionName() + "-1")) regionManager.removeRegion(getRegionName() + "-1"); + + // paste initial schematic to reset plot + try { + AbstractPlotLoader.pasteSchematic(null, PlotUtils.getOutlinesSchematicBytes(plot, plot.getInitialSchematicBytes(), getBukkitWorld()), this, true); + } catch (IOException e) { + PlotSystem.getPlugin().getComponentLogger().error(text("Could not paste schematic!"), e); + } + + List playersToTeleport = new ArrayList<>(getPlayersOnPlot(plot)); + playersToTeleport.forEach(p -> p.teleport(Utils.getSpawnLocation())); + + if (isWorldLoaded()) unloadWorld(false); + return super.onAbandon(); + } } \ No newline at end of file diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/IWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/IWorld.java index bca5b9bf..a1543b2d 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/IWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/IWorld.java @@ -24,6 +24,7 @@ package com.alpsbte.plotsystem.core.system.plot.world; +import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.generator.AbstractPlotGenerator; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldguard.protection.regions.ProtectedRegion; @@ -34,6 +35,9 @@ import java.io.IOException; +/** + * Represents the world of a given plot. There can be multiple instances of this interface per actual bukkit world. + */ public interface IWorld { /** * Generates the plot world with the required configurations and schematic @@ -147,4 +151,18 @@ public interface IWorld { * @return true if world is generated */ boolean isWorldGenerated(); + + /** + * Returns the plot associated with this world object + * + * @return the plot associated with this world object + */ + AbstractPlot getPlot(); + + /** + * Executes all World specific side effects needed to abandon a plot + * + * @return true if successfully executed + */ + boolean onAbandon(); } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java index 94a1a1cb..4fdfb9cf 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java @@ -31,6 +31,7 @@ import com.alpsbte.plotsystem.core.system.plot.TutorialPlot; import com.alpsbte.plotsystem.core.system.plot.generator.AbstractPlotGenerator; import com.alpsbte.plotsystem.core.system.plot.generator.TutorialPlotGenerator; +import com.alpsbte.plotsystem.core.system.plot.generator.loader.DefaultPlotLoader; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.core.system.plot.generator.DefaultPlotGenerator; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; @@ -46,9 +47,11 @@ public class OnePlotWorld extends PlotWorld { private final Builder plotOwner; + private final AbstractPlot plot; public OnePlotWorld(@NotNull AbstractPlot plot) { super((plot instanceof TutorialPlot ? "T-" : "P-") + plot.getID(), plot); + this.plot = plot; this.plotOwner = plot.getPlotOwner(); } @@ -57,36 +60,25 @@ public boolean generateWorld(@NotNull Class if (isWorldGenerated()) return false; if (generator.isAssignableFrom(DefaultPlotGenerator.class)) { - new DefaultPlotGenerator(getPlot(), plotOwner); + new DefaultPlotGenerator(plot, plotOwner); } else if (generator.isAssignableFrom(TutorialPlotGenerator.class)) { - new TutorialPlotGenerator(getPlot(), plotOwner); + new TutorialPlotGenerator(plot, plotOwner); } else return false; return true; } @Override public boolean loadWorld() { - if (getPlot() == null || isWorldGenerated()) return super.loadWorld(); + if (plot == null || isWorldGenerated()) return super.loadWorld(); // Generate plot if it doesn't exist - if (getPlot().getPlotType() == PlotType.TUTORIAL || ((Plot) getPlot()).getCompletedSchematic() == null) + if (plot.getPlotType() == PlotType.TUTORIAL || ((Plot) plot).getCompletedSchematic() == null) generateWorld(TutorialPlotGenerator.class); - new DefaultPlotGenerator(getPlot(), plotOwner, getPlot().getPlotType()) { - @Override - protected boolean init() { - return true; - } - - @Override - protected void onComplete(boolean failed, boolean unloadWorld) { - getPlot().getPermissions().clearAllPerms(); - super.onComplete(true, false); - } - }; + new DefaultPlotLoader(plot, plotOwner, plot.getPlotType(), this); if (!isWorldGenerated() || !isWorldLoaded()) { - PlotSystem.getPlugin().getComponentLogger().warn(text("Could not regenerate world " + getWorldName() + " for plot " + getPlot().getID() + "!")); + PlotSystem.getPlugin().getComponentLogger().warn(text("Could not regenerate world " + getWorldName() + " for plot " + plot.getID() + "!")); return false; } return true; @@ -95,13 +87,10 @@ protected void onComplete(boolean failed, boolean unloadWorld) { @Override public boolean unloadWorld(boolean movePlayers) { boolean isTutorialPlot; - isTutorialPlot = getPlot().getPlotType() == PlotType.TUTORIAL; + isTutorialPlot = plot.getPlotType() == PlotType.TUTORIAL; - if (getPlot() != null) { - if (isTutorialPlot) return deleteWorld(); - else return super.unloadWorld(movePlayers); - } - return false; + if (isTutorialPlot) return deleteWorld(); + else return super.unloadWorld(movePlayers); } @Override @@ -112,15 +101,15 @@ public boolean teleportPlayer(@NotNull Player player) { player.setAllowFlight(true); player.setFlying(true); - if (getPlot() == null) return true; - if (getPlot().getPlotType() != PlotType.TUTORIAL) { - player.sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(player, LangPaths.Message.Info.TELEPORTING_PLOT, String.valueOf(getPlot().getID())))); - PlotUtils.ChatFormatting.sendLinkMessages(getPlot(), player); + if (plot == null) return true; + if (plot.getPlotType() != PlotType.TUTORIAL) { + player.sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(player, LangPaths.Message.Info.TELEPORTING_PLOT, String.valueOf(plot.getID())))); + PlotUtils.ChatFormatting.sendLinkMessages(plot, player); } Utils.updatePlayerInventorySlots(player); - if (!getPlot().getPlotOwner().getUUID().equals(player.getUniqueId())) return true; - getPlot().setLastActivity(false); + if (!plot.getPlotOwner().getUUID().equals(player.getUniqueId())) return true; + plot.setLastActivity(false); return true; } @@ -134,4 +123,17 @@ public int getPlotHeight() { public int getPlotHeightCentered() throws IOException { return MIN_WORLD_HEIGHT + super.getPlotHeightCentered(); } + + @Override + public boolean onAbandon() { + if (!isWorldGenerated()) return super.onAbandon(); + if (isWorldLoaded()) { + for (Player player : getBukkitWorld().getPlayers()) player.teleport(Utils.getSpawnLocation()); + } + if (!deleteWorld()) { + PlotSystem.getPlugin().getComponentLogger().warn(text("Could not delete plot world " + getWorldName() + "!")); + return false; + } + return super.onAbandon(); + } } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java index 261d8ae6..f7d9fc92 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java @@ -66,7 +66,7 @@ public class PlotWorld implements IWorld { private final MultiverseCore mvCore = DependencyManager.getMultiverseCore(); private final String worldName; - private final AbstractPlot plot; + protected final AbstractPlot plot; public PlotWorld(@NotNull String worldName, @Nullable AbstractPlot plot) { this.worldName = worldName; @@ -204,6 +204,16 @@ public boolean isWorldGenerated() { return mvCore.getMVWorldManager().getMVWorld(worldName) != null || mvCore.getMVWorldManager().getUnloadedWorlds().contains(worldName); } + @Override + public AbstractPlot getPlot() { + return plot; + } + + @Override + public boolean onAbandon() { + return true; + } + private @Nullable ProtectedRegion getRegion(String regionName) { if (!loadWorld()) return null; @@ -215,11 +225,6 @@ public boolean isWorldGenerated() { return null; } - public AbstractPlot getPlot() { - return plot; - } - - /** * @param worldName - the name of the world * @return - true if the world is a plot world @@ -244,6 +249,7 @@ public static boolean isCityPlotWorld(@NotNull String worldName) { * @return - plot world */ public static @Nullable PlotWorld getPlotWorldByName(String worldName) { + // TODO: rework if (!isOnePlotWorld(worldName) && !isCityPlotWorld(worldName)) return null; Integer id = AlpsUtils.tryParseInt(worldName.substring(2)); @@ -254,6 +260,7 @@ public static boolean isCityPlotWorld(@NotNull String worldName) { } public static PlotWorld getByType(PlotType type, Plot plot) { + // TODO: rework boolean disableCIM = PlotSystem.getPlugin().getConfig().getBoolean(ConfigPaths.DISABLE_CITY_INSPIRATION_MODE); return disableCIM || type.hasOnePlotPerWorld() ? new OnePlotWorld(plot) : new CityPlotWorld(plot); } From a87a7e375bb1ffbdffc68fe1326377a976314f01 Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Mon, 14 Jul 2025 04:17:26 +0200 Subject: [PATCH 09/60] move world generation to SkeletonWorldGenerator and copy skeleton data in PlotWorldGenerator --- pom.xml | 5 + .../plot/generator/AbstractPlotGenerator.java | 1 + .../{ => world}/PlotWorldGenerator.java | 136 +++++++++--------- .../world/SkeletonWorldGenerator.java | 83 +++++++++++ 4 files changed, 160 insertions(+), 65 deletions(-) rename src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/{ => world}/PlotWorldGenerator.java (52%) create mode 100644 src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java diff --git a/pom.xml b/pom.xml index bbcc0a99..cb6a44ee 100644 --- a/pom.xml +++ b/pom.xml @@ -169,6 +169,11 @@ 2.17.0 provided + + com.github.Querz + NBT + 6.1 + diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java index 6b168e97..1634f8d7 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java @@ -4,6 +4,7 @@ import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.generator.world.PlotWorldGenerator; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.core.system.plot.world.CityPlotWorld; diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/PlotWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java similarity index 52% rename from src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/PlotWorldGenerator.java rename to src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java index 6dbd2041..50f07032 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/PlotWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java @@ -1,4 +1,28 @@ -package com.alpsbte.plotsystem.core.system.plot.generator; +/* + * The MIT License (MIT) + * + * Copyright © 2021-2025, Alps BTE + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.alpsbte.plotsystem.core.system.plot.generator.world; import com.alpsbte.plotsystem.PlotSystem; import com.alpsbte.plotsystem.utils.DependencyManager; @@ -11,36 +35,33 @@ import com.sk89q.worldguard.protection.managers.storage.StorageException; import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; import com.sk89q.worldguard.protection.regions.RegionContainer; -import org.bukkit.Bukkit; -import org.bukkit.Difficulty; -import org.bukkit.GameMode; -import org.bukkit.GameRule; -import org.bukkit.World; -import org.bukkit.WorldCreator; -import org.bukkit.WorldType; -import org.bukkit.entity.SpawnCategory; -import org.bukkit.generator.ChunkGenerator; +import net.querz.nbt.io.NBTUtil; +import net.querz.nbt.io.NamedTag; +import net.querz.nbt.tag.CompoundTag; +import org.apache.commons.io.FileUtils; +import org.bukkit.*; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.core.world.options.ImportWorldOptions; import org.mvplugins.multiverse.external.vavr.control.Option; -import javax.annotation.Nonnull; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Objects; -import java.util.Random; import static net.kyori.adventure.text.Component.text; public class PlotWorldGenerator { private final WorldManager worldManager = DependencyManager.getMultiverseCore().getWorldManager(); - private WorldCreator worldCreator; - private final String worldName; private static final World.Environment environment = World.Environment.NORMAL; private static final WorldType worldType = WorldType.FLAT; - private static final String generatorSettings = "{\"features\": false,\"layers\": [{\"block\": \"air\", \"height\": 1}],\"biome\":\"plains\"}"; - public PlotWorldGenerator(String worldName) throws Exception { + private World world = null; + + public PlotWorldGenerator(String worldName) throws IOException, StorageException { this.worldName = worldName; generateWorld(); createMultiverseWorld(); @@ -48,54 +69,48 @@ public PlotWorldGenerator(String worldName) throws Exception { createGlobalProtection(); } - protected void generateWorld() { - worldCreator = new WorldCreator(worldName); - worldCreator.environment(environment); - worldCreator.type(worldType); - worldCreator.generator(new EmptyChunkGenerator()); - worldCreator.generatorSettings(generatorSettings); - worldCreator.createWorld(); + protected void generateWorld() throws IOException { + // copy skeleton world with correct world name + Path skeletonPath = Bukkit.getWorldContainer().toPath().resolve("Skeleton"); + Path worldPath = Bukkit.getWorldContainer().toPath().resolve(worldName); + FileUtils.copyDirectory(skeletonPath.toFile(), worldPath.toFile()); + + // rename world name in level.dat + Path levelDat = worldPath.resolve("level.dat"); + NamedTag level = NBTUtil.read(levelDat.toFile()); + CompoundTag tag = (CompoundTag) level.getTag(); + tag.remove("LevelName"); + tag.putString("LevelName", worldName); + level.setTag(tag); + NBTUtil.write(level, levelDat.toFile()); + + // rename world in paper-world.yml + Path paperWorld = worldPath.resolve("paper-world.yml"); + String paperWorldContents = Files.readString(paperWorld); + String updatedContents = paperWorldContents + .replaceAll(SkeletonWorldGenerator.WORLD_NAME, worldName) + .replaceAll(SkeletonWorldGenerator.WORLD_NAME.toLowerCase(), worldName.toLowerCase()); + Files.writeString(paperWorld, updatedContents); + + // load world + this.world = Bukkit.getWorld(worldName); } - protected void createMultiverseWorld() throws Exception { - // Check if world creator is configured and add new world to multiverse world manager - if (worldCreator != null) { - if (!worldManager.isLoadedWorld(worldName)) { - worldManager.importWorld(ImportWorldOptions.worldName(worldName) - .environment(environment) - .generator("VoidGen:{\"caves\":false,\"decoration\":false,\"mobs\":false,\"structures\":false}") - .useSpawnAdjust(false) - ); - } - } else { - throw new Exception("World Creator is not configured"); + protected void createMultiverseWorld() { + assert this.world != null; + if (!worldManager.isLoadedWorld(worldName)) { + worldManager.importWorld(ImportWorldOptions.worldName(worldName) + .environment(environment) + .generator("VoidGen:{\"caves\":false,\"decoration\":false,\"mobs\":false,\"structures\":false}") + .useSpawnAdjust(false) + ); } } protected void configureWorld() { - World bukkitWorld = Bukkit.getWorld(worldName); + assert this.world != null; Option mvWorld = worldManager.getLoadedWorld(worldName); - if (mvWorld.isEmpty()) { - PlotSystem.getPlugin().getComponentLogger().warn(text("Multiverse world" + worldName + " is not loaded! Skipping world configuration...")); - return; - } - - // Set world time to midday - assert bukkitWorld != null; - bukkitWorld.setTime(6000); - - // Set Bukkit world game rules - bukkitWorld.setGameRule(GameRule.RANDOM_TICK_SPEED, 0); - bukkitWorld.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false); - bukkitWorld.setGameRule(GameRule.DO_FIRE_TICK, false); - bukkitWorld.setGameRule(GameRule.DO_WEATHER_CYCLE, false); - bukkitWorld.setGameRule(GameRule.KEEP_INVENTORY, true); - bukkitWorld.setGameRule(GameRule.DO_MOB_SPAWNING, false); - bukkitWorld.setGameRule(GameRule.ANNOUNCE_ADVANCEMENTS, false); - bukkitWorld.setGameRule(GameRule.DO_TILE_DROPS, false); - bukkitWorld.setGameRule(GameRule.DO_MOB_LOOT, false); - // Configure multiverse world mvWorld.get().setAllowFlight(true); mvWorld.get().setGameMode(GameMode.CREATIVE); @@ -130,15 +145,6 @@ protected void createGlobalProtection() throws StorageException { regionManager.saveChanges(); } else PlotSystem.getPlugin().getComponentLogger().warn(text("Region Manager is null!")); } - - public static class EmptyChunkGenerator extends ChunkGenerator { - @Override - @Nonnull - public ChunkData generateChunkData(@Nonnull World world, @Nonnull Random random, int x, int z, @Nonnull BiomeGrid biome) { - return createChunkData(world); - } - } - } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java new file mode 100644 index 00000000..43454daa --- /dev/null +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java @@ -0,0 +1,83 @@ +/* + * The MIT License (MIT) + * + * Copyright © 2021-2025, Alps BTE + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.alpsbte.plotsystem.core.system.plot.generator.world; + +import net.kyori.adventure.util.TriState; +import org.bukkit.*; +import org.bukkit.generator.ChunkGenerator; + +import javax.annotation.Nonnull; +import java.util.Random; + +public class SkeletonWorldGenerator { + public static final String WORLD_NAME = "Skeleton"; + private static final World.Environment ENVIRONMENT = World.Environment.NORMAL; + private static final WorldType WORLD_TYPE = WorldType.FLAT; + private static final String GENERATOR_SETTINGS = "{\"features\": false,\"layers\": [{\"block\": \"air\", \"height\": 1}],\"biome\":\"plains\"}"; + + private World world; + + public SkeletonWorldGenerator() { + generateWorld(); + configureWorld(); + } + + protected void generateWorld() { + WorldCreator worldCreator = new WorldCreator(WORLD_NAME) + .environment(ENVIRONMENT) + .type(WORLD_TYPE) + .generator(new SkeletonWorldGenerator.EmptyChunkGenerator()) + .generatorSettings(GENERATOR_SETTINGS) + .keepSpawnLoaded(TriState.FALSE); + this.world = worldCreator.createWorld(); + } + + protected void configureWorld() { + World bukkitWorld = this.world; + assert bukkitWorld != null; + + // Set game rules + bukkitWorld.setGameRule(GameRule.RANDOM_TICK_SPEED, 0); + bukkitWorld.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false); + bukkitWorld.setGameRule(GameRule.DO_FIRE_TICK, false); + bukkitWorld.setGameRule(GameRule.DO_WEATHER_CYCLE, false); + bukkitWorld.setGameRule(GameRule.KEEP_INVENTORY, true); + bukkitWorld.setGameRule(GameRule.DO_MOB_SPAWNING, false); + bukkitWorld.setGameRule(GameRule.ANNOUNCE_ADVANCEMENTS, false); + bukkitWorld.setGameRule(GameRule.DO_TILE_DROPS, false); + bukkitWorld.setGameRule(GameRule.SPAWN_CHUNK_RADIUS, 0); + + // Set time to noon + bukkitWorld.setTime(6000); + } + + public static class EmptyChunkGenerator extends ChunkGenerator { + @Override + @Nonnull + public ChunkData generateChunkData(@Nonnull World world, @Nonnull Random random, int x, int z, @Nonnull BiomeGrid biome) { + return createChunkData(world); + } + } +} From 73e9def21ffb61af3b609a636d7e3ccba95890d7 Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Mon, 14 Jul 2025 13:18:59 +0200 Subject: [PATCH 10/60] generate skeleton on startup if not exists. --- src/main/java/com/alpsbte/plotsystem/PlotSystem.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java index 201a5912..94c41885 100644 --- a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java +++ b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java @@ -12,6 +12,7 @@ import com.alpsbte.plotsystem.core.holograms.HologramRegister; import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.generator.world.SkeletonWorldGenerator; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.core.system.tutorial.AbstractTutorial; import com.alpsbte.plotsystem.core.system.tutorial.BeginnerTutorial; @@ -127,6 +128,13 @@ public void onEnable() { Bukkit.getScheduler().runTaskTimerAsynchronously(FancyNpcsPlugin.get().getPlugin(), new TutorialNPCTurnTracker(), 0, 1L); } + // Generate Skeleton World + if (Bukkit.getWorld("Skeleton") == null) { + getComponentLogger().info("No skeleton world found!"); + getComponentLogger().info("Generating skeleton world..."); + new SkeletonWorldGenerator(); + } + pluginEnabled = true; getComponentLogger().info(text("Enabled Plot-System plugin.", DARK_GREEN)); getComponentLogger().info(text("------------------------------------------------------", GOLD)); From 0783dc6402bc51f36d9cfea50aa404fa31cacbfd Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Mon, 14 Jul 2025 13:19:46 +0200 Subject: [PATCH 11/60] save skeleton to generate level.dat and remove uid.dat for multiverse loading --- .../system/plot/generator/world/PlotWorldGenerator.java | 3 +++ .../system/plot/generator/world/SkeletonWorldGenerator.java | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java index 50f07032..569ec666 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java @@ -75,6 +75,9 @@ protected void generateWorld() throws IOException { Path worldPath = Bukkit.getWorldContainer().toPath().resolve(worldName); FileUtils.copyDirectory(skeletonPath.toFile(), worldPath.toFile()); + // delete uid.dat + Files.delete(worldPath.resolve("uid.dat")); + // rename world name in level.dat Path levelDat = worldPath.resolve("level.dat"); NamedTag level = NBTUtil.read(levelDat.toFile()); diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java index 43454daa..7ced0a65 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java @@ -42,6 +42,7 @@ public class SkeletonWorldGenerator { public SkeletonWorldGenerator() { generateWorld(); configureWorld(); + saveWorld(); } protected void generateWorld() { @@ -73,6 +74,11 @@ protected void configureWorld() { bukkitWorld.setTime(6000); } + protected void saveWorld() { + assert this.world != null; + this.world.save(); + } + public static class EmptyChunkGenerator extends ChunkGenerator { @Override @Nonnull From 65716700eec5aae3b37d60d51cb6a6c9723c060d Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Sun, 20 Jul 2025 21:24:13 +0200 Subject: [PATCH 12/60] fix async issues and cleanup generation related classes --- .../plotsystem/core/system/plot/Plot.java | 11 +- .../plot/generator/AbstractPlotGenerator.java | 53 ++++-- .../plot/generator/DefaultPlotGenerator.java | 37 ++-- .../generator/world/PlotWorldGenerator.java | 22 ++- .../core/system/plot/world/CityPlotWorld.java | 65 ++++--- .../core/system/plot/world/PlotWorld.java | 167 ++++++++++-------- 6 files changed, 197 insertions(+), 158 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/Plot.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/Plot.java index cd5f2265..5e47a827 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/Plot.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/Plot.java @@ -6,7 +6,6 @@ import com.alpsbte.plotsystem.core.system.CityProject; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.core.system.plot.world.CityPlotWorld; -import com.alpsbte.plotsystem.core.system.plot.world.OnePlotWorld; import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; import com.alpsbte.plotsystem.core.system.review.PlotReview; import com.alpsbte.plotsystem.utils.enums.PlotDifficulty; @@ -127,14 +126,8 @@ public boolean setPlotType(PlotType type) { @SuppressWarnings("unchecked") @Override - public T getWorld() { - if (getVersion() <= 2 || getPlotType().hasOnePlotPerWorld()) { - if (onePlotWorld == null) onePlotWorld = new OnePlotWorld(this); - return (T) onePlotWorld; - } else { - if (cityPlotWorld == null) cityPlotWorld = new CityPlotWorld(this); - return (T) cityPlotWorld; - } + public PlotWorld getWorld() { + return PlotWorld.getByType(getPlotType(), this); } @Override diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java index 1634f8d7..2aedf1be 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java @@ -7,8 +7,6 @@ import com.alpsbte.plotsystem.core.system.plot.generator.world.PlotWorldGenerator; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; -import com.alpsbte.plotsystem.core.system.plot.world.CityPlotWorld; -import com.alpsbte.plotsystem.core.system.plot.world.OnePlotWorld; import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; import com.alpsbte.plotsystem.utils.DependencyManager; import com.alpsbte.plotsystem.utils.Utils; @@ -45,6 +43,7 @@ import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; import com.sk89q.worldguard.protection.regions.ProtectedRegion; import com.sk89q.worldguard.protection.regions.RegionContainer; +import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -54,6 +53,7 @@ import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.concurrent.CompletableFuture; import static net.kyori.adventure.text.Component.text; @@ -63,7 +63,6 @@ public abstract class AbstractPlotGenerator { protected final PlotWorld world; protected final double plotVersion; protected final PlotType plotType; - /** * Generates a new plot in the plot world * @@ -82,7 +81,7 @@ protected AbstractPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder bui * @param plotType - type of the plot */ protected AbstractPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder builder, @NotNull PlotType plotType) { - this(plot, builder, plotType, plot.getVersion() <= 2 || plotType.hasOnePlotPerWorld() ? new OnePlotWorld(plot) : new CityPlotWorld((Plot) plot)); + this(plot, builder, plotType, PlotWorld.getByType(plotType, (Plot) plot)); } /** @@ -99,24 +98,42 @@ private AbstractPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder build this.plotVersion = plot.getVersion(); this.plotType = plotType; - if (init()) { - Exception exception = null; + PlotSystem.getPlugin().getComponentLogger().info("plotType: " + plotType.getId()); + + if (!init()) return; + boolean generateWorld = plotType.hasOnePlotPerWorld() || !world.isWorldGenerated(); + boolean isWorldLoaded = world.isWorldLoaded(); + + CompletableFuture.runAsync(() -> { + final Exception[] exception = new Exception[]{null}; try { - if (plotType.hasOnePlotPerWorld() || !world.isWorldGenerated()) { + if (generateWorld) { new PlotWorldGenerator(world.getWorldName()); - } else if (!world.isWorldLoaded() && !world.loadWorld()) throw new Exception("Could not load world"); - generateOutlines(); - createPlotProtection(); + } else if (!isWorldLoaded) { + final boolean[] successful = new boolean[1]; + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> successful[0] = world.loadWorld()); + if (!successful[0]) throw new Exception("Could not load world"); + } + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + try { + createPlotProtection(); + generateOutlines(); + } catch (StorageException | IOException e) { + exception[0] = e; + } + }); } catch (Exception ex) { - exception = ex; + exception[0] = ex; } - this.onComplete(exception != null, false); - - if (exception != null) { - PlotUtils.Actions.abandonPlot(plot); - onException(exception); - } - } + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + this.onComplete(exception[0] != null, false); + + if (exception[0] != null) { + PlotUtils.Actions.abandonPlot(plot); + onException(exception[0]); + } + }); + }); } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java index 3071a704..e95e77c5 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java @@ -46,32 +46,31 @@ public DefaultPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder builder @Override protected boolean init() { - if (getBuilder().getFreeSlot() != null) { - if (DefaultPlotGenerator.playerPlotGenerationHistory.containsKey(getBuilder().getUUID())) { - if (DefaultPlotGenerator.playerPlotGenerationHistory.get(getBuilder().getUUID()).isBefore(LocalDateTime.now().minusSeconds(10))) { - DefaultPlotGenerator.playerPlotGenerationHistory.remove(getBuilder().getUUID()); - } else { - getBuilder().getPlayer().sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(getBuilder().getPlayer(), LangPaths.Message.Error.PLEASE_WAIT))); - getBuilder().getPlayer().playSound(getBuilder().getPlayer().getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); - return false; - } - } - - DefaultPlotGenerator.playerPlotGenerationHistory.put(getBuilder().getUUID(), LocalDateTime.now()); - getBuilder().getPlayer().sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(getBuilder().getPlayer(), LangPaths.Message.Info.CREATING_PLOT))); - getBuilder().getPlayer().playSound(getBuilder().getPlayer().getLocation(), Utils.SoundUtils.CREATE_PLOT_SOUND, 1, 1); - return true; - } else { + if (getBuilder().getFreeSlot() == null) { getBuilder().getPlayer().sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(getBuilder().getPlayer(), LangPaths.Message.Error.ALL_SLOTS_OCCUPIED))); getBuilder().getPlayer().playSound(getBuilder().getPlayer().getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); + return false; } - return false; + + if (DefaultPlotGenerator.playerPlotGenerationHistory.containsKey(getBuilder().getUUID())) { + if (!DefaultPlotGenerator.playerPlotGenerationHistory.get(getBuilder().getUUID()).isBefore(LocalDateTime.now().minusSeconds(10))) { + getBuilder().getPlayer().sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(getBuilder().getPlayer(), LangPaths.Message.Error.PLEASE_WAIT))); + getBuilder().getPlayer().playSound(getBuilder().getPlayer().getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); + return false; + } + DefaultPlotGenerator.playerPlotGenerationHistory.remove(getBuilder().getUUID()); + } + + DefaultPlotGenerator.playerPlotGenerationHistory.put(getBuilder().getUUID(), LocalDateTime.now()); + getBuilder().getPlayer().sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(getBuilder().getPlayer(), LangPaths.Message.Info.CREATING_PLOT))); + getBuilder().getPlayer().playSound(getBuilder().getPlayer().getLocation(), Utils.SoundUtils.CREATE_PLOT_SOUND, 1, 1); + return true; } @Override protected void generateOutlines() throws IOException, WorldEditException { - if (plot instanceof Plot) { - byte[] completedSchematic = ((Plot) plot).getCompletedSchematic(); + if (plot instanceof Plot p) { + byte[] completedSchematic = p.getCompletedSchematic(); if (completedSchematic != null) { PlotSystem.getPlugin().getComponentLogger().info("Found completed schematic, pasting only that."); Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(world.getBukkitWorld()), BlockTypes.AIR); diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java index 569ec666..1315c3f3 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java @@ -61,12 +61,21 @@ public class PlotWorldGenerator { private World world = null; - public PlotWorldGenerator(String worldName) throws IOException, StorageException { + public PlotWorldGenerator(String worldName) throws Exception { this.worldName = worldName; generateWorld(); - createMultiverseWorld(); - configureWorld(); - createGlobalProtection(); + + final Exception[] exception = {null}; + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + try { + createMultiverseWorld(); + configureWorld(); + createGlobalProtection(); + } catch (Exception e) { + exception[0] = e; + } + }); + if (exception[0] != null) throw exception[0]; } protected void generateWorld() throws IOException { @@ -94,13 +103,9 @@ protected void generateWorld() throws IOException { .replaceAll(SkeletonWorldGenerator.WORLD_NAME, worldName) .replaceAll(SkeletonWorldGenerator.WORLD_NAME.toLowerCase(), worldName.toLowerCase()); Files.writeString(paperWorld, updatedContents); - - // load world - this.world = Bukkit.getWorld(worldName); } protected void createMultiverseWorld() { - assert this.world != null; if (!worldManager.isLoadedWorld(worldName)) { worldManager.importWorld(ImportWorldOptions.worldName(worldName) .environment(environment) @@ -111,6 +116,7 @@ protected void createMultiverseWorld() { } protected void configureWorld() { + this.world = Bukkit.getWorld(worldName); assert this.world != null; Option mvWorld = worldManager.getLoadedWorld(worldName); diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java index 446d2710..1dcd22bf 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java @@ -24,25 +24,23 @@ public CityPlotWorld(@NotNull Plot plot) { @Override public boolean teleportPlayer(@NotNull Player player) { - if (super.teleportPlayer(player)) { - player.playSound(player.getLocation(), Utils.SoundUtils.TELEPORT_SOUND, 1, 1); - player.setAllowFlight(true); - player.setFlying(true); + if (!super.teleportPlayer(player)) return false; - if (getPlot() != null) { - player.sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(player, LangPaths.Message.Info.TELEPORTING_PLOT, String.valueOf(getPlot().getID())))); + player.playSound(player.getLocation(), Utils.SoundUtils.TELEPORT_SOUND, 1, 1); + player.setAllowFlight(true); + player.setFlying(true); - Utils.updatePlayerInventorySlots(player); - PlotUtils.ChatFormatting.sendLinkMessages(getPlot(), player); + if (getPlot() == null) return true; - if (getPlot().getPlotOwner().getUUID().equals(player.getUniqueId())) { - getPlot().setLastActivity(false); - } - } + player.sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(player, LangPaths.Message.Info.TELEPORTING_PLOT, String.valueOf(getPlot().getID())))); - return true; - } - return false; + Utils.updatePlayerInventorySlots(player); + PlotUtils.ChatFormatting.sendLinkMessages(getPlot(), player); + + if (!getPlot().getPlotOwner().getUUID().equals(player.getUniqueId())) return true; + getPlot().setLastActivity(false); + + return true; } @Override @@ -76,25 +74,24 @@ public int getWorldHeight() throws IOException { try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(inputStream)) { clipboard = reader.read(); } - if (clipboard != null) { - int plotHeight = clipboard.getMinimumPoint().y(); - - /// Minimum building height for a plot (this should be configurable depending on minecraft build limit) - /// This is in the case that a plot is created at y level 300 where the max build limit is 318, - /// so you don't want builder to only be able to build for 18 blocks - int minBuildingHeight = 128; - - /// Negative y level of the current minecraft version (1.21) - /// Additional ground layer the plot use to save as schematic need to be included for plot's y-level - int groundLayer = 64; - - // Plots created outside of vanilla build limit or the build-able height is too small - if (plotHeight + groundLayer < MIN_WORLD_HEIGHT + groundLayer - || plotHeight + groundLayer + minBuildingHeight > MAX_WORLD_HEIGHT + groundLayer) - return 0; // throw new IOException("Plot height is out of range."); - return plotHeight; - } - throw new IOException("A Plot's Outline schematic fails to load, cannot get clipboard."); + if (clipboard == null) throw new IOException("A Plot's Outline schematic fails to load, cannot get clipboard."); + + int plotHeight = clipboard.getMinimumPoint().y(); + + /// Minimum building height for a plot (this should be configurable depending on minecraft build limit) + /// This is in the case that a plot is created at y level 300 where the max build limit is 318, + /// so you don't want builder to only be able to build for 18 blocks + int minBuildingHeight = 128; + + /// Negative y level of the current minecraft version (1.21) + /// Additional ground layer the plot use to save as schematic need to be included for plot's y-level + int groundLayer = 64; + + // Plots created outside of vanilla build limit or the build-able height is too small + if (plotHeight + groundLayer < MIN_WORLD_HEIGHT + groundLayer + || plotHeight + groundLayer + minBuildingHeight > MAX_WORLD_HEIGHT + groundLayer) + return 0; // throw new IOException("Plot height is out of range."); + return plotHeight; } /** diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java index 87375756..162a4d59 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java @@ -1,13 +1,41 @@ +/* + * The MIT License (MIT) + * + * Copyright © 2021-2025, Alps BTE + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package com.alpsbte.plotsystem.core.system.plot.world; import com.alpsbte.alpslib.utils.AlpsUtils; import com.alpsbte.plotsystem.PlotSystem; import com.alpsbte.plotsystem.core.database.DataProvider; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; +import com.alpsbte.plotsystem.core.system.plot.Plot; import com.alpsbte.plotsystem.core.system.plot.TutorialPlot; import com.alpsbte.plotsystem.core.system.plot.generator.AbstractPlotGenerator; +import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.utils.DependencyManager; import com.alpsbte.plotsystem.utils.Utils; +import com.alpsbte.plotsystem.utils.io.ConfigPaths; +import com.onarandombox.MultiverseCore.MultiverseCore; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; @@ -59,50 +87,50 @@ public boolean regenWorld(@NotNull Class ge @Override public boolean deleteWorld() { - if (isWorldGenerated() && loadWorld()) { - if (Boolean.TRUE.equals(mvCore.getWorldManager().getWorld(getWorldName()) - .map(world -> mvCore.getWorldManager().deleteWorld(DeleteWorldOptions.world(world)).isSuccess()) - .getOrElse(false)) && mvCore.getWorldManager().saveWorldsConfig().isSuccess()) { - try { - File multiverseInventoriesConfig = new File(DependencyManager.getMultiverseInventoriesConfigPath(getWorldName())); - File worldGuardConfig = new File(DependencyManager.getWorldGuardConfigPath(getWorldName())); - if (multiverseInventoriesConfig.exists()) FileUtils.deleteDirectory(multiverseInventoriesConfig); - if (worldGuardConfig.exists()) FileUtils.deleteDirectory(worldGuardConfig); - } catch (IOException ex) { - PlotSystem.getPlugin().getComponentLogger().warn(text("Could not delete config files for world " + getWorldName() + "!")); - return false; - } - return true; - } else PlotSystem.getPlugin().getComponentLogger().warn(text("Could not delete world " + getWorldName() + "!")); + if (!isWorldGenerated() || !loadWorld()) return false; + if (!Boolean.TRUE.equals(mvCore.getWorldManager().getWorld(getWorldName()) + .map(world -> mvCore.getWorldManager().deleteWorld(DeleteWorldOptions.world(world)).isSuccess()) + .getOrElse(false)) || !mvCore.getWorldManager().saveWorldsConfig().isSuccess()) { + PlotSystem.getPlugin().getComponentLogger().warn(text("Could not delete world " + getWorldName() + "!")); + return false; } - return false; + try { + File multiverseInventoriesConfig = new File(DependencyManager.getMultiverseInventoriesConfigPath(getWorldName())); + File worldGuardConfig = new File(DependencyManager.getWorldGuardConfigPath(getWorldName())); + if (multiverseInventoriesConfig.exists()) FileUtils.deleteDirectory(multiverseInventoriesConfig); + if (worldGuardConfig.exists()) FileUtils.deleteDirectory(worldGuardConfig); + } catch (IOException ex) { + PlotSystem.getPlugin().getComponentLogger().warn(text("Could not delete config files for world " + getWorldName() + "!")); + return false; + } + return true; } @Override public boolean loadWorld() { - if (isWorldGenerated()) { - if (isWorldLoaded()) { - return true; - } else return mvCore.getWorldManager().loadWorld(getWorldName()).isSuccess() || isWorldLoaded(); - } else PlotSystem.getPlugin().getComponentLogger().warn(text("Could not load world " + worldName + " because it is not generated!")); - return false; + if (!isWorldGenerated()) { + PlotSystem.getPlugin().getComponentLogger().warn(text("Could not load world " + worldName + " because it is not generated!")); + return false; + } + + return isWorldLoaded() || mvCore.getWorldManager().loadWorld(getWorldName()).isSuccess(); } @Override public boolean unloadWorld(boolean movePlayers) { - if (isWorldGenerated()) { - if (isWorldLoaded()) { - if (movePlayers && !getBukkitWorld().getPlayers().isEmpty()) { - for (Player player : getBukkitWorld().getPlayers()) { - player.teleport(Utils.getSpawnLocation()); - } - } - - return Bukkit.unloadWorld(getBukkitWorld(), true); + if (!isWorldGenerated()) { + PlotSystem.getPlugin().getComponentLogger().warn(text("Could not unload world " + worldName + " because it is not generated!")); + return false; + } + if (!isWorldLoaded()) return true; + + if (movePlayers && !getBukkitWorld().getPlayers().isEmpty()) { + for (Player player : getBukkitWorld().getPlayers()) { + player.teleport(Utils.getSpawnLocation()); } - return true; - } else PlotSystem.getPlugin().getComponentLogger().warn(text("Could not unload world " + worldName + " because it is not generated!")); - return false; + } + + return Bukkit.unloadWorld(getBukkitWorld(), true); } @Override @@ -110,25 +138,21 @@ public boolean teleportPlayer(@NotNull Player player) { if (loadWorld() && plot != null) { player.teleport(getSpawnPoint(plot instanceof TutorialPlot ? null : plot.getCenter())); return true; - } else PlotSystem.getPlugin().getComponentLogger().warn(text("Could not teleport player " + player.getName() + " to world " + worldName + "!")); + } + PlotSystem.getPlugin().getComponentLogger().warn(text("Could not teleport player " + player.getName() + " to world " + worldName + "!")); return false; } @Override public Location getSpawnPoint(BlockVector3 plotVector) { - if (isWorldGenerated() && loadWorld()) { - Location spawnLocation; - if (plotVector == null) { - spawnLocation = getBukkitWorld().getSpawnLocation(); - } else { - spawnLocation = new Location(getBukkitWorld(), plotVector.x(), plotVector.y(), plotVector.z()); - } + if (!isWorldGenerated() || !loadWorld()) return null; + Location spawnLocation = plotVector == null + ? getBukkitWorld().getSpawnLocation() + : new Location(getBukkitWorld(), plotVector.x(), plotVector.y(), plotVector.z()); - // Set spawn point 1 block above the highest block at the spawn location - spawnLocation.setY(getBukkitWorld().getHighestBlockYAt((int) spawnLocation.getX(), (int) spawnLocation.getZ()) + 1d); - return spawnLocation; - } - return null; + // Set spawn point 1 block above the highest block at the spawn location + spawnLocation.setY(getBukkitWorld().getHighestBlockYAt((int) spawnLocation.getX(), (int) spawnLocation.getZ()) + 1d); + return spawnLocation; } @Override @@ -138,17 +162,15 @@ public int getPlotHeight() throws IOException { @Override public int getPlotHeightCentered() throws IOException { - if (plot != null) { - Clipboard clipboard; - ByteArrayInputStream inputStream = new ByteArrayInputStream(plot.getInitialSchematicBytes()); - try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(inputStream)) { - clipboard = reader.read(); - } - if (clipboard != null) { - return (int) clipboard.getRegion().getCenter().y() - clipboard.getMinimumPoint().y(); - } + if (plot == null) return 0; + + Clipboard clipboard; + ByteArrayInputStream inputStream = new ByteArrayInputStream(plot.getInitialSchematicBytes()); + try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(inputStream)) { + clipboard = reader.read(); } - return 0; + if (clipboard == null) return 0; + return (int) clipboard.getRegion().getCenter().y() - clipboard.getMinimumPoint().y(); } @Override @@ -187,13 +209,13 @@ public boolean isWorldGenerated() { } private @Nullable ProtectedRegion getRegion(String regionName) { + if (!loadWorld()) return null; + RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); - if (loadWorld()) { - RegionManager regionManager = container.get(BukkitAdapter.adapt(getBukkitWorld())); - if (regionManager != null) { - return regionManager.getRegion(regionName); - } else PlotSystem.getPlugin().getComponentLogger().warn(text("Region manager is null!")); - } + RegionManager regionManager = container.get(BukkitAdapter.adapt(getBukkitWorld())); + if (regionManager != null) return regionManager.getRegion(regionName); + + PlotSystem.getPlugin().getComponentLogger().warn(text("Region manager is null!")); return null; } @@ -226,12 +248,17 @@ public static boolean isCityPlotWorld(@NotNull String worldName) { * @return - plot world */ public static @Nullable PlotWorld getPlotWorldByName(String worldName) { - if (isOnePlotWorld(worldName) || isCityPlotWorld(worldName)) { - Integer id = AlpsUtils.tryParseInt(worldName.substring(2)); - if (id == null) return new PlotWorld(worldName, null); - AbstractPlot plot = worldName.toLowerCase().startsWith("t-") ? DataProvider.TUTORIAL_PLOT.getById(id).orElse(null) : DataProvider.PLOT.getPlotById(id); - return plot == null ? null : plot.getWorld(); - } - return null; + if (!isOnePlotWorld(worldName) && !isCityPlotWorld(worldName)) return null; + + Integer id = AlpsUtils.tryParseInt(worldName.substring(2)); + if (id == null) return new PlotWorld(worldName, null); + + AbstractPlot plot = worldName.toLowerCase().startsWith("t-") ? DataProvider.TUTORIAL_PLOT.getById(id).orElse(null) : DataProvider.PLOT.getPlotById(id); + return plot == null ? null : plot.getWorld(); + } + + public static PlotWorld getByType(PlotType type, Plot plot) { + boolean disableCIM = PlotSystem.getPlugin().getConfig().getBoolean(ConfigPaths.DISABLE_CITY_INSPIRATION_MODE); + return disableCIM || type.hasOnePlotPerWorld() ? new OnePlotWorld(plot) : new CityPlotWorld(plot); } } From 030be1fbe3779ce501798a360e1fd146a8a4a6d7 Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Mon, 4 Aug 2025 00:18:57 +0200 Subject: [PATCH 13/60] add util methods for passing up exceptions in bukkit scheduler --- .../com/alpsbte/plotsystem/utils/Utils.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/main/java/com/alpsbte/plotsystem/utils/Utils.java b/src/main/java/com/alpsbte/plotsystem/utils/Utils.java index 32bf9812..a59997ab 100644 --- a/src/main/java/com/alpsbte/plotsystem/utils/Utils.java +++ b/src/main/java/com/alpsbte/plotsystem/utils/Utils.java @@ -40,6 +40,9 @@ import java.util.Objects; import java.util.Random; import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import static com.alpsbte.plotsystem.core.system.tutorial.utils.TutorialUtils.TEXT_HIGHLIGHT_END; import static com.alpsbte.plotsystem.core.system.tutorial.utils.TutorialUtils.TEXT_HIGHLIGHT_START; @@ -102,6 +105,34 @@ public static ItemStack getConfiguredItem(@NotNull String material, Object custo return builder.build(); } + public static CompletableFuture runSync(Callable task) { + CompletableFuture future = new CompletableFuture<>(); + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + try { + var result = task.call(); + future.complete(result); + } catch (Exception e) { + future.completeExceptionally(e); + } + }); + + return future; + } + + public static CompletableFuture supplySync(Callable task) { + CompletableFuture future = new CompletableFuture<>(); + Executor executor = Bukkit.getScheduler().getMainThreadExecutor(PlotSystem.getPlugin()); + executor.execute(() -> { + try { + var result = task.call(); + future.complete(result); + } catch (Exception e) { + future.completeExceptionally(e); + } + }); + return future; + } + public static class SoundUtils { private SoundUtils() {} From fc403d631f6a130c60826e160eb09341e9ee67f0 Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Thu, 7 Aug 2025 02:53:52 +0200 Subject: [PATCH 14/60] - pass schematic bytes into generateOutlines method of PlotGenerators - measure old generator performance - add new AbstractPlotLoader, DefaultPlotLoader and PlotHandler --- .../plotsystem/core/EventListener.java | 2 +- .../core/menus/companion/CityProjectMenu.java | 57 ++-- .../core/system/plot/PlotHandler.java | 93 ++++++ .../plot/generator/AbstractPlotGenerator.java | 24 +- .../plot/generator/DefaultPlotGenerator.java | 36 ++- .../plot/generator/TutorialPlotGenerator.java | 2 +- .../generator/loader/AbstractPlotLoader.java | 270 ++++++++++++++++++ .../generator/loader/DefaultPlotLoader.java | 82 ++++++ .../generator/world/PlotWorldGenerator.java | 32 ++- .../core/system/plot/utils/PlotUtils.java | 6 +- 10 files changed, 554 insertions(+), 50 deletions(-) create mode 100644 src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java create mode 100644 src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java create mode 100644 src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java diff --git a/src/main/java/com/alpsbte/plotsystem/core/EventListener.java b/src/main/java/com/alpsbte/plotsystem/core/EventListener.java index c117c199..7110f866 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/EventListener.java +++ b/src/main/java/com/alpsbte/plotsystem/core/EventListener.java @@ -170,7 +170,7 @@ public void onInventoryClickEvent(@NotNull InventoryClickEvent event) { } @EventHandler - public void onlPlayerItemDropEvent(@NotNull PlayerDropItemEvent event) { + public void onPlayerItemDropEvent(@NotNull PlayerDropItemEvent event) { if (event.getItemDrop().getItemStack().equals(CompanionMenu.getMenuItem(event.getPlayer())) || event.getItemDrop().getItemStack().equals(ReviewMenu.getMenuItem(event.getPlayer()))) { event.setCancelled(true); diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java index a705108a..355a649c 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java +++ b/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java @@ -9,15 +9,19 @@ import com.alpsbte.plotsystem.core.system.CityProject; import com.alpsbte.plotsystem.core.system.Country; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.core.system.plot.generator.DefaultPlotGenerator; +import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.PlotDifficulty; import com.alpsbte.plotsystem.utils.enums.Status; import com.alpsbte.plotsystem.utils.io.ConfigPaths; +import com.alpsbte.plotsystem.utils.io.ConfigUtil; import com.alpsbte.plotsystem.utils.io.LangPaths; import com.alpsbte.plotsystem.utils.io.LangUtil; import com.alpsbte.plotsystem.utils.items.MenuItems; import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.Player; import org.ipvp.canvas.mask.BinaryMask; @@ -26,6 +30,7 @@ import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class CityProjectMenu extends AbstractPaginatedMenu { @@ -102,6 +107,34 @@ protected void setItemClickEventsAsync() { CompanionMenu.clickEventTutorialItem(getMenu()); } + public void handleCityProjectClick(Player player, CityProject city) { + Builder builder = Builder.byUUID(player.getUniqueId()); + + PlotDifficulty plotDifficultyForCity; + try { + plotDifficultyForCity = selectedPlotDifficulty != null ? selectedPlotDifficulty : Plot.getPlotDifficultyForBuilder(city, builder).get(); + } catch (ExecutionException | InterruptedException ex) { + sqlError(player, ex); + return; + } + + List unclaimedPlots = DataProvider.PLOT.getPlots(city, plotDifficultyForCity, Status.unclaimed); + if (unclaimedPlots.isEmpty()) { + player.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(player, LangPaths.Message.Error.NO_PLOTS_LEFT))); + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> player.playSound(player.getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1)); + return; + } + + Plot plot = unclaimedPlots.get(Utils.getRandom().nextInt(unclaimedPlots.size())); + + PlotType type = builder.getPlotType(); + if (type.equals(PlotType.CITY_INSPIRATION_MODE) && ConfigUtil.getInstance().configs[0].getBoolean(ConfigPaths.DISABLE_CITY_INSPIRATION_MODE)) + type = PlotType.LOCAL_INSPIRATION_MODE; + + boolean successful = PlotHandler.assignPlot(builder, plot); + if (successful) PlotHandler.generatePlot(builder, plot, type); + } + public static boolean generateRandomPlot(Player player, @NotNull List items, PlotDifficulty selectedPlotDifficulty) { PlotDifficulty difficulty = selectedPlotDifficulty; if (items.isEmpty()) { @@ -181,29 +214,9 @@ protected void setPaginatedItemClickEventsAsync(List source) { clickPlayer.playSound(clickPlayer.getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); return; } - clickPlayer.closeInventory(); - Builder builder = Builder.byUUID(clickPlayer.getUniqueId()); - - try { - PlotDifficulty plotDifficultyForCity = selectedPlotDifficulty != null ? selectedPlotDifficulty : Plot.getPlotDifficultyForBuilder(city, builder).get(); - List unclaimedPlots = DataProvider.PLOT.getPlots(city, plotDifficultyForCity, Status.unclaimed); - if (unclaimedPlots.isEmpty()) { - clickPlayer.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(clickPlayer, LangPaths.Message.Error.NO_PLOTS_LEFT))); - clickPlayer.playSound(clickPlayer.getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); - return; - } - - if (selectedPlotDifficulty != null && PlotSystem.getPlugin().getConfig().getBoolean(ConfigPaths.ENABLE_SCORE_REQUIREMENT) && !DataProvider.DIFFICULTY.builderMeetsRequirements(builder, selectedPlotDifficulty)) { - clickPlayer.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(clickPlayer, LangPaths.Message.Error.PLAYER_NEEDS_HIGHER_SCORE))); - clickPlayer.playSound(clickPlayer.getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); - return; - } - - new DefaultPlotGenerator(city, plotDifficultyForCity, builder); - } catch (ExecutionException | InterruptedException ex) { - sqlError(clickPlayer, ex); - } + + CompletableFuture.runAsync(() -> handleCityProjectClick(clickPlayer, city)); }); slot++; } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java new file mode 100644 index 00000000..88734796 --- /dev/null +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java @@ -0,0 +1,93 @@ +/* + * The MIT License (MIT) + * + * Copyright © 2021-2025, Alps BTE + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.alpsbte.plotsystem.core.system.plot; + +import com.alpsbte.plotsystem.PlotSystem; +import com.alpsbte.plotsystem.core.database.DataProvider; +import com.alpsbte.plotsystem.core.system.Builder; +import com.alpsbte.plotsystem.core.system.plot.generator.loader.DefaultPlotLoader; +import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; +import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; +import com.alpsbte.plotsystem.utils.Utils; +import com.alpsbte.plotsystem.utils.enums.Slot; +import com.alpsbte.plotsystem.utils.enums.Status; +import com.alpsbte.plotsystem.utils.io.ConfigPaths; +import com.alpsbte.plotsystem.utils.io.LangPaths; +import com.alpsbte.plotsystem.utils.io.LangUtil; +import org.bukkit.SoundCategory; +import org.bukkit.entity.Player; + +import java.time.LocalDateTime; +import java.util.*; + +public class PlotHandler { + private PlotHandler() {} + + private static final Map playerPlotGenerationHistory = new HashMap<>(); + + public static boolean assignPlot(Builder builder, Plot plot) { + Player player = builder.getPlayer(); + + // Score Requirement met? + if (PlotSystem.getPlugin().getConfig().getBoolean(ConfigPaths.ENABLE_SCORE_REQUIREMENT) && !DataProvider.DIFFICULTY.builderMeetsRequirements(builder, plot.getDifficulty())) { + player.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(player, LangPaths.Message.Error.PLAYER_NEEDS_HIGHER_SCORE))); + player.playSound(player.getLocation(), Utils.SoundUtils.ERROR_SOUND, SoundCategory.MASTER, 1, 1, 0); + return false; + } + + // Slot available? + Slot freeSlot = builder.getFreeSlot(); + if (freeSlot == null) { + player.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(player, LangPaths.Message.Error.ALL_SLOTS_OCCUPIED))); + player.playSound(player.getLocation(), Utils.SoundUtils.ERROR_SOUND, SoundCategory.MASTER, 1, 1, 0); + return false; + } + + // Assign + if (!builder.setSlot(builder.getFreeSlot(), plot.getID())) return false; + if (!plot.setStatus(Status.unfinished)) return false; + return plot.setPlotOwner(builder); + } + + public static void generatePlot(Builder builder, Plot plot, PlotType type) { + Player player = builder.getPlayer(); + + // Cooldown + if (playerPlotGenerationHistory.containsKey(builder.getUUID())) { + if (!playerPlotGenerationHistory.get(builder.getUUID()).isBefore(LocalDateTime.now().minusSeconds(10))) { + player.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(player, LangPaths.Message.Error.PLEASE_WAIT))); + player.playSound(player.getLocation(), Utils.SoundUtils.ERROR_SOUND, SoundCategory.MASTER, 1, 1, 0); + return; + } + playerPlotGenerationHistory.remove(builder.getUUID()); + } + playerPlotGenerationHistory.put(builder.getUUID(), LocalDateTime.now()); + + player.sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(player, LangPaths.Message.Info.CREATING_PLOT))); + player.playSound(player.getLocation(), Utils.SoundUtils.CREATE_PLOT_SOUND, SoundCategory.MASTER, 1, 1, 0); + + new DefaultPlotLoader(plot, builder, type, PlotWorld.getByType(type, plot)); + } +} diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java index 2aedf1be..a8dc2f34 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java @@ -100,28 +100,43 @@ private AbstractPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder build PlotSystem.getPlugin().getComponentLogger().info("plotType: " + plotType.getId()); + long initStartTime = System.nanoTime(); if (!init()) return; + PlotSystem.getPlugin().getComponentLogger().info("(APG) Init time: {}ms", (System.nanoTime() - initStartTime) / 1_000_000); + boolean generateWorld = plotType.hasOnePlotPerWorld() || !world.isWorldGenerated(); boolean isWorldLoaded = world.isWorldLoaded(); CompletableFuture.runAsync(() -> { final Exception[] exception = new Exception[]{null}; try { + // Generate and/or load world if (generateWorld) { + long worldGenerationStartTime = System.nanoTime(); new PlotWorldGenerator(world.getWorldName()); + PlotSystem.getPlugin().getComponentLogger().info("(APG) Total PlotWorldGenerator time: {}ms", (System.nanoTime() - worldGenerationStartTime) / 1_000_000); } else if (!isWorldLoaded) { final boolean[] successful = new boolean[1]; Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> successful[0] = world.loadWorld()); if (!successful[0]) throw new Exception("Could not load world"); } + + long schemFetchTime = System.nanoTime(); + // Fetch initial plot schematic + byte[] initialSchematic = plot.getInitialSchematicBytes(); + PlotSystem.getPlugin().getComponentLogger().info("(APG) schem fetching time: {}ms", (System.nanoTime() - schemFetchTime) / 1_000_000); + + long generateOutlinesTime = System.nanoTime(); Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { try { createPlotProtection(); - generateOutlines(); + generateOutlines(initialSchematic); } catch (StorageException | IOException e) { exception[0] = e; } }); + PlotSystem.getPlugin().getComponentLogger().info("(APG) generate outlines time (main thread): {}ms", (System.nanoTime() - generateOutlinesTime) / 1_000_000); + } catch (Exception ex) { exception[0] = ex; } @@ -133,6 +148,7 @@ private AbstractPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder build onException(exception[0]); } }); + PlotSystem.getPlugin().getComponentLogger().info("(APG) total time: {}ms", (System.nanoTime() - initStartTime) / 1_000_000); }); } @@ -148,12 +164,12 @@ private AbstractPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder build /** * Generates plot schematic and outlines */ - protected void generateOutlines() throws IOException { + protected void generateOutlines(byte[] initialSchematic) throws IOException { if (plotVersion >= 3 && plotType.hasEnvironment()) { - pasteSchematic(null, plot.getInitialSchematicBytes(), world, false); + pasteSchematic(null, initialSchematic, world, false); } else { Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(world.getBukkitWorld()), BlockTypes.AIR); - pasteSchematic(airMask, PlotUtils.getOutlinesSchematicBytes(plot, world.getBukkitWorld()), world, true); + pasteSchematic(airMask, PlotUtils.getOutlinesSchematicBytes(plot, initialSchematic, world.getBukkitWorld()), world, true); } } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java index e95e77c5..997682a1 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java @@ -1,3 +1,27 @@ +/* + * The MIT License (MIT) + * + * Copyright © 2025, Alps BTE + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package com.alpsbte.plotsystem.core.system.plot.generator; import com.alpsbte.plotsystem.PlotSystem; @@ -68,15 +92,17 @@ protected boolean init() { } @Override - protected void generateOutlines() throws IOException, WorldEditException { - if (plot instanceof Plot p) { + protected void generateOutlines(byte[] schematic) throws IOException, WorldEditException { + long startTime = System.nanoTime(); + if (!(plot instanceof Plot p)) super.generateOutlines(schematic); + else { byte[] completedSchematic = p.getCompletedSchematic(); if (completedSchematic != null) { PlotSystem.getPlugin().getComponentLogger().info("Found completed schematic, pasting only that."); Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(world.getBukkitWorld()), BlockTypes.AIR); pasteSchematic(airMask, completedSchematic, world, false); - } else super.generateOutlines(); - } else super.generateOutlines(); + } else super.generateOutlines(schematic); + } // If the player is playing in his own world, then additionally generate the plot in the city world if (PlotWorld.isOnePlotWorld(world.getWorldName()) && plotVersion >= 3 && plot.getStatus() != Status.completed) { @@ -101,6 +127,8 @@ protected void onException(Throwable ex) { } }; } + + PlotSystem.getPlugin().getComponentLogger().info("(DPG) Generate Outlines time: {}ms", (System.nanoTime() - startTime) / 1_000_000); } @Override diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/TutorialPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/TutorialPlotGenerator.java index b5662f25..ba9889d0 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/TutorialPlotGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/TutorialPlotGenerator.java @@ -36,7 +36,7 @@ protected boolean init() { public void generateOutlines(int schematicId) throws IOException, WorldEditException { ((TutorialPlot) plot).setTutorialSchematic(schematicId); - generateOutlines(); + generateOutlines(getPlot().getInitialSchematicBytes()); } @Override diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java new file mode 100644 index 00000000..d239e980 --- /dev/null +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java @@ -0,0 +1,270 @@ +/* + * The MIT License (MIT) + * + * Copyright © 2021-2025, Alps BTE + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.alpsbte.plotsystem.core.system.plot.generator.loader; + +import com.alpsbte.plotsystem.PlotSystem; +import com.alpsbte.plotsystem.core.system.Builder; +import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; +import com.alpsbte.plotsystem.core.system.plot.generator.world.PlotWorldGenerator; +import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; +import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; +import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; +import com.alpsbte.plotsystem.utils.DependencyManager; +import com.alpsbte.plotsystem.utils.Utils; +import com.alpsbte.plotsystem.utils.io.ConfigPaths; +import com.alpsbte.plotsystem.utils.io.ConfigUtil; +import com.alpsbte.plotsystem.utils.io.LangPaths; +import com.alpsbte.plotsystem.utils.io.LangUtil; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.BukkitWorld; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; +import com.sk89q.worldedit.function.mask.BlockTypeMask; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.mask.RegionMask; +import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.Vector2; +import com.sk89q.worldedit.regions.CylinderRegion; +import com.sk89q.worldedit.regions.Polygonal2DRegion; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.session.ClipboardHolder; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.RegionGroup; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.RegionContainer; +import org.bukkit.configuration.file.FileConfiguration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; + +import static net.kyori.adventure.text.Component.text; + +public abstract class AbstractPlotLoader { + protected final AbstractPlot plot; + protected final PlotType plotType; + protected final PlotWorld plotWorld; + + protected final Builder builder; + + protected byte[] schematicBytes = null; + + protected AbstractPlotLoader(@NotNull AbstractPlot plot, Builder builder, PlotType plotType, PlotWorld plotWorld) { + this.plot = plot; + this.plotType = plotType; + this.plotWorld = plotWorld; + this.builder = builder; + + PlotSystem.getPlugin().getComponentLogger().info("Loading plot #{}...", plot.getID()); + PlotSystem.getPlugin().getComponentLogger().info("Plot Type: {}", plot.getPlotType().name()); + + boolean successful = true; + try { + generateWorld(); + loadWorld(); + fetchSchematicData(); + createPlotProtection(); + generateStructure(); + } catch (Exception e) { + successful = false; + onException(e); + } + + if (successful) onCompletion(); + } + + protected void generateWorld() throws Exception { + boolean generateWorld = plotType.hasOnePlotPerWorld() || !Utils.supplySync(plotWorld::isWorldGenerated).get(); + if (!generateWorld) return; + + new PlotWorldGenerator(plotWorld.getWorldName()); + } + + protected void loadWorld() throws Exception { + Utils.runSync(() -> { + if (plotWorld.isWorldLoaded()) return null; + + boolean successful = plotWorld.loadWorld(); + if (!successful) throw new Exception("Could not load world!"); + return null; + }).get(); + } + + protected void fetchSchematicData() { + this.schematicBytes = plot.getInitialSchematicBytes(); + } + + protected void createPlotProtection() throws Exception { + Utils.runSync(() -> { + RegionContainer regionContainer = WorldGuard.getInstance().getPlatform().getRegionContainer(); + RegionManager regionManager = regionContainer.get(BukkitAdapter.adapt(plotWorld.getBukkitWorld())); + if (regionManager == null) { + PlotSystem.getPlugin().getComponentLogger().warn(text("Region Manager is null!")); + return null; + } + + // Create build region for plot from the outline of the plot + ProtectedRegion protectedBuildRegion = new ProtectedPolygonalRegion(plotWorld.getRegionName(), plot.getOutline(), PlotWorld.MIN_WORLD_HEIGHT, PlotWorld.MAX_WORLD_HEIGHT); + protectedBuildRegion.setPriority(100); + + // Create protected plot region for plot + ProtectedRegion protectedRegion = getProtectedRegion(); + + // Add plot owner + DefaultDomain owner = protectedBuildRegion.getOwners(); + owner.addPlayer(builder.getUUID()); + protectedBuildRegion.setOwners(owner); + + // Set protected build region permissions + setBuildRegionPermissions(protectedBuildRegion); + + // Set protected region permissions + setRegionPermissions(protectedRegion); + + // Add regions and save changes + if (regionManager.hasRegion(plotWorld.getRegionName())) regionManager.removeRegion(plotWorld.getRegionName()); + if (regionManager.hasRegion(plotWorld.getRegionName() + "-1")) regionManager.removeRegion(plotWorld.getRegionName() + "-1"); + regionManager.addRegion(protectedBuildRegion); + regionManager.addRegion(protectedRegion); + regionManager.saveChanges(); + return null; + }).get(); + } + + private @NotNull ProtectedRegion getProtectedRegion() { + World weWorld = new BukkitWorld(plotWorld.getBukkitWorld()); + CylinderRegion cylinderRegion = new CylinderRegion(weWorld, plot.getCenter(), Vector2.at(PlotWorld.PLOT_SIZE, PlotWorld.PLOT_SIZE), PlotWorld.MIN_WORLD_HEIGHT, PlotWorld.MAX_WORLD_HEIGHT); + ProtectedRegion protectedRegion = new ProtectedPolygonalRegion(plotWorld.getRegionName() + "-1", cylinderRegion.polygonize(-1), PlotWorld.MIN_WORLD_HEIGHT, PlotWorld.MAX_WORLD_HEIGHT); + protectedRegion.setPriority(50); + return protectedRegion; + } + + /** + * Sets the permissions for the plot build region only + * + * @param region build region + */ + protected void setBuildRegionPermissions(@NotNull ProtectedRegion region) { + region.setFlag(Flags.BUILD, StateFlag.State.ALLOW); + region.setFlag(Flags.BUILD.getRegionGroupFlag(), RegionGroup.OWNERS); + if (DependencyManager.isWorldGuardExtraFlagsEnabled()) + region.setFlag(new StateFlag("worldedit", true, RegionGroup.OWNERS), StateFlag.State.ALLOW); + } + + /** + * Sets the permissions for the whole plot region + * + * @param region plot region + */ + protected void setRegionPermissions(@NotNull ProtectedRegion region) { + region.setFlag(Flags.ENTRY, StateFlag.State.ALLOW); + region.setFlag(Flags.ENTRY.getRegionGroupFlag(), RegionGroup.ALL); + + FileConfiguration config = ConfigUtil.getInstance().configs[1]; + region.setFlag(Flags.BLOCKED_CMDS, new HashSet<>(getBlockedCommands(config))); + region.setFlag(Flags.BLOCKED_CMDS.getRegionGroupFlag(), RegionGroup.ALL); + } + + /** + * Reads the blocked commands for the plot region from the config + * + * @param config commands.yml config + * @return list of blocked commands + */ + protected List getBlockedCommands(@NotNull FileConfiguration config) { + List blockedCommands = config.getStringList(ConfigPaths.BLOCKED_COMMANDS_BUILDERS); + blockedCommands.removeIf(c -> c.equals("/cmd1")); + return blockedCommands; + } + + protected void generateStructure() throws Exception { + Utils.runSync(() -> { + if (plotType.hasEnvironment()) { + pasteSchematic(null, this.schematicBytes, this.plotWorld, false); + } else { + Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(this.plotWorld.getBukkitWorld()), BlockTypes.AIR); + pasteSchematic(airMask, PlotUtils.getOutlinesSchematicBytes(plot, this.schematicBytes, this.plotWorld.getBukkitWorld()), this.plotWorld, true); + } + return null; + }).get(); + } + + public static void pasteSchematic(@Nullable Mask pasteMask, byte[] schematicFile, @NotNull PlotWorld world, boolean clearArea) throws IOException { + // load world if not loaded already + if (!world.loadWorld()) return; + World weWorld = new BukkitWorld(world.getBukkitWorld()); + + // set outline region with air + if (clearArea) { + try (EditSession editSession = WorldEdit.getInstance().newEditSession(BukkitAdapter.adapt(world.getBukkitWorld()))) { + Polygonal2DRegion polyRegion = new Polygonal2DRegion(weWorld, world.getPlot().getOutline(), 0, PlotWorld.MAX_WORLD_HEIGHT); + editSession.setMask(new RegionMask(polyRegion)); + editSession.setBlocks((Region) polyRegion, Objects.requireNonNull(BlockTypes.AIR).getDefaultState()); + } + } + + // load schematic + Clipboard clipboard; + ByteArrayInputStream inputStream = new ByteArrayInputStream(schematicFile); + try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(inputStream)) { + clipboard = reader.read(); + } + + // paste schematic + try (EditSession editSession = WorldEdit.getInstance().newEditSession(BukkitAdapter.adapt(world.getBukkitWorld()))) { + if (pasteMask != null) editSession.setMask(pasteMask); + Operation clipboardHolder = new ClipboardHolder(clipboard) + .createPaste(editSession) + .to(BlockVector3.at(world.getPlot().getCenter().x(), world.getPlotHeight(), world.getPlot().getCenter().z())) + .build(); + Operations.complete(clipboardHolder); + } + } + + protected void onException(Exception e) { + // TODO: abandon plot + + PlotSystem.getPlugin().getComponentLogger().error(text("An error occurred while generating plot!"), e); + builder.getPlayer().sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(builder.getPlayer(), LangPaths.Message.Error.ERROR_OCCURRED))); + builder.getPlayer().playSound(builder.getPlayer().getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); + } + + protected abstract void onCompletion(); +} diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java new file mode 100644 index 00000000..4e581177 --- /dev/null +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java @@ -0,0 +1,82 @@ +/* + * The MIT License (MIT) + * + * Copyright © 2021-2025, Alps BTE + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.alpsbte.plotsystem.core.system.plot.generator.loader; + +import com.alpsbte.plotsystem.PlotSystem; +import com.alpsbte.plotsystem.core.system.Builder; +import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; +import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; +import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; +import com.alpsbte.plotsystem.utils.Utils; +import com.alpsbte.plotsystem.utils.enums.Status; +import com.alpsbte.plotsystem.utils.io.LangPaths; +import com.alpsbte.plotsystem.utils.io.LangUtil; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.function.mask.BlockTypeMask; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.world.block.BlockTypes; +import org.bukkit.Bukkit; +import org.jetbrains.annotations.NotNull; + +public class DefaultPlotLoader extends AbstractPlotLoader { + public DefaultPlotLoader(@NotNull AbstractPlot plot, Builder builder, PlotType plotType, PlotWorld plotWorld) { + super(plot, builder, plotType, plotWorld); + } + + @Override + protected void generateStructure() throws Exception { + if (!(plot instanceof Plot p)) super.generateStructure(); + else { + byte[] completedSchematic = p.getCompletedSchematic(); + if (completedSchematic != null) { + Utils.runSync(() -> { + Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(plotWorld.getBukkitWorld()), BlockTypes.AIR); + pasteSchematic(airMask, completedSchematic, plotWorld, true); + return null; + }).get(); + } else super.generateStructure(); + } + + copyToCityWorld(); + } + + @Override + protected void onCompletion() { + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + plot.getWorld().teleportPlayer(builder.getPlayer()); + LangUtil.getInstance().broadcast(LangPaths.Message.Info.CREATED_NEW_PLOT, builder.getName()); + }); + } + + protected void copyToCityWorld() { + if (plot.getStatus() == Status.completed) return; + if (!PlotWorld.isOnePlotWorld(plotWorld.getWorldName())) return; + + // If the player is playing in his own world, then additionally generate the plot in the city world + // TODO: generate plot on city world + // TODO: unload CIM world when done + } +} diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java index 1315c3f3..4812153e 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java @@ -26,6 +26,7 @@ import com.alpsbte.plotsystem.PlotSystem; import com.alpsbte.plotsystem.utils.DependencyManager; +import com.alpsbte.plotsystem.utils.Utils; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldguard.WorldGuard; import com.sk89q.worldguard.protection.flags.Flags; @@ -59,23 +60,24 @@ public class PlotWorldGenerator { private static final World.Environment environment = World.Environment.NORMAL; private static final WorldType worldType = WorldType.FLAT; - private World world = null; - public PlotWorldGenerator(String worldName) throws Exception { + long startTime = System.nanoTime(); this.worldName = worldName; + + // Async Part generateWorld(); - final Exception[] exception = {null}; - Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - try { - createMultiverseWorld(); - configureWorld(); - createGlobalProtection(); - } catch (Exception e) { - exception[0] = e; - } - }); - if (exception[0] != null) throw exception[0]; + // Sync Part + long mainThreadStart = System.nanoTime(); + Utils.runSync(() -> { + createMultiverseWorld(); + configureWorld(); + createGlobalProtection(); + return null; + }).get(); + + PlotSystem.getPlugin().getComponentLogger().info("(PWG) Total time to generate world: {}ms", (System.nanoTime() - startTime) / 1_000_000); + PlotSystem.getPlugin().getComponentLogger().info("(PWG) Total time on main thread: {}ms", (System.nanoTime() - mainThreadStart) / 1_000_000); } protected void generateWorld() throws IOException { @@ -116,8 +118,8 @@ protected void createMultiverseWorld() { } protected void configureWorld() { - this.world = Bukkit.getWorld(worldName); - assert this.world != null; + World world = Bukkit.getWorld(worldName); + assert world != null; Option mvWorld = worldManager.getLoadedWorld(worldName); // Configure multiverse world diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java index 45905dfa..4960432f 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java @@ -164,9 +164,9 @@ public static boolean isPlotWorld(@NotNull World world) { return DependencyManager.getMultiverseCore().getWorldManager().isLoadedWorld(world) && (PlotWorld.isOnePlotWorld(world.getName()) || PlotWorld.isCityPlotWorld(world.getName())); } - public static byte @Nullable [] getOutlinesSchematicBytes(@NotNull AbstractPlot plot, World world) throws IOException { + public static byte @Nullable [] getOutlinesSchematicBytes(@NotNull AbstractPlot plot, byte[] initialSchematic, World world) throws IOException { Clipboard clipboard; - ByteArrayInputStream inputStream = new ByteArrayInputStream(plot.getInitialSchematicBytes()); + ByteArrayInputStream inputStream = new ByteArrayInputStream(initialSchematic); try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(inputStream)) { clipboard = reader.read(); } @@ -388,7 +388,7 @@ public static boolean abandonPlot(@NotNull AbstractPlot plot) { if (regionManager.hasRegion(world.getRegionName())) regionManager.removeRegion(world.getRegionName()); if (regionManager.hasRegion(world.getRegionName() + "-1")) regionManager.removeRegion(world.getRegionName() + "-1"); - AbstractPlotGenerator.pasteSchematic(null, getOutlinesSchematicBytes(plot, world.getBukkitWorld()), world, true); + AbstractPlotGenerator.pasteSchematic(null, getOutlinesSchematicBytes(plot, plot.getInitialSchematicBytes(), world.getBukkitWorld()), world, true); } else PlotSystem.getPlugin().getComponentLogger().warn(text("Region Manager is null!")); playersToTeleport.forEach(p -> p.teleport(Utils.getSpawnLocation())); From 35931a8741a7d1ac216eac2057e1779692d3f386 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Mon, 25 Aug 2025 03:49:03 +0200 Subject: [PATCH 15/60] =?UTF-8?q?feat(gen):=20=E2=9C=A8=20update=20emptych?= =?UTF-8?q?unkgenerator=20and=20provide=20it=20outside=20of=20plugin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/alpsbte/plotsystem/PlotSystem.java | 6 ++++++ .../generator/world/SkeletonWorldGenerator.java | 14 +++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java index 94c41885..3881b1d6 100644 --- a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java +++ b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java @@ -29,6 +29,7 @@ import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.generator.ChunkGenerator; import org.bukkit.plugin.java.JavaPlugin; import org.ipvp.canvas.MenuFunctionListener; import org.jetbrains.annotations.NotNull; @@ -204,4 +205,9 @@ public void initDatabase() throws IOException, SQLException, ClassNotFoundExcept s.execute(initScript); } } + + @Override + public ChunkGenerator getDefaultWorldGenerator(@NotNull String worldName, String id) { + return new SkeletonWorldGenerator.EmptyChunkGenerator(); + } } \ No newline at end of file diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java index 7ced0a65..acf97e31 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java @@ -25,12 +25,12 @@ package com.alpsbte.plotsystem.core.system.plot.generator.world; import net.kyori.adventure.util.TriState; -import org.bukkit.*; +import org.bukkit.GameRule; +import org.bukkit.World; +import org.bukkit.WorldCreator; +import org.bukkit.WorldType; import org.bukkit.generator.ChunkGenerator; -import javax.annotation.Nonnull; -import java.util.Random; - public class SkeletonWorldGenerator { public static final String WORLD_NAME = "Skeleton"; private static final World.Environment ENVIRONMENT = World.Environment.NORMAL; @@ -80,10 +80,6 @@ protected void saveWorld() { } public static class EmptyChunkGenerator extends ChunkGenerator { - @Override - @Nonnull - public ChunkData generateChunkData(@Nonnull World world, @Nonnull Random random, int x, int z, @Nonnull BiomeGrid biome) { - return createChunkData(world); - } + // It should just do nothing } } From 91e97ad3594712a28fa092e2455533756a2c04a6 Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Sat, 1 Nov 2025 17:29:19 +0100 Subject: [PATCH 16/60] Implement plot abandon functionality in the PlotHandler and rework Plot Worlds to handle world specific abandon logic --- .../com/alpsbte/plotsystem/PlotSystem.java | 5 +- .../commands/admin/CMD_DeletePlot.java | 11 +- .../commands/plot/CMD_Plot_Abandon.java | 8 +- .../commands/plot/CMD_Plot_Submit.java | 3 +- .../commands/plot/CMD_Plot_UndoSubmit.java | 3 +- .../menus/review/ReviewPlotTogglesMenu.java | 3 +- .../core/system/plot/PlotHandler.java | 108 +++++++++++ .../plot/generator/AbstractPlotGenerator.java | 3 +- .../core/system/plot/utils/PlotUtils.java | 182 ++---------------- .../core/system/plot/world/CityPlotWorld.java | 71 +++++-- .../core/system/plot/world/IWorld.java | 18 ++ .../core/system/plot/world/OnePlotWorld.java | 60 +++--- .../core/system/plot/world/PlotWorld.java | 19 +- 13 files changed, 270 insertions(+), 224 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java index 3881b1d6..7f2f77a3 100644 --- a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java +++ b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java @@ -12,6 +12,7 @@ import com.alpsbte.plotsystem.core.holograms.HologramRegister; import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.core.system.plot.generator.world.SkeletonWorldGenerator; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.core.system.tutorial.AbstractTutorial; @@ -119,10 +120,12 @@ public void onEnable() { DecentHologramDisplay.registerPlugin(this); HologramRegister.init(); - PlotUtils.checkPlotsForLastActivity(); Utils.ChatUtils.checkForChatInputExpiry(); PlotUtils.Effects.startTimer(); + // Start task that checks for and abandons inactive plots every hour + Bukkit.getScheduler().runTaskTimerAsynchronously(PlotSystem.getPlugin(), PlotHandler::abandonInactivePlots, 0L, 20 * 60 * 60L); + // Register tutorials if (getConfig().getBoolean(ConfigPaths.TUTORIAL_ENABLE)) { AbstractTutorial.registerTutorials(Collections.singletonList(BeginnerTutorial.class)); diff --git a/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_DeletePlot.java b/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_DeletePlot.java index 72d6539b..45f12f17 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_DeletePlot.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_DeletePlot.java @@ -5,6 +5,7 @@ import com.alpsbte.plotsystem.commands.BaseCommand; import com.alpsbte.plotsystem.core.database.DataProvider; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.utils.Utils; import org.bukkit.Bukkit; @@ -38,10 +39,12 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N sender.sendMessage(Utils.ChatUtils.getInfoFormat("Deleting plot...")); Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - if (PlotUtils.Actions.deletePlot(plot)) { - sender.sendMessage(Utils.ChatUtils.getInfoFormat("Successfully deleted plot with the ID §6#" + plotID + "§a!")); - if (getPlayer(sender) != null) getPlayer(sender).playSound(getPlayer(sender).getLocation(), Utils.SoundUtils.DONE_SOUND, 1f, 1f); - } else sender.sendMessage(Utils.ChatUtils.getAlertFormat("An unexpected error has occurred!")); + if (!PlotHandler.deletePlot(plot)) { + sender.sendMessage(Utils.ChatUtils.getAlertFormat("An unexpected error has occurred!")); + return; + } + sender.sendMessage(Utils.ChatUtils.getInfoFormat("Successfully deleted plot with the ID §6#" + plotID + "§a!")); + if (getPlayer(sender) != null) getPlayer(sender).playSound(getPlayer(sender).getLocation(), Utils.SoundUtils.DONE_SOUND, 1f, 1f); }); }); return true; diff --git a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Abandon.java b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Abandon.java index 0a9f0fbb..b1abfd10 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Abandon.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Abandon.java @@ -8,6 +8,7 @@ import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.Status; @@ -67,10 +68,9 @@ public void onCommand(CommandSender sender, String[] args) { } Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - if (PlotUtils.Actions.abandonPlot(plot)) { - sender.sendMessage(Utils.ChatUtils.getInfoFormat(langUtil.get(sender, LangPaths.Message.Info.ABANDONED_PLOT, plot.getID() + ""))); - player.playSound(player.getLocation(), Utils.SoundUtils.ABANDON_PLOT_SOUND, 1, 1); - } + if (!PlotHandler.abandonPlot(plot)) return; + sender.sendMessage(Utils.ChatUtils.getInfoFormat(langUtil.get(sender, LangPaths.Message.Info.ABANDONED_PLOT, plot.getID() + ""))); + player.playSound(player.getLocation(), Utils.SoundUtils.ABANDON_PLOT_SOUND, 1, 1); }); }); } diff --git a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Submit.java b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Submit.java index 0dfc8834..82a48421 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Submit.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Submit.java @@ -8,6 +8,7 @@ import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.Status; @@ -75,7 +76,7 @@ public void onCommand(CommandSender sender, String[] args) { Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - PlotUtils.Actions.submitPlot(plot); + PlotHandler.submitPlot(plot); if (plotMembers.isEmpty()) { // Plot was made alone langUtil.broadcast(LangPaths.Message.Info.FINISHED_PLOT, String.valueOf(plot.getID()), plot.getPlotOwner().getName()); diff --git a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_UndoSubmit.java b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_UndoSubmit.java index d73e2006..5f556948 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_UndoSubmit.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_UndoSubmit.java @@ -8,6 +8,7 @@ import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.Status; @@ -69,7 +70,7 @@ public void onCommand(CommandSender sender, String[] args) { } Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - PlotUtils.Actions.undoSubmit(plot); + PlotHandler.undoSubmit(plot); sender.sendMessage(Utils.ChatUtils.getInfoFormat(langUtil.get(sender, LangPaths.Message.Info.UNDID_SUBMISSION, plot.getID() + ""))); player.playSound(player.getLocation(), Utils.SoundUtils.FINISH_PLOT_SOUND, 1, 1); diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java index 68e96739..d1a712b2 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java +++ b/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java @@ -7,6 +7,7 @@ import com.alpsbte.plotsystem.core.menus.AbstractMenu; import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.core.system.review.PlotReview; import com.alpsbte.plotsystem.core.system.review.ReviewRating; @@ -132,7 +133,7 @@ private void submitReview() { if(!acceptPlot(review.getScore(), review.getSplitScore())) return; } else { reviewerConfirmationMessage = Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Message.Info.PLOT_REJECTED, Integer.toString(plot.getID()), getParticipantsString())); - PlotUtils.Actions.undoSubmit(plot); + PlotHandler.undoSubmit(plot); } Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java index 88734796..8621fead 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java @@ -29,6 +29,9 @@ import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.generator.loader.DefaultPlotLoader; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; +import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; +import com.alpsbte.plotsystem.core.system.plot.world.CityPlotWorld; +import com.alpsbte.plotsystem.core.system.plot.world.OnePlotWorld; import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.Slot; @@ -36,11 +39,18 @@ import com.alpsbte.plotsystem.utils.io.ConfigPaths; import com.alpsbte.plotsystem.utils.io.LangPaths; import com.alpsbte.plotsystem.utils.io.LangUtil; +import org.bukkit.Bukkit; import org.bukkit.SoundCategory; +import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; +import java.util.concurrent.CompletableFuture; + +import static net.kyori.adventure.text.Component.text; public class PlotHandler { private PlotHandler() {} @@ -90,4 +100,102 @@ public static void generatePlot(Builder builder, Plot plot, PlotType type) { new DefaultPlotLoader(plot, builder, type, PlotWorld.getByType(type, plot)); } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + public static boolean abandonPlot(AbstractPlot plot) { + boolean successfullyAbandoned = plot.getWorld().onAbandon(); + if (!successfullyAbandoned) { + PlotSystem.getPlugin().getComponentLogger().error(text("Failed to abandon plot with the ID " + plot.getID() + "!")); + return false; + } + + CompletableFuture.runAsync(() -> { + if (plot.getPlotType() == PlotType.TUTORIAL) return; + Plot dPlot = (Plot) plot; + boolean successful; + successful = DataProvider.REVIEW.removeAllReviewsOfPlot(dPlot.getID()); + + for (Builder builder : dPlot.getPlotMembers()) { + if (!successful) break; + successful = dPlot.removePlotMember(builder); + } + + if (successful && plot.getPlotOwner() != null) { + PlotUtils.Cache.clearCache(plot.getPlotOwner().getUUID()); + successful = plot.getPlotOwner().setSlot(plot.getPlotOwner().getSlot(dPlot), -1); + } + + if (successful) { + successful = dPlot.setPlotOwner(null) + && dPlot.setLastActivity(true) + && dPlot.setStatus(Status.unclaimed) + && dPlot.setPlotType(PlotType.LOCAL_INSPIRATION_MODE); + } + + successful = successful && DataProvider.PLOT.setCompletedSchematic(plot.getID(), null); + if (!successful) PlotSystem.getPlugin().getComponentLogger().error(text("Failed to abandon plot with the ID " + plot.getID() + "!")); + }); + return true; + } + + public static boolean deletePlot(Plot plot) { + if (!abandonPlot(plot)) { + PlotSystem.getPlugin().getComponentLogger().warn(text("Failed to delete plot with the ID " + plot.getID() + "!")); + return false; + } + CompletableFuture.runAsync(() -> { + if (DataProvider.PLOT.deletePlot(plot.getID())) return; + PlotSystem.getPlugin().getComponentLogger().warn(text("Failed to delete plot with the ID " + plot.getID() + " from the database!")); + }); + return true; + } + + public static void abandonInactivePlots() { + FileConfiguration config = PlotSystem.getPlugin().getConfig(); + long inactivityIntervalDays = config.getLong(ConfigPaths.INACTIVITY_INTERVAL); + long rejectedInactivityIntervalDays = (config.getLong(ConfigPaths.REJECTED_INACTIVITY_INTERVAL) != -1) ? config.getLong(ConfigPaths.REJECTED_INACTIVITY_INTERVAL) : inactivityIntervalDays; + if (inactivityIntervalDays == -2 && rejectedInactivityIntervalDays == -2) return; + + for (Plot plot : DataProvider.PLOT.getPlots(Status.unfinished)) { + LocalDate lastActivity = plot.getLastActivity(); + long interval = plot.isRejected() ? rejectedInactivityIntervalDays : inactivityIntervalDays; + if (interval == -2 || lastActivity == null || lastActivity.plusDays(interval).isAfter(LocalDate.now())) continue; + + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + if (!abandonPlot(plot)) { + PlotSystem.getPlugin().getComponentLogger().warn(text("An error occurred while abandoning plot #" + plot.getID() + " due to inactivity!")); + return; + } + PlotSystem.getPlugin().getComponentLogger().info(text("Abandoned plot #" + plot.getID() + " due to inactivity!")); + }); + } + } + + public static void submitPlot(@NotNull Plot plot) { + plot.setStatus(Status.unreviewed); + + if (plot.getWorld().isWorldLoaded()) { + for (Player player : plot.getWorld() instanceof OnePlotWorld ? plot.getWorld().getBukkitWorld().getPlayers() : ((CityPlotWorld) plot.getWorld()).getPlayersOnPlot(plot)) { + player.teleport(Utils.getSpawnLocation()); + } + } + + plot.getPermissions().removeBuilderPerms(plot.getPlotOwner().getUUID()).save(); + if (!plot.getPlotMembers().isEmpty()) { + for (Builder builder : plot.getPlotMembers()) { + plot.getPermissions().removeBuilderPerms(builder.getUUID()); + } + } + } + + public static void undoSubmit(@NotNull Plot plot) { + plot.setStatus(Status.unfinished); + + plot.getPermissions().addBuilderPerms(plot.getPlotOwner().getUUID()).save(); + if (!plot.getPlotMembers().isEmpty()) { + for (Builder builder : plot.getPlotMembers()) { + plot.getPermissions().addBuilderPerms(builder.getUUID()); + } + } + } } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java index a8dc2f34..bd85fb4e 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java @@ -4,6 +4,7 @@ import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.core.system.plot.generator.world.PlotWorldGenerator; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; @@ -144,7 +145,7 @@ private AbstractPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder build this.onComplete(exception[0] != null, false); if (exception[0] != null) { - PlotUtils.Actions.abandonPlot(plot); + PlotHandler.abandonPlot(plot); onException(exception[0]); } }); diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java index 4960432f..45f1d2ed 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java @@ -6,10 +6,8 @@ import com.alpsbte.plotsystem.core.system.CityProject; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; -import com.alpsbte.plotsystem.core.system.plot.TutorialPlot; import com.alpsbte.plotsystem.core.system.plot.generator.AbstractPlotGenerator; import com.alpsbte.plotsystem.core.system.plot.world.CityPlotWorld; -import com.alpsbte.plotsystem.core.system.plot.world.OnePlotWorld; import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; import com.alpsbte.plotsystem.core.system.review.PlotReview; import com.alpsbte.plotsystem.core.system.review.ReviewNotification; @@ -38,17 +36,13 @@ import com.sk89q.worldedit.math.transform.AffineTransform; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Polygonal2DRegion; -import com.sk89q.worldguard.WorldGuard; -import com.sk89q.worldguard.protection.managers.RegionManager; import com.sk89q.worldguard.protection.regions.ProtectedRegion; -import com.sk89q.worldguard.protection.regions.RegionContainer; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Particle; import org.bukkit.World; -import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitTask; import org.jetbrains.annotations.NotNull; @@ -62,8 +56,6 @@ import java.net.URISyntaxException; import java.nio.file.Paths; import java.text.DecimalFormat; -import java.time.LocalDate; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -137,9 +129,10 @@ public static AbstractPlot getCurrentPlot(@NotNull Builder builder, Status... st } public static boolean isPlayerOnPlot(@NotNull AbstractPlot plot, Player player) { - if (plot.getWorld().isWorldLoaded() && plot.getWorld().getBukkitWorld().getPlayers().contains(player)) { + PlotWorld world = plot.getWorld(); + if (world.isWorldLoaded() && world.getBukkitWorld().getPlayers().contains(player)) { Location playerLoc = player.getLocation(); - ProtectedRegion protectedRegion = plot.getWorld().getProtectedRegion(); + ProtectedRegion protectedRegion = world.getProtectedRegion(); return protectedRegion == null || protectedRegion.contains(Vector3.toBlockPoint(playerLoc.getX(), playerLoc.getY(), playerLoc.getZ())); } return false; @@ -278,39 +271,17 @@ public static boolean savePlotAsSchematic(@NotNull Plot plot) throws IOException }; // Return coordinates if they are in the schematic plot region - ProtectedRegion protectedPlotRegion = plot.getWorld().getProtectedRegion() != null - ? plot.getWorld().getProtectedRegion() - : plot.getWorld().getProtectedBuildRegion(); - if (protectedPlotRegion.contains(BlockVector3.at((int) plotCoords[0], plot.getWorld().getPlotHeightCentered(), (int) plotCoords[1]))) { + PlotWorld world = plot.getWorld(); + ProtectedRegion protectedPlotRegion = world.getProtectedRegion() != null + ? world.getProtectedRegion() + : world.getProtectedBuildRegion(); + if (protectedPlotRegion.contains(BlockVector3.at((int) plotCoords[0], world.getPlotHeightCentered(), (int) plotCoords[1]))) { return CompletableFuture.completedFuture(plotCoords); } return null; } - public static void checkPlotsForLastActivity() { - Bukkit.getScheduler().runTaskTimerAsynchronously(PlotSystem.getPlugin(), () -> { - List plots = DataProvider.PLOT.getPlots(Status.unfinished); - FileConfiguration config = PlotSystem.getPlugin().getConfig(); - long inactivityIntervalDays = config.getLong(ConfigPaths.INACTIVITY_INTERVAL); - long rejectedInactivityIntervalDays = (config.getLong(ConfigPaths.REJECTED_INACTIVITY_INTERVAL) != -1) ? config.getLong(ConfigPaths.REJECTED_INACTIVITY_INTERVAL) : inactivityIntervalDays; - if (inactivityIntervalDays == -2 && rejectedInactivityIntervalDays == -2) return; - for (Plot plot : plots) { - LocalDate lastActivity = plot.getLastActivity(); - long interval = plot.isRejected() ? rejectedInactivityIntervalDays : inactivityIntervalDays; - if (interval == -2 || lastActivity == null || lastActivity.plusDays(interval).isAfter(LocalDate.now())) continue; - - Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - if (Actions.abandonPlot(plot)) { - PlotSystem.getPlugin().getComponentLogger().info(text("Abandoned plot #" + plot.getID() + " due to inactivity!")); - } else { - PlotSystem.getPlugin().getComponentLogger().warn(text("An error occurred while abandoning plot #" + plot.getID() + " due to inactivity!")); - } - }); - } - }, 0L, 20 * 60 * 60L); // Check every hour - } - public static void informPlayerAboutUnfinishedPlots(@NotNull Player player, Builder builder) { try { List plots = Cache.getCachedInProgressPlots(builder); @@ -330,119 +301,6 @@ public static void startUnfinishedPlotReminderTimer(Player player) { 20L * 60 * interval)); } - public static final class Actions { - private Actions() {} - - public static void submitPlot(@NotNull Plot plot) { - plot.setStatus(Status.unreviewed); - - if (plot.getWorld().isWorldLoaded()) { - for (Player player : plot.getWorld() instanceof OnePlotWorld ? plot.getWorld().getBukkitWorld().getPlayers() : ((CityPlotWorld) plot.getWorld()).getPlayersOnPlot()) { - player.teleport(Utils.getSpawnLocation()); - } - } - - plot.getPermissions().removeBuilderPerms(plot.getPlotOwner().getUUID()).save(); - if (!plot.getPlotMembers().isEmpty()) { - for (Builder builder : plot.getPlotMembers()) { - plot.getPermissions().removeBuilderPerms(builder.getUUID()); - } - } - } - - public static void undoSubmit(@NotNull Plot plot) { - plot.setStatus(Status.unfinished); - - plot.getPermissions().addBuilderPerms(plot.getPlotOwner().getUUID()).save(); - if (!plot.getPlotMembers().isEmpty()) { - for (Builder builder : plot.getPlotMembers()) { - plot.getPermissions().addBuilderPerms(builder.getUUID()); - } - } - } - - public static boolean abandonPlot(@NotNull AbstractPlot plot) { - try { - if (plot.getWorld() instanceof OnePlotWorld) { - if (plot.getWorld().isWorldGenerated()) { - if (plot.getWorld().isWorldLoaded()) { - for (Player player : plot.getWorld().getBukkitWorld().getPlayers()) { - player.teleport(Utils.getSpawnLocation()); - } - } - if (!plot.getWorld().deleteWorld()) PlotSystem.getPlugin().getComponentLogger().warn(text("Could not delete plot world " + plot.getWorld().getWorldName() + "!")); - } - } else if (!(plot instanceof TutorialPlot)) { - RegionContainer regionContainer = WorldGuard.getInstance().getPlatform().getRegionContainer(); - - if (plot.getWorld().loadWorld()) { - CityPlotWorld world = plot.getWorld(); - List playersToTeleport = new ArrayList<>(world.getPlayersOnPlot()); - - RegionManager regionManager = regionContainer.get(BukkitAdapter.adapt(world.getBukkitWorld())); - if (regionManager != null) { - for (Builder builder : ((Plot) plot).getPlotMembers()) { - ((Plot) plot).removePlotMember(builder); - } - - if (regionManager.hasRegion(world.getRegionName())) regionManager.removeRegion(world.getRegionName()); - if (regionManager.hasRegion(world.getRegionName() + "-1")) regionManager.removeRegion(world.getRegionName() + "-1"); - - AbstractPlotGenerator.pasteSchematic(null, getOutlinesSchematicBytes(plot, plot.getInitialSchematicBytes(), world.getBukkitWorld()), world, true); - } else PlotSystem.getPlugin().getComponentLogger().warn(text("Region Manager is null!")); - - playersToTeleport.forEach(p -> p.teleport(Utils.getSpawnLocation())); - if (plot.getWorld().isWorldLoaded()) plot.getWorld().unloadWorld(false); - } - } - } catch (IOException | WorldEditException ex) { - PlotSystem.getPlugin().getComponentLogger().error(text("Failed to abandon plot with the ID " + plot.getID() + "!"), ex); - return false; - } - - CompletableFuture.runAsync(() -> { - if (plot.getPlotType() == PlotType.TUTORIAL) return; - Plot dPlot = (Plot) plot; - boolean successful; - successful = DataProvider.REVIEW.removeAllReviewsOfPlot(dPlot.getID()); - - for (Builder builder : dPlot.getPlotMembers()) { - if (!successful) break; - successful = dPlot.removePlotMember(builder); - } - - if (successful && plot.getPlotOwner() != null) { - Cache.clearCache(plot.getPlotOwner().getUUID()); - successful = plot.getPlotOwner().setSlot(plot.getPlotOwner().getSlot(dPlot), -1); - } - - if (successful) { - successful = dPlot.setPlotOwner(null) - && dPlot.setLastActivity(true) - && dPlot.setStatus(Status.unclaimed) - && dPlot.setPlotType(PlotType.LOCAL_INSPIRATION_MODE); - } - - successful = successful && DataProvider.PLOT.setCompletedSchematic(plot.getID(), null); - - if (!successful) PlotSystem.getPlugin().getComponentLogger().error(text("Failed to abandon plot with the ID " + plot.getID() + "!")); - }); - return true; - } - - public static boolean deletePlot(Plot plot) { - if (abandonPlot(plot)) { - CompletableFuture.runAsync(() -> { - if (DataProvider.PLOT.deletePlot(plot.getID())) return; - PlotSystem.getPlugin().getComponentLogger().warn(text("Failed to abandon plot with the ID " + plot.getID() + "!")); - }); - return true; - } - PlotSystem.getPlugin().getComponentLogger().warn(text("Failed to abandon plot with the ID " + plot.getID() + "!")); - return false; - } - } - public static final class Cache { private Cache() {} @@ -524,19 +382,19 @@ public static void showOutlines() { List points = plot.getBlockOutline(); - for (BlockVector2 point : points) - if (point.distanceSq(playerPos2D) < 50 * 50) { - if (!particleAPIEnabled) { - player.spawnParticle(Particle.FLAME, point.x(), player.getLocation().getY() + 1, point.z(), 1, 0.0, 0.0, 0.0, 0); - } else { - Location loc = new Location(player.getWorld(), point.x(), player.getLocation().getY() + 1, point.z()); - // create a particle packet - Object packet = particles.FLAME().packet(true, loc); - - // send this packet to player - particles.sendPacket(player, packet); - } + for (BlockVector2 point : points) { + if (point.distanceSq(playerPos2D) >= 50 * 50) continue; + if (!particleAPIEnabled) { + player.spawnParticle(Particle.FLAME, point.x(), player.getLocation().getY() + 1, point.z(), 1, 0.0, 0.0, 0.0, 0); + continue; } + Location loc = new Location(player.getWorld(), point.x(), player.getLocation().getY() + 1, point.z()); + // create a particle packet + Object packet = particles.FLAME().packet(true, loc); + + // send this packet to player + particles.sendPacket(player, packet); + } } } } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java index 1dcd22bf..4110f815 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java @@ -1,14 +1,21 @@ package com.alpsbte.plotsystem.core.system.plot.world; +import com.alpsbte.plotsystem.PlotSystem; +import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.generator.loader.AbstractPlotLoader; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.io.LangPaths; import com.alpsbte.plotsystem.utils.io.LangUtil; import com.google.common.annotations.Beta; +import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.regions.RegionContainer; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -17,6 +24,8 @@ import java.util.ArrayList; import java.util.List; +import static net.kyori.adventure.text.Component.text; + public class CityPlotWorld extends PlotWorld { public CityPlotWorld(@NotNull Plot plot) { super("C-" + plot.getCityProject().getID(), plot); @@ -30,29 +39,29 @@ public boolean teleportPlayer(@NotNull Player player) { player.setAllowFlight(true); player.setFlying(true); - if (getPlot() == null) return true; + if (plot == null) return true; - player.sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(player, LangPaths.Message.Info.TELEPORTING_PLOT, String.valueOf(getPlot().getID())))); + player.sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(player, LangPaths.Message.Info.TELEPORTING_PLOT, String.valueOf(plot.getID())))); Utils.updatePlayerInventorySlots(player); - PlotUtils.ChatFormatting.sendLinkMessages(getPlot(), player); + PlotUtils.ChatFormatting.sendLinkMessages(plot, player); - if (!getPlot().getPlotOwner().getUUID().equals(player.getUniqueId())) return true; - getPlot().setLastActivity(false); + if (!plot.getPlotOwner().getUUID().equals(player.getUniqueId())) return true; + plot.setLastActivity(false); return true; } @Override public String getRegionName() { - return super.getRegionName() + "-" + getPlot().getID(); + return super.getRegionName() + "-" + plot.getID(); } @Beta @Override public int getPlotHeight() throws IOException { - return getPlot().getVersion() >= 3 ? MIN_WORLD_HEIGHT + getWorldHeight() : getPlotHeightCentered(); + return plot.getVersion() >= 3 ? MIN_WORLD_HEIGHT + getWorldHeight() : getPlotHeightCentered(); } @Beta @@ -70,7 +79,7 @@ public int getPlotHeightCentered() throws IOException { @Beta public int getWorldHeight() throws IOException { Clipboard clipboard; - ByteArrayInputStream inputStream = new ByteArrayInputStream(getPlot().getInitialSchematicBytes()); + ByteArrayInputStream inputStream = new ByteArrayInputStream(plot.getInitialSchematicBytes()); try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(inputStream)) { clipboard = reader.read(); } @@ -99,14 +108,48 @@ public int getWorldHeight() throws IOException { * * @return a list of players located on the plot */ - public List getPlayersOnPlot() { + public List getPlayersOnPlot(AbstractPlot plot) { List players = new ArrayList<>(); - if (getPlot() != null && getPlot().getWorld().isWorldLoaded() && !getPlot().getWorld().getBukkitWorld().getPlayers().isEmpty()) { - for (Player player : getPlot().getWorld().getBukkitWorld().getPlayers()) { - if (PlotUtils.isPlayerOnPlot(getPlot(), player)) players.add(player); - } - return players; + if (plot == null || !plot.getWorld().isWorldLoaded() || plot.getWorld().getBukkitWorld().getPlayers().isEmpty()) return players; + + for (Player player : plot.getWorld().getBukkitWorld().getPlayers()) { + if (PlotUtils.isPlayerOnPlot(plot, player)) players.add(player); } return players; } + + @Override + public boolean onAbandon() { + RegionContainer regionContainer = WorldGuard.getInstance().getPlatform().getRegionContainer(); + if (!loadWorld()) { + PlotSystem.getPlugin().getComponentLogger().warn(text("Could not load world!")); + return false; + } + + RegionManager regionManager = regionContainer.get(BukkitAdapter.adapt(getBukkitWorld())); + if (regionManager == null) { + PlotSystem.getPlugin().getComponentLogger().warn(text("Region Manager is null!")); + return false; + } + + for (Builder builder : ((Plot) plot).getPlotMembers()) { + ((Plot) plot).removePlotMember(builder); + } + + if (regionManager.hasRegion(getRegionName())) regionManager.removeRegion(getRegionName()); + if (regionManager.hasRegion(getRegionName() + "-1")) regionManager.removeRegion(getRegionName() + "-1"); + + // paste initial schematic to reset plot + try { + AbstractPlotLoader.pasteSchematic(null, PlotUtils.getOutlinesSchematicBytes(plot, plot.getInitialSchematicBytes(), getBukkitWorld()), this, true); + } catch (IOException e) { + PlotSystem.getPlugin().getComponentLogger().error(text("Could not paste schematic!"), e); + } + + List playersToTeleport = new ArrayList<>(getPlayersOnPlot(plot)); + playersToTeleport.forEach(p -> p.teleport(Utils.getSpawnLocation())); + + if (isWorldLoaded()) unloadWorld(false); + return super.onAbandon(); + } } \ No newline at end of file diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/IWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/IWorld.java index bf5cf74a..12fdc078 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/IWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/IWorld.java @@ -1,5 +1,6 @@ package com.alpsbte.plotsystem.core.system.plot.world; +import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.generator.AbstractPlotGenerator; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldguard.protection.regions.ProtectedRegion; @@ -10,6 +11,9 @@ import java.io.IOException; +/** + * Represents the world of a given plot. There can be multiple instances of this interface per actual bukkit world. + */ public interface IWorld { /** * Generates the plot world with the required configurations and schematic @@ -123,4 +127,18 @@ public interface IWorld { * @return true if world is generated */ boolean isWorldGenerated(); + + /** + * Returns the plot associated with this world object + * + * @return the plot associated with this world object + */ + AbstractPlot getPlot(); + + /** + * Executes all World specific side effects needed to abandon a plot + * + * @return true if successfully executed + */ + boolean onAbandon(); } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java index 8ea21905..08163e63 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java @@ -8,6 +8,7 @@ import com.alpsbte.plotsystem.core.system.plot.generator.AbstractPlotGenerator; import com.alpsbte.plotsystem.core.system.plot.generator.DefaultPlotGenerator; import com.alpsbte.plotsystem.core.system.plot.generator.TutorialPlotGenerator; +import com.alpsbte.plotsystem.core.system.plot.generator.loader.DefaultPlotLoader; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.utils.Utils; @@ -22,9 +23,11 @@ public class OnePlotWorld extends PlotWorld { private final Builder plotOwner; + private final AbstractPlot plot; public OnePlotWorld(@NotNull AbstractPlot plot) { super((plot instanceof TutorialPlot ? "T-" : "P-") + plot.getID(), plot); + this.plot = plot; this.plotOwner = plot.getPlotOwner(); } @@ -33,36 +36,25 @@ public boolean generateWorld(@NotNull Class if (isWorldGenerated()) return false; if (generator.isAssignableFrom(DefaultPlotGenerator.class)) { - new DefaultPlotGenerator(getPlot(), plotOwner); + new DefaultPlotGenerator(plot, plotOwner); } else if (generator.isAssignableFrom(TutorialPlotGenerator.class)) { - new TutorialPlotGenerator(getPlot(), plotOwner); + new TutorialPlotGenerator(plot, plotOwner); } else return false; return true; } @Override public boolean loadWorld() { - if (getPlot() == null || isWorldGenerated()) return super.loadWorld(); + if (plot == null || isWorldGenerated()) return super.loadWorld(); // Generate plot if it doesn't exist - if (getPlot().getPlotType() == PlotType.TUTORIAL || ((Plot) getPlot()).getCompletedSchematic() == null) + if (plot.getPlotType() == PlotType.TUTORIAL || ((Plot) plot).getCompletedSchematic() == null) generateWorld(TutorialPlotGenerator.class); - new DefaultPlotGenerator(getPlot(), plotOwner, getPlot().getPlotType()) { - @Override - protected boolean init() { - return true; - } - - @Override - protected void onComplete(boolean failed, boolean unloadWorld) { - getPlot().getPermissions().clearAllPerms(); - super.onComplete(true, false); - } - }; + new DefaultPlotLoader(plot, plotOwner, plot.getPlotType(), this); if (!isWorldGenerated() || !isWorldLoaded()) { - PlotSystem.getPlugin().getComponentLogger().warn(text("Could not regenerate world " + getWorldName() + " for plot " + getPlot().getID() + "!")); + PlotSystem.getPlugin().getComponentLogger().warn(text("Could not regenerate world " + getWorldName() + " for plot " + plot.getID() + "!")); return false; } return true; @@ -71,13 +63,10 @@ protected void onComplete(boolean failed, boolean unloadWorld) { @Override public boolean unloadWorld(boolean movePlayers) { boolean isTutorialPlot; - isTutorialPlot = getPlot().getPlotType() == PlotType.TUTORIAL; + isTutorialPlot = plot.getPlotType() == PlotType.TUTORIAL; - if (getPlot() != null) { - if (isTutorialPlot) return deleteWorld(); - else return super.unloadWorld(movePlayers); - } - return false; + if (isTutorialPlot) return deleteWorld(); + else return super.unloadWorld(movePlayers); } @Override @@ -88,15 +77,15 @@ public boolean teleportPlayer(@NotNull Player player) { player.setAllowFlight(true); player.setFlying(true); - if (getPlot() == null) return true; - if (getPlot().getPlotType() != PlotType.TUTORIAL) { - player.sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(player, LangPaths.Message.Info.TELEPORTING_PLOT, String.valueOf(getPlot().getID())))); - PlotUtils.ChatFormatting.sendLinkMessages(getPlot(), player); + if (plot == null) return true; + if (plot.getPlotType() != PlotType.TUTORIAL) { + player.sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(player, LangPaths.Message.Info.TELEPORTING_PLOT, String.valueOf(plot.getID())))); + PlotUtils.ChatFormatting.sendLinkMessages(plot, player); } Utils.updatePlayerInventorySlots(player); - if (!getPlot().getPlotOwner().getUUID().equals(player.getUniqueId())) return true; - getPlot().setLastActivity(false); + if (!plot.getPlotOwner().getUUID().equals(player.getUniqueId())) return true; + plot.setLastActivity(false); return true; } @@ -110,4 +99,17 @@ public int getPlotHeight() { public int getPlotHeightCentered() throws IOException { return MIN_WORLD_HEIGHT + super.getPlotHeightCentered(); } + + @Override + public boolean onAbandon() { + if (!isWorldGenerated()) return super.onAbandon(); + if (isWorldLoaded()) { + for (Player player : getBukkitWorld().getPlayers()) player.teleport(Utils.getSpawnLocation()); + } + if (!deleteWorld()) { + PlotSystem.getPlugin().getComponentLogger().warn(text("Could not delete plot world " + getWorldName() + "!")); + return false; + } + return super.onAbandon(); + } } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java index 162a4d59..ec27b98e 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java @@ -68,7 +68,7 @@ public class PlotWorld implements IWorld { private final MultiverseCoreApi mvCore = DependencyManager.getMultiverseCore(); private final String worldName; - private final AbstractPlot plot; + protected final AbstractPlot plot; public PlotWorld(@NotNull String worldName, @Nullable AbstractPlot plot) { this.worldName = worldName; @@ -208,6 +208,16 @@ public boolean isWorldGenerated() { return mvCore.getWorldManager().getWorld(worldName).isDefined(); } + @Override + public AbstractPlot getPlot() { + return plot; + } + + @Override + public boolean onAbandon() { + return true; + } + private @Nullable ProtectedRegion getRegion(String regionName) { if (!loadWorld()) return null; @@ -219,11 +229,6 @@ public boolean isWorldGenerated() { return null; } - public AbstractPlot getPlot() { - return plot; - } - - /** * @param worldName - the name of the world * @return - true if the world is a plot world @@ -248,6 +253,7 @@ public static boolean isCityPlotWorld(@NotNull String worldName) { * @return - plot world */ public static @Nullable PlotWorld getPlotWorldByName(String worldName) { + // TODO: rework if (!isOnePlotWorld(worldName) && !isCityPlotWorld(worldName)) return null; Integer id = AlpsUtils.tryParseInt(worldName.substring(2)); @@ -258,6 +264,7 @@ public static boolean isCityPlotWorld(@NotNull String worldName) { } public static PlotWorld getByType(PlotType type, Plot plot) { + // TODO: rework boolean disableCIM = PlotSystem.getPlugin().getConfig().getBoolean(ConfigPaths.DISABLE_CITY_INSPIRATION_MODE); return disableCIM || type.hasOnePlotPerWorld() ? new OnePlotWorld(plot) : new CityPlotWorld(plot); } From 73db5729480a89571b5d3506ad090d2bb67fc651 Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Sat, 1 Nov 2025 17:59:48 +0100 Subject: [PATCH 17/60] fix incorrectly rebased imports --- .../core/system/plot/generator/world/PlotWorldGenerator.java | 1 + .../com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java index 4812153e..40220f2b 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java @@ -41,6 +41,7 @@ import net.querz.nbt.tag.CompoundTag; import org.apache.commons.io.FileUtils; import org.bukkit.*; +import org.bukkit.entity.SpawnCategory; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.core.world.options.ImportWorldOptions; diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java index ec27b98e..3b94a713 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java @@ -35,7 +35,6 @@ import com.alpsbte.plotsystem.utils.DependencyManager; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.io.ConfigPaths; -import com.onarandombox.MultiverseCore.MultiverseCore; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; From a53c9f76fdd2bed838522cc037246893b43b980d Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Tue, 25 Nov 2025 13:42:33 +0100 Subject: [PATCH 18/60] Implement TutorialPlotLoader.java. move savePlotAsSchematic to PlotHandler. create methods for both assigning and generating in PlotHandler. remove all old PlotGenerator files and replace every usage with the new PlotLoaders --- .../commands/plot/CMD_Plot_Teleport.java | 4 +- .../plotsystem/core/EventListener.java | 4 +- .../core/menus/companion/CityProjectMenu.java | 16 +- .../menus/review/ReviewPlotTogglesMenu.java | 5 +- .../core/system/plot/PlotHandler.java | 98 ++++++ .../plot/generator/AbstractPlotGenerator.java | 333 ------------------ .../plot/generator/DefaultPlotGenerator.java | 148 -------- .../generator/loader/AbstractPlotLoader.java | 3 +- .../generator/loader/DefaultPlotLoader.java | 39 +- .../TutorialPlotLoader.java} | 35 +- .../core/system/plot/utils/PlotUtils.java | 58 --- .../core/system/plot/world/IWorld.java | 6 +- .../core/system/plot/world/OnePlotWorld.java | 17 +- .../core/system/plot/world/PlotWorld.java | 6 +- .../system/tutorial/AbstractPlotTutorial.java | 15 +- .../core/system/tutorial/PlotTutorial.java | 3 +- .../stage/tasks/PlotSchematicPasteTask.java | 4 +- 17 files changed, 174 insertions(+), 620 deletions(-) delete mode 100644 src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java delete mode 100644 src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java rename src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/{TutorialPlotGenerator.java => loader/TutorialPlotLoader.java} (70%) diff --git a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Teleport.java b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Teleport.java index b4c9033b..741f751c 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Teleport.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Teleport.java @@ -9,7 +9,7 @@ import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; -import com.alpsbte.plotsystem.core.system.plot.generator.DefaultPlotGenerator; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.Status; import com.alpsbte.plotsystem.utils.io.LangPaths; @@ -63,7 +63,7 @@ public void onCommand(CommandSender sender, String[] args) { return; } - Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> new DefaultPlotGenerator(plot, builder)); + PlotHandler.assignAndGeneratePlot(builder, plot); return; } diff --git a/src/main/java/com/alpsbte/plotsystem/core/EventListener.java b/src/main/java/com/alpsbte/plotsystem/core/EventListener.java index 7110f866..cd7fa485 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/EventListener.java +++ b/src/main/java/com/alpsbte/plotsystem/core/EventListener.java @@ -7,8 +7,8 @@ import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.CityProject; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.core.system.plot.TutorialPlot; -import com.alpsbte.plotsystem.core.system.plot.generator.DefaultPlotGenerator; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; import com.alpsbte.plotsystem.core.system.review.ReviewNotification; @@ -123,7 +123,7 @@ public void onPlayerInteractAtEntity(@NotNull PlayerInteractAtEntityEvent event) public void onPlayerQuitEvent(@NotNull PlayerQuitEvent event) { final World w = event.getPlayer().getWorld(); - DefaultPlotGenerator.playerPlotGenerationHistory.remove(event.getPlayer().getUniqueId()); + PlotHandler.removePlayerFromGenerationHistory(event.getPlayer().getUniqueId()); ChatInput.awaitChatInput.remove(event.getPlayer().getUniqueId()); PlotUtils.Cache.clearCache(event.getPlayer().getUniqueId()); diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java index 355a649c..bb612808 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java +++ b/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java @@ -10,7 +10,6 @@ import com.alpsbte.plotsystem.core.system.Country; import com.alpsbte.plotsystem.core.system.plot.Plot; import com.alpsbte.plotsystem.core.system.plot.PlotHandler; -import com.alpsbte.plotsystem.core.system.plot.generator.DefaultPlotGenerator; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.PlotDifficulty; @@ -126,13 +125,7 @@ public void handleCityProjectClick(Player player, CityProject city) { } Plot plot = unclaimedPlots.get(Utils.getRandom().nextInt(unclaimedPlots.size())); - - PlotType type = builder.getPlotType(); - if (type.equals(PlotType.CITY_INSPIRATION_MODE) && ConfigUtil.getInstance().configs[0].getBoolean(ConfigPaths.DISABLE_CITY_INSPIRATION_MODE)) - type = PlotType.LOCAL_INSPIRATION_MODE; - - boolean successful = PlotHandler.assignPlot(builder, plot); - if (successful) PlotHandler.generatePlot(builder, plot, type); + PlotHandler.assignAndGeneratePlot(builder, plot); } public static boolean generateRandomPlot(Player player, @NotNull List items, PlotDifficulty selectedPlotDifficulty) { @@ -147,14 +140,13 @@ public static boolean generateRandomPlot(Player player, @NotNull List { try { - if (!PlotUtils.savePlotAsSchematic(plot)) { + if (!PlotHandler.savePlotAsSchematic(plot)) { getMenuPlayer().sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Message.Error.ERROR_OCCURRED))); PlotSystem.getPlugin().getComponentLogger().warn(text("Could not save finished plot schematic (ID: " + plot.getID() + ")!")); } - } catch (IOException | WorldEditException ex) { + } catch (IOException | WorldEditException | ExecutionException | InterruptedException ex) { PlotSystem.getPlugin().getComponentLogger().error(text("Could not save finished plot schematic (ID: " + plot.getID() + ")!"), ex); } }); diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java index 8621fead..544fbca3 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java @@ -27,6 +27,8 @@ import com.alpsbte.plotsystem.PlotSystem; import com.alpsbte.plotsystem.core.database.DataProvider; import com.alpsbte.plotsystem.core.system.Builder; +import com.alpsbte.plotsystem.core.system.CityProject; +import com.alpsbte.plotsystem.core.system.plot.generator.loader.AbstractPlotLoader; import com.alpsbte.plotsystem.core.system.plot.generator.loader.DefaultPlotLoader; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; @@ -34,21 +36,41 @@ import com.alpsbte.plotsystem.core.system.plot.world.OnePlotWorld; import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; import com.alpsbte.plotsystem.utils.Utils; +import com.alpsbte.plotsystem.utils.enums.PlotDifficulty; import com.alpsbte.plotsystem.utils.enums.Slot; import com.alpsbte.plotsystem.utils.enums.Status; import com.alpsbte.plotsystem.utils.io.ConfigPaths; +import com.alpsbte.plotsystem.utils.io.ConfigUtil; import com.alpsbte.plotsystem.utils.io.LangPaths; import com.alpsbte.plotsystem.utils.io.LangUtil; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.bukkit.BukkitWorld; +import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter; +import com.sk89q.worldedit.function.operation.ForwardExtentCopy; +import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.math.transform.AffineTransform; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Polygonal2DRegion; import org.bukkit.Bukkit; import org.bukkit.SoundCategory; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import static net.kyori.adventure.text.Component.text; @@ -81,6 +103,23 @@ public static boolean assignPlot(Builder builder, Plot plot) { return plot.setPlotOwner(builder); } + public static boolean assignAndGeneratePlot(Builder builder, Plot plot) { + PlotType type = builder.getPlotType(); + if (type.equals(PlotType.CITY_INSPIRATION_MODE) && ConfigUtil.getInstance().configs[0].getBoolean(ConfigPaths.DISABLE_CITY_INSPIRATION_MODE)) + type = PlotType.LOCAL_INSPIRATION_MODE; + + boolean successful = assignPlot(builder, plot); + if (successful) generatePlot(builder, plot, type); + + return successful; + } + + public static boolean assignAndGenerateRandomPlot(Builder builder, CityProject city, PlotDifficulty difficulty) { + Plot randomPlot = DataProvider.PLOT.getPlots(city, difficulty, Status.unclaimed) + .get(Utils.getRandom().nextInt(DataProvider.PLOT.getPlots(city, difficulty, Status.unclaimed).size())); + return assignAndGeneratePlot(builder, randomPlot); + } + public static void generatePlot(Builder builder, Plot plot, PlotType type) { Player player = builder.getPlayer(); @@ -198,4 +237,63 @@ public static void undoSubmit(@NotNull Plot plot) { } } } + + public static void removePlayerFromGenerationHistory(UUID playerUuid) { + playerPlotGenerationHistory.remove(playerUuid); + } + + public static boolean savePlotAsSchematic(@NotNull Plot plot) throws IOException, WorldEditException, ExecutionException, InterruptedException { + if (plot.getVersion() < 4) { + PlotSystem.getPlugin().getComponentLogger().error(text("Saving schematics of legacy plots is no longer allowed!")); + return false; + } + + Clipboard clipboard; + ByteArrayInputStream inputStream = new ByteArrayInputStream(plot.getInitialSchematicBytes()); + try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(inputStream)) { + clipboard = reader.read(); + } + if (clipboard == null) return false; + + CuboidRegion cuboidRegion = PlotUtils.getPlotAsRegion(plot); + if (cuboidRegion == null) return false; + + BlockVector3 plotCenter = plot.getCenter(); + + // Get plot outline + List plotOutlines = plot.getOutline(); + + // Load finished plot region as cuboid region + if (!plot.getWorld().loadWorld()) return false; + com.sk89q.worldedit.world.World world = new BukkitWorld(plot.getWorld().getBukkitWorld()); + Polygonal2DRegion region = new Polygonal2DRegion(world, plotOutlines, cuboidRegion.getMinimumPoint().y(), cuboidRegion.getMaximumPoint().y()); + + // Copy and write finished plot clipboard to schematic + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (Clipboard cb = new BlockArrayClipboard(region)) { + cb.setOrigin(BlockVector3.at(plotCenter.x(), cuboidRegion.getMinimumY(), (double) plotCenter.z())); + + ForwardExtentCopy forwardExtentCopy = new ForwardExtentCopy(Objects.requireNonNull(region.getWorld()), region, cb, region.getMinimumPoint()); + Operations.complete(forwardExtentCopy); + + try (ClipboardWriter writer = AbstractPlot.CLIPBOARD_FORMAT.getWriter(outputStream)) { + double initialY = clipboard.getRegion().getMinimumY(); + double offset = initialY - cuboidRegion.getMinimumY(); + writer.write(cb.transform(new AffineTransform().translate(Vector3.at(0, offset, 0)))); + } + } + + // Set Completed Schematic + boolean successful = DataProvider.PLOT.setCompletedSchematic(plot.getID(), outputStream.toByteArray()); + if (!successful) return false; + + // If plot was created in a void world, copy the result to the city world + if (plot.getPlotType() != PlotType.CITY_INSPIRATION_MODE) { + Utils.runSync(() -> { + AbstractPlotLoader.pasteSchematic(null, outputStream.toByteArray(), new CityPlotWorld(plot), false); + return null; + }).get(); + } + return true; + } } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java deleted file mode 100644 index bd85fb4e..00000000 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java +++ /dev/null @@ -1,333 +0,0 @@ -package com.alpsbte.plotsystem.core.system.plot.generator; - -import com.alpsbte.plotsystem.PlotSystem; -import com.alpsbte.plotsystem.core.system.Builder; -import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; -import com.alpsbte.plotsystem.core.system.plot.Plot; -import com.alpsbte.plotsystem.core.system.plot.PlotHandler; -import com.alpsbte.plotsystem.core.system.plot.generator.world.PlotWorldGenerator; -import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; -import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; -import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; -import com.alpsbte.plotsystem.utils.DependencyManager; -import com.alpsbte.plotsystem.utils.Utils; -import com.alpsbte.plotsystem.utils.io.ConfigPaths; -import com.alpsbte.plotsystem.utils.io.ConfigUtil; -import com.alpsbte.plotsystem.utils.io.LangPaths; -import com.alpsbte.plotsystem.utils.io.LangUtil; -import com.sk89q.worldedit.EditSession; -import com.sk89q.worldedit.WorldEdit; -import com.sk89q.worldedit.bukkit.BukkitAdapter; -import com.sk89q.worldedit.bukkit.BukkitWorld; -import com.sk89q.worldedit.extent.clipboard.Clipboard; -import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; -import com.sk89q.worldedit.function.mask.BlockTypeMask; -import com.sk89q.worldedit.function.mask.Mask; -import com.sk89q.worldedit.function.mask.RegionMask; -import com.sk89q.worldedit.function.operation.Operation; -import com.sk89q.worldedit.function.operation.Operations; -import com.sk89q.worldedit.math.BlockVector3; -import com.sk89q.worldedit.math.Vector2; -import com.sk89q.worldedit.regions.CylinderRegion; -import com.sk89q.worldedit.regions.Polygonal2DRegion; -import com.sk89q.worldedit.regions.Region; -import com.sk89q.worldedit.session.ClipboardHolder; -import com.sk89q.worldedit.world.World; -import com.sk89q.worldedit.world.block.BlockTypes; -import com.sk89q.worldguard.WorldGuard; -import com.sk89q.worldguard.domains.DefaultDomain; -import com.sk89q.worldguard.protection.flags.Flags; -import com.sk89q.worldguard.protection.flags.RegionGroup; -import com.sk89q.worldguard.protection.flags.StateFlag; -import com.sk89q.worldguard.protection.managers.RegionManager; -import com.sk89q.worldguard.protection.managers.storage.StorageException; -import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; -import com.sk89q.worldguard.protection.regions.RegionContainer; -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.FileConfiguration; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; - -import static net.kyori.adventure.text.Component.text; - -public abstract class AbstractPlotGenerator { - protected final AbstractPlot plot; - private final Builder builder; - protected final PlotWorld world; - protected final double plotVersion; - protected final PlotType plotType; - /** - * Generates a new plot in the plot world - * - * @param plot - plot which should be generated - * @param builder - builder of the plot - */ - protected AbstractPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder builder) { - this(plot, builder, builder.getPlotType()); - } - - /** - * Generates a new plot in the given world - * - * @param plot - plot which should be generated - * @param builder - builder of the plot - * @param plotType - type of the plot - */ - protected AbstractPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder builder, @NotNull PlotType plotType) { - this(plot, builder, plotType, PlotWorld.getByType(plotType, (Plot) plot)); - } - - /** - * Generates a new plot in the given world - * - * @param plot - plot which should be generated - * @param builder - builder of the plot - * @param world - world of the plot - */ - private AbstractPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder builder, @NotNull PlotType plotType, @NotNull PlotWorld world) { - this.plot = plot; - this.builder = builder; - this.world = world; - this.plotVersion = plot.getVersion(); - this.plotType = plotType; - - PlotSystem.getPlugin().getComponentLogger().info("plotType: " + plotType.getId()); - - long initStartTime = System.nanoTime(); - if (!init()) return; - PlotSystem.getPlugin().getComponentLogger().info("(APG) Init time: {}ms", (System.nanoTime() - initStartTime) / 1_000_000); - - boolean generateWorld = plotType.hasOnePlotPerWorld() || !world.isWorldGenerated(); - boolean isWorldLoaded = world.isWorldLoaded(); - - CompletableFuture.runAsync(() -> { - final Exception[] exception = new Exception[]{null}; - try { - // Generate and/or load world - if (generateWorld) { - long worldGenerationStartTime = System.nanoTime(); - new PlotWorldGenerator(world.getWorldName()); - PlotSystem.getPlugin().getComponentLogger().info("(APG) Total PlotWorldGenerator time: {}ms", (System.nanoTime() - worldGenerationStartTime) / 1_000_000); - } else if (!isWorldLoaded) { - final boolean[] successful = new boolean[1]; - Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> successful[0] = world.loadWorld()); - if (!successful[0]) throw new Exception("Could not load world"); - } - - long schemFetchTime = System.nanoTime(); - // Fetch initial plot schematic - byte[] initialSchematic = plot.getInitialSchematicBytes(); - PlotSystem.getPlugin().getComponentLogger().info("(APG) schem fetching time: {}ms", (System.nanoTime() - schemFetchTime) / 1_000_000); - - long generateOutlinesTime = System.nanoTime(); - Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - try { - createPlotProtection(); - generateOutlines(initialSchematic); - } catch (StorageException | IOException e) { - exception[0] = e; - } - }); - PlotSystem.getPlugin().getComponentLogger().info("(APG) generate outlines time (main thread): {}ms", (System.nanoTime() - generateOutlinesTime) / 1_000_000); - - } catch (Exception ex) { - exception[0] = ex; - } - Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - this.onComplete(exception[0] != null, false); - - if (exception[0] != null) { - PlotHandler.abandonPlot(plot); - onException(exception[0]); - } - }); - PlotSystem.getPlugin().getComponentLogger().info("(APG) total time: {}ms", (System.nanoTime() - initStartTime) / 1_000_000); - }); - } - - - /** - * Executed before plot generation - * - * @return true if initialization was successful - */ - protected abstract boolean init(); - - - /** - * Generates plot schematic and outlines - */ - protected void generateOutlines(byte[] initialSchematic) throws IOException { - if (plotVersion >= 3 && plotType.hasEnvironment()) { - pasteSchematic(null, initialSchematic, world, false); - } else { - Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(world.getBukkitWorld()), BlockTypes.AIR); - pasteSchematic(airMask, PlotUtils.getOutlinesSchematicBytes(plot, initialSchematic, world.getBukkitWorld()), world, true); - } - } - - - /** - * Creates plot protection - */ - protected void createPlotProtection() throws StorageException { - RegionContainer regionContainer = WorldGuard.getInstance().getPlatform().getRegionContainer(); - RegionManager regionManager = regionContainer.get(BukkitAdapter.adapt(world.getBukkitWorld())); - - if (regionManager != null) { - // Create build region for plot from the outline of the plot - ProtectedRegion protectedBuildRegion = new ProtectedPolygonalRegion(world.getRegionName(), plot.getOutline(), PlotWorld.MIN_WORLD_HEIGHT, PlotWorld.MAX_WORLD_HEIGHT); - protectedBuildRegion.setPriority(100); - - // Create protected plot region for plot - World weWorld = new BukkitWorld(world.getBukkitWorld()); - CylinderRegion cylinderRegion = new CylinderRegion(weWorld, plot.getCenter(), Vector2.at(PlotWorld.PLOT_SIZE, PlotWorld.PLOT_SIZE), PlotWorld.MIN_WORLD_HEIGHT, PlotWorld.MAX_WORLD_HEIGHT); - ProtectedRegion protectedRegion = new ProtectedPolygonalRegion(world.getRegionName() + "-1", cylinderRegion.polygonize(-1), PlotWorld.MIN_WORLD_HEIGHT, PlotWorld.MAX_WORLD_HEIGHT); - protectedRegion.setPriority(50); - - // Add plot owner - DefaultDomain owner = protectedBuildRegion.getOwners(); - owner.addPlayer(builder.getUUID()); - protectedBuildRegion.setOwners(owner); - - // Set protected build region permissions - setBuildRegionPermissions(protectedBuildRegion); - - // Set protected region permissions - setRegionPermissions(protectedRegion); - - // Add regions and save changes - if (regionManager.hasRegion(world.getRegionName())) regionManager.removeRegion(world.getRegionName()); - if (regionManager.hasRegion(world.getRegionName() + "-1")) regionManager.removeRegion(world.getRegionName() + "-1"); - regionManager.addRegion(protectedBuildRegion); - regionManager.addRegion(protectedRegion); - regionManager.saveChanges(); - } else PlotSystem.getPlugin().getComponentLogger().warn(text("Region Manager is null!")); - } - - /** - * Sets the permissions for the plot build region only - * - * @param region build region - */ - protected void setBuildRegionPermissions(@NotNull ProtectedRegion region) { - region.setFlag(Flags.BUILD, StateFlag.State.ALLOW); - region.setFlag(Flags.BUILD.getRegionGroupFlag(), RegionGroup.OWNERS); - if (DependencyManager.isWorldGuardExtraFlagsEnabled()) - region.setFlag(new StateFlag("worldedit", true, RegionGroup.OWNERS), StateFlag.State.ALLOW); - } - - /** - * Sets the permissions for the whole plot region - * - * @param region plot region - */ - protected void setRegionPermissions(@NotNull ProtectedRegion region) { - region.setFlag(Flags.ENTRY, StateFlag.State.ALLOW); - region.setFlag(Flags.ENTRY.getRegionGroupFlag(), RegionGroup.ALL); - - FileConfiguration config = ConfigUtil.getInstance().configs[1]; - region.setFlag(Flags.BLOCKED_CMDS, new HashSet<>(getBlockedCommands(config))); - region.setFlag(Flags.BLOCKED_CMDS.getRegionGroupFlag(), RegionGroup.ALL); - } - - /** - * Reads the blocked commands for the plot region from the config - * - * @param config commands.yml config - * @return list of blocked commands - */ - protected List getBlockedCommands(@NotNull FileConfiguration config) { - List blockedCommands = config.getStringList(ConfigPaths.BLOCKED_COMMANDS_BUILDERS); - blockedCommands.removeIf(c -> c.equals("/cmd1")); - return blockedCommands; - } - - /** - * Gets invoked when generation is completed - * - * @param failed - true if generation has failed - * @param unloadWorld - try to unload world after generation - */ - protected void onComplete(boolean failed, boolean unloadWorld) { - // Unload plot world if it is not needed anymore - if (unloadWorld) world.unloadWorld(false); - } - - - /** - * Gets invoked when an exception has occurred - * - * @param ex - caused exception - */ - protected void onException(Throwable ex) { - PlotSystem.getPlugin().getComponentLogger().error(text("An error occurred while generating plot!"), ex); - builder.getPlayer().sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(builder.getPlayer(), LangPaths.Message.Error.ERROR_OCCURRED))); - builder.getPlayer().playSound(builder.getPlayer().getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); - } - - - /** - * @return - plot object - */ - public AbstractPlot getPlot() { - return plot; - } - - - /** - * @return - builder object - */ - public Builder getBuilder() { - return builder; - } - - - /** - * Pastes the schematic to the plot center in the given world - * - * @param pasteMask - sets a mask for the paste operation, can be null - * @param schematicFile - plot/environment schematic file - * @param world - world to paste in - * @param clearArea - clears the plot area with air before pasting - */ - public static void pasteSchematic(@Nullable Mask pasteMask, byte[] schematicFile, @NotNull PlotWorld world, boolean clearArea) throws IOException { - // load world - if (!world.loadWorld()) return; - World weWorld = new BukkitWorld(world.getBukkitWorld()); - - // set outline region with air - if (clearArea) { - try (EditSession editSession = WorldEdit.getInstance().newEditSession(BukkitAdapter.adapt(world.getBukkitWorld()))) { - Polygonal2DRegion polyRegion = new Polygonal2DRegion(weWorld, world.getPlot().getOutline(), 0, PlotWorld.MAX_WORLD_HEIGHT); - editSession.setMask(new RegionMask(polyRegion)); - editSession.setBlocks((Region) polyRegion, Objects.requireNonNull(BlockTypes.AIR).getDefaultState()); - } - } - - // load schematic - Clipboard clipboard; - ByteArrayInputStream inputStream = new ByteArrayInputStream(schematicFile); - try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(inputStream)) { - clipboard = reader.read(); - } - - // paste schematic - try (EditSession editSession = WorldEdit.getInstance().newEditSession(BukkitAdapter.adapt(world.getBukkitWorld()))) { - if (pasteMask != null) editSession.setMask(pasteMask); - Operation clipboardHolder = new ClipboardHolder(clipboard) - .createPaste(editSession) - .to(BlockVector3.at(world.getPlot().getCenter().x(), world.getPlotHeight(), world.getPlot().getCenter().z())) - .build(); - Operations.complete(clipboardHolder); - } - } -} diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java deleted file mode 100644 index 997682a1..00000000 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright © 2025, Alps BTE - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package com.alpsbte.plotsystem.core.system.plot.generator; - -import com.alpsbte.plotsystem.PlotSystem; -import com.alpsbte.plotsystem.core.database.DataProvider; -import com.alpsbte.plotsystem.core.system.Builder; -import com.alpsbte.plotsystem.core.system.CityProject; -import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; -import com.alpsbte.plotsystem.core.system.plot.Plot; -import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; -import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; -import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; -import com.alpsbte.plotsystem.utils.Utils; -import com.alpsbte.plotsystem.utils.enums.PlotDifficulty; -import com.alpsbte.plotsystem.utils.enums.Status; -import com.alpsbte.plotsystem.utils.io.LangPaths; -import com.alpsbte.plotsystem.utils.io.LangUtil; -import com.sk89q.worldedit.WorldEditException; -import com.sk89q.worldedit.bukkit.BukkitAdapter; -import com.sk89q.worldedit.function.mask.BlockTypeMask; -import com.sk89q.worldedit.function.mask.Mask; -import com.sk89q.worldedit.world.block.BlockTypes; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import static net.kyori.adventure.text.Component.text; - -public class DefaultPlotGenerator extends AbstractPlotGenerator { - public static final Map playerPlotGenerationHistory = new HashMap<>(); - - public DefaultPlotGenerator(CityProject city, PlotDifficulty plotDifficulty, Builder builder) { - this(DataProvider.PLOT.getPlots(city, plotDifficulty, Status.unclaimed).get(Utils.getRandom().nextInt(DataProvider.PLOT.getPlots(city, plotDifficulty, Status.unclaimed).size())), builder); - } - - public DefaultPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder builder) { - super(plot, builder); - } - - public DefaultPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder builder, PlotType plotType) { - super(plot, builder, plotType); - } - - @Override - protected boolean init() { - if (getBuilder().getFreeSlot() == null) { - getBuilder().getPlayer().sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(getBuilder().getPlayer(), LangPaths.Message.Error.ALL_SLOTS_OCCUPIED))); - getBuilder().getPlayer().playSound(getBuilder().getPlayer().getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); - return false; - } - - if (DefaultPlotGenerator.playerPlotGenerationHistory.containsKey(getBuilder().getUUID())) { - if (!DefaultPlotGenerator.playerPlotGenerationHistory.get(getBuilder().getUUID()).isBefore(LocalDateTime.now().minusSeconds(10))) { - getBuilder().getPlayer().sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(getBuilder().getPlayer(), LangPaths.Message.Error.PLEASE_WAIT))); - getBuilder().getPlayer().playSound(getBuilder().getPlayer().getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); - return false; - } - DefaultPlotGenerator.playerPlotGenerationHistory.remove(getBuilder().getUUID()); - } - - DefaultPlotGenerator.playerPlotGenerationHistory.put(getBuilder().getUUID(), LocalDateTime.now()); - getBuilder().getPlayer().sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(getBuilder().getPlayer(), LangPaths.Message.Info.CREATING_PLOT))); - getBuilder().getPlayer().playSound(getBuilder().getPlayer().getLocation(), Utils.SoundUtils.CREATE_PLOT_SOUND, 1, 1); - return true; - } - - @Override - protected void generateOutlines(byte[] schematic) throws IOException, WorldEditException { - long startTime = System.nanoTime(); - if (!(plot instanceof Plot p)) super.generateOutlines(schematic); - else { - byte[] completedSchematic = p.getCompletedSchematic(); - if (completedSchematic != null) { - PlotSystem.getPlugin().getComponentLogger().info("Found completed schematic, pasting only that."); - Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(world.getBukkitWorld()), BlockTypes.AIR); - pasteSchematic(airMask, completedSchematic, world, false); - } else super.generateOutlines(schematic); - } - - // If the player is playing in his own world, then additionally generate the plot in the city world - if (PlotWorld.isOnePlotWorld(world.getWorldName()) && plotVersion >= 3 && plot.getStatus() != Status.completed) { - // Generate city plot world if it doesn't exist - new AbstractPlotGenerator(plot, getBuilder(), PlotType.CITY_INSPIRATION_MODE) { - @Override - protected boolean init() { - return true; - } - - @Override - protected void createPlotProtection() {} - - @Override - protected void onComplete(boolean failed, boolean unloadWorld) { - super.onComplete(true, true); - } - - @Override - protected void onException(Throwable ex) { - PlotSystem.getPlugin().getComponentLogger().warn(text("Could not generate plot in city world " + world.getWorldName() + "!"), ex); - } - }; - } - - PlotSystem.getPlugin().getComponentLogger().info("(DPG) Generate Outlines time: {}ms", (System.nanoTime() - startTime) / 1_000_000); - } - - @Override - protected void onComplete(boolean failed, boolean unloadWorld) { - super.onComplete(failed, false); - if (failed) return; - - if (!getBuilder().setSlot(getBuilder().getFreeSlot(), plot.getID())) return; - if (!plot.setStatus(Status.unfinished)) return; - if (!((Plot) plot).setPlotType(plotType)) return; - if (!plot.setPlotOwner(getBuilder())) return; - - PlotUtils.Cache.clearCache(getBuilder().getUUID()); - plot.getWorld().teleportPlayer(getBuilder().getPlayer()); - LangUtil.getInstance().broadcast(LangPaths.Message.Info.CREATED_NEW_PLOT, getBuilder().getName()); - } -} diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java index d239e980..7681a699 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java @@ -27,6 +27,7 @@ import com.alpsbte.plotsystem.PlotSystem; import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.core.system.plot.generator.world.PlotWorldGenerator; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; @@ -259,7 +260,7 @@ public static void pasteSchematic(@Nullable Mask pasteMask, byte[] schematicFile } protected void onException(Exception e) { - // TODO: abandon plot + PlotHandler.abandonPlot(this.plot); PlotSystem.getPlugin().getComponentLogger().error(text("An error occurred while generating plot!"), e); builder.getPlayer().sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(builder.getPlayer(), LangPaths.Message.Error.ERROR_OCCURRED))); diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java index 4e581177..feafcc26 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java @@ -29,6 +29,7 @@ import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; +import com.alpsbte.plotsystem.core.system.plot.world.CityPlotWorld; import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.Status; @@ -41,25 +42,36 @@ import org.bukkit.Bukkit; import org.jetbrains.annotations.NotNull; +import java.io.IOException; + public class DefaultPlotLoader extends AbstractPlotLoader { public DefaultPlotLoader(@NotNull AbstractPlot plot, Builder builder, PlotType plotType, PlotWorld plotWorld) { super(plot, builder, plotType, plotWorld); } + public DefaultPlotLoader(@NotNull AbstractPlot plot, Builder builder) { + this(plot, builder, builder.getPlotType()); + } + + public DefaultPlotLoader(@NotNull AbstractPlot plot, Builder builder, PlotType plotType) { + this(plot, builder, plotType, PlotWorld.getByType(plotType, (Plot) plot)); + } + @Override protected void generateStructure() throws Exception { - if (!(plot instanceof Plot p)) super.generateStructure(); - else { - byte[] completedSchematic = p.getCompletedSchematic(); - if (completedSchematic != null) { - Utils.runSync(() -> { - Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(plotWorld.getBukkitWorld()), BlockTypes.AIR); - pasteSchematic(airMask, completedSchematic, plotWorld, true); - return null; - }).get(); - } else super.generateStructure(); + if (!(plot instanceof Plot p)) { + super.generateStructure(); + return; } + byte[] completedSchematic = p.getCompletedSchematic(); + if (completedSchematic != null) { + Utils.runSync(() -> { + Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(plotWorld.getBukkitWorld()), BlockTypes.AIR); + pasteSchematic(airMask, completedSchematic, plotWorld, true); + return null; + }).get(); + } else super.generateStructure(); copyToCityWorld(); } @@ -71,12 +83,13 @@ protected void onCompletion() { }); } - protected void copyToCityWorld() { + protected void copyToCityWorld() throws IOException { + assert plot instanceof Plot; if (plot.getStatus() == Status.completed) return; if (!PlotWorld.isOnePlotWorld(plotWorld.getWorldName())) return; // If the player is playing in his own world, then additionally generate the plot in the city world - // TODO: generate plot on city world - // TODO: unload CIM world when done + CityPlotWorld cityPlotWorld = new CityPlotWorld((Plot) plot); + AbstractPlotLoader.pasteSchematic(null, this.schematicBytes, cityPlotWorld, false); } } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/TutorialPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/TutorialPlotLoader.java similarity index 70% rename from src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/TutorialPlotGenerator.java rename to src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/TutorialPlotLoader.java index ba9889d0..77eb2bee 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/TutorialPlotGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/TutorialPlotLoader.java @@ -1,12 +1,12 @@ -package com.alpsbte.plotsystem.core.system.plot.generator; +package com.alpsbte.plotsystem.core.system.plot.generator.loader; import com.alpsbte.plotsystem.PlotSystem; import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.TutorialPlot; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; +import com.alpsbte.plotsystem.core.system.plot.world.OnePlotWorld; import com.alpsbte.plotsystem.utils.DependencyManager; -import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldguard.WorldGuard; import com.sk89q.worldguard.protection.flags.Flags; @@ -16,27 +16,22 @@ import com.sk89q.worldguard.protection.regions.ProtectedRegion; import org.jetbrains.annotations.NotNull; -import java.io.IOException; import java.util.Objects; import static net.kyori.adventure.text.Component.text; -public class TutorialPlotGenerator extends AbstractPlotGenerator { +public class TutorialPlotLoader extends AbstractPlotLoader { private boolean buildingEnabled = false; private boolean worldEditEnabled = false; - public TutorialPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder builder) { - super(plot, builder, PlotType.TUTORIAL); + public TutorialPlotLoader(@NotNull AbstractPlot plot, Builder builder) { + super(plot, builder, PlotType.TUTORIAL, new OnePlotWorld(plot)); } - @Override - protected boolean init() { - return true; - } - - public void generateOutlines(int schematicId) throws IOException, WorldEditException { + public void generateOutlines(int schematicId) throws Exception { ((TutorialPlot) plot).setTutorialSchematic(schematicId); - generateOutlines(getPlot().getInitialSchematicBytes()); + fetchSchematicData(); + generateStructure(); } @Override @@ -48,24 +43,19 @@ protected void setBuildRegionPermissions(@NotNull ProtectedRegion region) { region.setFlag(new StateFlag("worldedit", false, RegionGroup.OWNERS), isWorldEditEnabled() ? StateFlag.State.ALLOW : StateFlag.State.DENY); try { - Objects.requireNonNull(WorldGuard.getInstance().getPlatform().getRegionContainer().get(BukkitAdapter.adapt(world.getBukkitWorld()))).save(); + Objects.requireNonNull(WorldGuard.getInstance().getPlatform().getRegionContainer().get(BukkitAdapter.adapt(plotWorld.getBukkitWorld()))).save(); } catch (StorageException ex) { PlotSystem.getPlugin().getComponentLogger().error(text("An error occurred while saving plot tutorial region!"), ex); } } - @Override - protected void onComplete(boolean failed, boolean unloadWorld) { - super.onComplete(failed, false); - } - public boolean isBuildingEnabled() { return buildingEnabled; } public void setBuildingEnabled(boolean buildingEnabled) { this.buildingEnabled = buildingEnabled; - setBuildRegionPermissions(world.getProtectedBuildRegion()); + setBuildRegionPermissions(plotWorld.getProtectedBuildRegion()); } public boolean isWorldEditEnabled() { @@ -74,6 +64,9 @@ public boolean isWorldEditEnabled() { public void setWorldEditEnabled(boolean worldEditEnabled) { this.worldEditEnabled = worldEditEnabled; - setBuildRegionPermissions(world.getProtectedBuildRegion()); + setBuildRegionPermissions(plotWorld.getProtectedBuildRegion()); } + + @Override + protected void onCompletion() {} } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java index 45f1d2ed..bb17afd3 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java @@ -6,8 +6,6 @@ import com.alpsbte.plotsystem.core.system.CityProject; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; -import com.alpsbte.plotsystem.core.system.plot.generator.AbstractPlotGenerator; -import com.alpsbte.plotsystem.core.system.plot.world.CityPlotWorld; import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; import com.alpsbte.plotsystem.core.system.review.PlotReview; import com.alpsbte.plotsystem.core.system.review.ReviewNotification; @@ -21,9 +19,7 @@ import com.github.fierioziy.particlenativeapi.api.ParticleNativeAPI; import com.github.fierioziy.particlenativeapi.api.Particles_1_13; import com.github.fierioziy.particlenativeapi.plugin.ParticleNativePlugin; -import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.bukkit.BukkitAdapter; -import com.sk89q.worldedit.bukkit.BukkitWorld; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; @@ -33,7 +29,6 @@ import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; -import com.sk89q.worldedit.math.transform.AffineTransform; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Polygonal2DRegion; import com.sk89q.worldguard.protection.regions.ProtectedRegion; @@ -60,7 +55,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -193,58 +187,6 @@ public static boolean isPlotWorld(@NotNull World world) { return Paths.get(PlotSystem.getPlugin().getDataFolder().getAbsolutePath(), "schematics") + File.separator; } - public static boolean savePlotAsSchematic(@NotNull Plot plot) throws IOException, WorldEditException { - if (plot.getVersion() < 4) { - PlotSystem.getPlugin().getComponentLogger().error(text("Saving schematics of legacy plots is no longer allowed!")); - return false; - } - - Clipboard clipboard; - ByteArrayInputStream inputStream = new ByteArrayInputStream(plot.getInitialSchematicBytes()); - try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(inputStream)) { - clipboard = reader.read(); - } - if (clipboard == null) return false; - - CuboidRegion cuboidRegion = getPlotAsRegion(plot); - if (cuboidRegion == null) return false; - - BlockVector3 plotCenter = plot.getCenter(); - - // Get plot outline - List plotOutlines = plot.getOutline(); - - // Load finished plot region as cuboid region - if (!plot.getWorld().loadWorld()) return false; - com.sk89q.worldedit.world.World world = new BukkitWorld(plot.getWorld().getBukkitWorld()); - Polygonal2DRegion region = new Polygonal2DRegion(world, plotOutlines, cuboidRegion.getMinimumPoint().y(), cuboidRegion.getMaximumPoint().y()); - - // Copy and write finished plot clipboard to schematic - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try (Clipboard cb = new BlockArrayClipboard(region)) { - cb.setOrigin(BlockVector3.at(plotCenter.x(), cuboidRegion.getMinimumY(), (double) plotCenter.z())); - - ForwardExtentCopy forwardExtentCopy = new ForwardExtentCopy(Objects.requireNonNull(region.getWorld()), region, cb, region.getMinimumPoint()); - Operations.complete(forwardExtentCopy); - - try (ClipboardWriter writer = AbstractPlot.CLIPBOARD_FORMAT.getWriter(outputStream)) { - double initialY = clipboard.getRegion().getMinimumY(); - double offset = initialY - cuboidRegion.getMinimumY(); - writer.write(cb.transform(new AffineTransform().translate(Vector3.at(0, offset, 0)))); - } - } - - // Set Completed Schematic - boolean successful = DataProvider.PLOT.setCompletedSchematic(plot.getID(), outputStream.toByteArray()); - if (!successful) return false; - - // If plot was created in a void world, copy the result to the city world - if (plot.getPlotType() != PlotType.CITY_INSPIRATION_MODE) { - AbstractPlotGenerator.pasteSchematic(null, outputStream.toByteArray(), new CityPlotWorld(plot), false); - } - return true; - } - public static @Nullable CompletableFuture convertTerraToPlotXZ(@NotNull AbstractPlot plot, double[] terraCoords) throws IOException { // Load plot outlines schematic as clipboard Clipboard clipboard; diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/IWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/IWorld.java index 12fdc078..ea6a1a0e 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/IWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/IWorld.java @@ -1,7 +1,7 @@ package com.alpsbte.plotsystem.core.system.plot.world; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; -import com.alpsbte.plotsystem.core.system.plot.generator.AbstractPlotGenerator; +import com.alpsbte.plotsystem.core.system.plot.generator.loader.AbstractPlotLoader; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldguard.protection.regions.ProtectedRegion; import org.bukkit.Location; @@ -21,7 +21,7 @@ public interface IWorld { * @param generator generator type as class * @return true if world was generated successfully */ - boolean generateWorld(@NotNull Class generator); + boolean generateWorld(@NotNull Class generator); /** * Regenerates the current plot with an optional new generator type @@ -29,7 +29,7 @@ public interface IWorld { * @param generator generator type as class * @return true if world was regenerated successfully */ - boolean regenWorld(@NotNull Class generator); + boolean regenWorld(@NotNull Class generator); /** * Deletes the world file and entry in the config file diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java index 72de7d8c..ca3563c1 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java @@ -29,10 +29,9 @@ import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; import com.alpsbte.plotsystem.core.system.plot.TutorialPlot; -import com.alpsbte.plotsystem.core.system.plot.generator.AbstractPlotGenerator; -import com.alpsbte.plotsystem.core.system.plot.generator.DefaultPlotGenerator; -import com.alpsbte.plotsystem.core.system.plot.generator.TutorialPlotGenerator; +import com.alpsbte.plotsystem.core.system.plot.generator.loader.AbstractPlotLoader; import com.alpsbte.plotsystem.core.system.plot.generator.loader.DefaultPlotLoader; +import com.alpsbte.plotsystem.core.system.plot.generator.loader.TutorialPlotLoader; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.utils.Utils; @@ -56,13 +55,13 @@ public OnePlotWorld(@NotNull AbstractPlot plot) { } @Override - public boolean generateWorld(@NotNull Class generator) { + public boolean generateWorld(@NotNull Class generator) { if (isWorldGenerated()) return false; - if (generator.isAssignableFrom(DefaultPlotGenerator.class)) { - new DefaultPlotGenerator(plot, plotOwner); - } else if (generator.isAssignableFrom(TutorialPlotGenerator.class)) { - new TutorialPlotGenerator(plot, plotOwner); + if (generator.isAssignableFrom(DefaultPlotLoader.class)) { + new DefaultPlotLoader(plot, plotOwner); + } else if (generator.isAssignableFrom(TutorialPlotLoader.class)) { + new TutorialPlotLoader(plot, plotOwner); } else return false; return true; } @@ -73,7 +72,7 @@ public boolean loadWorld() { // Generate plot if it doesn't exist if (plot.getPlotType() == PlotType.TUTORIAL || ((Plot) plot).getCompletedSchematic() == null) - generateWorld(TutorialPlotGenerator.class); + generateWorld(TutorialPlotLoader.class); new DefaultPlotLoader(plot, plotOwner, plot.getPlotType(), this); diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java index 3b94a713..5eb2c7cd 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java @@ -30,7 +30,7 @@ import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; import com.alpsbte.plotsystem.core.system.plot.TutorialPlot; -import com.alpsbte.plotsystem.core.system.plot.generator.AbstractPlotGenerator; +import com.alpsbte.plotsystem.core.system.plot.generator.loader.AbstractPlotLoader; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.utils.DependencyManager; import com.alpsbte.plotsystem.utils.Utils; @@ -75,12 +75,12 @@ public PlotWorld(@NotNull String worldName, @Nullable AbstractPlot plot) { } @Override - public boolean generateWorld(@NotNull Class generator) { + public boolean generateWorld(@NotNull Class generator) { throw new UnsupportedOperationException("No world generator set for world " + getWorldName()); } @Override - public boolean regenWorld(@NotNull Class generator) { + public boolean regenWorld(@NotNull Class generator) { return deleteWorld() && generateWorld(generator); } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/AbstractPlotTutorial.java b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/AbstractPlotTutorial.java index 4a275a7d..f977afbc 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/AbstractPlotTutorial.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/AbstractPlotTutorial.java @@ -6,7 +6,7 @@ import com.alpsbte.plotsystem.core.database.providers.TutorialPlotProvider; import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.TutorialPlot; -import com.alpsbte.plotsystem.core.system.plot.generator.TutorialPlotGenerator; +import com.alpsbte.plotsystem.core.system.plot.generator.loader.TutorialPlotLoader; import com.alpsbte.plotsystem.core.system.tutorial.stage.AbstractPlotStage; import com.alpsbte.plotsystem.core.system.tutorial.stage.AbstractStage; import com.alpsbte.plotsystem.core.system.tutorial.utils.TutorialNPC; @@ -22,7 +22,6 @@ import org.bukkit.ChatColor; import org.bukkit.entity.Player; -import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.Optional; import java.util.UUID; @@ -40,7 +39,7 @@ public abstract class AbstractPlotTutorial extends AbstractTutorial implements PlotTutorial { protected TutorialPlot tutorialPlot; - private TutorialPlotGenerator plotGenerator; + private TutorialPlotLoader plotGenerator; private boolean isPasteSchematic; protected AbstractPlotTutorial(Player player, int tutorialId, int stageId) { @@ -91,7 +90,7 @@ public void setStage(int stageId) { } @Override - public void onPlotSchematicPaste(UUID playerUUID, int schematicId) throws IOException { + public void onPlotSchematicPaste(UUID playerUUID, int schematicId) throws Exception { if (!getPlayerUUID().toString().equals(playerUUID.toString())) return; if (plotGenerator != null && tutorialPlot.getWorld().isWorldGenerated() && tutorialPlot.getWorld().isWorldLoaded()) { plotGenerator.generateOutlines(schematicId); @@ -119,7 +118,7 @@ protected void prepareStage(PrepareStageAction action) { if (isPasteSchematic) { try { onPlotSchematicPaste(getPlayerUUID(), ((AbstractPlotStage) currentStage).getInitSchematicId()); - } catch (IOException ex) { + } catch (Exception ex) { onException(ex); return; } @@ -147,11 +146,11 @@ public void saveTutorial(int stageId) { @Override public void onSwitchWorld(UUID playerUUID, int tutorialWorldIndex) { if (!getPlayerUUID().toString().equals(playerUUID.toString())) return; - if (tutorialWorldIndex == 1 && (plotGenerator == null || !plotGenerator.getPlot().getWorld().isWorldGenerated())) { - plotGenerator = new TutorialPlotGenerator(tutorialPlot, Builder.byUUID(playerUUID)); + if (tutorialWorldIndex == 1 && (plotGenerator == null || !tutorialPlot.getWorld().isWorldGenerated())) { + plotGenerator = new TutorialPlotLoader(tutorialPlot, Builder.byUUID(playerUUID)); try { onPlotSchematicPaste(playerUUID, ((AbstractPlotStage) currentStage).getInitSchematicId()); - } catch (IOException ex) { + } catch (Exception ex) { onException(ex); return; } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/PlotTutorial.java b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/PlotTutorial.java index 261e52fc..43f281c4 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/PlotTutorial.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/PlotTutorial.java @@ -1,6 +1,5 @@ package com.alpsbte.plotsystem.core.system.tutorial; -import java.io.IOException; import java.util.UUID; public interface PlotTutorial extends Tutorial { @@ -11,7 +10,7 @@ public interface PlotTutorial extends Tutorial { * @param playerUUID uuid of the player * @param schematicId The schematic id */ - void onPlotSchematicPaste(UUID playerUUID, int schematicId) throws IOException; + void onPlotSchematicPaste(UUID playerUUID, int schematicId) throws Exception; /** * This method is called when the building and WorldEdit permissions on the plot need to be changed. diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/PlotSchematicPasteTask.java b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/PlotSchematicPasteTask.java index bc7e7e10..e1975aaa 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/PlotSchematicPasteTask.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/PlotSchematicPasteTask.java @@ -4,8 +4,6 @@ import com.alpsbte.plotsystem.core.system.tutorial.PlotTutorial; import org.bukkit.entity.Player; -import java.io.IOException; - public class PlotSchematicPasteTask extends AbstractTask { private final int schematicId; @@ -20,7 +18,7 @@ public void performTask() { if (tutorial != null) { try { tutorial.onPlotSchematicPaste(player.getUniqueId(), schematicId); - } catch (IOException ex) { + } catch (Exception ex) { tutorial.onException(ex); return; } From 17a83765862a7c036dd5fec7e768f28f01fa1822 Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Tue, 25 Nov 2025 13:43:29 +0100 Subject: [PATCH 19/60] remove unused imports --- .../plotsystem/core/menus/companion/CityProjectMenu.java | 2 -- .../plotsystem/core/menus/review/ReviewPlotTogglesMenu.java | 1 - 2 files changed, 3 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java index bb612808..574a35bd 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java +++ b/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java @@ -10,12 +10,10 @@ import com.alpsbte.plotsystem.core.system.Country; import com.alpsbte.plotsystem.core.system.plot.Plot; import com.alpsbte.plotsystem.core.system.plot.PlotHandler; -import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.PlotDifficulty; import com.alpsbte.plotsystem.utils.enums.Status; import com.alpsbte.plotsystem.utils.io.ConfigPaths; -import com.alpsbte.plotsystem.utils.io.ConfigUtil; import com.alpsbte.plotsystem.utils.io.LangPaths; import com.alpsbte.plotsystem.utils.io.LangUtil; import com.alpsbte.plotsystem.utils.items.MenuItems; diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java index 24b53a93..6d9ee902 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java +++ b/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java @@ -8,7 +8,6 @@ import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.Plot; import com.alpsbte.plotsystem.core.system.plot.PlotHandler; -import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.core.system.review.PlotReview; import com.alpsbte.plotsystem.core.system.review.ReviewRating; import com.alpsbte.plotsystem.core.system.review.ToggleCriteria; From 3ca8cb7372c9bba44c08d36ae1b85e2f8d443448 Mon Sep 17 00:00:00 2001 From: cinnazeyy Date: Tue, 2 Dec 2025 22:58:08 +0100 Subject: [PATCH 20/60] add nbt dependency to gradle for parity with maven --- build.gradle.kts | 1 + gradle/libs.versions.toml | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index f7dd808c..a22ef494 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -46,6 +46,7 @@ dependencies { implementation(libs.com.alpsbte.alpslib.alpslib.utils) implementation(libs.org.mariadb.jdbc.mariadb.java.client) implementation(libs.com.zaxxer.hikaricp) + implementation(libs.com.github.querz.nbt) compileOnly(libs.io.papermc.paper.paper.api) implementation(platform(libs.com.intellectualsites.bom.bom.newest)) compileOnly("com.fastasyncworldedit:FastAsyncWorldEdit-Core") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0af5093b..7b8d7fb4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,6 +18,7 @@ io-papermc-paper-paper-api = "1.21.8-R0.1-SNAPSHOT" li-cinnazeyy-langlibs-api = "1.5.1" org-mariadb-jdbc-mariadb-java-client = "2.7.4" com-intellectualsites-bom-bom-newest = "1.55" # Ref: https://github.com/IntellectualSites/bom +com-github-querz-nbt-newest = "6.1" [libraries] com-alpsbte-alpslib-alpslib-hologram = { module = "com.alpsbte.alpslib:alpslib-hologram", version.ref = "com-alpsbte-alpslib-alpslib-hologram" } @@ -27,6 +28,7 @@ com-alpsbte-canvas = { module = "com.alpsbte:canvas", version.ref = "com-alpsbte com-arcaniax-headdatabase-api = { module = "com.arcaniax:HeadDatabase-API", version.ref = "com-arcaniax-headdatabase-api" } com-github-decentsoftware-eu-decentholograms = { module = "com.github.decentsoftware-eu:decentholograms", version.ref = "com-github-decentsoftware-eu-decentholograms" } com-github-fierioziy-particlenativeapi-particlenativeapi-plugin = { module = "com.github.fierioziy.particlenativeapi:ParticleNativeAPI-plugin", version.ref = "com-github-fierioziy-particlenativeapi-particlenativeapi-plugin" } +com-github-querz-nbt = { module = "com.github.Querz:NBT", version.ref = "com-github-querz-nbt-newest" } multiverse-core = { module = "org.mvplugins.multiverse.core:multiverse-core", version.ref = "multiverse-core" } com-sk89q-worldguard-worldguard-bukkit = { module = "com.sk89q.worldguard:worldguard-bukkit", version.ref = "com-sk89q-worldguard-worldguard-bukkit" } com-zaxxer-hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "com-zaxxer-hikaricp" } @@ -35,4 +37,4 @@ de-oliver-fancynpcs = { module = "de.oliver:FancyNpcs", version.ref = "de-oliver io-papermc-paper-paper-api = { module = "io.papermc.paper:paper-api", version.ref = "io-papermc-paper-paper-api" } li-cinnazeyy-langlibs-api = { module = "li.cinnazeyy:LangLibs-API", version.ref = "li-cinnazeyy-langlibs-api" } org-mariadb-jdbc-mariadb-java-client = { module = "org.mariadb.jdbc:mariadb-java-client", version.ref = "org-mariadb-jdbc-mariadb-java-client" } -com-intellectualsites-bom-bom-newest = { module = "com.intellectualsites.bom:bom-newest", version.ref = "com-intellectualsites-bom-bom-newest" } \ No newline at end of file +com-intellectualsites-bom-bom-newest = { module = "com.intellectualsites.bom:bom-newest", version.ref = "com-intellectualsites-bom-bom-newest" } From 4a9a6cea4aa00e50543f53ddeb6998fb5a14630b Mon Sep 17 00:00:00 2001 From: cinnazeyy Date: Wed, 3 Dec 2025 00:17:18 +0100 Subject: [PATCH 21/60] fix merging issues --- .../core/system/plot/PlotHandler.java | 22 +++++++++---------- .../plot/generator/DefaultPlotGenerator.java | 0 .../generator/loader/AbstractPlotLoader.java | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) delete mode 100644 src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java index 544fbca3..c0230592 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java @@ -98,7 +98,7 @@ public static boolean assignPlot(Builder builder, Plot plot) { } // Assign - if (!builder.setSlot(builder.getFreeSlot(), plot.getID())) return false; + if (!builder.setSlot(builder.getFreeSlot(), plot.getId())) return false; if (!plot.setStatus(Status.unfinished)) return false; return plot.setPlotOwner(builder); } @@ -144,7 +144,7 @@ public static void generatePlot(Builder builder, Plot plot, PlotType type) { public static boolean abandonPlot(AbstractPlot plot) { boolean successfullyAbandoned = plot.getWorld().onAbandon(); if (!successfullyAbandoned) { - PlotSystem.getPlugin().getComponentLogger().error(text("Failed to abandon plot with the ID " + plot.getID() + "!")); + PlotSystem.getPlugin().getComponentLogger().error(text("Failed to abandon plot with the ID " + plot.getId() + "!")); return false; } @@ -152,7 +152,7 @@ public static boolean abandonPlot(AbstractPlot plot) { if (plot.getPlotType() == PlotType.TUTORIAL) return; Plot dPlot = (Plot) plot; boolean successful; - successful = DataProvider.REVIEW.removeAllReviewsOfPlot(dPlot.getID()); + successful = DataProvider.REVIEW.removeAllReviewsOfPlot(dPlot.getId()); for (Builder builder : dPlot.getPlotMembers()) { if (!successful) break; @@ -171,20 +171,20 @@ public static boolean abandonPlot(AbstractPlot plot) { && dPlot.setPlotType(PlotType.LOCAL_INSPIRATION_MODE); } - successful = successful && DataProvider.PLOT.setCompletedSchematic(plot.getID(), null); - if (!successful) PlotSystem.getPlugin().getComponentLogger().error(text("Failed to abandon plot with the ID " + plot.getID() + "!")); + successful = successful && DataProvider.PLOT.setCompletedSchematic(plot.getId(), null); + if (!successful) PlotSystem.getPlugin().getComponentLogger().error(text("Failed to abandon plot with the ID " + plot.getId() + "!")); }); return true; } public static boolean deletePlot(Plot plot) { if (!abandonPlot(plot)) { - PlotSystem.getPlugin().getComponentLogger().warn(text("Failed to delete plot with the ID " + plot.getID() + "!")); + PlotSystem.getPlugin().getComponentLogger().warn(text("Failed to delete plot with the ID " + plot.getId() + "!")); return false; } CompletableFuture.runAsync(() -> { - if (DataProvider.PLOT.deletePlot(plot.getID())) return; - PlotSystem.getPlugin().getComponentLogger().warn(text("Failed to delete plot with the ID " + plot.getID() + " from the database!")); + if (DataProvider.PLOT.deletePlot(plot.getId())) return; + PlotSystem.getPlugin().getComponentLogger().warn(text("Failed to delete plot with the ID " + plot.getId() + " from the database!")); }); return true; } @@ -202,10 +202,10 @@ public static void abandonInactivePlots() { Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { if (!abandonPlot(plot)) { - PlotSystem.getPlugin().getComponentLogger().warn(text("An error occurred while abandoning plot #" + plot.getID() + " due to inactivity!")); + PlotSystem.getPlugin().getComponentLogger().warn(text("An error occurred while abandoning plot #" + plot.getId() + " due to inactivity!")); return; } - PlotSystem.getPlugin().getComponentLogger().info(text("Abandoned plot #" + plot.getID() + " due to inactivity!")); + PlotSystem.getPlugin().getComponentLogger().info(text("Abandoned plot #" + plot.getId() + " due to inactivity!")); }); } } @@ -284,7 +284,7 @@ public static boolean savePlotAsSchematic(@NotNull Plot plot) throws IOException } // Set Completed Schematic - boolean successful = DataProvider.PLOT.setCompletedSchematic(plot.getID(), outputStream.toByteArray()); + boolean successful = DataProvider.PLOT.setCompletedSchematic(plot.getId(), outputStream.toByteArray()); if (!successful) return false; // If plot was created in a void world, copy the result to the city world diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java index 7681a699..7b5946f8 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java @@ -93,7 +93,7 @@ protected AbstractPlotLoader(@NotNull AbstractPlot plot, Builder builder, PlotTy this.plotWorld = plotWorld; this.builder = builder; - PlotSystem.getPlugin().getComponentLogger().info("Loading plot #{}...", plot.getID()); + PlotSystem.getPlugin().getComponentLogger().info("Loading plot #{}...", plot.getId()); PlotSystem.getPlugin().getComponentLogger().info("Plot Type: {}", plot.getPlotType().name()); boolean successful = true; From ac9a97a41545f48ea2532d46ff45366f36713ad8 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Mon, 16 Feb 2026 11:22:01 +0100 Subject: [PATCH 22/60] =?UTF-8?q?feat:=20=E2=9C=A8=20Remove=20VoidGen=20de?= =?UTF-8?q?pendency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/plot/generator/world/PlotWorldGenerator.java | 8 +++++--- src/main/resources/plugin.yml | 3 +-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java index 024253f3..b721c445 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java @@ -40,7 +40,10 @@ import net.querz.nbt.io.NamedTag; import net.querz.nbt.tag.CompoundTag; import org.apache.commons.io.FileUtils; -import org.bukkit.*; +import org.bukkit.Bukkit; +import org.bukkit.Difficulty; +import org.bukkit.GameMode; +import org.bukkit.World; import org.bukkit.entity.SpawnCategory; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; @@ -58,7 +61,6 @@ public class PlotWorldGenerator { private final WorldManager worldManager = DependencyManager.getMultiverseCore().getWorldManager(); private final String worldName; private static final World.Environment environment = World.Environment.NORMAL; - private static final WorldType worldType = WorldType.FLAT; public PlotWorldGenerator(String worldName) throws Exception { long startTime = System.nanoTime(); @@ -111,7 +113,7 @@ protected void createMultiverseWorld() { if (!worldManager.isLoadedWorld(worldName)) { worldManager.importWorld(ImportWorldOptions.worldName(worldName) .environment(environment) - .generator("VoidGen:{\"caves\":false,\"decoration\":false,\"mobs\":false,\"structures\":false}") + .generator("Plot-System") .useSpawnAdjust(false) ); } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index a9a177fd..8b50ac2b 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -4,8 +4,7 @@ api-version: "1.21" name: Plot-System description: "${description}" author: R3tuxn, Cinnazeyy & Zoriot -depend: [ LangLibs, FancyNpcs, VoidGen, FastAsyncWorldEdit, Multiverse-Core, - DecentHolograms, WorldGuard, HeadDatabase ] +depend: [ LangLibs, FancyNpcs, FastAsyncWorldEdit, Multiverse-Core, DecentHolograms, WorldGuard, HeadDatabase ] softdepend: [ WorldGuardExtraFlags, ParticleNativeAPI ] commands: From f3cfdfc447e900c889c2f55697c025d6aac5d393 Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Wed, 8 Apr 2026 17:16:13 +0200 Subject: [PATCH 23/60] fix merge conflicts and use new GameRules api --- .../core/system/plot/PlotHandler.java | 11 +++++---- .../generator/loader/AbstractPlotLoader.java | 6 ++--- .../generator/loader/DefaultPlotLoader.java | 4 ++-- .../world/SkeletonWorldGenerator.java | 23 ++++++++----------- .../core/system/plot/world/CityPlotWorld.java | 2 +- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java index c0230592..2f563b82 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java @@ -44,11 +44,14 @@ import com.alpsbte.plotsystem.utils.io.LangPaths; import com.alpsbte.plotsystem.utils.io.LangUtil; import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.BukkitWorld; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter; +import com.sk89q.worldedit.function.mask.BlockTypeMask; +import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.operation.ForwardExtentCopy; import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.math.BlockVector2; @@ -57,6 +60,7 @@ import com.sk89q.worldedit.math.transform.AffineTransform; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Polygonal2DRegion; +import com.sk89q.worldedit.world.block.BlockTypes; import org.bukkit.Bukkit; import org.bukkit.SoundCategory; import org.bukkit.configuration.file.FileConfiguration; @@ -289,10 +293,9 @@ public static boolean savePlotAsSchematic(@NotNull Plot plot) throws IOException // If plot was created in a void world, copy the result to the city world if (plot.getPlotType() != PlotType.CITY_INSPIRATION_MODE) { - Utils.runSync(() -> { - AbstractPlotLoader.pasteSchematic(null, outputStream.toByteArray(), new CityPlotWorld(plot), false); - return null; - }).get(); + var cpw = new CityPlotWorld(plot); + Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(cpw.getBukkitWorld()), BlockTypes.AIR); + AbstractPlotLoader.pasteSchematic(airMask, outputStream.toByteArray(), cpw, false, true); } return true; } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java index bede05df..02d4167d 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java @@ -218,16 +218,16 @@ protected List getBlockedCommands(@NotNull FileConfiguration config) { protected void generateStructure() throws Exception { Utils.runSync(() -> { if (plotType.hasEnvironment()) { - pasteSchematic(null, this.schematicBytes, this.plotWorld, false); + pasteSchematic(null, this.schematicBytes, this.plotWorld, false, false); } else { Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(this.plotWorld.getBukkitWorld()), BlockTypes.AIR); - pasteSchematic(airMask, PlotUtils.getOutlinesSchematicBytes(plot, this.schematicBytes, this.plotWorld.getBukkitWorld()), this.plotWorld, true); + pasteSchematic(airMask, PlotUtils.getOutlinesSchematicBytes(plot, this.schematicBytes, this.plotWorld.getBukkitWorld()), this.plotWorld, true, false); } return null; }).get(); } - public static void pasteSchematic(@Nullable Mask pasteMask, byte[] schematicFile, @NotNull PlotWorld world, boolean clearArea) throws IOException { + public static void pasteSchematic(@Nullable Mask pasteMask, byte[] schematicFile, @NotNull PlotWorld world, boolean clearArea, boolean offset) throws IOException { // load world if not loaded already if (!world.loadWorld()) return; World weWorld = new BukkitWorld(world.getBukkitWorld()); diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java index feafcc26..0750a995 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java @@ -68,7 +68,7 @@ protected void generateStructure() throws Exception { if (completedSchematic != null) { Utils.runSync(() -> { Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(plotWorld.getBukkitWorld()), BlockTypes.AIR); - pasteSchematic(airMask, completedSchematic, plotWorld, true); + pasteSchematic(airMask, completedSchematic, plotWorld, true, true); return null; }).get(); } else super.generateStructure(); @@ -90,6 +90,6 @@ protected void copyToCityWorld() throws IOException { // If the player is playing in his own world, then additionally generate the plot in the city world CityPlotWorld cityPlotWorld = new CityPlotWorld((Plot) plot); - AbstractPlotLoader.pasteSchematic(null, this.schematicBytes, cityPlotWorld, false); + AbstractPlotLoader.pasteSchematic(null, this.schematicBytes, cityPlotWorld, false, true); } } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java index acf97e31..586dec2b 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java @@ -24,8 +24,7 @@ package com.alpsbte.plotsystem.core.system.plot.generator.world; -import net.kyori.adventure.util.TriState; -import org.bukkit.GameRule; +import org.bukkit.GameRules; import org.bukkit.World; import org.bukkit.WorldCreator; import org.bukkit.WorldType; @@ -50,8 +49,7 @@ protected void generateWorld() { .environment(ENVIRONMENT) .type(WORLD_TYPE) .generator(new SkeletonWorldGenerator.EmptyChunkGenerator()) - .generatorSettings(GENERATOR_SETTINGS) - .keepSpawnLoaded(TriState.FALSE); + .generatorSettings(GENERATOR_SETTINGS); this.world = worldCreator.createWorld(); } @@ -60,15 +58,14 @@ protected void configureWorld() { assert bukkitWorld != null; // Set game rules - bukkitWorld.setGameRule(GameRule.RANDOM_TICK_SPEED, 0); - bukkitWorld.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false); - bukkitWorld.setGameRule(GameRule.DO_FIRE_TICK, false); - bukkitWorld.setGameRule(GameRule.DO_WEATHER_CYCLE, false); - bukkitWorld.setGameRule(GameRule.KEEP_INVENTORY, true); - bukkitWorld.setGameRule(GameRule.DO_MOB_SPAWNING, false); - bukkitWorld.setGameRule(GameRule.ANNOUNCE_ADVANCEMENTS, false); - bukkitWorld.setGameRule(GameRule.DO_TILE_DROPS, false); - bukkitWorld.setGameRule(GameRule.SPAWN_CHUNK_RADIUS, 0); + bukkitWorld.setGameRule(GameRules.RANDOM_TICK_SPEED, 0); + bukkitWorld.setGameRule(GameRules.ADVANCE_TIME, false); + bukkitWorld.setGameRule(GameRules.FIRE_SPREAD_RADIUS_AROUND_PLAYER, 0); + bukkitWorld.setGameRule(GameRules.ADVANCE_WEATHER, false); + bukkitWorld.setGameRule(GameRules.KEEP_INVENTORY, true); + bukkitWorld.setGameRule(GameRules.SPAWN_MOBS, false); + bukkitWorld.setGameRule(GameRules.SHOW_ADVANCEMENT_MESSAGES, false); + bukkitWorld.setGameRule(GameRules.BLOCK_DROPS, false); // Set time to noon bukkitWorld.setTime(6000); diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java index d4124697..d0c03ef6 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java @@ -141,7 +141,7 @@ public boolean onAbandon() { // paste initial schematic to reset plot try { - AbstractPlotLoader.pasteSchematic(null, PlotUtils.getOutlinesSchematicBytes(plot, plot.getInitialSchematicBytes(), getBukkitWorld()), this, true); + AbstractPlotLoader.pasteSchematic(null, PlotUtils.getOutlinesSchematicBytes(plot, plot.getInitialSchematicBytes(), getBukkitWorld()), this, true, false); } catch (IOException e) { PlotSystem.getPlugin().getComponentLogger().error(text("Could not paste schematic!"), e); } From ddeb946d23ecc42a01a69bf55db8dcd8a17ac3c4 Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Thu, 9 Apr 2026 16:38:35 +0200 Subject: [PATCH 24/60] ensure AbstractPlotLoader.pasteSchematic is run in main thread when copying plot to CityWorld --- .../plot/generator/loader/DefaultPlotLoader.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java index 0750a995..5ba9c274 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java @@ -90,6 +90,14 @@ protected void copyToCityWorld() throws IOException { // If the player is playing in his own world, then additionally generate the plot in the city world CityPlotWorld cityPlotWorld = new CityPlotWorld((Plot) plot); - AbstractPlotLoader.pasteSchematic(null, this.schematicBytes, cityPlotWorld, false, true); + try { + Utils.runSync(() -> { + AbstractPlotLoader.pasteSchematic(null, this.schematicBytes, cityPlotWorld, false, true); + return null; + }).get(); + } catch (Exception e) { + if (e.getCause() instanceof IOException ioException) throw ioException; + throw new IOException("Could not copy plot to city world!", e); + } } } From 75e066b552b948ce9fcfadea891f9c05bb5ab24f Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Thu, 9 Apr 2026 16:55:40 +0200 Subject: [PATCH 25/60] run thread unsafe methods in Companion menues in main thread --- .../core/menus/companion/CityProjectMenu.java | 19 +++++++------- .../core/menus/companion/ContinentMenu.java | 26 ++++++++++--------- .../core/menus/companion/CountryMenu.java | 5 ++-- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java index df1902f8..a118fc56 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java +++ b/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java @@ -16,9 +16,7 @@ import com.alpsbte.plotsystem.utils.io.LangPaths; import com.alpsbte.plotsystem.utils.io.LangUtil; import com.alpsbte.plotsystem.utils.items.MenuItems; -import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; -import org.bukkit.Material; import org.bukkit.entity.Player; import org.ipvp.canvas.mask.BinaryMask; import org.ipvp.canvas.mask.Mask; @@ -68,7 +66,8 @@ protected void setPreviewItems() { @Override protected void setItemClickEventsAsync() { getMenu().getSlot(0).setClickHandler((clickPlayer, clickInformation) -> - generateRandomPlot(clickPlayer, getValidCityProjects(selectedPlotDifficulty, clickPlayer, country), selectedPlotDifficulty)); + CompletableFuture.runAsync(() -> + generateRandomPlot(clickPlayer, getValidCityProjects(selectedPlotDifficulty, clickPlayer, country), selectedPlotDifficulty))); getMenu().getSlot(1).setClickHandler((clickPlayer, clickInformation) -> new CountryMenu(clickPlayer, country.getContinent(), selectedPlotDifficulty)); @@ -125,7 +124,7 @@ public void handleCityProjectClick(Player player, CityProject city) { public static boolean generateRandomPlot(Player player, @NotNull List items, PlotDifficulty selectedPlotDifficulty) { PlotDifficulty difficulty = selectedPlotDifficulty; if (items.isEmpty()) { - player.playSound(player, Utils.SoundUtils.ERROR_SOUND, 1, 1); + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> player.playSound(player, Utils.SoundUtils.ERROR_SOUND, 1, 1)); return false; } CityProject randomCity = items.get(Utils.getRandom().nextInt(items.size())); @@ -138,10 +137,10 @@ public static boolean generateRandomPlot(Player player, @NotNull List player.closeInventory()); boolean successful = PlotHandler.assignAndGenerateRandomPlot(builder, randomCity, difficulty); - if (successful) player.playSound(player, Utils.SoundUtils.DONE_SOUND, 1, 1); - return true; + if (successful) Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> player.playSound(player, Utils.SoundUtils.DONE_SOUND, 1, 1)); + return successful; } public static @NotNull @Unmodifiable List getValidCityProjects(PlotDifficulty selectedPlotDifficulty, Player player, @NotNull Country country) { @@ -210,8 +209,10 @@ protected void setPaginatedItemClickEventsAsync(@NotNull List source) { private static void sqlError(@NotNull Player clickPlayer, Exception ex) { Utils.logSqlException(ex); - clickPlayer.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(clickPlayer, LangPaths.Message.Error.ERROR_OCCURRED))); - clickPlayer.playSound(clickPlayer.getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + clickPlayer.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(clickPlayer, LangPaths.Message.Error.ERROR_OCCURRED))); + clickPlayer.playSound(clickPlayer.getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); + }); Thread.currentThread().interrupt(); } } diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/companion/ContinentMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/companion/ContinentMenu.java index 46c965d4..b64749de 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/menus/companion/ContinentMenu.java +++ b/src/main/java/com/alpsbte/plotsystem/core/menus/companion/ContinentMenu.java @@ -14,6 +14,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; public class ContinentMenu extends AbstractMenu { private final HashMap layout = new HashMap<>(); @@ -47,18 +48,19 @@ protected void setMenuItemsAsync() { @Override protected void setItemClickEventsAsync() { - getMenu().getSlot(0).setClickHandler((clickPlayer, clickInformation) -> { // Set click event for random selection item - List layout2 = new java.util.ArrayList<>(layout.values().stream().toList()); - while (!layout2.isEmpty()) { - var rndContinent = layout2.get(Utils.getRandom().nextInt(layout2.size())); - var successful = CountryMenu.generateRandomPlot(clickPlayer, DataProvider.COUNTRY.getCountriesByContinent(rndContinent), null); - if (successful) { - return; - } else { - layout2.remove(rndContinent); - } - } - }); + getMenu().getSlot(0).setClickHandler((clickPlayer, clickInformation) -> + CompletableFuture.runAsync(() -> { // Set click event for random selection item + List layout2 = new java.util.ArrayList<>(layout.values().stream().toList()); + while (!layout2.isEmpty()) { + var rndContinent = layout2.get(Utils.getRandom().nextInt(layout2.size())); + var successful = CountryMenu.generateRandomPlot(clickPlayer, DataProvider.COUNTRY.getCountriesByContinent(rndContinent), null); + if (successful) { + return; + } else { + layout2.remove(rndContinent); + } + } + })); for (Map.Entry continent : layout.entrySet()) { getMenu().getSlot(continent.getKey()).setClickHandler((clickPlayer, clickInfo) -> new CountryMenu(clickPlayer, continent.getValue())); diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CountryMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CountryMenu.java index 8e90f2f2..16a73c6c 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CountryMenu.java +++ b/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CountryMenu.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; import static net.kyori.adventure.text.Component.text; import static net.kyori.adventure.text.format.NamedTextColor.AQUA; @@ -77,7 +78,7 @@ protected void setMenuItemsAsync() { @Override protected void setItemClickEventsAsync() { getMenu().getSlot(0).setClickHandler((clickPlayer, clickInformation) -> // Set click event for random selection item - generateRandomPlot(clickPlayer, countryProjects, selectedPlotDifficulty)); + CompletableFuture.runAsync(() -> generateRandomPlot(clickPlayer, countryProjects, selectedPlotDifficulty))); // Set click event for plots difficulty item getMenu().getSlot(6).setClickHandler((clickPlayer, clickInformation) -> { @@ -163,4 +164,4 @@ private void setCountryItems() { .build()); } } -} \ No newline at end of file +} From 546f4c6e953f8724252b863875185840baef1dd4 Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Thu, 9 Apr 2026 17:13:16 +0200 Subject: [PATCH 26/60] make sure onException feedback runs on mainthread --- .../system/plot/generator/loader/AbstractPlotLoader.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java index 02d4167d..3c6e4505 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java @@ -271,8 +271,11 @@ protected void onException(Exception e) { PlotHandler.abandonPlot(this.plot); PlotSystem.getPlugin().getComponentLogger().error(text("An error occurred while generating plot!"), e); - builder.getPlayer().sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(builder.getPlayer(), LangPaths.Message.Error.ERROR_OCCURRED))); - builder.getPlayer().playSound(builder.getPlayer().getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); + Utils.runSync(() -> { + builder.getPlayer().sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(builder.getPlayer(), LangPaths.Message.Error.ERROR_OCCURRED))); + builder.getPlayer().playSound(builder.getPlayer().getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); + return null; + }); } protected abstract void onCompletion(); From 7c3c4f1e2badd64c1aea3a5688c27e769613adde Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Thu, 9 Apr 2026 18:28:16 +0200 Subject: [PATCH 27/60] make the tutorial system run async when possible --- .../com/alpsbte/plotsystem/PlotSystem.java | 2 +- .../generator/loader/AbstractPlotLoader.java | 9 +++- .../generator/loader/TutorialPlotLoader.java | 14 +++++- .../generator/world/PlotWorldGenerator.java | 4 ++ .../system/tutorial/AbstractPlotTutorial.java | 29 +++++++----- .../system/tutorial/AbstractTutorial.java | 44 ++++++++++++++----- .../system/tutorial/stage/StageTimeline.java | 2 +- .../stage/tasks/PlotSchematicPasteTask.java | 24 +++++++--- .../tutorial/stage/tasks/TeleportTask.java | 17 ++++++- .../stage/tasks/events/BuildEventTask.java | 2 +- .../tasks/events/TeleportPointEventTask.java | 2 +- .../system/tutorial/utils/TutorialNPC.java | 9 +++- .../tutorial/utils/TutorialNPCHologram.java | 1 + .../utils/TutorialNPCTurnTracker.java | 2 +- 14 files changed, 122 insertions(+), 39 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java index 4c730784..93c1a623 100644 --- a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java +++ b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java @@ -129,7 +129,7 @@ public void onEnable() { // Register tutorials if (getConfig().getBoolean(ConfigPaths.TUTORIAL_ENABLE)) { AbstractTutorial.registerTutorials(Collections.singletonList(BeginnerTutorial.class)); - Bukkit.getScheduler().runTaskTimerAsynchronously(FancyNpcsPlugin.get().getPlugin(), new TutorialNPCTurnTracker(), 0, 1L); + Bukkit.getScheduler().runTaskTimer(FancyNpcsPlugin.get().getPlugin(), new TutorialNPCTurnTracker(), 0, 1L); } // Generate Skeleton World diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java index 3c6e4505..464dc66a 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java @@ -268,7 +268,14 @@ public static void pasteSchematic(@Nullable Mask pasteMask, byte[] schematicFile } protected void onException(Exception e) { - PlotHandler.abandonPlot(this.plot); + try { + Utils.runSync(() -> { + PlotHandler.abandonPlot(this.plot); + return null; + }).get(); + } catch (Exception ex) { + PlotSystem.getPlugin().getComponentLogger().error(text("Failed to clean up plot after generation error!"), ex); + } PlotSystem.getPlugin().getComponentLogger().error(text("An error occurred while generating plot!"), e); Utils.runSync(() -> { diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/TutorialPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/TutorialPlotLoader.java index 77eb2bee..5fba9c50 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/TutorialPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/TutorialPlotLoader.java @@ -55,7 +55,12 @@ public boolean isBuildingEnabled() { public void setBuildingEnabled(boolean buildingEnabled) { this.buildingEnabled = buildingEnabled; - setBuildRegionPermissions(plotWorld.getProtectedBuildRegion()); + ProtectedRegion buildRegion = plotWorld.getProtectedBuildRegion(); + if (buildRegion == null) { + PlotSystem.getPlugin().getComponentLogger().warn("Tutorial build region was not available while updating build permissions for plot {}.", plot.getId()); + return; + } + setBuildRegionPermissions(buildRegion); } public boolean isWorldEditEnabled() { @@ -64,7 +69,12 @@ public boolean isWorldEditEnabled() { public void setWorldEditEnabled(boolean worldEditEnabled) { this.worldEditEnabled = worldEditEnabled; - setBuildRegionPermissions(plotWorld.getProtectedBuildRegion()); + ProtectedRegion buildRegion = plotWorld.getProtectedBuildRegion(); + if (buildRegion == null) { + PlotSystem.getPlugin().getComponentLogger().warn("Tutorial build region was not available while updating WorldEdit permissions for plot {}.", plot.getId()); + return; + } + setBuildRegionPermissions(buildRegion); } @Override diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java index b721c445..13d2d7b4 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java @@ -124,6 +124,10 @@ protected void configureWorld() { assert world != null; Option mvWorld = worldManager.getLoadedWorld(worldName); + world.setDifficulty(Difficulty.PEACEFUL); + world.setSpawnFlags(false, false); + world.setAutoSave(false); + // Configure multiverse world mvWorld.get().setAllowFlight(true); mvWorld.get().setGameMode(GameMode.CREATIVE); diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/AbstractPlotTutorial.java b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/AbstractPlotTutorial.java index 607531cc..ca620bd5 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/AbstractPlotTutorial.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/AbstractPlotTutorial.java @@ -147,18 +147,27 @@ public void saveTutorial(int stageId) { } @Override - public void onSwitchWorld(@NotNull UUID playerUUID, int tutorialWorldIndex) { - if (!getPlayerUUID().toString().equals(playerUUID.toString())) return; + public CompletableFuture switchWorldAsync(@NotNull UUID playerUUID, int tutorialWorldIndex) { + if (!getPlayerUUID().toString().equals(playerUUID.toString())) return CompletableFuture.completedFuture(null); if (tutorialWorldIndex == 1 && (plotGenerator == null || !tutorialPlot.getWorld().isWorldGenerated())) { - plotGenerator = new TutorialPlotLoader(tutorialPlot, Builder.byUUID(playerUUID)); - try { - onPlotSchematicPaste(playerUUID, ((AbstractPlotStage) currentStage).getInitSchematicId()); - } catch (Exception ex) { - onException(ex); - return; - } + return CompletableFuture.runAsync(() -> { + plotGenerator = new TutorialPlotLoader(tutorialPlot, Builder.byUUID(playerUUID)); + try { + onPlotSchematicPaste(playerUUID, ((AbstractPlotStage) currentStage).getInitSchematicId()); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + }).thenCompose(ignored -> { + CompletableFuture future = new CompletableFuture<>(); + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> + super.switchWorldAsync(playerUUID, tutorialWorldIndex).whenComplete((result, throwable) -> { + if (throwable != null) future.completeExceptionally(throwable); + else future.complete(result); + })); + return future; + }); } - super.onSwitchWorld(playerUUID, tutorialWorldIndex); + return super.switchWorldAsync(playerUUID, tutorialWorldIndex); } @Override diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/AbstractTutorial.java b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/AbstractTutorial.java index 124e03a1..008b3630 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/AbstractTutorial.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/AbstractTutorial.java @@ -17,6 +17,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import static net.kyori.adventure.text.Component.text; @@ -196,18 +197,28 @@ protected void nextStage() { try { // Switch to the next stage currentStage = getStage(); - - // Check if player has to switch world - onSwitchWorld(player.getUniqueId(), currentStage.getInitWorldIndex()); - - // Ge the timeline of the current stage - stageTimeline = currentStage.getTimeline(); - - // Start tasks timeline - prepareStage(stageTimeline::StartTimeline); } catch (Exception ex) { onException(ex); + return; } + + switchWorldAsync(player.getUniqueId(), currentStage.getInitWorldIndex()).whenComplete((ignored, throwable) -> + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + if (throwable != null) { + onException(throwable instanceof Exception exception ? exception : new Exception(throwable)); + return; + } + + try { + // Ge the timeline of the current stage + stageTimeline = currentStage.getTimeline(); + + // Start tasks timeline + prepareStage(stageTimeline::StartTimeline); + } catch (Exception ex) { + onException(ex); + } + })); } } @@ -220,12 +231,20 @@ public void onStageComplete(UUID playerUUID) { @Override public void onSwitchWorld(UUID playerUUID, int tutorialWorldIndex) { - if (!player.getUniqueId().toString().equals(playerUUID.toString())) return; - if (currentWorldIndex == tutorialWorldIndex) return; + switchWorldAsync(playerUUID, tutorialWorldIndex).exceptionally(ex -> { + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> + onException(ex instanceof Exception exception ? exception : new Exception(ex))); + return null; + }); + } + + public CompletableFuture switchWorldAsync(UUID playerUUID, int tutorialWorldIndex) { + if (!player.getUniqueId().toString().equals(playerUUID.toString())) return CompletableFuture.completedFuture(null); + if (currentWorldIndex == tutorialWorldIndex) return CompletableFuture.completedFuture(null); currentWorldIndex = tutorialWorldIndex; TutorialWorld world = worlds.get(tutorialWorldIndex); - if (world == null) return; + if (world == null) return CompletableFuture.completedFuture(null); player.teleport(world.getPlayerSpawnLocation()); if (npc.getNpc() != null) { npc.move(player, world.getNpcSpawnLocation()); @@ -233,6 +252,7 @@ public void onSwitchWorld(UUID playerUUID, int tutorialWorldIndex) { npc.create(world.getNpcSpawnLocation()); npc.spawn(player); } + return CompletableFuture.completedFuture(null); } @Override diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/StageTimeline.java b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/StageTimeline.java index ae1fa2a9..ae4f2deb 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/StageTimeline.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/StageTimeline.java @@ -75,7 +75,7 @@ public void run() { this.cancel(); } else updatePlayerActionBar(); } - }.runTaskTimerAsynchronously(PlotSystem.getPlugin(), 0, 20); + }.runTaskTimer(PlotSystem.getPlugin(), 0, 20); } // If the task has npc interaction show the hologram click info, otherwise hide it diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/PlotSchematicPasteTask.java b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/PlotSchematicPasteTask.java index e1975aaa..4ba50d01 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/PlotSchematicPasteTask.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/PlotSchematicPasteTask.java @@ -1,9 +1,13 @@ package com.alpsbte.plotsystem.core.system.tutorial.stage.tasks; +import com.alpsbte.plotsystem.PlotSystem; import com.alpsbte.plotsystem.core.system.tutorial.AbstractTutorial; import com.alpsbte.plotsystem.core.system.tutorial.PlotTutorial; +import org.bukkit.Bukkit; import org.bukkit.entity.Player; +import java.util.concurrent.CompletableFuture; + public class PlotSchematicPasteTask extends AbstractTask { private final int schematicId; @@ -16,12 +20,20 @@ public PlotSchematicPasteTask(Player player, int schematicId) { public void performTask() { PlotTutorial tutorial = (PlotTutorial) AbstractTutorial.getActiveTutorial(player.getUniqueId()); if (tutorial != null) { - try { - tutorial.onPlotSchematicPaste(player.getUniqueId(), schematicId); - } catch (Exception ex) { - tutorial.onException(ex); - return; - } + CompletableFuture.runAsync(() -> { + try { + tutorial.onPlotSchematicPaste(player.getUniqueId(), schematicId); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + }).whenComplete((ignored, throwable) -> Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + if (throwable != null) { + tutorial.onException(throwable instanceof Exception exception ? exception : new Exception(throwable)); + return; + } + setTaskDone(); + })); + return; } setTaskDone(); } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/TeleportTask.java b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/TeleportTask.java index 650066b7..ef50ed57 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/TeleportTask.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/TeleportTask.java @@ -24,8 +24,21 @@ public TeleportTask(Player player, Location location) { public void performTask() { Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { if (location == null) { - for (int i = 0; i < AbstractTutorial.getActiveTutorials().size(); i++) - AbstractTutorial.getActiveTutorials().get(i).onSwitchWorld(player.getUniqueId(), tutorialWorldIndex); + AbstractTutorial tutorial = AbstractTutorial.getActiveTutorial(player.getUniqueId()); + if (tutorial == null) { + setTaskDone(); + return; + } + + tutorial.switchWorldAsync(player.getUniqueId(), tutorialWorldIndex).whenComplete((ignored, throwable) -> + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + if (throwable != null) { + tutorial.onException(throwable instanceof Exception exception ? exception : new Exception(throwable)); + return; + } + setTaskDone(); + })); + return; } else { player.teleport(location); } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/events/BuildEventTask.java b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/events/BuildEventTask.java index cf0c06d4..b70fb093 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/events/BuildEventTask.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/events/BuildEventTask.java @@ -38,7 +38,7 @@ public void run() { player.getWorld().spawnParticle(Particle.FLAME, blockVector.getBlockX() + 0.5, blockVector.getBlockY() + 0.5, blockVector.getBlockZ() + 0.5, 1, 0, 0, 0, 0); } - }.runTaskTimerAsynchronously(PlotSystem.getPlugin(), 0, 10); + }.runTaskTimer(PlotSystem.getPlugin(), 0, 10); } @Override diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/events/TeleportPointEventTask.java b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/events/TeleportPointEventTask.java index fb2a5736..5be18801 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/events/TeleportPointEventTask.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/events/TeleportPointEventTask.java @@ -40,7 +40,7 @@ public void run() { player.getWorld().spawnParticle(Particle.FLAME, blockVector.getBlockX() + 0.5, blockVector.getBlockY() + 1.5, blockVector.getBlockZ() + 0.5, 1, 0, 0, 0, 0); } - }.runTaskTimerAsynchronously(PlotSystem.getPlugin(), 0, 10); + }.runTaskTimer(PlotSystem.getPlugin(), 0, 10); } @Override diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/utils/TutorialNPC.java b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/utils/TutorialNPC.java index a7a03448..6e6fd490 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/utils/TutorialNPC.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/utils/TutorialNPC.java @@ -48,6 +48,10 @@ public TutorialNPC(String npcId, String npcDisplayName, String npcInteractionPro public void create(Location spawnPos) { if (npc != null) delete(); + if (FancyNpcsPlugin.get().getNpcAdapter() == null) { + throw new IllegalStateException("FancyNpcs NPC adapter is unavailable. This usually means the installed FancyNpcs version is incompatible with Plot-System."); + } + NpcData npcData = new NpcData(id, UUID.randomUUID(), spawnPos); npc = FancyNpcsPlugin.get().getNpcAdapter().apply(npcData); npc.getData().setSkinData(skin); @@ -66,9 +70,10 @@ public void create(Location spawnPos) { */ public void spawn(Player player) { if (npc == null) return; + String playerWorldName = player.getWorld().getName(); Bukkit.getScheduler().runTaskAsynchronously(FancyNpcsPlugin.get().getPlugin(), () -> { npc.spawn(player); - if (hologram != null && player.getWorld().getName().equals( + if (hologram != null && playerWorldName.equals( Objects.requireNonNull(hologram.getLocation().getWorld()).getName())) hologram.create(player); }); @@ -95,6 +100,8 @@ public void delete() { if (npc != null) npc.removeForAll(); if (hologram != null) hologram.delete(); activeTutorialNPCs.remove(this); + npc = null; + hologram = null; } /** diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/utils/TutorialNPCHologram.java b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/utils/TutorialNPCHologram.java index 9b7058f5..3b8d9335 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/utils/TutorialNPCHologram.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/utils/TutorialNPCHologram.java @@ -42,6 +42,7 @@ public TutorialNPCHologram(@NotNull String id, Location location, TutorialNPC np @Override public void create(Player player) { Bukkit.getScheduler().runTask(FancyNpcsPlugin.get().getPlugin(), () -> { + if (npc.getNpc() == null) return; if (npc.getNpc().getIsVisibleForPlayer().containsKey(player.getUniqueId()) && npc.getNpc().getIsVisibleForPlayer().get(player.getUniqueId())) { super.create(player); diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/utils/TutorialNPCTurnTracker.java b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/utils/TutorialNPCTurnTracker.java index fb451c33..7d7a7f95 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/utils/TutorialNPCTurnTracker.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/utils/TutorialNPCTurnTracker.java @@ -10,7 +10,7 @@ public class TutorialNPCTurnTracker implements Runnable { @Override public void run() { for (AbstractTutorial tutorial : AbstractTutorial.getActiveTutorials()) { - if (tutorial.getNPC() == null) continue; + if (tutorial.getNPC() == null || tutorial.getNPC().getNpc() == null) continue; Location playerLoc = tutorial.getPlayer().getLocation(); Location npcLoc = tutorial.getNPC().getNpc().getData().getLocation(); if (npcLoc == null || !npcLoc.getWorld().getName() From 9947a051a270fd0b42835befd6774306fc1bdb98 Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Thu, 9 Apr 2026 22:18:14 +0200 Subject: [PATCH 28/60] set fixed spawn location for EmptyChunkGenerator --- .../plot/generator/world/SkeletonWorldGenerator.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java index 586dec2b..a26fe069 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java @@ -25,10 +25,15 @@ package com.alpsbte.plotsystem.core.system.plot.generator.world; import org.bukkit.GameRules; +import org.bukkit.Location; import org.bukkit.World; import org.bukkit.WorldCreator; import org.bukkit.WorldType; import org.bukkit.generator.ChunkGenerator; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Random; public class SkeletonWorldGenerator { public static final String WORLD_NAME = "Skeleton"; @@ -78,5 +83,10 @@ protected void saveWorld() { public static class EmptyChunkGenerator extends ChunkGenerator { // It should just do nothing + + @Override + public @Nullable Location getFixedSpawnLocation(@NotNull World world, @NotNull Random random) { + return new Location(world, 0, 0, 0); + } } } From 96859ec95676b88cc8c673760410f7b39831c597 Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Thu, 9 Apr 2026 22:20:12 +0200 Subject: [PATCH 29/60] use DefaultPlotLoader for plots with no completed schematic --- .../plotsystem/core/system/plot/world/OnePlotWorld.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java index 4eb4dfbb..c6c539b7 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java @@ -71,8 +71,10 @@ public boolean loadWorld() { if (plot == null || isWorldGenerated()) return super.loadWorld(); // Generate plot if it doesn't exist - if (plot.getPlotType() == PlotType.TUTORIAL || ((Plot) plot).getCompletedSchematic() == null) + if (plot.getPlotType() == PlotType.TUTORIAL) generateWorld(TutorialPlotLoader.class); + else if (((Plot) plot).getCompletedSchematic() == null) + generateWorld(DefaultPlotLoader.class); new DefaultPlotLoader(plot, plotOwner, plot.getPlotType(), this); From fefcd9993b35fae86cad2f39a5c95d61854358eb Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Wed, 6 May 2026 11:54:20 +0200 Subject: [PATCH 30/60] add restorePlotSlot method and no longer treat all slots occupied as a failure and skip instead --- .../core/system/review/PlotReview.java | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/review/PlotReview.java b/src/main/java/com/alpsbte/plotsystem/core/system/review/PlotReview.java index bc58723c..a2e3c407 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/review/PlotReview.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/review/PlotReview.java @@ -75,20 +75,12 @@ public boolean updateFeedback(String feedback) { public boolean undoReview() { // remove owner score and remove plot from slot if (!plot.getPlotOwner().addScore(splitScore == -1 ? -score : -splitScore)) return false; + if (!restorePlotSlot(plot.getPlotOwner())) return false; - Slot slot = plot.getPlotOwner().getSlotByPlotId(plot.getId()); // get slot if plot is still in slots (rejected) - if (slot == null) slot = plot.getPlotOwner().getFreeSlot(); // get new slot otherwise (completed) - if (slot == null) return false; - - if (!plot.getPlotOwner().setSlot(slot, plot.getId())) return false; - - // remove members score and remove plot from slot + // remove member's score and remove plot from slot for (Builder member : plot.getPlotMembers()) { if (!member.addScore(-splitScore)) return false; - - Slot memberSlot = member.getSlotByPlotId(plot.getId()); - if (memberSlot == null) memberSlot = member.getFreeSlot(); - if (memberSlot == null || member.setSlot(memberSlot, plot.getId())) return false; + if (!restorePlotSlot(member)) return false; } boolean successful = true; @@ -109,4 +101,23 @@ public boolean undoReview() { return successful; } + + /** + * Tries to assign the plot to a slot again if possible. + * If all slots are occupied, the plot won't be assigned to a slot, but still accessible via /plots + * + * @param builder target builder (owner or member) + * @return True if the slot was assigned successfully, false otherwise + */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private boolean restorePlotSlot(Builder builder) { + Slot slot = builder.getSlotByPlotId(plot.getId()); // get slot if plot is still in slots (rejected) + if (slot == null) slot = builder.getFreeSlot(); // get new slot otherwise (completed) + if (slot == null) { + PlotSystem.getPlugin().getComponentLogger().warn("Skipping slot restore for plot #{} and builder {}. All slots are occupied!", plot.getId(), builder.getName()); + return true; + } + + return builder.setSlot(slot, plot.getId()); + } } From ca923667989edd133905226ce110ab72700e628a Mon Sep 17 00:00:00 2001 From: Cinnazeyy Date: Mon, 11 May 2026 04:34:07 +0200 Subject: [PATCH 31/60] when freeing up a slot, check for orphans and assign if possible --- .../com/alpsbte/plotsystem/core/system/Builder.java | 13 +++++++++++++ .../plotsystem/core/system/plot/PlotHandler.java | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/Builder.java b/src/main/java/com/alpsbte/plotsystem/core/system/Builder.java index 62c8809b..a0080e84 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/Builder.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/Builder.java @@ -5,10 +5,12 @@ import com.alpsbte.plotsystem.core.system.plot.Plot; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.utils.enums.Slot; +import com.alpsbte.plotsystem.utils.enums.Status; import com.alpsbte.plotsystem.utils.io.ConfigPaths; import org.bukkit.Bukkit; import org.bukkit.entity.Player; +import java.util.List; import java.util.UUID; public class Builder { @@ -79,6 +81,12 @@ public Slot getSlot(Plot plot) { @SuppressWarnings("BooleanMethodIsAlwaysInverted") public boolean setSlot(Slot slot, int plotId) { + // check for orphans and assign orphan if available + if (plotId == -1) { + List orphans = getOrphans(); + if (!orphans.isEmpty()) plotId = orphans.getFirst().getId(); + } + if (DataProvider.BUILDER.setSlot(this.uuid, plotId, slot)) { switch (slot) { case FIRST -> firstSlot = plotId; @@ -129,6 +137,11 @@ public Slot getSlotByPlotId(int plotId) { return null; } + private List getOrphans() { + List currentPlots = DataProvider.PLOT.getPlots(this, Status.unfinished, Status.unreviewed); + return currentPlots.stream().filter(plot -> getSlotByPlotId(plot.getId()) == null).toList(); + } + public static Builder byUUID(UUID uuid) { return DataProvider.BUILDER.getBuilderByUUID(uuid); } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java index 2f563b82..97d4b1c0 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java @@ -102,7 +102,7 @@ public static boolean assignPlot(Builder builder, Plot plot) { } // Assign - if (!builder.setSlot(builder.getFreeSlot(), plot.getId())) return false; + if (!builder.setSlot(freeSlot, plot.getId())) return false; if (!plot.setStatus(Status.unfinished)) return false; return plot.setPlotOwner(builder); } From 29b1c7897f2acfa8d28807f9d0278158c03f8ec8 Mon Sep 17 00:00:00 2001 From: cinnazeyy Date: Sat, 23 May 2026 17:06:48 +0200 Subject: [PATCH 32/60] fix: plot world loading not running on main thread if called via PlotActionsMenu and fix main thread deadlock if trying to runSync when already on main thread --- .../core/menus/PlotActionsMenu.java | 11 ++++++++++- .../core/system/plot/world/PlotWorld.java | 9 ++++++++- .../com/alpsbte/plotsystem/utils/Utils.java | 19 ++++++++++++------- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/PlotActionsMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/PlotActionsMenu.java index 32eedcb7..6c3d7b8c 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/menus/PlotActionsMenu.java +++ b/src/main/java/com/alpsbte/plotsystem/core/menus/PlotActionsMenu.java @@ -20,6 +20,8 @@ import org.ipvp.canvas.mask.Mask; import org.jetbrains.annotations.NotNull; +import java.util.concurrent.CompletableFuture; + import static net.kyori.adventure.text.Component.text; import static net.kyori.adventure.text.format.NamedTextColor.AQUA; import static net.kyori.adventure.text.format.NamedTextColor.GOLD; @@ -133,7 +135,14 @@ protected void setItemClickEventsAsync() { clickPlayer.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(clickPlayer, LangPaths.Message.Error.CANNOT_LOAD_LEGACY_PLOT))); return; } - plot.getWorld().teleportPlayer(clickPlayer); + CompletableFuture.runAsync(() -> { + if (!plot.getWorld().isWorldGenerated() && !plot.getWorld().loadWorld()) return; + + Utils.runSync(() -> { + plot.getWorld().teleportPlayer(clickPlayer); + return null; + }); + }); }); // Set click event for abandon plot item diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java index c7f50d9c..07c878c5 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java @@ -115,7 +115,14 @@ public boolean loadWorld() { return false; } - return isWorldLoaded() || mvCore.getWorldManager().loadWorld(getWorldName()).isSuccess(); + if (isWorldLoaded()) return true; + + try { + return Utils.supplySync(() -> mvCore.getWorldManager().loadWorld(getWorldName()).isSuccess()).get(); + } catch (Exception e) { + PlotSystem.getPlugin().getComponentLogger().warn(text("Could not load world " + worldName + "!"), e); + return false; + } } @Override diff --git a/src/main/java/com/alpsbte/plotsystem/utils/Utils.java b/src/main/java/com/alpsbte/plotsystem/utils/Utils.java index a59997ab..cd3db3bf 100644 --- a/src/main/java/com/alpsbte/plotsystem/utils/Utils.java +++ b/src/main/java/com/alpsbte/plotsystem/utils/Utils.java @@ -42,7 +42,6 @@ import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; import static com.alpsbte.plotsystem.core.system.tutorial.utils.TutorialUtils.TEXT_HIGHLIGHT_END; import static com.alpsbte.plotsystem.core.system.tutorial.utils.TutorialUtils.TEXT_HIGHLIGHT_START; @@ -107,29 +106,35 @@ public static ItemStack getConfiguredItem(@NotNull String material, Object custo public static CompletableFuture runSync(Callable task) { CompletableFuture future = new CompletableFuture<>(); - Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + Runnable runnable = () -> { try { var result = task.call(); future.complete(result); } catch (Exception e) { future.completeExceptionally(e); } - }); + }; + + if (Bukkit.isPrimaryThread()) runnable.run(); + else Bukkit.getScheduler().getMainThreadExecutor(PlotSystem.getPlugin()).execute(runnable); return future; } public static CompletableFuture supplySync(Callable task) { CompletableFuture future = new CompletableFuture<>(); - Executor executor = Bukkit.getScheduler().getMainThreadExecutor(PlotSystem.getPlugin()); - executor.execute(() -> { + Runnable runnable = () -> { try { var result = task.call(); future.complete(result); } catch (Exception e) { future.completeExceptionally(e); } - }); + }; + + if (Bukkit.isPrimaryThread()) runnable.run(); + else Bukkit.getScheduler().getMainThreadExecutor(PlotSystem.getPlugin()).execute(runnable); + return future; } @@ -283,4 +288,4 @@ public static boolean isOwnerOrReviewer(CommandSender sender, @Nullable Player p } return hasPermission; } -} \ No newline at end of file +} From 92913b0a078a084c586e0174e515933cec556b09 Mon Sep 17 00:00:00 2001 From: cinnazeyy Date: Sat, 23 May 2026 17:10:49 +0200 Subject: [PATCH 33/60] fix: codacy best practice issue --- .../com/alpsbte/plotsystem/core/system/Builder.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/Builder.java b/src/main/java/com/alpsbte/plotsystem/core/system/Builder.java index a0080e84..cca7856e 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/Builder.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/Builder.java @@ -81,17 +81,18 @@ public Slot getSlot(Plot plot) { @SuppressWarnings("BooleanMethodIsAlwaysInverted") public boolean setSlot(Slot slot, int plotId) { + int newPlotId = plotId; // check for orphans and assign orphan if available if (plotId == -1) { List orphans = getOrphans(); - if (!orphans.isEmpty()) plotId = orphans.getFirst().getId(); + if (!orphans.isEmpty()) newPlotId = orphans.getFirst().getId(); } - if (DataProvider.BUILDER.setSlot(this.uuid, plotId, slot)) { + if (DataProvider.BUILDER.setSlot(this.uuid, newPlotId, slot)) { switch (slot) { - case FIRST -> firstSlot = plotId; - case SECOND -> secondSlot = plotId; - default -> thirdSlot = plotId; + case FIRST -> firstSlot = newPlotId; + case SECOND -> secondSlot = newPlotId; + default -> thirdSlot = newPlotId; } return true; } From d11965543e56c99b346e992cfba2fa6dc33de3c9 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sun, 24 May 2026 23:04:05 +0200 Subject: [PATCH 34/60] [WIP] feat: Support 26.1 world changes This currently doesn't really work. When you have the old skeleton world it may work but as soon as you have the new one there are some exceptions. Needs to be polished before released --- .../generator/world/PlotWorldGenerator.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java index 13d2d7b4..7356acfc 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java @@ -84,21 +84,23 @@ public PlotWorldGenerator(String worldName) throws Exception { protected void generateWorld() throws IOException { // copy skeleton world with correct world name - Path skeletonPath = Bukkit.getWorldContainer().toPath().resolve("Skeleton"); - Path worldPath = Bukkit.getWorldContainer().toPath().resolve(worldName); + Path skeletonPath = Bukkit.getWorld("Skeleton").getWorldPath(); + Path worldPath = skeletonPath.getParent().resolve(worldName); // TODO Switch to world tag once we only support 26.1+ & trmove the checks FileUtils.copyDirectory(skeletonPath.toFile(), worldPath.toFile()); // delete uid.dat - Files.delete(worldPath.resolve("uid.dat")); + if (worldPath.resolve("uid.dat").toFile().exists()) Files.delete(worldPath.resolve("uid.dat")); // rename world name in level.dat - Path levelDat = worldPath.resolve("level.dat"); - NamedTag level = NBTUtil.read(levelDat.toFile()); - CompoundTag tag = (CompoundTag) level.getTag(); - tag.remove("LevelName"); - tag.putString("LevelName", worldName); - level.setTag(tag); - NBTUtil.write(level, levelDat.toFile()); + if (worldPath.resolve("level.dat").toFile().exists()) { + Path levelDat = worldPath.resolve("level.dat"); + NamedTag level = NBTUtil.read(levelDat.toFile()); + CompoundTag tag = (CompoundTag) level.getTag(); + tag.remove("LevelName"); + tag.putString("LevelName", worldName); + level.setTag(tag); + NBTUtil.write(level, levelDat.toFile()); + } // rename world in paper-world.yml Path paperWorld = worldPath.resolve("paper-world.yml"); From 0021ab2cc201a2d9c7831f5a8986241e90da8663 Mon Sep 17 00:00:00 2001 From: cinnazeyy Date: Tue, 9 Jun 2026 01:18:25 +0200 Subject: [PATCH 35/60] fix: handle Paper 26.1 world folder changes --- .../generator/world/PlotWorldGenerator.java | 47 ++++++++++++++----- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java index 7356acfc..7bff13d2 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java @@ -83,17 +83,38 @@ public PlotWorldGenerator(String worldName) throws Exception { } protected void generateWorld() throws IOException { - // copy skeleton world with correct world name Path skeletonPath = Bukkit.getWorld("Skeleton").getWorldPath(); - Path worldPath = skeletonPath.getParent().resolve(worldName); // TODO Switch to world tag once we only support 26.1+ & trmove the checks - FileUtils.copyDirectory(skeletonPath.toFile(), worldPath.toFile()); + + // detect world format + // old = level.dat in world root + // new = paper 26.1+ dimension layout + Path skeletonLevelPath; + Path worldLevelPath; + + if (skeletonPath.resolve("level.dat").toFile().exists()) { + // old format (pre-26.1): world path is the level root + skeletonLevelPath = skeletonPath; + worldLevelPath = skeletonPath.getParent().resolve(worldName); + } else { + // new format (26.1+): world path is dimension folder, level root is 3 parents up + skeletonLevelPath = skeletonPath.getParent().getParent().getParent(); + worldLevelPath = skeletonLevelPath.getParent().resolve(worldName); + } + + // copy entire skeleton level directory with correct name + FileUtils.copyDirectory(skeletonLevelPath.toFile(), worldLevelPath.toFile()); + + // get dimension folder + Path dimensionRelativePath = skeletonLevelPath.relativize(skeletonPath); + Path worldDimensionPath = worldLevelPath.resolve(dimensionRelativePath); // delete uid.dat - if (worldPath.resolve("uid.dat").toFile().exists()) Files.delete(worldPath.resolve("uid.dat")); + Path uidDat = worldDimensionPath.resolve("uid.dat"); + if (uidDat.toFile().exists()) Files.delete(uidDat); // rename world name in level.dat - if (worldPath.resolve("level.dat").toFile().exists()) { - Path levelDat = worldPath.resolve("level.dat"); + Path levelDat = worldLevelPath.resolve("level.dat"); + if (levelDat.toFile().exists()) { NamedTag level = NBTUtil.read(levelDat.toFile()); CompoundTag tag = (CompoundTag) level.getTag(); tag.remove("LevelName"); @@ -103,12 +124,14 @@ protected void generateWorld() throws IOException { } // rename world in paper-world.yml - Path paperWorld = worldPath.resolve("paper-world.yml"); - String paperWorldContents = Files.readString(paperWorld); - String updatedContents = paperWorldContents - .replaceAll(SkeletonWorldGenerator.WORLD_NAME, worldName) - .replaceAll(SkeletonWorldGenerator.WORLD_NAME.toLowerCase(), worldName.toLowerCase()); - Files.writeString(paperWorld, updatedContents); + Path paperWorld = worldDimensionPath.resolve("paper-world.yml"); + if (paperWorld.toFile().exists()) { + String paperWorldContents = Files.readString(paperWorld); + String updatedContents = paperWorldContents + .replaceAll(SkeletonWorldGenerator.WORLD_NAME, worldName) + .replaceAll(SkeletonWorldGenerator.WORLD_NAME.toLowerCase(), worldName.toLowerCase()); + Files.writeString(paperWorld, updatedContents); + } } protected void createMultiverseWorld() { From ba40e779ebb0e82c3a65ef117e50d8761a8ba0aa Mon Sep 17 00:00:00 2001 From: Zoriot Date: Mon, 15 Jun 2026 15:43:29 +0200 Subject: [PATCH 36/60] fix(plot): Fix world ref when copy the completed schematic --- .../alpsbte/plotsystem/core/system/plot/PlotHandler.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java index 922e96c2..05cbf1fc 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java @@ -45,6 +45,7 @@ import com.alpsbte.plotsystem.utils.io.LangUtil; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.BukkitWorld; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; @@ -59,6 +60,7 @@ import com.sk89q.worldedit.math.transform.AffineTransform; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Polygonal2DRegion; +import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.block.BlockTypes; import org.bukkit.Bukkit; import org.bukkit.SoundCategory; @@ -74,7 +76,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -279,7 +280,8 @@ public static boolean savePlotAsSchematic(@NotNull Plot plot) throws IOException try (Clipboard cb = new BlockArrayClipboard(region)) { cb.setOrigin(BlockVector3.at(plotCenter.x(), cuboidRegion.getMinimumY(), (double) plotCenter.z())); - ForwardExtentCopy forwardExtentCopy = new ForwardExtentCopy(Objects.requireNonNull(region.getWorld()), region, cb, region.getMinimumPoint()); + World world = new BukkitWorld(plot.getWorld().getBukkitWorld()); + ForwardExtentCopy forwardExtentCopy = new ForwardExtentCopy(world, region, cb, region.getMinimumPoint()); Operations.complete(forwardExtentCopy); try (ClipboardWriter writer = AbstractPlot.CLIPBOARD_FORMAT.getWriter(outputStream)) { From fa51b38f1b4a3f445edfb9422edd99e091070d5b Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sat, 20 Jun 2026 03:11:29 +0200 Subject: [PATCH 37/60] fix: Fully support only new world format (26.1+) This removes handling for <=1.21.11 Worlds, the old changes made that the migration was started from paper. We always just support the latest version. --- build.gradle.kts | 3 +- gradle/libs.versions.toml | 3 - .../generator/world/PlotWorldGenerator.java | 62 +++---------------- 3 files changed, 9 insertions(+), 59 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index fd97d9bd..a9a7ff07 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -67,8 +67,7 @@ dependencies { if (paperNextEnabled.get()) { constraints { - compileOnly("io.papermc.paper:paper-api:26.1.2.build.+") - compileOnly("com.github.decentsoftware-eu:decentholograms:2.10.0") + compileOnly("io.papermc.paper:paper-api:26.2-rc-2.build.+") } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d8343991..89e1ea11 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,8 +17,6 @@ com-arcaniax-headdatabase-api = "1.3.3-SNAPSHOT" com-github-decentsoftware-eu-decentholograms = "2.9.9" # @pin https://github.com/Fierioziy/ParticleNativeAPI/releases - we are stuck at v3 for now. Need to migrate some time or remove it com-github-fierioziy-particlenativeapi-particlenativeapi-plugin = "3.3.2" -# Ref: https://github.com/Querz/NBT -com-github-querz-nbt-newest = "6.1" # Ref: https://github.com/IntellectualSites/bom com-intellectualsites-bom-bom-newest = "1.56" # https://maven.enginehub.org/repo/com/sk89q/worldguard/worldguard-bukkit/ @@ -53,7 +51,6 @@ com-alpsbte-canvas = { module = "com.alpsbte:canvas", version.ref = "com-alpsbte com-arcaniax-headdatabase-api = { module = "com.arcaniax:HeadDatabase-API", version.ref = "com-arcaniax-headdatabase-api" } com-github-decentsoftware-eu-decentholograms = { module = "com.github.decentsoftware-eu:decentholograms", version.ref = "com-github-decentsoftware-eu-decentholograms" } com-github-fierioziy-particlenativeapi-particlenativeapi-plugin = { module = "com.github.fierioziy.particlenativeapi:ParticleNativeAPI-plugin", version.ref = "com-github-fierioziy-particlenativeapi-particlenativeapi-plugin" } -com-github-querz-nbt = { module = "com.github.Querz:NBT", version.ref = "com-github-querz-nbt-newest" } com-intellectualsites-bom-bom-newest = { module = "com.intellectualsites.bom:bom-newest", version.ref = "com-intellectualsites-bom-bom-newest" } com-sk89q-worldguard-worldguard-bukkit = { module = "com.sk89q.worldguard:worldguard-bukkit", version.ref = "com-sk89q-worldguard-worldguard-bukkit" } com-zaxxer-hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "com-zaxxer-hikaricp" } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java index 7bff13d2..3bf2d8cd 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java @@ -36,13 +36,13 @@ import com.sk89q.worldguard.protection.managers.storage.StorageException; import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; import com.sk89q.worldguard.protection.regions.RegionContainer; -import net.querz.nbt.io.NBTUtil; -import net.querz.nbt.io.NamedTag; -import net.querz.nbt.tag.CompoundTag; import org.apache.commons.io.FileUtils; +import org.apache.commons.io.filefilter.NameFileFilter; +import org.apache.commons.io.filefilter.NotFileFilter; import org.bukkit.Bukkit; import org.bukkit.Difficulty; import org.bukkit.GameMode; +import org.bukkit.NamespacedKey; import org.bukkit.World; import org.bukkit.entity.SpawnCategory; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; @@ -51,8 +51,8 @@ import org.mvplugins.multiverse.external.vavr.control.Option; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; +import java.util.Locale; import java.util.Objects; import static net.kyori.adventure.text.Component.text; @@ -83,60 +83,14 @@ public PlotWorldGenerator(String worldName) throws Exception { } protected void generateWorld() throws IOException { - Path skeletonPath = Bukkit.getWorld("Skeleton").getWorldPath(); - - // detect world format - // old = level.dat in world root - // new = paper 26.1+ dimension layout - Path skeletonLevelPath; - Path worldLevelPath; - - if (skeletonPath.resolve("level.dat").toFile().exists()) { - // old format (pre-26.1): world path is the level root - skeletonLevelPath = skeletonPath; - worldLevelPath = skeletonPath.getParent().resolve(worldName); - } else { - // new format (26.1+): world path is dimension folder, level root is 3 parents up - skeletonLevelPath = skeletonPath.getParent().getParent().getParent(); - worldLevelPath = skeletonLevelPath.getParent().resolve(worldName); - } - - // copy entire skeleton level directory with correct name - FileUtils.copyDirectory(skeletonLevelPath.toFile(), worldLevelPath.toFile()); - - // get dimension folder - Path dimensionRelativePath = skeletonLevelPath.relativize(skeletonPath); - Path worldDimensionPath = worldLevelPath.resolve(dimensionRelativePath); - - // delete uid.dat - Path uidDat = worldDimensionPath.resolve("uid.dat"); - if (uidDat.toFile().exists()) Files.delete(uidDat); - - // rename world name in level.dat - Path levelDat = worldLevelPath.resolve("level.dat"); - if (levelDat.toFile().exists()) { - NamedTag level = NBTUtil.read(levelDat.toFile()); - CompoundTag tag = (CompoundTag) level.getTag(); - tag.remove("LevelName"); - tag.putString("LevelName", worldName); - level.setTag(tag); - NBTUtil.write(level, levelDat.toFile()); - } - - // rename world in paper-world.yml - Path paperWorld = worldDimensionPath.resolve("paper-world.yml"); - if (paperWorld.toFile().exists()) { - String paperWorldContents = Files.readString(paperWorld); - String updatedContents = paperWorldContents - .replaceAll(SkeletonWorldGenerator.WORLD_NAME, worldName) - .replaceAll(SkeletonWorldGenerator.WORLD_NAME.toLowerCase(), worldName.toLowerCase()); - Files.writeString(paperWorld, updatedContents); - } + Path skeletonPath = Objects.requireNonNull(Bukkit.getWorld("Skeleton"), "Skeleton World is required at this point").getWorldPath(); + Path worldLevelPath = skeletonPath.getParent().resolve(worldName.toLowerCase(Locale.ROOT)); + FileUtils.copyDirectory(skeletonPath.toFile(), worldLevelPath.toFile(), new NotFileFilter(new NameFileFilter("metadata.dat"))); } protected void createMultiverseWorld() { if (!worldManager.isLoadedWorld(worldName)) { - worldManager.importWorld(ImportWorldOptions.worldName(worldName) + worldManager.importWorld(ImportWorldOptions.worldKey(NamespacedKey.minecraft(worldName.toLowerCase(Locale.ROOT))) .environment(environment) .generator("Plot-System") .useSpawnAdjust(false) From 20c4cc318437ace41c3e2a7a7486513e472bbbc0 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sat, 20 Jun 2026 03:12:20 +0200 Subject: [PATCH 38/60] build(dependencies): update dependency versions for mc 26.1.2 and migrate to Java 25 - Upgraded multiple library dependencies for compatibility. - Updated Java version to 25 in build scripts and workflows. - Modified Gradle configurations to exclude unwanted transitive dependencies. - Adjusted workflows to use actions/checkout@v7 and relevant Java configurations. --- .github/workflows/build.yml | 8 ++++---- .github/workflows/release-draft.yml | 2 +- .github/workflows/release.yml | 6 +++--- build.gradle.kts | 26 ++++++++++++++++++-------- gradle/libs.versions.toml | 18 +++++++++--------- 5 files changed, 35 insertions(+), 25 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 912c8edd..21242e14 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout PR merge commit if: github.event_name == 'pull_request' - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: ref: refs/pull/${{ github.event.pull_request.number }}/merge fetch-depth: 0 @@ -24,15 +24,15 @@ jobs: - name: Checkout main on push if: github.event_name == 'push' - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 fetch-tags: true - - name: Set up JDK 21 + - name: Set up JDK 25 uses: actions/setup-java@v5 with: - java-version: '21' + java-version: '25' distribution: 'temurin' - name: Setup Gradle diff --git a/.github/workflows/release-draft.yml b/.github/workflows/release-draft.yml index 1bffe0b1..1dbe8e3e 100644 --- a/.github/workflows/release-draft.yml +++ b/.github/workflows/release-draft.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 fetch-tags: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 77990d16..84ef07d4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,16 +11,16 @@ jobs: contents: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 fetch-tags: true ref: ${{ github.event.release.tag_name }} - - name: Set up JDK 21 + - name: Set up JDK 25 uses: actions/setup-java@v5 with: - java-version: '21' + java-version: '25' distribution: 'temurin' - name: Setup Gradle diff --git a/build.gradle.kts b/build.gradle.kts index a9a7ff07..71a49ce2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -43,6 +43,9 @@ val paperNextEnabled = providers.gradleProperty("paperNext") .map(String::toBoolean) .orElse(false) +val guavaPackage = "com.google.guava" +val gsonPackage = "com.google.code.gson" + dependencies { implementation(libs.com.alpsbte.canvas) implementation(libs.com.alpsbte.alpslib.alpslib.io) @@ -52,10 +55,18 @@ dependencies { implementation(libs.com.zaxxer.hikaricp) { exclude(group = "org.slf4j") } - implementation(libs.com.github.querz.nbt) - implementation(platform(libs.com.intellectualsites.bom.bom.newest)) - compileOnly("com.fastasyncworldedit:FastAsyncWorldEdit-Core") - compileOnly(libs.com.sk89q.worldguard.worldguard.bukkit) + implementation(platform(libs.com.intellectualsites.bom.bom.newest)) { + exclude(group = guavaPackage) + exclude(group = gsonPackage) + } + compileOnly("com.fastasyncworldedit:FastAsyncWorldEdit-Core") { + exclude(group = guavaPackage) + exclude(group = gsonPackage) + } + compileOnly(libs.com.sk89q.worldguard.worldguard.bukkit) { + exclude(group = guavaPackage) + exclude(group = gsonPackage) + } compileOnly(libs.multiverse.core) compileOnly(libs.com.github.fierioziy.particlenativeapi.particlenativeapi.plugin) compileOnly(libs.com.arcaniax.headdatabase.api) @@ -122,7 +133,7 @@ tasks.processResources { val targetJava = providers.gradleProperty("targetJava") .map(String::toInt) - .orElse(21) + .orElse(25) java { toolchain { @@ -141,14 +152,13 @@ tasks.withType().configureEach { tasks.register("buildPaperNext") { group = "verification" - description = "Builds against Java 25 and the Paper-next dependency set" + description = "Builds against Paper-next dependency set" tasks = listOf("clean", "build") startParameter.projectProperties.putAll( mapOf( - "paperNext" to "true", - "targetJava" to "25" + "paperNext" to "true" ) ) } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 89e1ea11..84c2f379 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,27 +8,27 @@ com-alpsbte-alpslib-alpslib-hologram = "1.1.3" # https://mvn.alps-bte.com/service/rest/repository/browse/alps-bte/com/alpsbte/alpslib/alpslib-io/ com-alpsbte-alpslib-alpslib-io = "1.2.5" # https://mvn.alps-bte.com/service/rest/repository/browse/alps-bte/com/alpsbte/alpslib/alpslib-utils/ -com-alpsbte-alpslib-alpslib-utils = "1.4.6" +com-alpsbte-alpslib-alpslib-utils = "1.5.0" # https://mvn.alps-bte.com/service/rest/repository/browse/alps-bte/com/alpsbte/canvas/ -com-alpsbte-canvas = "1.3" +com-alpsbte-canvas = "1.3.2" # https://github.com/Arcaniax-Development/HeadDatabase-API/releases com-arcaniax-headdatabase-api = "1.3.3-SNAPSHOT" -# @pin https://github.com/DecentSoftware-eu/DecentHolograms/releases 2.10+ require java 25 (MC 26.1+ mainly) -com-github-decentsoftware-eu-decentholograms = "2.9.9" +# https://github.com/DecentSoftware-eu/DecentHolograms/releases +com-github-decentsoftware-eu-decentholograms = "2.10.0" # @pin https://github.com/Fierioziy/ParticleNativeAPI/releases - we are stuck at v3 for now. Need to migrate some time or remove it com-github-fierioziy-particlenativeapi-particlenativeapi-plugin = "3.3.2" # Ref: https://github.com/IntellectualSites/bom com-intellectualsites-bom-bom-newest = "1.56" # https://maven.enginehub.org/repo/com/sk89q/worldguard/worldguard-bukkit/ -com-sk89q-worldguard-worldguard-bukkit = "7.0.17" +com-sk89q-worldguard-worldguard-bukkit = "7.0.18-SNAPSHOT" # https://github.com/brettwooldridge/HikariCP/tags -com-zaxxer-hikaricp = "7.0.2" +com-zaxxer-hikaricp = "7.1.0" # Provided by Minecraft (Libs folder) commons-io-commons-io = "2.22.0" # https://fancyspaces.net/spaces/fancynpcs/maven-repos/fi-releases/de.oliver:FancyNpcs de-oliver-fancynpcs = "2.9.2" -# @pin https://artifactory.papermc.io/ui/native/universe/io/papermc/paper/paper-api/ 26.1+ requires java 25 -io-papermc-paper-paper-api = "1.21.11-R0.1-SNAPSHOT" +# @pin https://artifactory.papermc.io/ui/native/universe/io/papermc/paper/paper-api/ We want to use the latest api buld +io-papermc-paper-paper-api = "26.1.2.build.+" # https://mvn.alps-bte.com/service/rest/repository/browse/alps-bte/li/cinnazeyy/LangLibs-API/ li-cinnazeyy-langlibs-api = "1.5.2" # https://repo.onarandombox.com/#/multiverse-releases/org/mvplugins/multiverse/core/multiverse-core @@ -39,7 +39,7 @@ org-mariadb-jdbc-mariadb-java-client = "3.6.0-SNAPSHOT" # Plugins # # https://github.com/jmongard/Git.SemVersioning.Gradle/releases -git-semver = "0.19.1" +git-semver = "0.19.2" # https://github.com/GradleUp/shadow/releases shadow = "9.4.2" From 8a9e1805e843374084e39452ec0ab072d5fe061f Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sat, 16 May 2026 22:27:51 +0200 Subject: [PATCH 39/60] =?UTF-8?q?feat(plot-utils):=20=E2=9C=A8=20use=20Alp?= =?UTF-8?q?sLib=20MapLinks=20with=20configurable=20provider=20entries?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor plot link message generation to rely on AlpsLib `MapLinks` and iterate providers via `getAllLinks()` instead of hardcoded provider slots. - Replace fixed Google/OSM/Apple link handling with dynamic provider iteration - Build chat link components from resolved `MapLink` entries - Keep shortlink behavior for each generated provider URL - Add plugin-side config mapping for `map-links.links` into `MapLinksConfig` and `MapLinkEntryConfig` so providers can be disabled/extended without plugin code changes - Add `ConfigPaths.MAP_LINKS` - Document optional `map-links` config section in `config.yml` This keeps provider definitions in AlpsLib defaults while allowing server-side overrides (enable/disable/add providers) through plugin config. Implements #177. --- gradle/libs.versions.toml | 2 +- .../core/system/plot/utils/PlotUtils.java | 123 +++++++++++------- .../plotsystem/utils/io/ConfigPaths.java | 3 + .../plotsystem/utils/io/ConfigUtil.java | 2 +- src/main/resources/config.yml | 24 +++- 5 files changed, 107 insertions(+), 47 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d8343991..a76e18e6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ com-alpsbte-alpslib-alpslib-hologram = "1.1.3" # https://mvn.alps-bte.com/service/rest/repository/browse/alps-bte/com/alpsbte/alpslib/alpslib-io/ com-alpsbte-alpslib-alpslib-io = "1.2.5" # https://mvn.alps-bte.com/service/rest/repository/browse/alps-bte/com/alpsbte/alpslib/alpslib-utils/ -com-alpsbte-alpslib-alpslib-utils = "1.4.6" +com-alpsbte-alpslib-alpslib-utils = "1.5.0" # https://mvn.alps-bte.com/service/rest/repository/browse/alps-bte/com/alpsbte/canvas/ com-alpsbte-canvas = "1.3" # https://github.com/Arcaniax-Development/HeadDatabase-API/releases diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java index db053619..83aea3c4 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java @@ -1,5 +1,9 @@ package com.alpsbte.plotsystem.core.system.plot.utils; +import com.alpsbte.alpslib.utils.MapLink; +import com.alpsbte.alpslib.utils.MapLinkEntryConfig; +import com.alpsbte.alpslib.utils.MapLinks; +import com.alpsbte.alpslib.utils.MapLinksConfig; import com.alpsbte.plotsystem.PlotSystem; import com.alpsbte.plotsystem.commands.plot.CMD_Plot_Members; import com.alpsbte.plotsystem.core.database.DataProvider; @@ -25,8 +29,6 @@ import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter; -import com.sk89q.worldedit.function.mask.BlockTypeMask; -import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.operation.ForwardExtentCopy; import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.math.BlockVector2; @@ -34,9 +36,6 @@ import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Polygonal2DRegion; -import com.sk89q.worldedit.world.block.BlockTypes; -import com.sk89q.worldguard.WorldGuard; -import com.sk89q.worldguard.protection.managers.RegionManager; import com.sk89q.worldguard.protection.regions.ProtectedRegion; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; @@ -44,6 +43,7 @@ import org.bukkit.Location; import org.bukkit.Particle; import org.bukkit.World; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitTask; import org.jetbrains.annotations.NotNull; @@ -57,7 +57,9 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.text.DecimalFormat; +import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -384,46 +386,30 @@ private ChatFormatting() {} public static void sendLinkMessages(AbstractPlot plot, Player player) { Bukkit.getScheduler().runTaskAsynchronously(PlotSystem.getPlugin(), () -> { - Component[] tc = new Component[4]; - - String shortLinkGoogleMaps = null; - String shortLinkGoogleEarth = null; - String shortLinkOSM = null; - String shortLinkAppleLookAround = null; - String googleMaps = " Google Maps "; - String googleEarthWeb = " Google Earth Web "; - String openStreetMap = " Open Street Map "; - String appleLookAround = " Apple Maps Look Around "; + List linkMessages; try { - if (PlotSystem.getPlugin().getConfig().getBoolean(ConfigPaths.SHORTLINK_ENABLE)) { - shortLinkGoogleMaps = ShortLink.generateShortLink(plot.getGoogleMapsLink()); - tc[0] = text("» ", DARK_GRAY).append(LangUtil.getInstance().getComponent(player.getUniqueId(), LangPaths.Note.Action.CLICK_TO_OPEN_LINK_WITH_SHORTLINK, GRAY, text(googleMaps, GREEN), text(shortLinkGoogleMaps, GREEN))); - shortLinkGoogleEarth = ShortLink.generateShortLink(plot.getGoogleEarthLink()); - tc[1] = text("» ", DARK_GRAY).append(LangUtil.getInstance().getComponent(player.getUniqueId(), LangPaths.Note.Action.CLICK_TO_OPEN_LINK_WITH_SHORTLINK, GRAY, text(googleEarthWeb, GREEN), text(shortLinkGoogleEarth, GREEN))); - shortLinkOSM = ShortLink.generateShortLink(plot.getOSMMapsLink()); - tc[2] = text("» ", DARK_GRAY).append(LangUtil.getInstance().getComponent(player.getUniqueId(), LangPaths.Note.Action.CLICK_TO_OPEN_LINK_WITH_SHORTLINK, GRAY, text(openStreetMap, GREEN), text(shortLinkOSM, GREEN))); - shortLinkAppleLookAround = ShortLink.generateShortLink(plot.getAppleLookAroundLink()); - tc[3] = text("» ", DARK_GRAY).append(LangUtil.getInstance().getComponent(player.getUniqueId(), LangPaths.Note.Action.CLICK_TO_OPEN_LINK_WITH_SHORTLINK, GRAY, text(appleLookAround, GREEN), text(shortLinkAppleLookAround, GREEN))); - } else { - tc[0] = text("» ", DARK_GRAY).append(LangUtil.getInstance().getComponent(player.getUniqueId(), LangPaths.Note.Action.CLICK_TO_OPEN_LINK, GRAY, text(googleMaps, GREEN))); - tc[1] = text("» ", DARK_GRAY).append(LangUtil.getInstance().getComponent(player.getUniqueId(), LangPaths.Note.Action.CLICK_TO_OPEN_LINK, GRAY, text(googleEarthWeb, GREEN))); - tc[2] = text("» ", DARK_GRAY).append(LangUtil.getInstance().getComponent(player.getUniqueId(), LangPaths.Note.Action.CLICK_TO_OPEN_LINK, GRAY, text(openStreetMap, GREEN))); - tc[3] = text("» ", DARK_GRAY).append(LangUtil.getInstance().getComponent(player.getUniqueId(), LangPaths.Note.Action.CLICK_TO_OPEN_LINK, GRAY, text(appleLookAround, GREEN))); + String[] coordsSplit = plot.getGeoCoordinates().split(","); + double lat = Double.parseDouble(coordsSplit[0]); + double lon = Double.parseDouble(coordsSplit[1]); + + MapLinksConfig mapLinksConfig = getMapLinksConfigFromPlugin(); + MapLinks mapLinks = mapLinksConfig == null ? new MapLinks(lat, lon) : new MapLinks(lat, lon, mapLinksConfig); + + if (mapLinks.getAllLinks().isEmpty()) { + PlotSystem.getPlugin().getComponentLogger().warn(text("No map links are enabled for plot #" + plot.getId())); + return; } - tc[0] = tc[0].clickEvent(ClickEvent.openUrl((shortLinkGoogleMaps != null) ? shortLinkGoogleMaps : plot.getGoogleMapsLink())); - tc[1] = tc[1].clickEvent(ClickEvent.openUrl((shortLinkGoogleEarth != null) ? shortLinkGoogleEarth : plot.getGoogleEarthLink())); - tc[2] = tc[2].clickEvent(ClickEvent.openUrl((shortLinkOSM != null) ? shortLinkOSM : plot.getOSMMapsLink())); - tc[3] = tc[3].clickEvent(ClickEvent.openUrl((shortLinkAppleLookAround != null) ? shortLinkAppleLookAround : plot.getAppleLookAroundLink())); - } catch (IOException | URISyntaxException ex) { - PlotSystem.getPlugin().getComponentLogger().error(text("An error occurred while creating short link!"), ex); + boolean shortLinksEnabled = PlotSystem.getPlugin().getConfig().getBoolean(ConfigPaths.SHORTLINK_ENABLE); + linkMessages = new ArrayList<>(mapLinks.getAllLinks().size()); + for (MapLink link : mapLinks.getAllLinks()) { + linkMessages.add(buildMapLinkMessage(player, link.name(), link.url(), shortLinksEnabled)); + } + } catch (IOException | URISyntaxException | NumberFormatException | NullPointerException ex) { + PlotSystem.getPlugin().getComponentLogger().error(text("An error occurred while creating map links!"), ex); + return; } - tc[0] = tc[0].hoverEvent(text(googleMaps)); - tc[1] = tc[1].hoverEvent(text(googleEarthWeb)); - tc[2] = tc[2].hoverEvent(text(openStreetMap)); - tc[3] = tc[3].hoverEvent(text(appleLookAround)); - // Temporary fix for bedrock players Component coords = null; try { @@ -442,16 +428,65 @@ public static void sendLinkMessages(AbstractPlot plot, Player player) { player.sendMessage(text(MSG_LINE, DARK_GRAY)); if (coords != null) player.sendMessage(text("Coords: ", GRAY).append(coords)); - player.sendMessage(tc[0]); - player.sendMessage(tc[1]); - player.sendMessage(tc[2]); - player.sendMessage(tc[3]); + for (Component linkMessage : linkMessages) { + player.sendMessage(linkMessage); + } player.sendMessage(text(MSG_LINE, DARK_GRAY)); if (plot instanceof Plot p) sendGroupTipMessage(p, player); }); } + private static @NotNull Component buildMapLinkMessage(@NotNull Player player, @NotNull String providerName, @NotNull String targetUrl, + boolean shortLinksEnabled) throws IOException, URISyntaxException { + String providerLabel = " " + providerName + " "; + Component message; + String clickUrl = targetUrl; + + if (shortLinksEnabled) { + clickUrl = ShortLink.generateShortLink(targetUrl); + message = text("» ", DARK_GRAY) + .append(LangUtil.getInstance().getComponent(player.getUniqueId(), LangPaths.Note.Action.CLICK_TO_OPEN_LINK_WITH_SHORTLINK, + GRAY, text(providerLabel, GREEN), text(clickUrl, GREEN))); + } else { + message = text("» ", DARK_GRAY) + .append(LangUtil.getInstance().getComponent(player.getUniqueId(), LangPaths.Note.Action.CLICK_TO_OPEN_LINK, + GRAY, text(providerLabel, GREEN))); + } + + return message + .clickEvent(ClickEvent.openUrl(clickUrl)) + .hoverEvent(text(providerLabel)); + } + + private static @Nullable MapLinksConfig getMapLinksConfigFromPlugin() { + ConfigurationSection linksRoot = PlotSystem.getPlugin().getConfig().getConfigurationSection(ConfigPaths.MAP_LINKS); + if (linksRoot == null) { + return null; + } + + ConfigurationSection linksSection = linksRoot.getConfigurationSection("links"); + if (linksSection == null) { + return null; + } + + Map links = new LinkedHashMap<>(); + for (String key : linksSection.getKeys(false)) { + ConfigurationSection entry = linksSection.getConfigurationSection(key); + if (entry == null) { + continue; + } + + String id = entry.getString("id", key); + String name = entry.getString("name"); + String template = entry.getString("url-template", entry.getString("urlTemplate")); + Boolean enabled = entry.isSet("enabled") ? entry.getBoolean("enabled") : null; + links.put(key, new MapLinkEntryConfig(id, name, template, enabled)); + } + + return links.isEmpty() ? null : new MapLinksConfig(links); + } + public static void sendGroupTipMessage(@NotNull Plot plot, Player player) { if (plot.getPlotMembers().isEmpty() && PlotSystem.getPlugin().getConfig().getBoolean(ConfigPaths.ENABLE_GROUP_SUPPORT) && player.hasPermission(CMD_Plot_Members.PERMISSION)) { diff --git a/src/main/java/com/alpsbte/plotsystem/utils/io/ConfigPaths.java b/src/main/java/com/alpsbte/plotsystem/utils/io/ConfigPaths.java index 0f263ca1..dfd2cb39 100644 --- a/src/main/java/com/alpsbte/plotsystem/utils/io/ConfigPaths.java +++ b/src/main/java/com/alpsbte/plotsystem/utils/io/ConfigPaths.java @@ -44,6 +44,9 @@ public abstract class ConfigPaths { public static final String SHORTLINK_APIKEY = SHORTLINK + "apikey"; public static final String SHORTLINK_HOST = SHORTLINK + "host"; + // MAP LINKS + public static final String MAP_LINKS = "map-links"; + // TUTORIALS private static final String TUTORIALS = "tutorials."; public static final String TUTORIAL_ENABLE = TUTORIALS + "tutorial-enable"; diff --git a/src/main/java/com/alpsbte/plotsystem/utils/io/ConfigUtil.java b/src/main/java/com/alpsbte/plotsystem/utils/io/ConfigUtil.java index 45dbd18e..85dbe948 100644 --- a/src/main/java/com/alpsbte/plotsystem/utils/io/ConfigUtil.java +++ b/src/main/java/com/alpsbte/plotsystem/utils/io/ConfigUtil.java @@ -20,7 +20,7 @@ private ConfigUtil() {} public static void init() throws ConfigNotImplementedException { if (configUtilInstance == null) { configUtilInstance = new ConfigurationUtil(new ConfigurationUtil.ConfigFile[]{ - new ConfigurationUtil.ConfigFile(Paths.get("config.yml"), 4.2, true), + new ConfigurationUtil.ConfigFile(Paths.get("config.yml"), 4.3, true), new ConfigurationUtil.ConfigFile(Paths.get("commands.yml"), 1.1, false), new ConfigurationUtil.ConfigFile(Paths.get("items.yml"), 1.3, false) }); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 1ef96243..06f92a8d 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -91,6 +91,28 @@ shortlink: host: https://your.shortlink.host +# ----------------------------------------------------- +# | Map Links +# | Optional overrides for provider links shown in /plot messages. +# | You can disable defaults or add custom providers. +# | Supported placeholders in url-template: {lat}, {lon} +# ----------------------------------------------------- +# map-links: +# links: +# googleMaps: +# enabled: true +# googleEarth: +# enabled: true +# osm: +# enabled: true +# appleLookAround: +# enabled: true +# bingMaps: +# name: "Bing Maps" +# url-template: "https://www.bing.com/maps?q={lat},{lon}" +# enabled: true + + # ----------------------------------------------------- # | Tutorials # ----------------------------------------------------- @@ -104,4 +126,4 @@ tutorials: # NOTE: Do not change -config-version: 4.2 \ No newline at end of file +config-version: 4.3 \ No newline at end of file From 3eda352462fd206d3d9a89b076db7d0558d33d74 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Tue, 25 Aug 2026 14:18:46 +0200 Subject: [PATCH 40/60] fix: Switch to new keyed world structure --- src/main/java/com/alpsbte/plotsystem/PlotSystem.java | 5 ++--- .../plot/generator/world/SkeletonWorldGenerator.java | 5 +++-- .../plotsystem/core/system/plot/world/PlotWorld.java | 9 ++++++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java index 93c1a623..529a3de3 100644 --- a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java +++ b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java @@ -133,9 +133,8 @@ public void onEnable() { } // Generate Skeleton World - if (Bukkit.getWorld("Skeleton") == null) { - getComponentLogger().info("No skeleton world found!"); - getComponentLogger().info("Generating skeleton world..."); + if (Bukkit.getWorld(SkeletonWorldGenerator.WORLD_KEY) == null) { + getComponentLogger().info("No skeleton world found! Generating skeleton world..."); new SkeletonWorldGenerator(); } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java index a26fe069..e25d270a 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java @@ -26,6 +26,7 @@ import org.bukkit.GameRules; import org.bukkit.Location; +import org.bukkit.NamespacedKey; import org.bukkit.World; import org.bukkit.WorldCreator; import org.bukkit.WorldType; @@ -36,7 +37,7 @@ import java.util.Random; public class SkeletonWorldGenerator { - public static final String WORLD_NAME = "Skeleton"; + public static final NamespacedKey WORLD_KEY = NamespacedKey.minecraft("skeleton"); private static final World.Environment ENVIRONMENT = World.Environment.NORMAL; private static final WorldType WORLD_TYPE = WorldType.FLAT; private static final String GENERATOR_SETTINGS = "{\"features\": false,\"layers\": [{\"block\": \"air\", \"height\": 1}],\"biome\":\"plains\"}"; @@ -50,7 +51,7 @@ public SkeletonWorldGenerator() { } protected void generateWorld() { - WorldCreator worldCreator = new WorldCreator(WORLD_NAME) + WorldCreator worldCreator = WorldCreator.ofKey(WORLD_KEY) .environment(ENVIRONMENT) .type(WORLD_TYPE) .generator(new SkeletonWorldGenerator.EmptyChunkGenerator()) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java index 07c878c5..1f62a151 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java @@ -51,7 +51,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.mvplugins.multiverse.core.MultiverseCoreApi; +import org.mvplugins.multiverse.core.world.MultiverseWorld; import org.mvplugins.multiverse.core.world.options.DeleteWorldOptions; +import org.mvplugins.multiverse.core.world.options.LoadWorldOptions; +import org.mvplugins.multiverse.external.vavr.control.Option; import java.io.ByteArrayInputStream; import java.io.File; @@ -118,7 +121,11 @@ public boolean loadWorld() { if (isWorldLoaded()) return true; try { - return Utils.supplySync(() -> mvCore.getWorldManager().loadWorld(getWorldName()).isSuccess()).get(); + return Utils.supplySync(() -> { + Option world = mvCore.getWorldManager().getWorld(getWorldName()); + if (world.isEmpty()) return false; + return mvCore.getWorldManager().loadWorld(LoadWorldOptions.world(world.get())).isSuccess(); + }).get(); } catch (Exception e) { PlotSystem.getPlugin().getComponentLogger().warn(text("Could not load world " + worldName + "!"), e); return false; From e7d82b08f6935b003726ed3c1cebdbed80bbe0a5 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Tue, 25 Aug 2026 20:23:05 +0200 Subject: [PATCH 41/60] fix: Use Multiverse for skeleton world --- src/main/java/com/alpsbte/plotsystem/PlotSystem.java | 3 ++- .../plot/generator/world/PlotWorldGenerator.java | 10 +++++++++- .../plot/generator/world/SkeletonWorldGenerator.java | 3 +++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java index 529a3de3..41bda4c3 100644 --- a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java +++ b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java @@ -21,6 +21,7 @@ import com.alpsbte.plotsystem.core.system.tutorial.TutorialEventListener; import com.alpsbte.plotsystem.core.system.tutorial.utils.TutorialNPCTurnTracker; import com.alpsbte.plotsystem.core.system.tutorial.utils.TutorialUtils; +import com.alpsbte.plotsystem.utils.DependencyManager; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.io.ConfigPaths; import com.alpsbte.plotsystem.utils.io.ConfigUtil; @@ -133,7 +134,7 @@ public void onEnable() { } // Generate Skeleton World - if (Bukkit.getWorld(SkeletonWorldGenerator.WORLD_KEY) == null) { + if (!DependencyManager.getMultiverseCore().getWorldManager().isWorld(SkeletonWorldGenerator.WORLD_KEY.value())) { getComponentLogger().info("No skeleton world found! Generating skeleton world..."); new SkeletonWorldGenerator(); } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java index 3bf2d8cd..1331d07d 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java @@ -83,12 +83,16 @@ public PlotWorldGenerator(String worldName) throws Exception { } protected void generateWorld() throws IOException { - Path skeletonPath = Objects.requireNonNull(Bukkit.getWorld("Skeleton"), "Skeleton World is required at this point").getWorldPath(); + Path skeletonPath = worldManager.getWorld(SkeletonWorldGenerator.WORLD_KEY.value()).get().getOfflineWorldFolder().toPath(); Path worldLevelPath = skeletonPath.getParent().resolve(worldName.toLowerCase(Locale.ROOT)); FileUtils.copyDirectory(skeletonPath.toFile(), worldLevelPath.toFile(), new NotFileFilter(new NameFileFilter("metadata.dat"))); } protected void createMultiverseWorld() { + createMultiverseWorld(worldManager, worldName); + } + + protected static void createMultiverseWorld(WorldManager worldManager, String worldName) { if (!worldManager.isLoadedWorld(worldName)) { worldManager.importWorld(ImportWorldOptions.worldKey(NamespacedKey.minecraft(worldName.toLowerCase(Locale.ROOT))) .environment(environment) @@ -99,6 +103,10 @@ protected void createMultiverseWorld() { } protected void configureWorld() { + configureWorld(worldManager, worldName); + } + + protected static void configureWorld(WorldManager worldManager, String worldName) { World world = Bukkit.getWorld(worldName); assert world != null; Option mvWorld = worldManager.getLoadedWorld(worldName); diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java index e25d270a..e87f76f6 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java @@ -24,6 +24,7 @@ package com.alpsbte.plotsystem.core.system.plot.generator.world; +import com.alpsbte.plotsystem.utils.DependencyManager; import org.bukkit.GameRules; import org.bukkit.Location; import org.bukkit.NamespacedKey; @@ -46,7 +47,9 @@ public class SkeletonWorldGenerator { public SkeletonWorldGenerator() { generateWorld(); + PlotWorldGenerator.createMultiverseWorld(DependencyManager.getMultiverseCore().getWorldManager(), this.world.getName()); configureWorld(); + PlotWorldGenerator.configureWorld(DependencyManager.getMultiverseCore().getWorldManager(), this.world.getName()); saveWorld(); } From 791938565fe50e5dfb921ff482bb59cbbbb81ec7 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Tue, 25 Aug 2026 21:47:50 +0200 Subject: [PATCH 42/60] fix(plot): make world generation lifecycle safe and asynchronous Persist plot state consistently, prevent duplicate generation, and move heavy WorldEdit work onto FAWE's async executor. --- .../commands/admin/CMD_DeletePlot.java | 8 +- .../commands/plot/CMD_Plot_Abandon.java | 11 +- .../commands/plot/CMD_Plot_Teleport.java | 1 + .../core/menus/PlotActionsMenu.java | 4 +- .../menus/review/ReviewPlotTogglesMenu.java | 38 ++-- .../core/system/plot/AbstractPlot.java | 50 +++-- .../core/system/plot/PlotHandler.java | 194 +++++++++++------- .../core/system/plot/TutorialPlot.java | 1 + .../generator/loader/AbstractPlotLoader.java | 98 +++++++-- .../generator/loader/DefaultPlotLoader.java | 29 ++- .../core/system/plot/utils/PlotUtils.java | 28 +-- .../core/system/plot/world/CityPlotWorld.java | 86 ++++---- .../core/system/plot/world/OnePlotWorld.java | 44 ++-- .../core/system/plot/world/PlotWorld.java | 13 +- 14 files changed, 375 insertions(+), 230 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_DeletePlot.java b/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_DeletePlot.java index 7d325b24..0224e631 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_DeletePlot.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_DeletePlot.java @@ -43,11 +43,11 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N } sender.sendMessage(Utils.ChatUtils.getInfoFormat("Deleting plot...")); + if (!PlotHandler.deletePlot(plot)) { + sender.sendMessage(Utils.ChatUtils.getAlertFormat("An unexpected error has occurred!")); + return; + } Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - if (!PlotHandler.deletePlot(plot)) { - sender.sendMessage(Utils.ChatUtils.getAlertFormat("An unexpected error has occurred!")); - return; - } sender.sendMessage(Utils.ChatUtils.getInfoFormat("Successfully deleted plot with the ID §6#" + plotID + "§a!")); if (getPlayer(sender) != null) getPlayer(sender).playSound(getPlayer(sender).getLocation(), Utils.SoundUtils.DONE_SOUND, 1f, 1f); }); diff --git a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Abandon.java b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Abandon.java index c06a6b09..c35ca18c 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Abandon.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Abandon.java @@ -67,11 +67,12 @@ public void onCommand(CommandSender sender, String[] args) { return; } - Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - if (!PlotHandler.abandonPlot(plot)) return; - sender.sendMessage(Utils.ChatUtils.getInfoFormat(langUtil.get(sender, LangPaths.Message.Info.ABANDONED_PLOT, plot.getId() + ""))); - player.playSound(player.getLocation(), Utils.SoundUtils.ABANDON_PLOT_SOUND, 1, 1); - }); + if (PlotHandler.abandonPlot(plot)) { + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + sender.sendMessage(Utils.ChatUtils.getInfoFormat(langUtil.get(sender, LangPaths.Message.Info.ABANDONED_PLOT, plot.getId() + ""))); + player.playSound(player.getLocation(), Utils.SoundUtils.ABANDON_PLOT_SOUND, 1, 1); + }); + } }); } diff --git a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Teleport.java b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Teleport.java index b879c81b..18ae0a0e 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Teleport.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Teleport.java @@ -67,6 +67,7 @@ public void onCommand(CommandSender sender, String[] args) { return; } + if (!plot.getWorld().loadWorld()) return; Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> plot.getWorld().teleportPlayer(player)); }); } diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/PlotActionsMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/PlotActionsMenu.java index 31c452cf..0090b81e 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/menus/PlotActionsMenu.java +++ b/src/main/java/com/alpsbte/plotsystem/core/menus/PlotActionsMenu.java @@ -91,7 +91,7 @@ protected void setMenuItemsAsync() { .setLore(new LoreBuilder().addLine(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.MenuDescription.FEEDBACK), true).build()) .build()); } - + // Set plot members item if (!plot.isReviewed() && config.getBoolean(ConfigPaths.ENABLE_GROUP_SUPPORT)) { if ((getMenuPlayer() == plot.getPlotOwner().getPlayer() || getMenuPlayer().hasPermission("plotsystem.admin"))) { @@ -136,7 +136,7 @@ protected void setItemClickEventsAsync() { return; } CompletableFuture.runAsync(() -> { - if (!plot.getWorld().isWorldGenerated() && !plot.getWorld().loadWorld()) return; + if (!plot.getWorld().loadWorld()) return; Utils.runSync(() -> { plot.getWorld().teleportPlayer(clickPlayer); diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java index 2d837e4e..17b4c279 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java +++ b/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java @@ -34,6 +34,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import static net.kyori.adventure.text.Component.text; @@ -82,7 +83,8 @@ protected void setItemClickEventsAsync() { })); // Set click event for submit item - getMenu().getSlot(50).setClickHandler(((player, clickInformation) -> submitReview())); + getMenu().getSlot(50).setClickHandler(((player, clickInformation) -> + CompletableFuture.runAsync(this::submitReview))); // Set click event for toggle items for (int i = 0; i < Math.min(buildTeamCriteria.size(), 36); i++) { @@ -129,7 +131,7 @@ private void submitReview() { Component reviewerConfirmationMessage; if (!isRejected) { reviewerConfirmationMessage = Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Message.Info.PLOT_MARKED_REVIEWED, Integer.toString(plot.getId()), getParticipantsString())); - if(!acceptPlot(review.getScore(), review.getSplitScore())) return; + if (!acceptPlot(review.getScore(), review.getSplitScore())) return; } else { reviewerConfirmationMessage = Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Message.Info.PLOT_REJECTED, Integer.toString(plot.getId()), getParticipantsString())); PlotHandler.undoSubmit(plot); @@ -170,19 +172,25 @@ private String getParticipantsString() { } private boolean acceptPlot(int score, int splitScore) { - getMenuPlayer().sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Message.Info.SAVING_PLOT))); - Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - try { - if (!PlotHandler.savePlotAsSchematic(plot)) { + Utils.runSync(() -> { + getMenuPlayer().sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Message.Info.SAVING_PLOT))); + return null; + }); + try { + if (!PlotHandler.savePlotAsSchematic(plot)) { + Utils.runSync(() -> { getMenuPlayer().sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Message.Error.ERROR_OCCURRED))); - PlotSystem.getPlugin().getComponentLogger().warn(text("Could not save finished plot schematic (ID: " + plot.getId() + ")!")); - } - } catch (IOException | WorldEditException | ExecutionException | InterruptedException ex) { - PlotSystem.getPlugin().getComponentLogger().error(text("Could not save finished plot schematic (ID: " + plot.getId() + ")!"), ex); + return null; + }); + PlotSystem.getPlugin().getComponentLogger().warn(text("Could not save finished plot schematic (ID: " + plot.getId() + ")!")); + return false; } - }); + } catch (IOException | WorldEditException | ExecutionException | InterruptedException ex) { + PlotSystem.getPlugin().getComponentLogger().error(text("Could not save finished plot schematic (ID: " + plot.getId() + ")!"), ex); + return false; + } - plot.setStatus(Status.completed); + if (!plot.setStatus(Status.completed)) return false; // Remove Plot from Owner if (!plot.getPlotOwner().setSlot(plot.getPlotOwner().getSlotByPlotId(plot.getId()), -1)) return false; @@ -250,11 +258,11 @@ private ItemStack getSubmitItem() { .setLore(new LoreBuilder() .addLine(text(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.MenuDescription.SUBMIT_REVIEW), NamedTextColor.GRAY), true) .emptyLine() - .addLine(text( accuracyPointsText + ": ", NamedTextColor.GRAY) + .addLine(text(accuracyPointsText + ": ", NamedTextColor.GRAY) .append(text(rating.getAccuracyPoints(), NamedTextColor.WHITE))) - .addLine(text( blockPalettePointsText + ": ", NamedTextColor.GRAY) + .addLine(text(blockPalettePointsText + ": ", NamedTextColor.GRAY) .append(text(rating.getBlockPalettePoints(), NamedTextColor.WHITE))) - .addLine(text( togglesPointsText + ": ", NamedTextColor.GRAY) + .addLine(text(togglesPointsText + ": ", NamedTextColor.GRAY) .append(text(togglePoints, NamedTextColor.WHITE)) .append(text(" (" + checkedToggles + "/" + totalToggles + " → " + String.format("%.02f", togglePercentage) + "%)", NamedTextColor.DARK_GRAY))) .addLine(text("-----", NamedTextColor.DARK_GRAY)) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/AbstractPlot.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/AbstractPlot.java index 70200825..f45ae52b 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/AbstractPlot.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/AbstractPlot.java @@ -17,7 +17,6 @@ import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; -import com.sk89q.worldedit.math.Vector3; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -45,6 +44,7 @@ public abstract class AbstractPlot { protected List outline; protected List blockOutline; + private volatile SchematicMetadata schematicMetadata; protected AbstractPlot(int id, UUID plotOwnerUUID) { this.id = id; @@ -67,6 +67,7 @@ public Builder getPlotOwner() { /** * sets the plot owner of the current plot + * * @param plotOwner uuid of player * @return if true, the execution was successful */ @@ -138,28 +139,49 @@ public String getGeoCoordinates() throws IOException { * @see com.alpsbte.plotsystem.utils.conversion.CoordinateConversion#convertFromGeo(double, double) */ public BlockVector3 getCoordinates() throws IOException { - ByteArrayInputStream inputStream = new ByteArrayInputStream(getInitialSchematicBytes()); - try (ClipboardReader reader = CLIPBOARD_FORMAT.getReader(inputStream)) { - Clipboard clipboard = reader.read(); - if (clipboard != null) return clipboard.getOrigin(); - } - return null; + return getSchematicMetadata().origin(); } public BlockVector3 getCenter() { - ByteArrayInputStream inputStream = new ByteArrayInputStream(getInitialSchematicBytes()); - try (ClipboardReader reader = CLIPBOARD_FORMAT.getReader(inputStream)){ - Clipboard clipboard = reader.read(); - if (clipboard != null) { - Vector3 clipboardCenter = clipboard.getRegion().getCenter(); - return BlockVector3.at(clipboardCenter.x(), this.getWorld().getPlotHeightCentered(), clipboardCenter.z()); - } + try { + return getSchematicMetadata().center().withY(this.getWorld().getPlotHeightCentered()); } catch (IOException ex) { PlotSystem.getPlugin().getComponentLogger().error(text("Failed to load schematic file to clipboard!"), ex); } return null; } + public SchematicMetadata getSchematicMetadata() throws IOException { + SchematicMetadata cachedMetadata = schematicMetadata; + if (cachedMetadata != null) return cachedMetadata; + + synchronized (this) { + if (schematicMetadata != null) return schematicMetadata; + try (ClipboardReader reader = CLIPBOARD_FORMAT.getReader(new ByteArrayInputStream(getInitialSchematicBytes()))) { + Clipboard clipboard = reader.read(); + if (clipboard == null) throw new IOException("Could not read schematic metadata"); + schematicMetadata = new SchematicMetadata( + clipboard.getOrigin(), + clipboard.getMinimumPoint(), + clipboard.getMaximumPoint(), + clipboard.getRegion().getCenter().toBlockPoint() + ); + return schematicMetadata; + } + } + } + + protected void invalidateSchematicMetadata() { + schematicMetadata = null; + } + + public record SchematicMetadata( + BlockVector3 origin, + BlockVector3 minimumPoint, + BlockVector3 maximumPoint, + BlockVector3 center + ) {} + /** * @return plot permission manager to add or remove build rights */ diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java index 05cbf1fc..851c3d51 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java @@ -62,7 +62,6 @@ import com.sk89q.worldedit.regions.Polygonal2DRegion; import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.block.BlockTypes; -import org.bukkit.Bukkit; import org.bukkit.SoundCategory; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; @@ -73,11 +72,11 @@ import java.io.IOException; import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import static net.kyori.adventure.text.Component.text; @@ -85,30 +84,50 @@ public class PlotHandler { private PlotHandler() {} - private static final Map playerPlotGenerationHistory = new HashMap<>(); + private static final Map playerPlotGenerationHistory = new ConcurrentHashMap<>(); - public static boolean assignPlot(Builder builder, Plot plot) { + private static boolean assignPlot(Builder builder, Plot plot, PlotType plotType) { Player player = builder.getPlayer(); // Score Requirement met? if (PlotSystem.getPlugin().getConfig().getBoolean(ConfigPaths.ENABLE_SCORE_REQUIREMENT) && !DataProvider.DIFFICULTY.builderMeetsRequirements(builder, plot.getDifficulty())) { - player.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(player, LangPaths.Message.Error.PLAYER_NEEDS_HIGHER_SCORE))); - player.playSound(player.getLocation(), Utils.SoundUtils.ERROR_SOUND, SoundCategory.MASTER, 1, 1, 0); + if (player != null) { + player.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(player, LangPaths.Message.Error.PLAYER_NEEDS_HIGHER_SCORE))); + player.playSound(player.getLocation(), Utils.SoundUtils.ERROR_SOUND, SoundCategory.MASTER, 1, 1, 0); + } return false; } // Slot available? Slot freeSlot = builder.getFreeSlot(); if (freeSlot == null) { - player.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(player, LangPaths.Message.Error.ALL_SLOTS_OCCUPIED))); - player.playSound(player.getLocation(), Utils.SoundUtils.ERROR_SOUND, SoundCategory.MASTER, 1, 1, 0); + if (player != null) { + player.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(player, LangPaths.Message.Error.ALL_SLOTS_OCCUPIED))); + player.playSound(player.getLocation(), Utils.SoundUtils.ERROR_SOUND, SoundCategory.MASTER, 1, 1, 0); + } return false; } // Assign + Status previousStatus = plot.getStatus(); + PlotType previousType = plot.getPlotType(); if (!builder.setSlot(freeSlot, plot.getId())) return false; - if (!plot.setStatus(Status.unfinished)) return false; - return plot.setPlotOwner(builder); + if (!plot.setStatus(Status.unfinished)) { + builder.setSlot(freeSlot, -1); + return false; + } + if (!plot.setPlotType(plotType)) { + plot.setStatus(previousStatus); + builder.setSlot(freeSlot, -1); + return false; + } + if (!plot.setPlotOwner(builder)) { + plot.setPlotType(previousType); + plot.setStatus(previousStatus); + builder.setSlot(freeSlot, -1); + return false; + } + return true; } public static boolean assignAndGeneratePlot(Builder builder, Plot plot) { @@ -116,10 +135,15 @@ public static boolean assignAndGeneratePlot(Builder builder, Plot plot) { if (type.equals(PlotType.CITY_INSPIRATION_MODE) && ConfigUtil.getInstance().configs[0].getBoolean(ConfigPaths.DISABLE_CITY_INSPIRATION_MODE)) type = PlotType.LOCAL_INSPIRATION_MODE; - boolean successful = assignPlot(builder, plot); - if (successful) generatePlot(builder, plot, type); + if (!tryStartGeneration(builder)) return false; - return successful; + boolean assigned = assignPlot(builder, plot, type); + if (!assigned) { + removePlayerFromGenerationHistory(builder.getUUID()); + return false; + } + + return generatePlot(builder, plot, type); } public static boolean assignAndGenerateRandomPlot(Builder builder, CityProject city, PlotDifficulty difficulty) { @@ -128,61 +152,78 @@ public static boolean assignAndGenerateRandomPlot(Builder builder, CityProject c return assignAndGeneratePlot(builder, randomPlot); } - public static void generatePlot(Builder builder, Plot plot, PlotType type) { + public static boolean generatePlot(Builder builder, Plot plot, PlotType type) { Player player = builder.getPlayer(); - // Cooldown - if (playerPlotGenerationHistory.containsKey(builder.getUUID())) { - if (!playerPlotGenerationHistory.get(builder.getUUID()).isBefore(LocalDateTime.now().minusSeconds(10))) { - player.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(player, LangPaths.Message.Error.PLEASE_WAIT))); - player.playSound(player.getLocation(), Utils.SoundUtils.ERROR_SOUND, SoundCategory.MASTER, 1, 1, 0); - return; - } - playerPlotGenerationHistory.remove(builder.getUUID()); + if (player != null) { + Utils.runSync(() -> { + player.sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(player, LangPaths.Message.Info.CREATING_PLOT))); + player.playSound(player.getLocation(), Utils.SoundUtils.CREATE_PLOT_SOUND, SoundCategory.MASTER, 1, 1, 0); + return null; + }); } - playerPlotGenerationHistory.put(builder.getUUID(), LocalDateTime.now()); - player.sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(player, LangPaths.Message.Info.CREATING_PLOT))); - player.playSound(player.getLocation(), Utils.SoundUtils.CREATE_PLOT_SOUND, SoundCategory.MASTER, 1, 1, 0); + DefaultPlotLoader loader = new DefaultPlotLoader(plot, builder, type, PlotWorld.getByType(type, plot)); + return loader.isSuccessful(); + } - new DefaultPlotLoader(plot, builder, type, PlotWorld.getByType(type, plot)); + private static boolean tryStartGeneration(Builder builder) { + LocalDateTime now = LocalDateTime.now(); + synchronized (playerPlotGenerationHistory) { + LocalDateTime lastGeneration = playerPlotGenerationHistory.get(builder.getUUID()); + if (lastGeneration != null && !lastGeneration.isBefore(now.minusSeconds(10))) { + Player player = builder.getPlayer(); + if (player == null) return false; + Utils.runSync(() -> { + player.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(player, LangPaths.Message.Error.PLEASE_WAIT))); + player.playSound(player.getLocation(), Utils.SoundUtils.ERROR_SOUND, SoundCategory.MASTER, 1, 1, 0); + return null; + }); + return false; + } + playerPlotGenerationHistory.put(builder.getUUID(), now); + return true; + } } @SuppressWarnings("BooleanMethodIsAlwaysInverted") public static boolean abandonPlot(AbstractPlot plot) { - boolean successfullyAbandoned = plot.getWorld().onAbandon(); - if (!successfullyAbandoned) { - PlotSystem.getPlugin().getComponentLogger().error(text("Failed to abandon plot with the ID " + plot.getId() + "!")); + try { + boolean successfullyAbandoned = plot.getWorld().onAbandon(); + if (!successfullyAbandoned) { + PlotSystem.getPlugin().getComponentLogger().error(text("Failed to abandon plot with the ID " + plot.getId() + "!")); + return false; + } + } catch (Exception exception) { + PlotSystem.getPlugin().getComponentLogger().error(text("Failed to clean up the world for plot " + plot.getId() + "!"), exception); return false; } - CompletableFuture.runAsync(() -> { - if (plot.getPlotType() == PlotType.TUTORIAL) return; - Plot dPlot = (Plot) plot; - boolean successful; - successful = DataProvider.REVIEW.removeAllReviewsOfPlot(dPlot.getId()); + if (plot.getPlotType() == PlotType.TUTORIAL) return true; - for (Builder builder : dPlot.getPlotMembers()) { - if (!successful) break; - successful = dPlot.removePlotMember(builder); - } + Plot dPlot = (Plot) plot; + boolean successful = DataProvider.REVIEW.removeAllReviewsOfPlot(dPlot.getId()); - if (successful && plot.getPlotOwner() != null) { - PlotUtils.Cache.clearCache(plot.getPlotOwner().getUUID()); - successful = plot.getPlotOwner().setSlot(plot.getPlotOwner().getSlot(dPlot), -1); - } + for (Builder builder : List.copyOf(dPlot.getPlotMembers())) { + successful &= dPlot.removePlotMember(builder); + } - if (successful) { - successful = dPlot.setPlotOwner(null) - && dPlot.setLastActivity(true) - && dPlot.setStatus(Status.unclaimed) - && dPlot.setPlotType(PlotType.LOCAL_INSPIRATION_MODE); - } + Builder owner = plot.getPlotOwner(); + if (owner != null) { + PlotUtils.Cache.clearCache(owner.getUUID()); + Slot slot = owner.getSlot(dPlot); + successful &= slot != null && owner.setSlot(slot, -1); + } - successful = successful && DataProvider.PLOT.setCompletedSchematic(plot.getId(), null); - if (!successful) PlotSystem.getPlugin().getComponentLogger().error(text("Failed to abandon plot with the ID " + plot.getId() + "!")); - }); - return true; + successful &= dPlot.setPlotOwner(null); + successful &= dPlot.setLastActivity(true); + successful &= dPlot.setStatus(Status.unclaimed); + successful &= dPlot.setPlotType(PlotType.LOCAL_INSPIRATION_MODE); + successful &= DataProvider.PLOT.setCompletedSchematic(plot.getId(), null); + if (!successful) { + PlotSystem.getPlugin().getComponentLogger().error(text("Failed to abandon plot with the ID " + plot.getId() + "!")); + } + return successful; } public static boolean deletePlot(Plot plot) { @@ -190,11 +231,9 @@ public static boolean deletePlot(Plot plot) { PlotSystem.getPlugin().getComponentLogger().warn(text("Failed to delete plot with the ID " + plot.getId() + "!")); return false; } - CompletableFuture.runAsync(() -> { - if (DataProvider.PLOT.deletePlot(plot.getId())) return; - PlotSystem.getPlugin().getComponentLogger().warn(text("Failed to delete plot with the ID " + plot.getId() + " from the database!")); - }); - return true; + if (DataProvider.PLOT.deletePlot(plot.getId())) return true; + PlotSystem.getPlugin().getComponentLogger().warn(text("Failed to delete plot with the ID " + plot.getId() + " from the database!")); + return false; } public static void abandonInactivePlots() { @@ -208,7 +247,7 @@ public static void abandonInactivePlots() { long interval = plot.isRejected() ? rejectedInactivityIntervalDays : inactivityIntervalDays; if (interval == -2 || lastActivity == null || lastActivity.plusDays(interval).isAfter(LocalDate.now())) continue; - Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + CompletableFuture.runAsync(() -> { if (!abandonPlot(plot)) { PlotSystem.getPlugin().getComponentLogger().warn(text("An error occurred while abandoning plot #" + plot.getId() + " due to inactivity!")); return; @@ -277,19 +316,21 @@ public static boolean savePlotAsSchematic(@NotNull Plot plot) throws IOException // Copy and write finished plot clipboard to schematic ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try (Clipboard cb = new BlockArrayClipboard(region)) { - cb.setOrigin(BlockVector3.at(plotCenter.x(), cuboidRegion.getMinimumY(), (double) plotCenter.z())); - - World world = new BukkitWorld(plot.getWorld().getBukkitWorld()); - ForwardExtentCopy forwardExtentCopy = new ForwardExtentCopy(world, region, cb, region.getMinimumPoint()); - Operations.complete(forwardExtentCopy); - - try (ClipboardWriter writer = AbstractPlot.CLIPBOARD_FORMAT.getWriter(outputStream)) { - double initialY = clipboard.getRegion().getMinimumY(); - double offset = initialY - cuboidRegion.getMinimumY(); - writer.write(cb.transform(new AffineTransform().translate(Vector3.at(0, offset, 0)))); + AbstractPlotLoader.runFaweAsync(() -> { + try (Clipboard cb = new BlockArrayClipboard(region)) { + cb.setOrigin(BlockVector3.at(plotCenter.x(), cuboidRegion.getMinimumY(), (double) plotCenter.z())); + + World world = new BukkitWorld(plot.getWorld().getBukkitWorld()); + ForwardExtentCopy forwardExtentCopy = new ForwardExtentCopy(world, region, cb, region.getMinimumPoint()); + Operations.complete(forwardExtentCopy); + + try (ClipboardWriter writer = AbstractPlot.CLIPBOARD_FORMAT.getWriter(outputStream)) { + double initialY = clipboard.getRegion().getMinimumY(); + double offset = initialY - cuboidRegion.getMinimumY(); + writer.write(cb.transform(new AffineTransform().translate(Vector3.at(0, offset, 0)))); + } } - } + }).get(); // Set Completed Schematic boolean successful = DataProvider.PLOT.setCompletedSchematic(plot.getId(), outputStream.toByteArray()); @@ -298,8 +339,17 @@ public static boolean savePlotAsSchematic(@NotNull Plot plot) throws IOException // If plot was created in a void world, copy the result to the city world if (plot.getPlotType() != PlotType.CITY_INSPIRATION_MODE) { var cpw = new CityPlotWorld(plot); - Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(cpw.getBukkitWorld()), BlockTypes.AIR); - AbstractPlotLoader.pasteSchematic(airMask, outputStream.toByteArray(), cpw, false, true); + if (!cpw.isWorldGenerated()) { + try { + AbstractPlotLoader.ensureWorldGenerated(cpw); + } catch (Exception exception) { + throw new IOException("Could not generate city plot world!", exception); + } + } + AbstractPlotLoader.runFaweAsync(() -> { + Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(cpw.getBukkitWorld()), BlockTypes.AIR); + AbstractPlotLoader.pasteSchematic(airMask, outputStream.toByteArray(), cpw, false, true); + }).get(); } return true; } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/TutorialPlot.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/TutorialPlot.java index 44103bc9..05378699 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/TutorialPlot.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/TutorialPlot.java @@ -168,6 +168,7 @@ public byte[] getInitialSchematicBytes() { public boolean setTutorialSchematic(int schematicId) { if (schematicId < 0) return false; currentSchematicId = schematicId; + invalidateSchematicMetadata(); return true; } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java index 739a4f52..dcda938a 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java @@ -14,6 +14,7 @@ import com.alpsbte.plotsystem.utils.io.ConfigUtil; import com.alpsbte.plotsystem.utils.io.LangPaths; import com.alpsbte.plotsystem.utils.io.LangUtil; +import com.fastasyncworldedit.core.FaweAPI; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.bukkit.BukkitAdapter; @@ -51,28 +52,45 @@ import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; import static net.kyori.adventure.text.Component.text; public abstract class AbstractPlotLoader { + private static final ConcurrentHashMap WORLD_GENERATION_LOCKS = new ConcurrentHashMap<>(); + protected final AbstractPlot plot; protected final PlotType plotType; protected final PlotWorld plotWorld; protected final Builder builder; + protected final boolean completionActionsEnabled; protected byte[] schematicBytes = null; + private boolean successful; protected AbstractPlotLoader(@NotNull AbstractPlot plot, Builder builder, PlotType plotType, PlotWorld plotWorld) { + this(plot, builder, plotType, plotWorld, true); + } + + protected AbstractPlotLoader( + @NotNull AbstractPlot plot, + Builder builder, + PlotType plotType, + PlotWorld plotWorld, + boolean completionActionsEnabled + ) { this.plot = plot; this.plotType = plotType; this.plotWorld = plotWorld; this.builder = builder; + this.completionActionsEnabled = completionActionsEnabled; PlotSystem.getPlugin().getComponentLogger().info("Loading plot #{}...", plot.getId()); - PlotSystem.getPlugin().getComponentLogger().info("Plot Type: {}", plot.getPlotType().name()); + PlotSystem.getPlugin().getComponentLogger().info("Plot Type: {}", plotType.name()); - boolean successful = true; + successful = true; try { generateWorld(); loadWorld(); @@ -88,10 +106,22 @@ protected AbstractPlotLoader(@NotNull AbstractPlot plot, Builder builder, PlotTy } protected void generateWorld() throws Exception { - boolean generateWorld = plotType.hasOnePlotPerWorld() || !Utils.supplySync(plotWorld::isWorldGenerated).get(); - if (!generateWorld) return; + ensureWorldGenerated(plotWorld); + } + + public static void ensureWorldGenerated(@NotNull PlotWorld world) throws Exception { + if (Utils.supplySync(world::isWorldGenerated).get()) return; - new PlotWorldGenerator(plotWorld.getWorldName()); + Object lock = WORLD_GENERATION_LOCKS.computeIfAbsent(world.getWorldName(), ignored -> new Object()); + try { + synchronized (lock) { + if (!Utils.supplySync(world::isWorldGenerated).get()) { + new PlotWorldGenerator(world.getWorldName()); + } + } + } finally { + WORLD_GENERATION_LOCKS.remove(world.getWorldName(), lock); + } } protected void loadWorld() throws Exception { @@ -117,8 +147,9 @@ protected void createPlotProtection() throws Exception { return null; } + int maxWorldHeight = plotWorld.getBukkitWorld().getMaxHeight() - 1; // Create build region for plot from the outline of the plot - ProtectedRegion protectedBuildRegion = new ProtectedPolygonalRegion(plotWorld.getRegionName(), plot.getOutline(), PlotWorld.MIN_WORLD_HEIGHT, PlotWorld.MAX_WORLD_HEIGHT); + ProtectedRegion protectedBuildRegion = new ProtectedPolygonalRegion(plotWorld.getRegionName(), plot.getOutline(), PlotWorld.MIN_WORLD_HEIGHT, maxWorldHeight); protectedBuildRegion.setPriority(100); // Create protected plot region for plot @@ -147,8 +178,9 @@ protected void createPlotProtection() throws Exception { private @NotNull ProtectedRegion getProtectedRegion() { World weWorld = new BukkitWorld(plotWorld.getBukkitWorld()); - CylinderRegion cylinderRegion = new CylinderRegion(weWorld, plot.getCenter(), Vector2.at(PlotWorld.PLOT_SIZE, PlotWorld.PLOT_SIZE), PlotWorld.MIN_WORLD_HEIGHT, PlotWorld.MAX_WORLD_HEIGHT); - ProtectedRegion protectedRegion = new ProtectedPolygonalRegion(plotWorld.getRegionName() + "-1", cylinderRegion.polygonize(-1), PlotWorld.MIN_WORLD_HEIGHT, PlotWorld.MAX_WORLD_HEIGHT); + int maxWorldHeight = plotWorld.getBukkitWorld().getMaxHeight() - 1; + CylinderRegion cylinderRegion = new CylinderRegion(weWorld, plot.getCenter(), Vector2.at(PlotWorld.PLOT_SIZE, PlotWorld.PLOT_SIZE), PlotWorld.MIN_WORLD_HEIGHT, maxWorldHeight); + ProtectedRegion protectedRegion = new ProtectedPolygonalRegion(plotWorld.getRegionName() + "-1", cylinderRegion.polygonize(-1), PlotWorld.MIN_WORLD_HEIGHT, maxWorldHeight); protectedRegion.setPriority(50); return protectedRegion; } @@ -192,17 +224,32 @@ protected List getBlockedCommands(@NotNull FileConfiguration config) { } protected void generateStructure() throws Exception { - Utils.runSync(() -> { + runFaweAsync(() -> { if (plotType.hasEnvironment()) { pasteSchematic(null, this.schematicBytes, this.plotWorld, false, false); } else { Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(this.plotWorld.getBukkitWorld()), BlockTypes.AIR); - pasteSchematic(airMask, PlotUtils.getOutlinesSchematicBytes(plot, this.schematicBytes, this.plotWorld.getBukkitWorld()), this.plotWorld, true, false); + pasteSchematic(airMask, PlotUtils.getOutlinesSchematicBytes(plot, this.schematicBytes), this.plotWorld, true, false); } - return null; }).get(); } + /** + * Runs a WorldEdit operation on FAWE's asynchronous task executor. + */ + public static CompletableFuture runFaweAsync(@NotNull FaweTask task) { + CompletableFuture future = new CompletableFuture<>(); + FaweAPI.getTaskManager().async(() -> { + try { + task.run(); + future.complete(null); + } catch (Exception exception) { + future.completeExceptionally(exception); + } + }); + return future; + } + /** * Pastes the schematic to the plot center in the given world * @@ -220,7 +267,12 @@ public static void pasteSchematic(@Nullable Mask pasteMask, byte[] schematicFile // set outline region with air if (clearArea) { try (EditSession editSession = WorldEdit.getInstance().newEditSession(BukkitAdapter.adapt(world.getBukkitWorld()))) { - Polygonal2DRegion polyRegion = new Polygonal2DRegion(weWorld, world.getPlot().getOutline(), 0, PlotWorld.MAX_WORLD_HEIGHT); + Polygonal2DRegion polyRegion = new Polygonal2DRegion( + weWorld, + world.getPlot().getOutline(), + world.getBukkitWorld().getMinHeight(), + world.getBukkitWorld().getMaxHeight() - 1 + ); editSession.setMask(new RegionMask(polyRegion)); editSession.setBlocks((Region) polyRegion, Objects.requireNonNull(BlockTypes.AIR).getDefaultState()); } @@ -255,21 +307,31 @@ public static void pasteSchematic(@Nullable Mask pasteMask, byte[] schematicFile protected void onException(Exception e) { try { - Utils.runSync(() -> { - PlotHandler.abandonPlot(this.plot); - return null; - }).get(); + if (!PlotHandler.abandonPlot(this.plot)) { + PlotSystem.getPlugin().getComponentLogger().error("Failed to clean up plot #{} after generation error!", plot.getId()); + } } catch (Exception ex) { PlotSystem.getPlugin().getComponentLogger().error(text("Failed to clean up plot after generation error!"), ex); } PlotSystem.getPlugin().getComponentLogger().error(text("An error occurred while generating plot!"), e); Utils.runSync(() -> { - builder.getPlayer().sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(builder.getPlayer(), LangPaths.Message.Error.ERROR_OCCURRED))); - builder.getPlayer().playSound(builder.getPlayer().getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); + if (builder != null && builder.getPlayer() != null) { + builder.getPlayer().sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(builder.getPlayer(), LangPaths.Message.Error.ERROR_OCCURRED))); + builder.getPlayer().playSound(builder.getPlayer().getLocation(), Utils.SoundUtils.ERROR_SOUND, 1, 1); + } return null; }); } + public boolean isSuccessful() { + return successful; + } + + @FunctionalInterface + public interface FaweTask { + void run() throws Exception; + } + protected abstract void onCompletion(); } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java index 5ba9c274..26002ca2 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java @@ -29,9 +29,9 @@ import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; +import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.core.system.plot.world.CityPlotWorld; import com.alpsbte.plotsystem.core.system.plot.world.PlotWorld; -import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.Status; import com.alpsbte.plotsystem.utils.io.LangPaths; import com.alpsbte.plotsystem.utils.io.LangUtil; @@ -49,6 +49,10 @@ public DefaultPlotLoader(@NotNull AbstractPlot plot, Builder builder, PlotType p super(plot, builder, plotType, plotWorld); } + public DefaultPlotLoader(@NotNull AbstractPlot plot, Builder builder, PlotType plotType, PlotWorld plotWorld, boolean completionActionsEnabled) { + super(plot, builder, plotType, plotWorld, completionActionsEnabled); + } + public DefaultPlotLoader(@NotNull AbstractPlot plot, Builder builder) { this(plot, builder, builder.getPlotType()); } @@ -66,24 +70,26 @@ protected void generateStructure() throws Exception { byte[] completedSchematic = p.getCompletedSchematic(); if (completedSchematic != null) { - Utils.runSync(() -> { + runFaweAsync(() -> { Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(plotWorld.getBukkitWorld()), BlockTypes.AIR); - pasteSchematic(airMask, completedSchematic, plotWorld, true, true); - return null; + pasteSchematic(airMask, completedSchematic, plotWorld, false, true); }).get(); } else super.generateStructure(); - copyToCityWorld(); + copyToCityWorld(completedSchematic == null ? schematicBytes : completedSchematic, completedSchematic != null); } @Override protected void onCompletion() { + if (!completionActionsEnabled) return; Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - plot.getWorld().teleportPlayer(builder.getPlayer()); + if (builder == null || builder.getPlayer() == null) return; + PlotUtils.Cache.clearCache(builder.getUUID()); + plotWorld.teleportPlayer(builder.getPlayer()); LangUtil.getInstance().broadcast(LangPaths.Message.Info.CREATED_NEW_PLOT, builder.getName()); }); } - protected void copyToCityWorld() throws IOException { + protected void copyToCityWorld(byte[] structureBytes, boolean completedSchematic) throws IOException { assert plot instanceof Plot; if (plot.getStatus() == Status.completed) return; if (!PlotWorld.isOnePlotWorld(plotWorld.getWorldName())) return; @@ -91,9 +97,12 @@ protected void copyToCityWorld() throws IOException { // If the player is playing in his own world, then additionally generate the plot in the city world CityPlotWorld cityPlotWorld = new CityPlotWorld((Plot) plot); try { - Utils.runSync(() -> { - AbstractPlotLoader.pasteSchematic(null, this.schematicBytes, cityPlotWorld, false, true); - return null; + ensureWorldGenerated(cityPlotWorld); + runFaweAsync(() -> { + Mask mask = completedSchematic + ? new BlockTypeMask(BukkitAdapter.adapt(cityPlotWorld.getBukkitWorld()), BlockTypes.AIR) + : null; + AbstractPlotLoader.pasteSchematic(mask, structureBytes, cityPlotWorld, false, true); }).get(); } catch (Exception e) { if (e.getCause() instanceof IOException ioException) throw ioException; diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java index 83aea3c4..0cf18c37 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java @@ -24,7 +24,6 @@ import com.github.fierioziy.particlenativeapi.api.ParticleNativeAPI; import com.github.fierioziy.particlenativeapi.api.Particles_1_13; import com.github.fierioziy.particlenativeapi.plugin.ParticleNativePlugin; -import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; @@ -172,25 +171,21 @@ public static boolean isPlayerOnPlot(@NotNull AbstractPlot plot, Player player) } public static @Nullable CuboidRegion getPlotAsRegion(@NotNull AbstractPlot plot) throws IOException { - Clipboard clipboard; - try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(new ByteArrayInputStream(plot.getInitialSchematicBytes()))) { - clipboard = reader.read(); - } - if (clipboard == null) return null; - // No longer supported! if (plot.getVersion() < 4) return null; + if (!plot.getWorld().loadWorld()) return null; + AbstractPlot.SchematicMetadata metadata = plot.getSchematicMetadata(); return new CuboidRegion( - clipboard.getMinimumPoint().withY(plot.getWorld().getPlotHeight()), - clipboard.getMaximumPoint().withY(PlotWorld.MAX_WORLD_HEIGHT)); + metadata.minimumPoint().withY(plot.getWorld().getPlotHeight()), + metadata.maximumPoint().withY(plot.getWorld().getBukkitWorld().getMaxHeight() - 1)); } public static boolean isPlotWorld(@NotNull World world) { return DependencyManager.getMultiverseCore().getWorldManager().isLoadedWorld(world) && (PlotWorld.isOnePlotWorld(world.getName()) || PlotWorld.isCityPlotWorld(world.getName())); } - public static byte @Nullable [] getOutlinesSchematicBytes(@NotNull AbstractPlot plot, byte[] initialSchematic, World world) throws IOException { + public static byte @Nullable [] getOutlinesSchematicBytes(@NotNull AbstractPlot plot, byte[] initialSchematic) throws IOException { Clipboard clipboard; ByteArrayInputStream inputStream = new ByteArrayInputStream(initialSchematic); try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(inputStream)) { @@ -227,22 +222,15 @@ public static boolean isPlotWorld(@NotNull World world) { } public static @Nullable CompletableFuture convertTerraToPlotXZ(@NotNull AbstractPlot plot, double[] terraCoords) throws IOException { - // Load plot outlines schematic as clipboard - Clipboard clipboard; - ByteArrayInputStream inputStream = new ByteArrayInputStream(plot.getInitialSchematicBytes()); - try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(inputStream)) { - clipboard = reader.read(); - } - if (clipboard == null) return null; - // Calculate min and max points of schematic CuboidRegion plotRegion = getPlotAsRegion(plot); if (plotRegion == null) return null; + AbstractPlot.SchematicMetadata metadata = plot.getSchematicMetadata(); // Convert terra schematic coordinates into relative plot schematic coordinates double[] schematicCoords = { - terraCoords[0] - clipboard.getMinimumPoint().x(), - terraCoords[1] - clipboard.getMinimumPoint().z() + terraCoords[0] - metadata.minimumPoint().x(), + terraCoords[1] - metadata.minimumPoint().z() }; // Add additional plot sizes to relative plot schematic coordinates diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java index d0c03ef6..19df531f 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java @@ -1,7 +1,6 @@ package com.alpsbte.plotsystem.core.system.plot.world; import com.alpsbte.plotsystem.PlotSystem; -import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; import com.alpsbte.plotsystem.core.system.plot.generator.loader.AbstractPlotLoader; @@ -11,15 +10,12 @@ import com.alpsbte.plotsystem.utils.io.LangUtil; import com.google.common.annotations.Beta; import com.sk89q.worldedit.bukkit.BukkitAdapter; -import com.sk89q.worldedit.extent.clipboard.Clipboard; -import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; import com.sk89q.worldguard.WorldGuard; import com.sk89q.worldguard.protection.managers.RegionManager; import com.sk89q.worldguard.protection.regions.RegionContainer; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -67,7 +63,8 @@ public int getPlotHeight() throws IOException { @Beta @Override public int getPlotHeightCentered() throws IOException { - return Math.min(MIN_WORLD_HEIGHT + getWorldHeight() + super.getPlotHeightCentered(), PlotWorld.MAX_WORLD_HEIGHT); + int maxWorldHeight = getBukkitWorld() == null ? PlotWorld.MAX_WORLD_HEIGHT : getBukkitWorld().getMaxHeight() - 1; + return Math.min(MIN_WORLD_HEIGHT + getWorldHeight() + super.getPlotHeightCentered(), maxWorldHeight); } /** @@ -78,23 +75,16 @@ public int getPlotHeightCentered() throws IOException { */ @Beta public int getWorldHeight() throws IOException { - Clipboard clipboard; - ByteArrayInputStream inputStream = new ByteArrayInputStream(plot.getInitialSchematicBytes()); - try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(inputStream)) { - clipboard = reader.read(); - } - if (clipboard == null) throw new IOException("A Plot's Outline schematic fails to load, cannot get clipboard."); - - int plotHeight = clipboard.getMinimumPoint().y(); + int plotHeight = plot.getSchematicMetadata().minimumPoint().y(); - // Minimum building height for a plot (this should be configurable depending on minecraft build limit) - // This is in the case that a plot is created at y level 300 where the max build limit is 318, - // so you don't want builder to only be able to build for 18 blocks - int minBuildingHeight = 128; + // Minimum building height for a plot (this should be configurable depending on minecraft build limit) + // This is in the case that a plot is created at y level 300 where the max build limit is 318, + // so you don't want builder to only be able to build for 18 blocks + int minBuildingHeight = 128; - // Negative y level of the current minecraft version (1.21) - // Additional ground layer the plot use to save as schematic need to be included for plot's y-level - int groundLayer = 64; + // Negative y level of the current minecraft version (1.21) + // Additional ground layer the plot use to save as schematic need to be included for plot's y-level + int groundLayer = 64; // Plots created outside of vanilla build limit or the build-able height is too small if (plotHeight + groundLayer < MIN_WORLD_HEIGHT + groundLayer @@ -120,36 +110,56 @@ public List getPlayersOnPlot(AbstractPlot plot) { @Override public boolean onAbandon() { - RegionContainer regionContainer = WorldGuard.getInstance().getPlatform().getRegionContainer(); if (!loadWorld()) { PlotSystem.getPlugin().getComponentLogger().warn(text("Could not load world!")); return false; } - RegionManager regionManager = regionContainer.get(BukkitAdapter.adapt(getBukkitWorld())); - if (regionManager == null) { - PlotSystem.getPlugin().getComponentLogger().warn(text("Region Manager is null!")); + try { + boolean regionsRemoved = Utils.supplySync(() -> { + RegionContainer regionContainer = WorldGuard.getInstance().getPlatform().getRegionContainer(); + RegionManager regionManager = regionContainer.get(BukkitAdapter.adapt(getBukkitWorld())); + if (regionManager == null) return false; + + if (regionManager.hasRegion(getRegionName())) regionManager.removeRegion(getRegionName()); + if (regionManager.hasRegion(getRegionName() + "-1")) regionManager.removeRegion(getRegionName() + "-1"); + regionManager.saveChanges(); + return true; + }).get(); + if (!regionsRemoved) { + PlotSystem.getPlugin().getComponentLogger().warn(text("Region Manager is null!")); + return false; + } + } catch (Exception exception) { + PlotSystem.getPlugin().getComponentLogger().error(text("Could not save WorldGuard regions!"), exception); return false; } - for (Builder builder : ((Plot) plot).getPlotMembers()) { - ((Plot) plot).removePlotMember(builder); - } - - if (regionManager.hasRegion(getRegionName())) regionManager.removeRegion(getRegionName()); - if (regionManager.hasRegion(getRegionName() + "-1")) regionManager.removeRegion(getRegionName() + "-1"); - // paste initial schematic to reset plot try { - AbstractPlotLoader.pasteSchematic(null, PlotUtils.getOutlinesSchematicBytes(plot, plot.getInitialSchematicBytes(), getBukkitWorld()), this, true, false); - } catch (IOException e) { + AbstractPlotLoader.runFaweAsync(() -> + AbstractPlotLoader.pasteSchematic( + null, + PlotUtils.getOutlinesSchematicBytes(plot, plot.getInitialSchematicBytes()), + this, + true, + false + ) + ).get(); + } catch (Exception e) { PlotSystem.getPlugin().getComponentLogger().error(text("Could not paste schematic!"), e); + return false; } - List playersToTeleport = new ArrayList<>(getPlayersOnPlot(plot)); - playersToTeleport.forEach(p -> p.teleport(Utils.getSpawnLocation())); - - if (isWorldLoaded()) unloadWorld(false); - return super.onAbandon(); + try { + return Utils.supplySync(() -> { + List playersToTeleport = new ArrayList<>(getPlayersOnPlot(plot)); + playersToTeleport.forEach(player -> player.teleport(Utils.getSpawnLocation())); + return !isWorldLoaded() || unloadWorld(false); + }).get() && super.onAbandon(); + } catch (Exception exception) { + PlotSystem.getPlugin().getComponentLogger().error(text("Could not unload city plot world!"), exception); + return false; + } } } \ No newline at end of file diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java index c6c539b7..ca586cd5 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java @@ -27,7 +27,6 @@ import com.alpsbte.plotsystem.PlotSystem; import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; -import com.alpsbte.plotsystem.core.system.plot.Plot; import com.alpsbte.plotsystem.core.system.plot.TutorialPlot; import com.alpsbte.plotsystem.core.system.plot.generator.loader.AbstractPlotLoader; import com.alpsbte.plotsystem.core.system.plot.generator.loader.DefaultPlotLoader; @@ -58,27 +57,24 @@ public OnePlotWorld(@NotNull AbstractPlot plot) { public boolean generateWorld(@NotNull Class generator) { if (isWorldGenerated()) return false; - if (generator.isAssignableFrom(DefaultPlotLoader.class)) { - new DefaultPlotLoader(plot, plotOwner); - } else if (generator.isAssignableFrom(TutorialPlotLoader.class)) { - new TutorialPlotLoader(plot, plotOwner); + AbstractPlotLoader loader; + if (DefaultPlotLoader.class.isAssignableFrom(generator)) { + loader = new DefaultPlotLoader(plot, plotOwner); + } else if (TutorialPlotLoader.class.isAssignableFrom(generator)) { + loader = new TutorialPlotLoader(plot, plotOwner); } else return false; - return true; + return loader.isSuccessful(); } @Override public boolean loadWorld() { if (plot == null || isWorldGenerated()) return super.loadWorld(); - // Generate plot if it doesn't exist - if (plot.getPlotType() == PlotType.TUTORIAL) - generateWorld(TutorialPlotLoader.class); - else if (((Plot) plot).getCompletedSchematic() == null) - generateWorld(DefaultPlotLoader.class); - - new DefaultPlotLoader(plot, plotOwner, plot.getPlotType(), this); - - if (!isWorldGenerated() || !isWorldLoaded()) { + // Rebuild a missing plot world exactly once, including completed plots. + AbstractPlotLoader loader = plot.getPlotType() == PlotType.TUTORIAL + ? new TutorialPlotLoader(plot, plotOwner) + : new DefaultPlotLoader(plot, plotOwner, plot.getPlotType(), this, false); + if (!loader.isSuccessful() || !isWorldGenerated() || !isWorldLoaded()) { PlotSystem.getPlugin().getComponentLogger().warn(text("Could not regenerate world " + getWorldName() + " for plot " + plot.getId() + "!")); return false; } @@ -128,13 +124,19 @@ public int getPlotHeightCentered() throws IOException { @Override public boolean onAbandon() { if (!isWorldGenerated()) return super.onAbandon(); - if (isWorldLoaded()) { - for (Player player : getBukkitWorld().getPlayers()) player.teleport(Utils.getSpawnLocation()); - } - if (!deleteWorld()) { - PlotSystem.getPlugin().getComponentLogger().warn(text("Could not delete plot world " + getWorldName() + "!")); + try { + boolean deleted = Utils.supplySync(() -> { + if (isWorldLoaded()) { + for (Player player : getBukkitWorld().getPlayers()) player.teleport(Utils.getSpawnLocation()); + } + return deleteWorld(); + }).get(); + if (deleted) return super.onAbandon(); + } catch (Exception exception) { + PlotSystem.getPlugin().getComponentLogger().error(text("Could not delete plot world " + getWorldName() + "!"), exception); return false; } - return super.onAbandon(); + PlotSystem.getPlugin().getComponentLogger().warn(text("Could not delete plot world " + getWorldName() + "!")); + return false; } } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java index 1f62a151..d99c6c25 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java @@ -36,8 +36,6 @@ import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.io.ConfigPaths; import com.sk89q.worldedit.bukkit.BukkitAdapter; -import com.sk89q.worldedit.extent.clipboard.Clipboard; -import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldguard.WorldGuard; import com.sk89q.worldguard.protection.managers.RegionManager; @@ -56,7 +54,6 @@ import org.mvplugins.multiverse.core.world.options.LoadWorldOptions; import org.mvplugins.multiverse.external.vavr.control.Option; -import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.util.Locale; @@ -174,14 +171,8 @@ public int getPlotHeight() throws IOException { @Override public int getPlotHeightCentered() throws IOException { if (plot == null) return 0; - - Clipboard clipboard; - ByteArrayInputStream inputStream = new ByteArrayInputStream(plot.getInitialSchematicBytes()); - try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(inputStream)) { - clipboard = reader.read(); - } - if (clipboard == null) return 0; - return (int) clipboard.getRegion().getCenter().y() - clipboard.getMinimumPoint().y(); + AbstractPlot.SchematicMetadata metadata = plot.getSchematicMetadata(); + return metadata.center().y() - metadata.minimumPoint().y(); } @Override From da581270f400178047291f5bf7fc9e69debf1c94 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Thu, 3 Sep 2026 12:14:34 +0200 Subject: [PATCH 43/60] fix(plot): simplify schematic pasting mask logic - Now we don't construct a Air Mask in several places anymore. - We just use ignore air This fixes a issue where we adapt a cim world before it is actually loaded, that resulted into a fawe exception, which resulted in the plot not beeing pasted. --- .../core/system/plot/PlotHandler.java | 9 +------- .../generator/loader/AbstractPlotLoader.java | 22 ++++++------------- .../generator/loader/DefaultPlotLoader.java | 16 ++------------ .../core/system/plot/world/CityPlotWorld.java | 2 +- 4 files changed, 11 insertions(+), 38 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java index 851c3d51..2cae823c 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java @@ -44,14 +44,11 @@ import com.alpsbte.plotsystem.utils.io.LangPaths; import com.alpsbte.plotsystem.utils.io.LangUtil; import com.sk89q.worldedit.WorldEditException; -import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.BukkitWorld; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter; -import com.sk89q.worldedit.function.mask.BlockTypeMask; -import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.operation.ForwardExtentCopy; import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.math.BlockVector2; @@ -61,7 +58,6 @@ import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Polygonal2DRegion; import com.sk89q.worldedit.world.World; -import com.sk89q.worldedit.world.block.BlockTypes; import org.bukkit.SoundCategory; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; @@ -346,10 +342,7 @@ public static boolean savePlotAsSchematic(@NotNull Plot plot) throws IOException throw new IOException("Could not generate city plot world!", exception); } } - AbstractPlotLoader.runFaweAsync(() -> { - Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(cpw.getBukkitWorld()), BlockTypes.AIR); - AbstractPlotLoader.pasteSchematic(airMask, outputStream.toByteArray(), cpw, false, true); - }).get(); + AbstractPlotLoader.runFaweAsync(() -> AbstractPlotLoader.pasteSchematic(true, outputStream.toByteArray(), cpw, false, true)).get(); } return true; } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java index dcda938a..13531694 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java @@ -21,8 +21,6 @@ import com.sk89q.worldedit.bukkit.BukkitWorld; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; -import com.sk89q.worldedit.function.mask.BlockTypeMask; -import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.RegionMask; import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.Operations; @@ -45,7 +43,6 @@ import com.sk89q.worldguard.protection.regions.RegionContainer; import org.bukkit.configuration.file.FileConfiguration; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -223,15 +220,11 @@ protected List getBlockedCommands(@NotNull FileConfiguration config) { return blockedCommands; } + /** + * Generates the structure for the plot + */ protected void generateStructure() throws Exception { - runFaweAsync(() -> { - if (plotType.hasEnvironment()) { - pasteSchematic(null, this.schematicBytes, this.plotWorld, false, false); - } else { - Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(this.plotWorld.getBukkitWorld()), BlockTypes.AIR); - pasteSchematic(airMask, PlotUtils.getOutlinesSchematicBytes(plot, this.schematicBytes), this.plotWorld, true, false); - } - }).get(); + runFaweAsync(() -> pasteSchematic(!plotType.hasEnvironment(), PlotUtils.getOutlinesSchematicBytes(plot, this.schematicBytes), this.plotWorld, true, false)).get(); } /** @@ -253,13 +246,13 @@ public static CompletableFuture runFaweAsync(@NotNull FaweTask task) { /** * Pastes the schematic to the plot center in the given world * - * @param pasteMask - sets a mask for the paste operation, can be null - if the mast is not null, the paste operation ignores air blocks + * @param ignoreAir - if true, the paste operation will ignore air blocks * @param schematicFile - plot/environment schematic file * @param world - world to paste in * @param clearArea - clears the plot area with air before pasting * @param offset - offset for the paste operation */ - public static void pasteSchematic(@Nullable Mask pasteMask, byte[] schematicFile, @NotNull PlotWorld world, boolean clearArea, boolean offset) throws IOException { + public static void pasteSchematic(boolean ignoreAir, byte[] schematicFile, @NotNull PlotWorld world, boolean clearArea, boolean offset) throws IOException { // load world if not loaded already if (!world.loadWorld()) return; World weWorld = new BukkitWorld(world.getBukkitWorld()); @@ -295,11 +288,10 @@ public static void pasteSchematic(@Nullable Mask pasteMask, byte[] schematicFile // paste schematic try (EditSession editSession = WorldEdit.getInstance().newEditSession(BukkitAdapter.adapt(world.getBukkitWorld()))) { - if (pasteMask != null) editSession.setMask(pasteMask); Operation clipboardHolder = new ClipboardHolder(clipboard) .createPaste(editSession) .to(BlockVector3.at(world.getPlot().getCenter().x(), pasteY, world.getPlot().getCenter().z())) - .ignoreAirBlocks(pasteMask != null) + .ignoreAirBlocks(ignoreAir) .build(); Operations.complete(clipboardHolder); } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java index 26002ca2..8906de4d 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java @@ -35,10 +35,6 @@ import com.alpsbte.plotsystem.utils.enums.Status; import com.alpsbte.plotsystem.utils.io.LangPaths; import com.alpsbte.plotsystem.utils.io.LangUtil; -import com.sk89q.worldedit.bukkit.BukkitAdapter; -import com.sk89q.worldedit.function.mask.BlockTypeMask; -import com.sk89q.worldedit.function.mask.Mask; -import com.sk89q.worldedit.world.block.BlockTypes; import org.bukkit.Bukkit; import org.jetbrains.annotations.NotNull; @@ -70,10 +66,7 @@ protected void generateStructure() throws Exception { byte[] completedSchematic = p.getCompletedSchematic(); if (completedSchematic != null) { - runFaweAsync(() -> { - Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(plotWorld.getBukkitWorld()), BlockTypes.AIR); - pasteSchematic(airMask, completedSchematic, plotWorld, false, true); - }).get(); + runFaweAsync(() -> pasteSchematic(true, completedSchematic, plotWorld, false, true)).get(); } else super.generateStructure(); copyToCityWorld(completedSchematic == null ? schematicBytes : completedSchematic, completedSchematic != null); } @@ -98,12 +91,7 @@ protected void copyToCityWorld(byte[] structureBytes, boolean completedSchematic CityPlotWorld cityPlotWorld = new CityPlotWorld((Plot) plot); try { ensureWorldGenerated(cityPlotWorld); - runFaweAsync(() -> { - Mask mask = completedSchematic - ? new BlockTypeMask(BukkitAdapter.adapt(cityPlotWorld.getBukkitWorld()), BlockTypes.AIR) - : null; - AbstractPlotLoader.pasteSchematic(mask, structureBytes, cityPlotWorld, false, true); - }).get(); + runFaweAsync(() -> AbstractPlotLoader.pasteSchematic(completedSchematic, structureBytes, cityPlotWorld, false, true)).get(); } catch (Exception e) { if (e.getCause() instanceof IOException ioException) throw ioException; throw new IOException("Could not copy plot to city world!", e); diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java index 19df531f..99f21767 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java @@ -139,7 +139,7 @@ public boolean onAbandon() { try { AbstractPlotLoader.runFaweAsync(() -> AbstractPlotLoader.pasteSchematic( - null, + false, PlotUtils.getOutlinesSchematicBytes(plot, plot.getInitialSchematicBytes()), this, true, From 339d69b18506251792060594eebca708acf655dc Mon Sep 17 00:00:00 2001 From: Zoriot Date: Thu, 3 Sep 2026 12:18:13 +0200 Subject: [PATCH 44/60] chore: Throw exception when plot is outside of supported range --- .../plotsystem/core/system/plot/world/CityPlotWorld.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java index 99f21767..0ee49c96 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java @@ -89,7 +89,7 @@ public int getWorldHeight() throws IOException { // Plots created outside of vanilla build limit or the build-able height is too small if (plotHeight + groundLayer < MIN_WORLD_HEIGHT + groundLayer || plotHeight + groundLayer + minBuildingHeight > MAX_WORLD_HEIGHT + groundLayer) - return 0; // throw new IOException("Plot height is out of range."); + throw new IOException("Plot height is out of range."); return plotHeight; } From dc6140c81ef084f38ebc7b459733416ea80941bb Mon Sep 17 00:00:00 2001 From: Zoriot Date: Thu, 3 Sep 2026 12:29:36 +0200 Subject: [PATCH 45/60] fix: Use world heights for checking plot height range --- .../core/system/plot/world/CityPlotWorld.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java index 0ee49c96..8d457227 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java @@ -13,6 +13,7 @@ import com.sk89q.worldguard.WorldGuard; import com.sk89q.worldguard.protection.managers.RegionManager; import com.sk89q.worldguard.protection.regions.RegionContainer; +import org.bukkit.World; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -86,9 +87,20 @@ public int getWorldHeight() throws IOException { // Additional ground layer the plot use to save as schematic need to be included for plot's y-level int groundLayer = 64; + World bukkitWorld = getBukkitWorld(); + int minHeight; + int maxHeight; + + if (bukkitWorld != null) { + minHeight = bukkitWorld.getMinHeight(); + maxHeight = bukkitWorld.getMaxHeight() - 1; + } else { + minHeight = MIN_WORLD_HEIGHT + groundLayer; + maxHeight = MAX_WORLD_HEIGHT + groundLayer; + } + // Plots created outside of vanilla build limit or the build-able height is too small - if (plotHeight + groundLayer < MIN_WORLD_HEIGHT + groundLayer - || plotHeight + groundLayer + minBuildingHeight > MAX_WORLD_HEIGHT + groundLayer) + if (plotHeight + groundLayer < minHeight || plotHeight + groundLayer + minBuildingHeight > maxHeight) throw new IOException("Plot height is out of range."); return plotHeight; } From 80935a97e11d3f8000368066564afd6a804a9e2d Mon Sep 17 00:00:00 2001 From: Zoriot Date: Thu, 3 Sep 2026 12:33:25 +0200 Subject: [PATCH 46/60] chore: Remove License notice from source files We switched to only the per repo license. --- .../core/system/plot/PlotHandler.java | 24 ----------------- .../generator/loader/DefaultPlotLoader.java | 24 ----------------- .../generator/world/PlotWorldGenerator.java | 24 ----------------- .../world/SkeletonWorldGenerator.java | 24 ----------------- .../core/system/plot/world/OnePlotWorld.java | 24 ----------------- .../core/system/plot/world/PlotWorld.java | 26 +------------------ 6 files changed, 1 insertion(+), 145 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java index 2cae823c..ac752b52 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java @@ -1,27 +1,3 @@ -/* - * The MIT License (MIT) - * - * Copyright © 2021-2025, Alps BTE - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - package com.alpsbte.plotsystem.core.system.plot; import com.alpsbte.plotsystem.PlotSystem; diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java index 8906de4d..b88b0219 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java @@ -1,27 +1,3 @@ -/* - * The MIT License (MIT) - * - * Copyright © 2021-2025, Alps BTE - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - package com.alpsbte.plotsystem.core.system.plot.generator.loader; import com.alpsbte.plotsystem.PlotSystem; diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java index 1331d07d..31b7b6dc 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java @@ -1,27 +1,3 @@ -/* - * The MIT License (MIT) - * - * Copyright © 2021-2025, Alps BTE - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - package com.alpsbte.plotsystem.core.system.plot.generator.world; import com.alpsbte.plotsystem.PlotSystem; diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java index e87f76f6..f56e3761 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java @@ -1,27 +1,3 @@ -/* - * The MIT License (MIT) - * - * Copyright © 2021-2025, Alps BTE - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - package com.alpsbte.plotsystem.core.system.plot.generator.world; import com.alpsbte.plotsystem.utils.DependencyManager; diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java index ca586cd5..d0b6a997 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java @@ -1,27 +1,3 @@ -/* - * The MIT License (MIT) - * - * Copyright © 2023, Alps BTE - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - package com.alpsbte.plotsystem.core.system.plot.world; import com.alpsbte.plotsystem.PlotSystem; diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java index d99c6c25..0caba9b3 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java @@ -1,27 +1,3 @@ -/* - * The MIT License (MIT) - * - * Copyright © 2021-2025, Alps BTE - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - package com.alpsbte.plotsystem.core.system.plot.world; import com.alpsbte.alpslib.utils.AlpsUtils; @@ -102,7 +78,7 @@ public boolean deleteWorld() { File worldGuardConfig = new File(DependencyManager.getWorldGuardConfigPath(getWorldName())); if (worldGuardConfig.exists()) FileUtils.deleteDirectory(worldGuardConfig); } catch (IOException ex) { - PlotSystem.getPlugin().getComponentLogger().warn(text("Could not delete config files for world " + getWorldName() + "!")); + PlotSystem.getPlugin().getComponentLogger().warn(text("Could not delete config files for world " + getWorldName() + "!"), ex); return false; } return true; From 6bc1691f3c114c1c6920558a87eadc8a45e2c7da Mon Sep 17 00:00:00 2001 From: Zoriot Date: Thu, 3 Sep 2026 14:12:12 +0200 Subject: [PATCH 47/60] ci: Update crowdin workflows --- .github/workflows/crowdin-download.yml | 26 +++++++++++++++++++++----- .github/workflows/crowdin-upload.yml | 2 +- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/.github/workflows/crowdin-download.yml b/.github/workflows/crowdin-download.yml index c42729ec..2c5b90c0 100644 --- a/.github/workflows/crowdin-download.yml +++ b/.github/workflows/crowdin-download.yml @@ -6,9 +6,8 @@ on: schedule: - cron: '0 0 */7 * *' - pull_request: - branches: [ main ] - types: [ opened ] + pull_request_review: + types: [ submitted ] permissions: contents: write @@ -21,6 +20,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v7 + with: + ref: main - name: Save existing translation config-versions shell: bash @@ -55,7 +56,7 @@ jobs: PY - name: Download translations from Crowdin - uses: crowdin/github-action@v2 + uses: crowdin/github-action@v3 with: upload_sources: false upload_translations: false @@ -79,7 +80,20 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Check Crowdin branch + id: crowdin_branch + shell: bash + run: | + BRANCH="l10n_crowdin_translations" + + if git ls-remote --exit-code --heads origin "$BRANCH" > /dev/null 2>&1; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + - name: Checkout Crowdin branch + if: steps.crowdin_branch.outputs.exists == 'true' shell: bash run: | sudo chown -R "$USER:$USER" "$GITHUB_WORKSPACE" @@ -90,6 +104,7 @@ jobs: git checkout -B "$BRANCH" "origin/$BRANCH" - name: Restore/bump config-version and update LangUtil + if: steps.crowdin_branch.outputs.exists == 'true' shell: bash run: | sudo chown -R "$USER:$USER" "$GITHUB_WORKSPACE" || true @@ -400,13 +415,14 @@ jobs: PY - name: Amend Crowdin commit + if: steps.crowdin_branch.outputs.exists == 'true' shell: bash run: | BRANCH="l10n_crowdin_translations" git config \ user.name \ - "github-actions[bot]" + "Crowdin[bot]" git config \ user.email \ diff --git a/.github/workflows/crowdin-upload.yml b/.github/workflows/crowdin-upload.yml index 3bec1935..79f46c10 100644 --- a/.github/workflows/crowdin-upload.yml +++ b/.github/workflows/crowdin-upload.yml @@ -15,7 +15,7 @@ jobs: uses: actions/checkout@v7 - name: Crowdin push - uses: crowdin/github-action@v2 + uses: crowdin/github-action@v3 with: upload_sources: true upload_translations: true From 2726556b7aff0fc395b79a5b1a9da1ab8d8c1986 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sun, 6 Sep 2026 22:57:03 +0200 Subject: [PATCH 48/60] fix: Handle player passenger on teleport --- .../plotsystem/core/system/plot/world/PlotWorld.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java index 0caba9b3..2a9adae1 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java @@ -11,6 +11,8 @@ import com.alpsbte.plotsystem.utils.DependencyManager; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.io.ConfigPaths; +import com.alpsbte.plotsystem.utils.io.LangPaths; +import com.alpsbte.plotsystem.utils.io.LangUtil; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldguard.WorldGuard; @@ -119,10 +121,12 @@ public boolean unloadWorld(boolean movePlayers) { @Override public boolean teleportPlayer(@NotNull Player player) { - if (loadWorld() && plot != null) { - player.teleport(getSpawnPoint(plot instanceof TutorialPlot ? null : plot.getCenter())); + if (loadWorld() && plot != null && + (player.getPassengers().isEmpty() || plot.getWorld().getBukkitWorld().equals(player.getWorld()) || player.eject()) && + player.teleport(getSpawnPoint(plot instanceof TutorialPlot ? null : plot.getCenter()))) { return true; } + player.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(player, LangPaths.Message.Error.ERROR_OCCURRED))); PlotSystem.getPlugin().getComponentLogger().warn(text("Could not teleport player " + player.getName() + " to world " + worldName + "!")); return false; } From 86488fdb687ce0c7c9d08e30cfe5e24ee421546d Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sun, 6 Sep 2026 23:00:57 +0200 Subject: [PATCH 49/60] fix: Handle nulls in last activity gracefully --- .../com/alpsbte/plotsystem/core/menus/review/ReviewMenu.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewMenu.java index 5243e509..bf2ca539 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewMenu.java +++ b/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewMenu.java @@ -100,7 +100,7 @@ protected void setPaginatedMenuItemsAsync(List source) { long inactivityIntervalDays = PlotSystem.getPlugin().getConfig().getLong(ConfigPaths.INACTIVITY_INTERVAL); long rejectedInactivityIntervalDays = (PlotSystem.getPlugin().getConfig().getLong(ConfigPaths.REJECTED_INACTIVITY_INTERVAL) != -1) ? PlotSystem.getPlugin().getConfig().getLong(ConfigPaths.REJECTED_INACTIVITY_INTERVAL) : inactivityIntervalDays; long interval = plot.isRejected() ? rejectedInactivityIntervalDays : inactivityIntervalDays; - if (interval > -1) { + if (interval > -1 && plot.getLastActivity() != null) { loreBuilder .emptyLine() .addLine(text(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Review.ABANDONED_IN_DAYS, String.valueOf(DAYS.between(LocalDate.now(), plot.getLastActivity().plusDays(interval)))), GRAY)); From 7313c203500c1d2d13ea6f24f39b9e7af7bf99f1 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sun, 6 Sep 2026 23:26:44 +0200 Subject: [PATCH 50/60] fix(plot): keep plot world generation off the main thread Teleporting called loadWorld(), which regenerated missing plot worlds and blocked on FAWE tasks that themselves need the main thread, freezing the server until the watchdog fired. World regeneration now lives in prepareWorld(), which callers invoke asynchronously before teleporting, and blocking waits fail fast when they run on the main thread. --- .../commands/plot/CMD_Plot_Teleport.java | 2 +- .../commands/review/CMD_Review.java | 4 +++ .../core/menus/PlotActionsMenu.java | 2 +- .../menus/review/ReviewPlotTogglesMenu.java | 5 +-- .../core/system/plot/PlotHandler.java | 10 +++--- .../generator/loader/AbstractPlotLoader.java | 33 +++++++++++++++-- .../generator/loader/DefaultPlotLoader.java | 11 +++--- .../core/system/plot/world/CityPlotWorld.java | 4 +-- .../core/system/plot/world/IWorld.java | 8 +++++ .../core/system/plot/world/OnePlotWorld.java | 4 +-- .../core/system/plot/world/PlotWorld.java | 5 +++ .../system/tutorial/AbstractPlotTutorial.java | 35 ++++++++++++------- 12 files changed, 88 insertions(+), 35 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Teleport.java b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Teleport.java index 18ae0a0e..cbdb6805 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Teleport.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Teleport.java @@ -67,7 +67,7 @@ public void onCommand(CommandSender sender, String[] args) { return; } - if (!plot.getWorld().loadWorld()) return; + if (!plot.getWorld().prepareWorld()) return; Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> plot.getWorld().teleportPlayer(player)); }); } diff --git a/src/main/java/com/alpsbte/plotsystem/commands/review/CMD_Review.java b/src/main/java/com/alpsbte/plotsystem/commands/review/CMD_Review.java index 6d4b813f..8922d27f 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/review/CMD_Review.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/review/CMD_Review.java @@ -117,6 +117,10 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N // If the reviewer is not on the plot, teleport the player first if (teleportPlayer) { + if (!plotToReview.getWorld().prepareWorld()) { + player.sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(player, LangPaths.Message.Error.ERROR_OCCURRED))); + return; + } Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { boolean teleported = plotToReview.getWorld().teleportPlayer(player); if (!teleported) return; diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/PlotActionsMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/PlotActionsMenu.java index 0090b81e..282eea99 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/menus/PlotActionsMenu.java +++ b/src/main/java/com/alpsbte/plotsystem/core/menus/PlotActionsMenu.java @@ -136,7 +136,7 @@ protected void setItemClickEventsAsync() { return; } CompletableFuture.runAsync(() -> { - if (!plot.getWorld().loadWorld()) return; + if (!plot.getWorld().prepareWorld()) return; Utils.runSync(() -> { plot.getWorld().teleportPlayer(clickPlayer); diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java index 17b4c279..988c00ca 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java +++ b/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java @@ -19,7 +19,6 @@ import com.alpsbte.plotsystem.utils.io.LangUtil; import com.alpsbte.plotsystem.utils.items.BaseItems; import com.alpsbte.plotsystem.utils.items.MenuItems; -import com.sk89q.worldedit.WorldEditException; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.NamedTextColor; @@ -31,11 +30,9 @@ import org.ipvp.canvas.mask.Mask; import org.jetbrains.annotations.NotNull; -import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import static net.kyori.adventure.text.Component.text; @@ -185,7 +182,7 @@ private boolean acceptPlot(int score, int splitScore) { PlotSystem.getPlugin().getComponentLogger().warn(text("Could not save finished plot schematic (ID: " + plot.getId() + ")!")); return false; } - } catch (IOException | WorldEditException | ExecutionException | InterruptedException ex) { + } catch (Exception ex) { PlotSystem.getPlugin().getComponentLogger().error(text("Could not save finished plot schematic (ID: " + plot.getId() + ")!"), ex); return false; } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java index ac752b52..e82a398a 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java @@ -19,7 +19,6 @@ import com.alpsbte.plotsystem.utils.io.ConfigUtil; import com.alpsbte.plotsystem.utils.io.LangPaths; import com.alpsbte.plotsystem.utils.io.LangUtil; -import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.bukkit.BukkitWorld; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; @@ -49,7 +48,6 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; import static net.kyori.adventure.text.Component.text; @@ -261,7 +259,7 @@ public static void removePlayerFromGenerationHistory(UUID playerUuid) { playerPlotGenerationHistory.remove(playerUuid); } - public static boolean savePlotAsSchematic(@NotNull Plot plot) throws IOException, WorldEditException, ExecutionException, InterruptedException { + public static boolean savePlotAsSchematic(@NotNull Plot plot) throws Exception { if (plot.getVersion() < 4) { PlotSystem.getPlugin().getComponentLogger().error(text("Saving schematics of legacy plots is no longer allowed!")); return false; @@ -288,7 +286,7 @@ public static boolean savePlotAsSchematic(@NotNull Plot plot) throws IOException // Copy and write finished plot clipboard to schematic ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - AbstractPlotLoader.runFaweAsync(() -> { + AbstractPlotLoader.runFaweBlocking(() -> { try (Clipboard cb = new BlockArrayClipboard(region)) { cb.setOrigin(BlockVector3.at(plotCenter.x(), cuboidRegion.getMinimumY(), (double) plotCenter.z())); @@ -302,7 +300,7 @@ public static boolean savePlotAsSchematic(@NotNull Plot plot) throws IOException writer.write(cb.transform(new AffineTransform().translate(Vector3.at(0, offset, 0)))); } } - }).get(); + }); // Set Completed Schematic boolean successful = DataProvider.PLOT.setCompletedSchematic(plot.getId(), outputStream.toByteArray()); @@ -318,7 +316,7 @@ public static boolean savePlotAsSchematic(@NotNull Plot plot) throws IOException throw new IOException("Could not generate city plot world!", exception); } } - AbstractPlotLoader.runFaweAsync(() -> AbstractPlotLoader.pasteSchematic(true, outputStream.toByteArray(), cpw, false, true)).get(); + AbstractPlotLoader.runFaweBlocking(() -> AbstractPlotLoader.pasteSchematic(true, outputStream.toByteArray(), cpw, false, true)); } return true; } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java index 13531694..af8ad0de 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java @@ -41,6 +41,7 @@ import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; import com.sk89q.worldguard.protection.regions.ProtectedRegion; import com.sk89q.worldguard.protection.regions.RegionContainer; +import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; import org.jetbrains.annotations.NotNull; @@ -51,6 +52,7 @@ import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; import static net.kyori.adventure.text.Component.text; @@ -78,6 +80,8 @@ protected AbstractPlotLoader( PlotWorld plotWorld, boolean completionActionsEnabled ) { + requireAsyncThread("Loading a plot"); + this.plot = plot; this.plotType = plotType; this.plotWorld = plotWorld; @@ -107,6 +111,7 @@ protected void generateWorld() throws Exception { } public static void ensureWorldGenerated(@NotNull PlotWorld world) throws Exception { + requireAsyncThread("Generating a plot world"); if (Utils.supplySync(world::isWorldGenerated).get()) return; Object lock = WORLD_GENERATION_LOCKS.computeIfAbsent(world.getWorldName(), ignored -> new Object()); @@ -224,13 +229,13 @@ protected List getBlockedCommands(@NotNull FileConfiguration config) { * Generates the structure for the plot */ protected void generateStructure() throws Exception { - runFaweAsync(() -> pasteSchematic(!plotType.hasEnvironment(), PlotUtils.getOutlinesSchematicBytes(plot, this.schematicBytes), this.plotWorld, true, false)).get(); + runFaweBlocking(() -> pasteSchematic(!plotType.hasEnvironment(), PlotUtils.getOutlinesSchematicBytes(plot, this.schematicBytes), this.plotWorld, true, false)); } /** * Runs a WorldEdit operation on FAWE's asynchronous task executor. */ - public static CompletableFuture runFaweAsync(@NotNull FaweTask task) { + private static CompletableFuture runFaweAsync(@NotNull FaweTask task) { CompletableFuture future = new CompletableFuture<>(); FaweAPI.getTaskManager().async(() -> { try { @@ -243,6 +248,30 @@ public static CompletableFuture runFaweAsync(@NotNull FaweTask task) { return future; } + /** + * Runs a WorldEdit operation on FAWE's asynchronous task executor and waits for it to finish. + */ + public static void runFaweBlocking(@NotNull FaweTask task) throws Exception { + requireAsyncThread("Waiting for a WorldEdit operation"); + try { + runFaweAsync(task).get(); + } catch (ExecutionException exception) { + if (exception.getCause() instanceof Exception cause) throw cause; + throw exception; + } + } + + /** + * Guards operations that block until the main thread has run part of their work. Calling them on the + * main thread stalls the server until the watchdog kicks in, because the work they wait for needs the + * very thread that is blocked. + */ + public static void requireAsyncThread(String operation) { + if (Bukkit.isPrimaryThread()) { + throw new IllegalStateException(operation + " must not happen on the main thread!"); + } + } + /** * Pastes the schematic to the plot center in the given world * diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java index b88b0219..81bf3129 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java @@ -42,7 +42,7 @@ protected void generateStructure() throws Exception { byte[] completedSchematic = p.getCompletedSchematic(); if (completedSchematic != null) { - runFaweAsync(() -> pasteSchematic(true, completedSchematic, plotWorld, false, true)).get(); + runFaweBlocking(() -> pasteSchematic(true, completedSchematic, plotWorld, false, true)); } else super.generateStructure(); copyToCityWorld(completedSchematic == null ? schematicBytes : completedSchematic, completedSchematic != null); } @@ -67,10 +67,11 @@ protected void copyToCityWorld(byte[] structureBytes, boolean completedSchematic CityPlotWorld cityPlotWorld = new CityPlotWorld((Plot) plot); try { ensureWorldGenerated(cityPlotWorld); - runFaweAsync(() -> AbstractPlotLoader.pasteSchematic(completedSchematic, structureBytes, cityPlotWorld, false, true)).get(); - } catch (Exception e) { - if (e.getCause() instanceof IOException ioException) throw ioException; - throw new IOException("Could not copy plot to city world!", e); + runFaweBlocking(() -> AbstractPlotLoader.pasteSchematic(completedSchematic, structureBytes, cityPlotWorld, false, true)); + } catch (IOException exception) { + throw exception; + } catch (Exception exception) { + throw new IOException("Could not copy plot to city world!", exception); } } } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java index 8d457227..bfa25581 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java @@ -149,7 +149,7 @@ public boolean onAbandon() { // paste initial schematic to reset plot try { - AbstractPlotLoader.runFaweAsync(() -> + AbstractPlotLoader.runFaweBlocking(() -> AbstractPlotLoader.pasteSchematic( false, PlotUtils.getOutlinesSchematicBytes(plot, plot.getInitialSchematicBytes()), @@ -157,7 +157,7 @@ public boolean onAbandon() { true, false ) - ).get(); + ); } catch (Exception e) { PlotSystem.getPlugin().getComponentLogger().error(text("Could not paste schematic!"), e); return false; diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/IWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/IWorld.java index ea6a1a0e..8c35348e 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/IWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/IWorld.java @@ -45,6 +45,14 @@ public interface IWorld { */ boolean loadWorld(); + /** + * Loads the plot world and generates it beforehand if its world files are missing. + * Generation waits for work that has to run on the main thread, so this must be called asynchronously. + * + * @return true if the world is generated and loaded + */ + boolean prepareWorld(); + /** * Unloads the plot world from memory. Plot cannot be used anymore. Plot has to be generated. * diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java index d0b6a997..ba067547 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java @@ -43,8 +43,8 @@ public boolean generateWorld(@NotNull Class ge } @Override - public boolean loadWorld() { - if (plot == null || isWorldGenerated()) return super.loadWorld(); + public boolean prepareWorld() { + if (plot == null || isWorldGenerated()) return super.prepareWorld(); // Rebuild a missing plot world exactly once, including completed plots. AbstractPlotLoader loader = plot.getPlotType() == PlotType.TUTORIAL diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java index 2a9adae1..2141f9dd 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java @@ -107,6 +107,11 @@ public boolean loadWorld() { } } + @Override + public boolean prepareWorld() { + return loadWorld(); + } + @Override public boolean unloadWorld(boolean movePlayers) { if (!isWorldLoaded()) return true; diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/AbstractPlotTutorial.java b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/AbstractPlotTutorial.java index 2b86f61d..d851fe59 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/AbstractPlotTutorial.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/AbstractPlotTutorial.java @@ -28,6 +28,7 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import static com.alpsbte.plotsystem.core.system.tutorial.utils.TutorialUtils.Sound; import static net.kyori.adventure.text.Component.text; @@ -122,22 +123,32 @@ protected void prepareStage(PrepareStageAction action) { if (stage != currentStage) return; // paste initial schematic outlines of stage - if (pasteSchematic) { - try { - onPlotSchematicPaste(getPlayerUUID(), ((AbstractPlotStage) stage).getInitSchematicId()); - } catch (Exception ex) { - onException(ex); + CompletableFuture schematicPaste = pasteSchematic + ? CompletableFuture.runAsync(() -> { + try { + onPlotSchematicPaste(getPlayerUUID(), ((AbstractPlotStage) stage).getInitSchematicId()); + } catch (Exception ex) { + throw new CompletionException(ex); + } + }) + : CompletableFuture.completedFuture(null); + + schematicPaste.whenComplete((ignored, throwable) -> Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + if (throwable != null) { + Throwable cause = throwable instanceof CompletionException && throwable.getCause() != null ? throwable.getCause() : throwable; + onException(cause instanceof Exception exception ? exception : new Exception(cause)); return; } - } - isPasteSchematic = false; + if (stage != currentStage) return; + isPasteSchematic = false; - // Send a new stage unlocked message to the player - sendStageUnlockedMessage(getPlayer(), currentStage.getTitle()); - getPlayer().playSound(getPlayer().getLocation(), Sound.STAGE_COMPLETED, 1f, 0.7f); + // Send a new stage unlocked message to the player + sendStageUnlockedMessage(getPlayer(), currentStage.getTitle()); + getPlayer().playSound(getPlayer().getLocation(), Sound.STAGE_COMPLETED, 1f, 0.7f); - // Mark stage preparation as done - action.setDone(); + // Mark stage preparation as done + action.setDone(); + })); }, 20); } From 32f59640e2325e494ac2cf7efffa7ab04df011da Mon Sep 17 00:00:00 2001 From: Zoriot Date: Mon, 7 Sep 2026 22:38:11 +0200 Subject: [PATCH 51/60] chore: Log Plot Id on Generation error --- .../core/system/plot/generator/loader/AbstractPlotLoader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java index af8ad0de..9a174f1e 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java @@ -332,10 +332,10 @@ protected void onException(Exception e) { PlotSystem.getPlugin().getComponentLogger().error("Failed to clean up plot #{} after generation error!", plot.getId()); } } catch (Exception ex) { - PlotSystem.getPlugin().getComponentLogger().error(text("Failed to clean up plot after generation error!"), ex); + PlotSystem.getPlugin().getComponentLogger().error(text("Failed to clean up plot #{} after generation error!"), plot.getId(), ex); } - PlotSystem.getPlugin().getComponentLogger().error(text("An error occurred while generating plot!"), e); + PlotSystem.getPlugin().getComponentLogger().error(text("An error occurred while generating plot #{}!"), plot.getId(), e); Utils.runSync(() -> { if (builder != null && builder.getPlayer() != null) { builder.getPlayer().sendMessage(Utils.ChatUtils.getAlertFormat(LangUtil.getInstance().get(builder.getPlayer(), LangPaths.Message.Error.ERROR_OCCURRED))); From 928e84125df7984cf2d76952d367a73eab6f6745 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Mon, 7 Sep 2026 22:49:10 +0200 Subject: [PATCH 52/60] Revert "chore: Throw exception when plot is outside of supported range" This reverts commit 339d69b1 We paste it on the original position anyway so it's fine to fallback to 0 there --- .../plotsystem/core/system/plot/world/CityPlotWorld.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java index bfa25581..819db500 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java @@ -100,8 +100,8 @@ public int getWorldHeight() throws IOException { } // Plots created outside of vanilla build limit or the build-able height is too small - if (plotHeight + groundLayer < minHeight || plotHeight + groundLayer + minBuildingHeight > maxHeight) - throw new IOException("Plot height is out of range."); + if (plotHeight + groundLayer < minHeight + groundLayer || plotHeight + groundLayer + minBuildingHeight > maxHeight) + return 0; return plotHeight; } From 5bf4bc496fffda2490000daa4b2c97016dfd6f18 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Tue, 8 Sep 2026 20:22:01 +0200 Subject: [PATCH 53/60] fix(plot): preserve existing plots during world recovery Prevent failed rebuilds of existing one-plot worlds from abandoning their plots, while retaining rollback behavior for newly assigned plots. Resolve existing plot worlds from persisted type and legacy compatibility instead of the new-plot CIM configuration. --- .../generator/loader/AbstractPlotLoader.java | 25 +++++++++++++++---- .../generator/loader/DefaultPlotLoader.java | 11 ++++++-- .../core/system/plot/world/OnePlotWorld.java | 2 +- .../core/system/plot/world/PlotWorld.java | 7 +++--- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java index 9a174f1e..b4437fb6 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java @@ -65,6 +65,7 @@ public abstract class AbstractPlotLoader { protected final Builder builder; protected final boolean completionActionsEnabled; + private final boolean abandonOnFailure; protected byte[] schematicBytes = null; private boolean successful; @@ -79,6 +80,17 @@ protected AbstractPlotLoader( PlotType plotType, PlotWorld plotWorld, boolean completionActionsEnabled + ) { + this(plot, builder, plotType, plotWorld, completionActionsEnabled, true); + } + + protected AbstractPlotLoader( + @NotNull AbstractPlot plot, + Builder builder, + PlotType plotType, + PlotWorld plotWorld, + boolean completionActionsEnabled, + boolean abandonOnFailure ) { requireAsyncThread("Loading a plot"); @@ -87,6 +99,7 @@ protected AbstractPlotLoader( this.plotWorld = plotWorld; this.builder = builder; this.completionActionsEnabled = completionActionsEnabled; + this.abandonOnFailure = abandonOnFailure; PlotSystem.getPlugin().getComponentLogger().info("Loading plot #{}...", plot.getId()); PlotSystem.getPlugin().getComponentLogger().info("Plot Type: {}", plotType.name()); @@ -327,12 +340,14 @@ public static void pasteSchematic(boolean ignoreAir, byte[] schematicFile, @NotN } protected void onException(Exception e) { - try { - if (!PlotHandler.abandonPlot(this.plot)) { - PlotSystem.getPlugin().getComponentLogger().error("Failed to clean up plot #{} after generation error!", plot.getId()); + if (abandonOnFailure) { + try { + if (!PlotHandler.abandonPlot(this.plot)) { + PlotSystem.getPlugin().getComponentLogger().error("Failed to clean up plot #{} after generation error!", plot.getId()); + } + } catch (Exception ex) { + PlotSystem.getPlugin().getComponentLogger().error(text("Failed to clean up plot #{} after generation error!"), plot.getId(), ex); } - } catch (Exception ex) { - PlotSystem.getPlugin().getComponentLogger().error(text("Failed to clean up plot #{} after generation error!"), plot.getId(), ex); } PlotSystem.getPlugin().getComponentLogger().error(text("An error occurred while generating plot #{}!"), plot.getId(), e); diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java index 81bf3129..30b9519c 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java @@ -21,8 +21,15 @@ public DefaultPlotLoader(@NotNull AbstractPlot plot, Builder builder, PlotType p super(plot, builder, plotType, plotWorld); } - public DefaultPlotLoader(@NotNull AbstractPlot plot, Builder builder, PlotType plotType, PlotWorld plotWorld, boolean completionActionsEnabled) { - super(plot, builder, plotType, plotWorld, completionActionsEnabled); + public DefaultPlotLoader( + @NotNull AbstractPlot plot, + Builder builder, + PlotType plotType, + PlotWorld plotWorld, + boolean completionActionsEnabled, + boolean abandonOnFailure + ) { + super(plot, builder, plotType, plotWorld, completionActionsEnabled, abandonOnFailure); } public DefaultPlotLoader(@NotNull AbstractPlot plot, Builder builder) { diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java index ba067547..b91ad1cd 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/OnePlotWorld.java @@ -49,7 +49,7 @@ public boolean prepareWorld() { // Rebuild a missing plot world exactly once, including completed plots. AbstractPlotLoader loader = plot.getPlotType() == PlotType.TUTORIAL ? new TutorialPlotLoader(plot, plotOwner) - : new DefaultPlotLoader(plot, plotOwner, plot.getPlotType(), this, false); + : new DefaultPlotLoader(plot, plotOwner, plot.getPlotType(), this, false, false); if (!loader.isSuccessful() || !isWorldGenerated() || !isWorldLoaded()) { PlotSystem.getPlugin().getComponentLogger().warn(text("Could not regenerate world " + getWorldName() + " for plot " + plot.getId() + "!")); return false; diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java index 2141f9dd..13b87969 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java @@ -10,7 +10,6 @@ import com.alpsbte.plotsystem.core.system.plot.utils.PlotType; import com.alpsbte.plotsystem.utils.DependencyManager; import com.alpsbte.plotsystem.utils.Utils; -import com.alpsbte.plotsystem.utils.io.ConfigPaths; import com.alpsbte.plotsystem.utils.io.LangPaths; import com.alpsbte.plotsystem.utils.io.LangUtil; import com.sk89q.worldedit.bukkit.BukkitAdapter; @@ -251,8 +250,8 @@ public static boolean isCityPlotWorld(@NotNull String worldName) { } public static PlotWorld getByType(PlotType type, Plot plot) { - // TODO: rework - boolean disableCIM = PlotSystem.getPlugin().getConfig().getBoolean(ConfigPaths.DISABLE_CITY_INSPIRATION_MODE); - return disableCIM || type.hasOnePlotPerWorld() ? new OnePlotWorld(plot) : new CityPlotWorld(plot); + return plot.getVersion() <= AbstractPlot.LEGACY_VERSION_THRESHOLD || type.hasOnePlotPerWorld() + ? new OnePlotWorld(plot) + : new CityPlotWorld(plot); } } From c9c9df4c7ff338f226c01e39177e298f1ea2f29e Mon Sep 17 00:00:00 2001 From: Zoriot Date: Tue, 8 Sep 2026 21:11:52 +0200 Subject: [PATCH 54/60] refactor: modernize tutorial APIs and plot schematic saving Reduce schematic-save complexity, replace raw exception wrappers with specific completion exceptions, and document the tutorial loader lifecycle hook. Migrate tutorial menus, holograms, and NPC text to Adventure components and NamedTextColor. --- .../core/holograms/PlotTutorialHologram.java | 29 +++-- .../menus/tutorial/TutorialStagesMenu.java | 37 +++--- .../core/system/plot/PlotHandler.java | 112 ++++++++++-------- .../generator/loader/AbstractPlotLoader.java | 2 +- .../generator/loader/TutorialPlotLoader.java | 4 +- .../system/tutorial/AbstractPlotTutorial.java | 9 +- .../stage/tasks/PlotSchematicPasteTask.java | 3 +- 7 files changed, 108 insertions(+), 88 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/holograms/PlotTutorialHologram.java b/src/main/java/com/alpsbte/plotsystem/core/holograms/PlotTutorialHologram.java index 9ed7dd9c..ee24f4a4 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/holograms/PlotTutorialHologram.java +++ b/src/main/java/com/alpsbte/plotsystem/core/holograms/PlotTutorialHologram.java @@ -8,17 +8,20 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.Sound; import org.bukkit.entity.Player; -import static org.bukkit.ChatColor.BOLD; -import static org.bukkit.ChatColor.DARK_GRAY; -import static org.bukkit.ChatColor.GOLD; -import static org.bukkit.ChatColor.GRAY; -import static org.bukkit.ChatColor.GREEN; -import static org.bukkit.ChatColor.YELLOW; +import static net.kyori.adventure.text.format.NamedTextColor.DARK_GRAY; +import static net.kyori.adventure.text.format.NamedTextColor.GOLD; +import static net.kyori.adventure.text.format.NamedTextColor.GRAY; +import static net.kyori.adventure.text.format.NamedTextColor.GREEN; +import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; +import static net.kyori.adventure.text.format.TextDecoration.BOLD; +import static net.kyori.adventure.text.Component.text; public class PlotTutorialHologram extends AbstractTutorialHologram { + private static final LegacyComponentSerializer LEGACY_SERIALIZER = LegacyComponentSerializer.legacySection(); public PlotTutorialHologram(Player player, int plotTutorialId, int holoId, String content) { this(player, plotTutorialId, holoId, content, -1); } @@ -29,12 +32,13 @@ public PlotTutorialHologram(Player player, int plotTutorialId, int holoId, Strin @Override protected String getTitle() { - return GOLD + BOLD.toString() + LangUtil.getInstance().get(player, LangPaths.Note.TIP).toUpperCase(); + return serialize(text(LangUtil.getInstance().get(player, LangPaths.Note.TIP).toUpperCase(), GOLD).decorate(BOLD)); } @Override protected String getReadMoreActionText() { - return DARK_GRAY + "[" + GRAY + LangUtil.getInstance().get(player, LangPaths.Note.Action.READ_MORE) + DARK_GRAY + "]"; + return serialize(text("[", DARK_GRAY).append(text(LangUtil.getInstance().get(player, LangPaths.Note.Action.READ_MORE), GRAY)) + .append(text("]", DARK_GRAY))); } @Override @@ -46,11 +50,16 @@ protected void handleReadMoreClickAction() { @Override protected String getMarkAsReadActionText() { - return DARK_GRAY + "[" + YELLOW + LangUtil.getInstance().get(player, LangPaths.Note.Action.MARK_AS_READ) + " " + READ_EMOJI + DARK_GRAY + "]"; + return serialize(text("[", DARK_GRAY).append(text(LangUtil.getInstance().get(player, LangPaths.Note.Action.MARK_AS_READ) + " " + READ_EMOJI, YELLOW)) + .append(text("]", DARK_GRAY))); } @Override protected String getMarkAsReadClickedActionText() { - return GREEN + LangUtil.getInstance().get(player, LangPaths.Note.Action.READ) + " " + READ_EMOJI; + return serialize(text(LangUtil.getInstance().get(player, LangPaths.Note.Action.READ) + " " + READ_EMOJI, GREEN)); + } + + private static String serialize(Component component) { + return LEGACY_SERIALIZER.serialize(component); } } diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/tutorial/TutorialStagesMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/tutorial/TutorialStagesMenu.java index 78b2507e..ff7703f3 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/menus/tutorial/TutorialStagesMenu.java +++ b/src/main/java/com/alpsbte/plotsystem/core/menus/tutorial/TutorialStagesMenu.java @@ -1,7 +1,7 @@ package com.alpsbte.plotsystem.core.menus.tutorial; import com.alpsbte.alpslib.utils.item.ItemBuilder; -import com.alpsbte.alpslib.utils.item.LegacyLoreBuilder; +import com.alpsbte.alpslib.utils.item.LoreBuilder; import com.alpsbte.plotsystem.core.database.DataProvider; import com.alpsbte.plotsystem.core.menus.AbstractMenu; import com.alpsbte.plotsystem.core.system.plot.TutorialPlot; @@ -14,7 +14,9 @@ import com.alpsbte.plotsystem.utils.io.LangPaths; import com.alpsbte.plotsystem.utils.io.LangUtil; import com.alpsbte.plotsystem.utils.items.MenuItems; -import net.md_5.bungee.api.ChatColor; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; import org.bukkit.Material; import org.bukkit.Sound; import org.bukkit.entity.Player; @@ -22,13 +24,6 @@ import org.ipvp.canvas.mask.BinaryMask; import org.ipvp.canvas.mask.Mask; -import static net.md_5.bungee.api.ChatColor.AQUA; -import static net.md_5.bungee.api.ChatColor.BOLD; -import static net.md_5.bungee.api.ChatColor.GREEN; -import static net.md_5.bungee.api.ChatColor.RED; -import static net.md_5.bungee.api.ChatColor.WHITE; -import static net.md_5.bungee.api.ChatColor.YELLOW; - public class TutorialStagesMenu extends AbstractMenu { private static final int TOTAL_STAGES_ROWS = 2; private static final int TOTAL_STAGES_SLOTS = TOTAL_STAGES_ROWS * (9 - 2); @@ -94,8 +89,8 @@ protected void setPreviewItems() { // Set end tutorial item if the player is in a tutorial, otherwise set back item if (playerCurrentStage != -1) { getMenu().getSlot(49).setItem(new ItemBuilder(Material.BARRIER) - .setName(RED + BOLD.toString() + LangUtil.getInstance().get(getMenuPlayer(), LangPaths.MenuTitle.TUTORIAL_END)) - .setStringLore(new LegacyLoreBuilder().addLine(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.MenuDescription.TUTORIAL_END)).build()) + .setName(Component.text(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.MenuTitle.TUTORIAL_END), NamedTextColor.RED, TextDecoration.BOLD)) + .setLore(new LoreBuilder().addLine(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.MenuDescription.TUTORIAL_END), true).build()) .build()); } else getMenu().getSlot(49).setItem(MenuItems.backMenuItem(getMenuPlayer())); @@ -112,12 +107,12 @@ protected void setMenuItemsAsync() { // Set tutorial stats item ItemBuilder tutorialItem = new ItemBuilder(Material.valueOf(tutorialItemName)); - tutorialItem.setName(AQUA + BOLD.toString() + LangUtil.getInstance().get(getMenuPlayer(), LangPaths.MenuTitle.TUTORIAL_BEGINNER)); + tutorialItem.setName(Component.text(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.MenuTitle.TUTORIAL_BEGINNER), NamedTextColor.AQUA, TextDecoration.BOLD)); if (plot != null) { - tutorialItem.setStringLore( - new LegacyLoreBuilder().addLines("", - LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Tutorials.STAGE) + ": " + WHITE + - (playerHighestStage + (isTutorialCompleted ? 1 : 0)) + "/" + ConfigUtil.getTutorialInstance().configs[tutorialId].getInt(TutorialUtils.Path.TUTORIAL_STAGES)) + tutorialItem.setLore( + new LoreBuilder().addLines(Component.empty(), + Component.text(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Tutorials.STAGE) + ": ") + .append(Component.text((playerHighestStage + (isTutorialCompleted ? 1 : 0)) + "/" + ConfigUtil.getTutorialInstance().configs[tutorialId].getInt(TutorialUtils.Path.TUTORIAL_STAGES), NamedTextColor.WHITE))) .build()); } getMenu().getSlot(4).setItem(tutorialItem.build()); @@ -146,7 +141,7 @@ protected void setItemClickEventsAsync() { } // Set click event for back item - getMenu().getSlot(49).setClickHandler((clickPlayer, clickInformation) -> { + getMenu().getSlot(49).setClickHandler((clickPlayer, _) -> { if (playerCurrentStage != -1) { clickPlayer.closeInventory(); tutorial.onTutorialStop(clickPlayer.getUniqueId()); @@ -204,10 +199,10 @@ private void setStageClickEvent(int slot, int stageId) { * @return The menu stage item */ private ItemStack getStageItem(int tutorialId, int stageId) { - LegacyLoreBuilder lore = new LegacyLoreBuilder().addLine(getStageTitle(getMenuPlayer(), tutorialId, stageId + 1)); + LoreBuilder lore = new LoreBuilder().addLine(getStageTitle(getMenuPlayer(), tutorialId, stageId + 1)); boolean isInProgress = playerHighestStage == stageId && plot != null && !isTutorialCompleted; - ChatColor titleColor = isInProgress ? YELLOW : (stageId < playerHighestStage || isTutorialCompleted ? GREEN : RED); + NamedTextColor titleColor = isInProgress ? NamedTextColor.YELLOW : (stageId < playerHighestStage || isTutorialCompleted ? NamedTextColor.GREEN : NamedTextColor.RED); ItemStack stageItem = new ItemStack(isInProgress ? Material.YELLOW_STAINED_GLASS_PANE : (stageId < playerHighestStage || isTutorialCompleted ? Material.LIME_STAINED_GLASS_PANE : Material.RED_STAINED_GLASS_PANE), stageId + 1); boolean isClickable = stageId != playerCurrentStage && stageId <= playerHighestStage; @@ -216,8 +211,8 @@ private ItemStack getStageItem(int tutorialId, int stageId) { "§e" + LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Note.Action.START)); return new ItemBuilder(stageItem) - .setName(titleColor + BOLD.toString() + LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Tutorials.STAGE) + " " + (stageId + 1)) - .setStringLore(lore.build()) + .setName(Component.text(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Tutorials.STAGE) + " " + (stageId + 1), titleColor, TextDecoration.BOLD)) + .setLore(lore.build()) .setEnchanted(playerCurrentStage == stageId) .build(); } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java index e82a398a..102f8cf2 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java @@ -260,64 +260,76 @@ public static void removePlayerFromGenerationHistory(UUID playerUuid) { } public static boolean savePlotAsSchematic(@NotNull Plot plot) throws Exception { - if (plot.getVersion() < 4) { - PlotSystem.getPlugin().getComponentLogger().error(text("Saving schematics of legacy plots is no longer allowed!")); - return false; - } + if (!isSchematicSaveSupported(plot)) return false; - Clipboard clipboard; - ByteArrayInputStream inputStream = new ByteArrayInputStream(plot.getInitialSchematicBytes()); - try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader(inputStream)) { - clipboard = reader.read(); - } - if (clipboard == null) return false; + Clipboard initialSchematic = readInitialSchematic(plot); + if (initialSchematic == null) return false; - CuboidRegion cuboidRegion = PlotUtils.getPlotAsRegion(plot); - if (cuboidRegion == null) return false; + byte[] completedSchematic = createCompletedSchematic(plot, initialSchematic); + if (!DataProvider.PLOT.setCompletedSchematic(plot.getId(), completedSchematic)) return false; - BlockVector3 plotCenter = plot.getCenter(); + copySchematicToCityWorld(plot, completedSchematic); + return true; + } - // Get plot outline - List plotOutlines = plot.getOutline(); + private static boolean isSchematicSaveSupported(Plot plot) { + if (plot.getVersion() >= 4) return true; + PlotSystem.getPlugin().getComponentLogger().error(text("Saving schematics of legacy plots is no longer allowed!")); + return false; + } - // Load finished plot region as cuboid region - if (!plot.getWorld().loadWorld()) return false; - Polygonal2DRegion region = new Polygonal2DRegion(null, plotOutlines, cuboidRegion.getMinimumPoint().y(), cuboidRegion.getMaximumPoint().y()); + private static Clipboard readInitialSchematic(Plot plot) throws IOException { + try (ClipboardReader reader = AbstractPlot.CLIPBOARD_FORMAT.getReader( + new ByteArrayInputStream(plot.getInitialSchematicBytes()))) { + return reader.read(); + } + } - // Copy and write finished plot clipboard to schematic + private static byte[] createCompletedSchematic(Plot plot, Clipboard initialSchematic) throws Exception { + CuboidRegion cuboidRegion = PlotUtils.getPlotAsRegion(plot); + if (cuboidRegion == null || !plot.getWorld().loadWorld()) return null; + + Polygonal2DRegion region = new Polygonal2DRegion( + null, + plot.getOutline(), + cuboidRegion.getMinimumPoint().y(), + cuboidRegion.getMaximumPoint().y() + ); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - AbstractPlotLoader.runFaweBlocking(() -> { - try (Clipboard cb = new BlockArrayClipboard(region)) { - cb.setOrigin(BlockVector3.at(plotCenter.x(), cuboidRegion.getMinimumY(), (double) plotCenter.z())); - - World world = new BukkitWorld(plot.getWorld().getBukkitWorld()); - ForwardExtentCopy forwardExtentCopy = new ForwardExtentCopy(world, region, cb, region.getMinimumPoint()); - Operations.complete(forwardExtentCopy); - - try (ClipboardWriter writer = AbstractPlot.CLIPBOARD_FORMAT.getWriter(outputStream)) { - double initialY = clipboard.getRegion().getMinimumY(); - double offset = initialY - cuboidRegion.getMinimumY(); - writer.write(cb.transform(new AffineTransform().translate(Vector3.at(0, offset, 0)))); - } - } - }); - - // Set Completed Schematic - boolean successful = DataProvider.PLOT.setCompletedSchematic(plot.getId(), outputStream.toByteArray()); - if (!successful) return false; - - // If plot was created in a void world, copy the result to the city world - if (plot.getPlotType() != PlotType.CITY_INSPIRATION_MODE) { - var cpw = new CityPlotWorld(plot); - if (!cpw.isWorldGenerated()) { - try { - AbstractPlotLoader.ensureWorldGenerated(cpw); - } catch (Exception exception) { - throw new IOException("Could not generate city plot world!", exception); - } + AbstractPlotLoader.runFaweBlocking(() -> writeCompletedSchematic(plot, initialSchematic, cuboidRegion, region, outputStream)); + return outputStream.toByteArray(); + } + + private static void writeCompletedSchematic( + Plot plot, + Clipboard initialSchematic, + CuboidRegion cuboidRegion, + Polygonal2DRegion region, + ByteArrayOutputStream outputStream + ) throws Exception { + try (Clipboard clipboard = new BlockArrayClipboard(region)) { + clipboard.setOrigin(BlockVector3.at(plot.getCenter().x(), cuboidRegion.getMinimumY(), (double) plot.getCenter().z())); + ForwardExtentCopy copy = new ForwardExtentCopy( + new BukkitWorld(plot.getWorld().getBukkitWorld()), + region, + clipboard, + region.getMinimumPoint() + ); + Operations.complete(copy); + + try (ClipboardWriter writer = AbstractPlot.CLIPBOARD_FORMAT.getWriter(outputStream)) { + double offset = initialSchematic.getRegion().getMinimumY() - cuboidRegion.getMinimumY(); + writer.write(clipboard.transform(new AffineTransform().translate(Vector3.at(0, offset, 0)))); } - AbstractPlotLoader.runFaweBlocking(() -> AbstractPlotLoader.pasteSchematic(true, outputStream.toByteArray(), cpw, false, true)); } - return true; + } + + private static void copySchematicToCityWorld(Plot plot, byte[] schematic) throws Exception { + if (plot.getPlotType() == PlotType.CITY_INSPIRATION_MODE) return; + + CityPlotWorld cityPlotWorld = new CityPlotWorld(plot); + if (!cityPlotWorld.isWorldGenerated()) AbstractPlotLoader.ensureWorldGenerated(cityPlotWorld); + AbstractPlotLoader.runFaweBlocking(() -> + AbstractPlotLoader.pasteSchematic(true, schematic, cityPlotWorld, false, true)); } } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java index b4437fb6..a8285352 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java @@ -144,7 +144,7 @@ protected void loadWorld() throws Exception { if (plotWorld.isWorldLoaded()) return null; boolean successful = plotWorld.loadWorld(); - if (!successful) throw new Exception("Could not load world!"); + if (!successful) throw new IOException("Could not load world!"); return null; }).get(); } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/TutorialPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/TutorialPlotLoader.java index 2d68195d..9899c5f2 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/TutorialPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/TutorialPlotLoader.java @@ -78,5 +78,7 @@ public void setWorldEditEnabled(boolean worldEditEnabled) { } @Override - protected void onCompletion() {} + protected void onCompletion() { + // Tutorial completion is handled by the tutorial lifecycle. + } } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/AbstractPlotTutorial.java b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/AbstractPlotTutorial.java index d851fe59..eab47a04 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/AbstractPlotTutorial.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/AbstractPlotTutorial.java @@ -19,7 +19,7 @@ import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; import org.bukkit.Bukkit; -import org.bukkit.ChatColor; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -78,10 +78,11 @@ protected AbstractPlotTutorial(Player player, int tutorialId, int stageId) { @Override protected TutorialNPC initNpc() { + LegacyComponentSerializer serializer = LegacyComponentSerializer.legacySection(); return new TutorialNPC( "ps-tutorial-" + tutorialPlot.getId(), - ChatColor.GOLD + ChatColor.BOLD.toString() + PlotSystem.getPlugin().getConfig().getString(ConfigPaths.TUTORIAL_NPC_NAME), - ChatColor.GRAY + "(" + LangUtil.getInstance().get(getPlayer(), LangPaths.Note.Action.RIGHT_CLICK) + ")", + serializer.serialize(text(PlotSystem.getPlugin().getConfig().getString(ConfigPaths.TUTORIAL_NPC_NAME), GOLD).decorate(BOLD)), + serializer.serialize(text("(" + LangUtil.getInstance().get(getPlayer(), LangPaths.Note.Action.RIGHT_CLICK) + ")", GRAY)), PlotSystem.getPlugin().getConfig().getString(ConfigPaths.TUTORIAL_NPC_TEXTURE), PlotSystem.getPlugin().getConfig().getString(ConfigPaths.TUTORIAL_NPC_SIGNATURE)); } @@ -174,7 +175,7 @@ public CompletableFuture switchWorldAsync(@NotNull UUID playerUUID, int tu tutorialPlot.setTutorialSchematic(schematicId); onPlotSchematicPaste(playerUUID, schematicId); } catch (Exception ex) { - throw new RuntimeException(ex); + throw new CompletionException(ex); } }).thenCompose(ignored -> { CompletableFuture future = new CompletableFuture<>(); diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/PlotSchematicPasteTask.java b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/PlotSchematicPasteTask.java index 4ba50d01..d18f9e9f 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/PlotSchematicPasteTask.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/tutorial/stage/tasks/PlotSchematicPasteTask.java @@ -6,6 +6,7 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; +import java.util.concurrent.CompletionException; import java.util.concurrent.CompletableFuture; public class PlotSchematicPasteTask extends AbstractTask { @@ -24,7 +25,7 @@ public void performTask() { try { tutorial.onPlotSchematicPaste(player.getUniqueId(), schematicId); } catch (Exception ex) { - throw new RuntimeException(ex); + throw new CompletionException(ex); } }).whenComplete((ignored, throwable) -> Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { if (throwable != null) { From 01ad7a3d21f302170130a537e970cfdf28abdafc Mon Sep 17 00:00:00 2001 From: R3tuxn Date: Sat, 12 Sep 2026 14:46:47 +0200 Subject: [PATCH 55/60] fix: adjust plot structure generation to handle plots with environments --- .../system/plot/generator/loader/AbstractPlotLoader.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java index a8285352..066c0f3c 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java @@ -242,7 +242,12 @@ protected List getBlockedCommands(@NotNull FileConfiguration config) { * Generates the structure for the plot */ protected void generateStructure() throws Exception { - runFaweBlocking(() -> pasteSchematic(!plotType.hasEnvironment(), PlotUtils.getOutlinesSchematicBytes(plot, this.schematicBytes), this.plotWorld, true, false)); + if (plotType.hasEnvironment()) { + // The schematic already contains the environment around the plot, so it is pasted as a whole. + runFaweBlocking(() -> pasteSchematic(false, this.schematicBytes, this.plotWorld, false, false)); + return; + } + runFaweBlocking(() -> pasteSchematic(true, PlotUtils.getOutlinesSchematicBytes(plot, this.schematicBytes), this.plotWorld, true, false)); } /** From 7c8aaa9ccda474dbc9cc4b38befccd945f4f57f9 Mon Sep 17 00:00:00 2001 From: R3tuxn Date: Sat, 12 Sep 2026 14:59:13 +0200 Subject: [PATCH 56/60] fix: log world generation metrics only in dev mode --- .../system/plot/generator/world/PlotWorldGenerator.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java index 31b7b6dc..8bf38a70 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/PlotWorldGenerator.java @@ -3,6 +3,7 @@ import com.alpsbte.plotsystem.PlotSystem; import com.alpsbte.plotsystem.utils.DependencyManager; import com.alpsbte.plotsystem.utils.Utils; +import com.alpsbte.plotsystem.utils.io.ConfigPaths; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldguard.WorldGuard; import com.sk89q.worldguard.protection.flags.Flags; @@ -39,6 +40,7 @@ public class PlotWorldGenerator { private static final World.Environment environment = World.Environment.NORMAL; public PlotWorldGenerator(String worldName) throws Exception { + boolean dev = PlotSystem.getPlugin().getConfig().getBoolean(ConfigPaths.DEV_MODE); long startTime = System.nanoTime(); this.worldName = worldName; @@ -54,8 +56,10 @@ public PlotWorldGenerator(String worldName) throws Exception { return null; }).get(); - PlotSystem.getPlugin().getComponentLogger().info("(PWG) Total time to generate world: {}ms", (System.nanoTime() - startTime) / 1_000_000); - PlotSystem.getPlugin().getComponentLogger().info("(PWG) Total time on main thread: {}ms", (System.nanoTime() - mainThreadStart) / 1_000_000); + if (dev) { + PlotSystem.getPlugin().getComponentLogger().info("(PWG) Total time to generate world: {}ms", (System.nanoTime() - startTime) / 1_000_000); + PlotSystem.getPlugin().getComponentLogger().info("(PWG) Total time on main thread: {}ms", (System.nanoTime() - mainThreadStart) / 1_000_000); + } } protected void generateWorld() throws IOException { From 51d21ad6319dae0e3d094669ff9055230cce441b Mon Sep 17 00:00:00 2001 From: R3tuxn Date: Sat, 12 Sep 2026 16:14:45 +0200 Subject: [PATCH 57/60] fix: normalize world name handling in config deletion --- .../plotsystem/core/system/plot/utils/PlotUtils.java | 2 +- .../plotsystem/core/system/plot/world/PlotWorld.java | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java index 0cf18c37..68a1e0fe 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/utils/PlotUtils.java @@ -344,7 +344,7 @@ public static void showOutlines() { if (plots.isEmpty()) continue; for (Plot plot : plots) { - if ((!plot.getWorld().getWorldName().equals(player.getWorld().getName())) || + if ((!plot.getWorld().getWorldName().equalsIgnoreCase(player.getWorld().getName())) || (!plot.getPlotOwner().getPlotType().hasEnvironment() || plot.getVersion() <= 2)) { continue; } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java index 13b87969..86ff0b7f 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/PlotWorld.java @@ -71,12 +71,14 @@ public boolean deleteWorld() { return false; } try { - var mviConfig = DependencyManager.getMultiverseInventoriesConfigPath(getWorldName()); + String configWorldName = getWorldName().toLowerCase(Locale.ROOT); + + var mviConfig = DependencyManager.getMultiverseInventoriesConfigPath(configWorldName); if (mviConfig != null) { File multiverseInventoriesConfig = new File(mviConfig); if (multiverseInventoriesConfig.exists()) FileUtils.deleteDirectory(multiverseInventoriesConfig); } - File worldGuardConfig = new File(DependencyManager.getWorldGuardConfigPath(getWorldName())); + File worldGuardConfig = new File(DependencyManager.getWorldGuardConfigPath(configWorldName)); if (worldGuardConfig.exists()) FileUtils.deleteDirectory(worldGuardConfig); } catch (IOException ex) { PlotSystem.getPlugin().getComponentLogger().warn(text("Could not delete config files for world " + getWorldName() + "!"), ex); From e52b1f39f8bb259e34fb64b1acb3bdb792e443f6 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sat, 12 Sep 2026 23:06:18 +0200 Subject: [PATCH 58/60] refactor: enhance command output formatting with Adventure API --- .../plotsystem/commands/BaseCommand.java | 76 ++++++++++++++----- .../plotsystem/commands/SubCommand.java | 43 ++++++----- .../commands/admin/CMD_DeletePlot.java | 9 ++- .../commands/admin/CMD_SetLeaderboard.java | 23 +++++- .../plotsystem/core/menus/PlotMemberMenu.java | 4 +- .../menus/tutorial/TutorialStagesMenu.java | 6 +- .../com/alpsbte/plotsystem/utils/Utils.java | 9 ++- 7 files changed, 124 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/commands/BaseCommand.java b/src/main/java/com/alpsbte/plotsystem/commands/BaseCommand.java index 9f696a6d..17819b79 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/BaseCommand.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/BaseCommand.java @@ -2,6 +2,9 @@ import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.io.LangUtil; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; @@ -89,26 +92,65 @@ protected void registerSubCommand(SubCommand subCommand) { @Override public void sendInfo(CommandSender sender) { - List lines = new ArrayList<>(); + List lines = new ArrayList<>(); + if (!subCommands.isEmpty()) { - lines.add("§8--------------------------"); - getSubCommands().forEach(sub -> { - StringBuilder subCommand = new StringBuilder("§7§l> §b/" + getNames()[0] + " §6" + sub.getNames()[0] + "§7"); - for (String parameter : sub.getParameter()) { - subCommand.append(" <").append(parameter).append(">"); - } - if (sub.getDescription() != null) subCommand.append(" §f- ").append(sub.getDescription()); - lines.add(subCommand.toString()); - }); - lines.add("§8--------------------------"); + lines.add(Component.text("--------------------------", NamedTextColor.DARK_GRAY)); + + getSubCommands().forEach(sub -> lines.add(createUsageLine( + "/" + getNames()[0] + " ", + sub.getNames()[0], + NamedTextColor.GOLD, + sub.getParameter(), + sub.getDescription() + ))); + + lines.add(Component.text("--------------------------", NamedTextColor.DARK_GRAY)); } else { - StringBuilder baseCommand = new StringBuilder("§7§l> §b/" + getNames()[0] + "§7"); - for (String parameter : getParameter()) { - baseCommand.append(" <").append(parameter).append(">"); - } - if (getDescription() != null) baseCommand.append(" §f- ").append(getDescription()); - lines.add(baseCommand.toString()); + lines.add(createUsageLine( + "/" + getNames()[0], + null, + NamedTextColor.AQUA, + getParameter(), + getDescription() + )); } + lines.forEach(sender::sendMessage); } + + public static Component createUsageLine( + String prefix, + String command, + NamedTextColor commandColor, + String[] parameters, + String description + ) { + Component component = Component.empty() + .append( + Component.text("> ", NamedTextColor.GRAY) + .decorate(TextDecoration.BOLD) + ) + .append(Component.text(prefix, NamedTextColor.AQUA)); + + if (command != null) { + component = component.append( + Component.text(command, commandColor) + ); + } + + for (String parameter : parameters) { + component = component.append( + Component.text(" <" + parameter + ">", NamedTextColor.GRAY) + ); + } + + if (description != null) { + component = component + .append(Component.text(" - ", NamedTextColor.WHITE)) + .append(Component.text(description, NamedTextColor.WHITE)); + } + + return component; + } } diff --git a/src/main/java/com/alpsbte/plotsystem/commands/SubCommand.java b/src/main/java/com/alpsbte/plotsystem/commands/SubCommand.java index ae6fbd9a..bc6de015 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/SubCommand.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/SubCommand.java @@ -1,6 +1,8 @@ package com.alpsbte.plotsystem.commands; import com.alpsbte.plotsystem.utils.io.LangUtil; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; @@ -75,26 +77,33 @@ public List getSubCommands() { @Override public void sendInfo(CommandSender sender) { - List lines = new ArrayList<>(); + List lines = new ArrayList<>(); + if (!subCommands.isEmpty()) { - lines.add("§8--------------------------"); - getSubCommands().forEach(sub -> { - StringBuilder subCommand = new StringBuilder("§7§l> §b/" + getBaseCommand().getNames()[0] + " §6" + getNames()[0] + " " + sub.getNames()[0] + "§7"); - for (String parameter : sub.getParameter()) { - subCommand.append(" <").append(parameter).append(">"); - } - if (sub.getDescription() != null) subCommand.append(" §f- ").append(sub.getDescription()); - lines.add(subCommand.toString()); - }); - lines.add("§8--------------------------"); + lines.add(Component.text("--------------------------", NamedTextColor.DARK_GRAY)); + + getSubCommands().forEach(sub -> lines.add(BaseCommand.createUsageLine( + "/" + getBaseCommand().getNames()[0] + " ", + getNames()[0] + " " + sub.getNames()[0], + NamedTextColor.GOLD, + sub.getParameter(), + sub.getDescription() + ))); + + lines.add(Component.text("--------------------------", NamedTextColor.DARK_GRAY)); } else { - StringBuilder baseCommand = new StringBuilder("§7§l> §b/" + getBaseCommand().getNames()[0] + " §6" + (getSubCommand() != null ? getSubCommand().getNames()[0] + " " : "") + getNames()[0] + "§7"); - for (String parameter : getParameter()) { - baseCommand.append(" <").append(parameter).append(">"); - } - if (getDescription() != null) baseCommand.append(" §f- ").append(getDescription()); - lines.add(baseCommand.toString()); + lines.add(BaseCommand.createUsageLine( + "/" + getBaseCommand().getNames()[0] + " " + + (getSubCommand() != null + ? getSubCommand().getNames()[0] + " " + : ""), + getNames()[0], + NamedTextColor.GOLD, + getParameter(), + getDescription() + )); } + lines.forEach(sender::sendMessage); } } diff --git a/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_DeletePlot.java b/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_DeletePlot.java index 0224e631..3f0bbac7 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_DeletePlot.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_DeletePlot.java @@ -6,8 +6,9 @@ import com.alpsbte.plotsystem.core.database.DataProvider; import com.alpsbte.plotsystem.core.system.plot.Plot; import com.alpsbte.plotsystem.core.system.plot.PlotHandler; -import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; import com.alpsbte.plotsystem.utils.Utils; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; @@ -48,7 +49,11 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N return; } Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - sender.sendMessage(Utils.ChatUtils.getInfoFormat("Successfully deleted plot with the ID §6#" + plotID + "§a!")); + sender.sendMessage(Utils.ChatUtils.getInfoFormat( + Component.text("Successfully deleted plot with the ID ") + .append(Component.text("#" + plotID, NamedTextColor.GOLD)) + .append(Component.text("!")) + )); if (getPlayer(sender) != null) getPlayer(sender).playSound(getPlayer(sender).getLocation(), Utils.SoundUtils.DONE_SOUND, 1f, 1f); }); }); diff --git a/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_SetLeaderboard.java b/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_SetLeaderboard.java index d67122a4..dcff9de1 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_SetLeaderboard.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_SetLeaderboard.java @@ -7,6 +7,7 @@ import com.alpsbte.plotsystem.utils.Utils; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -29,11 +30,27 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N Player player = (Player) sender; if (args.length != 1) { sendInfo(sender); - player.sendMessage("§8------- §6§lLeaderboards §8-------"); + Component message = Component.text() + .append(Component.text("------- ", NamedTextColor.DARK_GRAY)) + .append( + Component.text("Leaderboards", NamedTextColor.GOLD, TextDecoration.BOLD) + ) + .append(Component.text(" -------", NamedTextColor.DARK_GRAY)) + .append(Component.newline()) + .build(); + for (DecentHologramDisplay holo : DecentHologramDisplay.activeDisplays) { - player.sendMessage(" §6> §f" + holo.getId()); + message = message + .append(Component.text(" > ", NamedTextColor.GOLD)) + .append(Component.text(holo.getId(), NamedTextColor.WHITE)) + .append(Component.newline()); } - player.sendMessage("§8--------------------------"); + + message = message.append( + Component.text("--------------------------", NamedTextColor.DARK_GRAY) + ); + + player.sendMessage(message); return true; } diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/PlotMemberMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/PlotMemberMenu.java index 7be9da62..f5ed4046 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/menus/PlotMemberMenu.java +++ b/src/main/java/com/alpsbte/plotsystem/core/menus/PlotMemberMenu.java @@ -97,7 +97,7 @@ protected void setMenuItemsAsync() { .setLore(new LoreBuilder() .addLines(text(plot.getPlotOwner().getName()), empty(), - text(Utils.ItemUtils.getActionFormat(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Note.Action.CLICK_TO_OPEN_PLOTS_MENUE)))) + Utils.ItemUtils.getActionFormat(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Note.Action.CLICK_TO_OPEN_PLOTS_MENUE))) .build()) .build()); @@ -113,7 +113,7 @@ protected void setMenuItemsAsync() { .setLore(new LoreBuilder() .addLines(text(builder.getName()), empty(), - text(Utils.ItemUtils.getActionFormat(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Note.Action.CLICK_TO_REMOVE_PLOT_MEMBER)))) + Utils.ItemUtils.getActionFormat(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Note.Action.CLICK_TO_REMOVE_PLOT_MEMBER))) .build()) .build()); } diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/tutorial/TutorialStagesMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/tutorial/TutorialStagesMenu.java index ff7703f3..3cd65d9f 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/menus/tutorial/TutorialStagesMenu.java +++ b/src/main/java/com/alpsbte/plotsystem/core/menus/tutorial/TutorialStagesMenu.java @@ -207,8 +207,10 @@ private ItemStack getStageItem(int tutorialId, int stageId) { Material.LIME_STAINED_GLASS_PANE : Material.RED_STAINED_GLASS_PANE), stageId + 1); boolean isClickable = stageId != playerCurrentStage && stageId <= playerHighestStage; - if (isClickable) lore.addLines("", LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Note.Action.LEFT_CLICK) + " §8» " + - "§e" + LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Note.Action.START)); + if (isClickable) lore.addLines(Component.empty(), + Component.text(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Note.Action.LEFT_CLICK)) + .append(Component.text(" » ", NamedTextColor.DARK_GRAY)) + .append(Component.text(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Note.Action.START), NamedTextColor.YELLOW))); return new ItemBuilder(stageItem) .setName(Component.text(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Tutorials.STAGE) + " " + (stageId + 1), titleColor, TextDecoration.BOLD)) diff --git a/src/main/java/com/alpsbte/plotsystem/utils/Utils.java b/src/main/java/com/alpsbte/plotsystem/utils/Utils.java index cd3db3bf..72d53e68 100644 --- a/src/main/java/com/alpsbte/plotsystem/utils/Utils.java +++ b/src/main/java/com/alpsbte/plotsystem/utils/Utils.java @@ -18,6 +18,8 @@ import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -27,7 +29,6 @@ import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; @@ -212,8 +213,10 @@ private ItemUtils() {} return text("Note: ", RED).decoration(BOLD, true).append(text(note, DARK_GRAY).decoration(BOLD, false)); } - @Contract(pure = true) - public static @NotNull String getActionFormat(String action) {return "§8§l> §c" + action;} + public static @NotNull TextComponent getActionFormat(String action) { + return text("> ", NamedTextColor.DARK_GRAY, TextDecoration.BOLD) + .append(text(action, NamedTextColor.RED)); + } public static @NotNull Component getColoredPointsComponent(int points, int maxPoints) { return switch ((int) ((double) points / maxPoints * 5)) { From c05c465ce58e751b9a485b326d252cd1f9e71734 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sat, 12 Sep 2026 23:18:39 +0200 Subject: [PATCH 59/60] refactor: simplify color references in action formatting + remove unused Utils#getConfiguredItem --- .../com/alpsbte/plotsystem/utils/Utils.java | 25 ++----------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/alpsbte/plotsystem/utils/Utils.java b/src/main/java/com/alpsbte/plotsystem/utils/Utils.java index 72d53e68..c55797c2 100644 --- a/src/main/java/com/alpsbte/plotsystem/utils/Utils.java +++ b/src/main/java/com/alpsbte/plotsystem/utils/Utils.java @@ -2,8 +2,6 @@ import com.alpsbte.alpslib.io.database.SqlHelper; import com.alpsbte.alpslib.utils.AlpsUtils; -import com.alpsbte.alpslib.utils.head.AlpsHeadUtils; -import com.alpsbte.alpslib.utils.item.ItemBuilder; import com.alpsbte.plotsystem.PlotSystem; import com.alpsbte.plotsystem.core.menus.companion.CompanionMenu; import com.alpsbte.plotsystem.core.menus.review.ReviewMenu; @@ -18,12 +16,9 @@ import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; -import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.Bukkit; import org.bukkit.Location; -import org.bukkit.Material; import org.bukkit.Sound; import org.bukkit.command.CommandSender; import org.bukkit.configuration.file.FileConfiguration; @@ -37,7 +32,6 @@ import java.sql.SQLException; import java.time.LocalDateTime; import java.util.HashSet; -import java.util.Locale; import java.util.Objects; import java.util.Random; import java.util.Set; @@ -90,21 +84,6 @@ public static void updatePlayerInventorySlots(Player player) { }); } - public static ItemStack getConfiguredItem(@NotNull String material, Object customModelData) { - ItemStack base; - if (material.startsWith("head(") && material.endsWith(")")) { - String headId = material.substring(material.indexOf("(") + 1, material.lastIndexOf(")")); - base = AlpsHeadUtils.getCustomHead(headId); - } else { - Material mat = Material.getMaterial(material.toUpperCase(Locale.ROOT)); - base = new ItemStack(mat == null ? Material.BARRIER : mat); - } - ItemBuilder builder = new ItemBuilder(base); - if (customModelData != null) builder.setItemModel(customModelData); - - return builder.build(); - } - public static CompletableFuture runSync(Callable task) { CompletableFuture future = new CompletableFuture<>(); Runnable runnable = () -> { @@ -214,8 +193,8 @@ private ItemUtils() {} } public static @NotNull TextComponent getActionFormat(String action) { - return text("> ", NamedTextColor.DARK_GRAY, TextDecoration.BOLD) - .append(text(action, NamedTextColor.RED)); + return text("> ", DARK_GRAY, BOLD) + .append(text(action, RED)); } public static @NotNull Component getColoredPointsComponent(int points, int maxPoints) { From 98beccbcdc9001f219538073c3261550a46ae2e0 Mon Sep 17 00:00:00 2001 From: Zoriot Date: Sat, 12 Sep 2026 23:35:22 +0200 Subject: [PATCH 60/60] ci: Skip ref checkout in crowdin action so it works from pr's --- .github/workflows/crowdin-download.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/crowdin-download.yml b/.github/workflows/crowdin-download.yml index 2c5b90c0..beacd9fe 100644 --- a/.github/workflows/crowdin-download.yml +++ b/.github/workflows/crowdin-download.yml @@ -68,6 +68,7 @@ jobs: pull_request_title: "New Crowdin translations" commit_message: "New Crowdin translations" pull_request_base_branch_name: "main" + skip_ref_checkout: true project_id: ${{ vars.CROWDIN_PROJECT_ID }} token: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}