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 1cad216a..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,9 +55,18 @@ dependencies { implementation(libs.com.zaxxer.hikaricp) { exclude(group = "org.slf4j") } - 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) @@ -66,8 +78,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.+") } } } @@ -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" diff --git a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java index 6a7c0f67..93c1a623 100644 --- a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java +++ b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java @@ -12,6 +12,8 @@ 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; import com.alpsbte.plotsystem.core.system.tutorial.BeginnerTutorial; @@ -28,6 +30,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; @@ -117,14 +120,23 @@ 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)); - Bukkit.getScheduler().runTaskTimerAsynchronously(FancyNpcsPlugin.get().getPlugin(), new TutorialNPCTurnTracker(), 0, 1L); + Bukkit.getScheduler().runTaskTimer(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; @@ -196,4 +208,9 @@ public void initDatabase() throws IOException, SQLException { 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/commands/admin/CMD_DeletePlot.java b/src/main/java/com/alpsbte/plotsystem/commands/admin/CMD_DeletePlot.java index 35775b98..7d325b24 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; @@ -43,10 +44,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 7dd10f9a..c06a6b09 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 dd24a910..605e54e0 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_Teleport.java b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Teleport.java index 4f4b1883..b879c81b 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/commands/plot/CMD_Plot_UndoSubmit.java b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_UndoSubmit.java index a9b89bb9..a14013fc 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/EventListener.java b/src/main/java/com/alpsbte/plotsystem/core/EventListener.java index 8961f0d2..8d93279b 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()); @@ -172,7 +172,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/PlotActionsMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/PlotActionsMenu.java index 773294a1..31c452cf 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/menus/companion/CityProjectMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/companion/CityProjectMenu.java index fb9b4f47..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 @@ -8,7 +8,7 @@ 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.generator.DefaultPlotGenerator; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.PlotDifficulty; import com.alpsbte.plotsystem.utils.enums.Status; @@ -16,6 +16,7 @@ import com.alpsbte.plotsystem.utils.io.LangPaths; import com.alpsbte.plotsystem.utils.io.LangUtil; import com.alpsbte.plotsystem.utils.items.MenuItems; +import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.ipvp.canvas.mask.BinaryMask; import org.ipvp.canvas.mask.Mask; @@ -24,6 +25,7 @@ import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class CityProjectMenu extends AbstractPaginatedMenu { @@ -64,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)); @@ -96,10 +99,32 @@ 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())); + PlotHandler.assignAndGeneratePlot(builder, plot); + } + 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())); @@ -108,15 +133,14 @@ public static boolean generateRandomPlot(Player player, @NotNull List player.closeInventory()); + boolean successful = PlotHandler.assignAndGenerateRandomPlot(builder, randomCity, difficulty); + 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) { @@ -175,29 +199,9 @@ protected void setPaginatedItemClickEventsAsync(@NotNull 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++; } @@ -205,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 +} 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 2058c47c..2d837e4e 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,7 +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.utils.PlotUtils; +import com.alpsbte.plotsystem.core.system.plot.PlotHandler; import com.alpsbte.plotsystem.core.system.review.PlotReview; import com.alpsbte.plotsystem.core.system.review.ReviewRating; import com.alpsbte.plotsystem.core.system.review.ToggleCriteria; @@ -34,6 +34,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ExecutionException; import static net.kyori.adventure.text.Component.text; @@ -131,7 +132,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(), () -> { @@ -172,11 +173,11 @@ 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 (!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/Builder.java b/src/main/java/com/alpsbte/plotsystem/core/system/Builder.java index 62c8809b..cca7856e 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,11 +81,18 @@ public Slot getSlot(Plot plot) { @SuppressWarnings("BooleanMethodIsAlwaysInverted") public boolean setSlot(Slot slot, int plotId) { - if (DataProvider.BUILDER.setSlot(this.uuid, plotId, slot)) { + int newPlotId = plotId; + // check for orphans and assign orphan if available + if (plotId == -1) { + List orphans = getOrphans(); + if (!orphans.isEmpty()) newPlotId = orphans.getFirst().getId(); + } + + 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; } @@ -129,6 +138,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/Plot.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/Plot.java index eaee4697..62766942 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/PlotHandler.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java new file mode 100644 index 00000000..05cbf1fc --- /dev/null +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/PlotHandler.java @@ -0,0 +1,306 @@ +/* + * 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.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; +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.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.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; +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.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; +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.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static net.kyori.adventure.text.Component.text; + +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(freeSlot, plot.getId())) return false; + if (!plot.setStatus(Status.unfinished)) return false; + 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(); + + // 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)); + } + + @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()); + } + } + } + + 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; + Polygonal2DRegion region = new Polygonal2DRegion(null, 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())); + + 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); + 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/DefaultPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java deleted file mode 100644 index 617777f9..00000000 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java +++ /dev/null @@ -1,121 +0,0 @@ -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) { - 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 { - 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; - } - - @Override - protected void generateOutlines() throws IOException, WorldEditException { - if (plot instanceof Plot) { - byte[] completedSchematic = ((Plot) plot).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, true); - } else super.generateOutlines(); - } else super.generateOutlines(); - - // 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); - } - }; - } - } - - @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/AbstractPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java similarity index 59% rename from src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java rename to src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/AbstractPlotLoader.java index a49d235b..739a4f52 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/loader/AbstractPlotLoader.java @@ -1,13 +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.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.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; @@ -40,7 +39,6 @@ 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; @@ -56,107 +54,75 @@ import static net.kyori.adventure.text.Component.text; -public abstract class AbstractPlotGenerator { +public abstract class AbstractPlotLoader { protected final AbstractPlot plot; - private final Builder builder; - protected final PlotWorld world; - protected final double plotVersion; protected final PlotType plotType; + protected final PlotWorld plotWorld; - /** - * 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()); - } + protected final Builder builder; - /** - * 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, plot.getVersion() <= 2 || plotType.hasOnePlotPerWorld() ? new OnePlotWorld(plot) : new CityPlotWorld((Plot) plot)); - } + protected byte[] schematicBytes = null; - /** - * 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) { + protected AbstractPlotLoader(@NotNull AbstractPlot plot, Builder builder, PlotType plotType, PlotWorld plotWorld) { this.plot = plot; - this.builder = builder; - this.world = world; - this.plotVersion = plot.getVersion(); this.plotType = plotType; + this.plotWorld = plotWorld; + this.builder = builder; - if (init()) { - Exception exception = null; - try { - if (plotType.hasOnePlotPerWorld() || !world.isWorldGenerated()) { - new PlotWorldGenerator(world.getWorldName()); - } else if (!world.isWorldLoaded() && !world.loadWorld()) throw new Exception("Could not load world"); - generateOutlines(); - createPlotProtection(); - } catch (Exception ex) { - exception = ex; - } - this.onComplete(exception != null, false); - - if (exception != null) { - PlotUtils.Actions.abandonPlot(plot); - onException(exception); - } + 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; - /** - * Executed before plot generation - * - * @return true if initialization was successful - */ - protected abstract boolean init(); + new PlotWorldGenerator(plotWorld.getWorldName()); + } + protected void loadWorld() throws Exception { + Utils.runSync(() -> { + if (plotWorld.isWorldLoaded()) return null; - /** - * Generates plot schematic and outlines - */ - protected void generateOutlines() throws IOException { - if (plotVersion >= 3 && plotType.hasEnvironment()) { - pasteSchematic(null, plot.getInitialSchematicBytes(), world, false, false); - } else { - Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(world.getBukkitWorld()), BlockTypes.AIR); - pasteSchematic(airMask, PlotUtils.getOutlinesSchematicBytes(plot, world.getBukkitWorld()), world, true, false); - } + boolean successful = plotWorld.loadWorld(); + if (!successful) throw new Exception("Could not load world!"); + return null; + }).get(); } + protected void fetchSchematicData() { + this.schematicBytes = plot.getInitialSchematicBytes(); + } - /** - * Creates plot protection - */ - protected void createPlotProtection() throws StorageException { - RegionContainer regionContainer = WorldGuard.getInstance().getPlatform().getRegionContainer(); - RegionManager regionManager = regionContainer.get(BukkitAdapter.adapt(world.getBukkitWorld())); + 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; + } - 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); + 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 - 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); + ProtectedRegion protectedRegion = getProtectedRegion(); // Add plot owner DefaultDomain owner = protectedBuildRegion.getOwners(); @@ -170,12 +136,21 @@ protected void createPlotProtection() throws StorageException { 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"); + 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(); - } else PlotSystem.getPlugin().getComponentLogger().warn(text("Region Manager is null!")); + 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; } /** @@ -216,46 +191,18 @@ protected List getBlockedCommands(@NotNull FileConfiguration config) { 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; + protected void generateStructure() throws Exception { + Utils.runSync(() -> { + 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); + } + return null; + }).get(); } - /** * Pastes the schematic to the plot center in the given world * @@ -266,7 +213,7 @@ public Builder getBuilder() { * @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 { - // load world + // load world if not loaded already if (!world.loadWorld()) return; World weWorld = new BukkitWorld(world.getBukkitWorld()); @@ -305,4 +252,24 @@ public static void pasteSchematic(@Nullable Mask pasteMask, byte[] schematicFile Operations.complete(clipboardHolder); } } + + protected void onException(Exception e) { + 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(() -> { + 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(); } 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..5ba9c274 --- /dev/null +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/loader/DefaultPlotLoader.java @@ -0,0 +1,103 @@ +/* + * 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.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; +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; + +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(); + 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, 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() 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 + CityPlotWorld cityPlotWorld = new CityPlotWorld((Plot) plot); + 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); + } + } +} 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 62% 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 26778125..2d68195d 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 { if (!((TutorialPlot) plot).setTutorialSchematic(schematicId)) return; - generateOutlines(); + fetchSchematicData(); + generateStructure(); } @Override @@ -48,24 +43,24 @@ 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()); + 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() { @@ -74,6 +69,14 @@ public boolean isWorldEditEnabled() { public void setWorldEditEnabled(boolean worldEditEnabled) { this.worldEditEnabled = worldEditEnabled; - setBuildRegionPermissions(world.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 + protected void onCompletion() {} } 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 54% 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..3bf2d8cd 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,7 +1,32 @@ -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; +import com.alpsbte.plotsystem.utils.Utils; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldguard.WorldGuard; import com.sk89q.worldguard.protection.flags.Flags; @@ -11,90 +36,76 @@ import com.sk89q.worldguard.protection.managers.storage.StorageException; import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; import com.sk89q.worldguard.protection.regions.RegionContainer; +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.GameRule; +import org.bukkit.NamespacedKey; import org.bukkit.World; -import org.bukkit.WorldCreator; -import org.bukkit.WorldType; import org.bukkit.entity.SpawnCategory; -import org.bukkit.generator.ChunkGenerator; 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.Path; +import java.util.Locale; 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 { + long startTime = System.nanoTime(); this.worldName = worldName; + + // Async Part generateWorld(); - createMultiverseWorld(); - configureWorld(); - createGlobalProtection(); + + // 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() { - worldCreator = new WorldCreator(worldName); - worldCreator.environment(environment); - worldCreator.type(worldType); - worldCreator.generator(new EmptyChunkGenerator()); - worldCreator.generatorSettings(generatorSettings); - worldCreator.createWorld(); + protected void generateWorld() throws IOException { + 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() 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() { + if (!worldManager.isLoadedWorld(worldName)) { + worldManager.importWorld(ImportWorldOptions.worldKey(NamespacedKey.minecraft(worldName.toLowerCase(Locale.ROOT))) + .environment(environment) + .generator("Plot-System") + .useSpawnAdjust(false) + ); } } protected void configureWorld() { - World bukkitWorld = Bukkit.getWorld(worldName); + World world = Bukkit.getWorld(worldName); + assert 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); + world.setDifficulty(Difficulty.PEACEFUL); + world.setSpawnFlags(false, false); + world.setAutoSave(false); // Configure multiverse world mvWorld.get().setAllowFlight(true); @@ -130,15 +141,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..a26fe069 --- /dev/null +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/world/SkeletonWorldGenerator.java @@ -0,0 +1,92 @@ +/* + * 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 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"; + 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(); + saveWorld(); + } + + protected void generateWorld() { + WorldCreator worldCreator = new WorldCreator(WORLD_NAME) + .environment(ENVIRONMENT) + .type(WORLD_TYPE) + .generator(new SkeletonWorldGenerator.EmptyChunkGenerator()) + .generatorSettings(GENERATOR_SETTINGS); + this.world = worldCreator.createWorld(); + } + + protected void configureWorld() { + World bukkitWorld = this.world; + assert bukkitWorld != null; + + // Set game rules + 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); + } + + protected void saveWorld() { + assert this.world != null; + this.world.save(); + } + + 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); + } + } +} 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 74b69408..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; @@ -7,10 +11,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.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; @@ -24,35 +24,26 @@ 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; 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; 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.worldedit.world.block.BlockTypes; -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.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitTask; import org.jetbrains.annotations.NotNull; @@ -66,9 +57,9 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.text.DecimalFormat; -import java.time.LocalDate; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -171,9 +162,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; @@ -198,9 +190,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(); } @@ -234,60 +226,6 @@ public static boolean isPlotWorld(@NotNull World world) { return Paths.get(PlotSystem.getPlugin().getDataFolder().getAbsolutePath(), "tutorial", "schematics"); } - 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(null, 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(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); - Mask airMask = new BlockTypeMask(BukkitAdapter.adapt(cpw.getBukkitWorld()), BlockTypes.AIR); - AbstractPlotGenerator.pasteSchematic(airMask, outputStream.toByteArray(), cpw, false, true); - } - return true; - } - public static @Nullable CompletableFuture convertTerraToPlotXZ(@NotNull AbstractPlot plot, double[] terraCoords) throws IOException { // Load plot outlines schematic as clipboard Clipboard clipboard; @@ -314,39 +252,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); @@ -366,119 +282,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, world.getBukkitWorld()), world, true, false); - } 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() {} @@ -560,19 +363,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); + } } } } @@ -583,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 { @@ -641,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/core/system/plot/world/CityPlotWorld.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/world/CityPlotWorld.java index d37dcccc..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 @@ -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); @@ -24,37 +33,35 @@ 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 (plot == 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(plot.getId())))); - return true; - } - return false; + Utils.updatePlayerInventorySlots(player); + PlotUtils.ChatFormatting.sendLinkMessages(plot, player); + + 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 @@ -72,12 +79,13 @@ 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(); } - if (clipboard != null) { - int plotHeight = clipboard.getMinimumPoint().y(); + 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, @@ -88,13 +96,11 @@ 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; - // 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."); + // 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; } /** @@ -102,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, false); + } 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..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,6 +1,7 @@ package com.alpsbte.plotsystem.core.system.plot.world; -import com.alpsbte.plotsystem.core.system.plot.generator.AbstractPlotGenerator; +import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; +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; @@ -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 @@ -17,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 @@ -25,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 @@ -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 2f439bec..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 @@ -1,3 +1,27 @@ +/* + * 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; @@ -5,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.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; @@ -22,47 +46,40 @@ 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(); } @Override - public boolean generateWorld(@NotNull Class generator) { + public boolean generateWorld(@NotNull Class generator) { if (isWorldGenerated()) return false; - if (generator.isAssignableFrom(DefaultPlotGenerator.class)) { - new DefaultPlotGenerator(getPlot(), plotOwner); - } else if (generator.isAssignableFrom(TutorialPlotGenerator.class)) { - new TutorialPlotGenerator(getPlot(), 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; } @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) - 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); - } - }; + 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()) { - 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 +88,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 +102,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 +124,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 28adf25e..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 @@ -1,13 +1,40 @@ +/* + * 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.generator.loader.AbstractPlotLoader; +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.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; @@ -40,7 +67,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; @@ -48,47 +75,54 @@ 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); } @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 { - var mviConfig = DependencyManager.getMultiverseInventoriesConfigPath(getWorldName()); - if (mviConfig != null) { - File multiverseInventoriesConfig = new File(mviConfig); - if (multiverseInventoriesConfig.exists()) FileUtils.deleteDirectory(multiverseInventoriesConfig); - } - 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() + "!")); - 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 { + var mviConfig = DependencyManager.getMultiverseInventoriesConfigPath(getWorldName()); + if (mviConfig != null) { + File multiverseInventoriesConfig = new File(mviConfig); + if (multiverseInventoriesConfig.exists()) FileUtils.deleteDirectory(multiverseInventoriesConfig); + } + 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() + "!")); + 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; + } + + 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 @@ -108,25 +142,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 @@ -136,17 +166,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 @@ -184,21 +212,26 @@ public boolean isWorldGenerated() { return mvCore.getWorldManager().getWorld(worldName).isDefined(); } - private @Nullable ProtectedRegion getRegion(String regionName) { - 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!")); - } - return null; - } - + @Override public AbstractPlot getPlot() { return plot; } + @Override + public boolean onAbandon() { + return true; + } + + private @Nullable ProtectedRegion getRegion(String regionName) { + if (!loadWorld()) return null; + + RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); + 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; + } /** * @param worldName - the name of the world @@ -224,12 +257,19 @@ 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; + // TODO: rework + 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) { + // TODO: rework + boolean disableCIM = PlotSystem.getPlugin().getConfig().getBoolean(ConfigPaths.DISABLE_CITY_INSPIRATION_MODE); + return disableCIM || type.hasOnePlotPerWorld() ? new OnePlotWorld(plot) : new CityPlotWorld(plot); } } 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()); + } } 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 75e55092..2b86f61d 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; @@ -41,7 +41,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) { @@ -92,7 +92,7 @@ public void setStage(int stageId) { } @Override - public void onPlotSchematicPaste(@NotNull UUID playerUUID, int schematicId) throws IOException { + public void onPlotSchematicPaste(@NotNull UUID playerUUID, int schematicId) throws Exception { if (!getPlayerUUID().toString().equals(playerUUID.toString())) return; if (schematicId < 0) return; if (plotGenerator != null && tutorialPlot.getWorld().isWorldGenerated() && tutorialPlot.getWorld().isWorldLoaded()) { @@ -125,7 +125,7 @@ protected void prepareStage(PrepareStageAction action) { if (pasteSchematic) { try { onPlotSchematicPaste(getPlayerUUID(), ((AbstractPlotStage) stage).getInitSchematicId()); - } catch (IOException ex) { + } catch (Exception ex) { onException(ex); return; } @@ -153,21 +153,29 @@ public void saveTutorial(int stageId) { } @Override - public void onSwitchWorld(@NotNull UUID playerUUID, int tutorialWorldIndex) { - if (!getPlayerUUID().toString().equals(playerUUID.toString())) return; - int schematicId = ((AbstractPlotStage) currentStage).getInitSchematicId(); - tutorialPlot.setTutorialSchematic(schematicId); - - if (tutorialWorldIndex == 1 && (plotGenerator == null || !plotGenerator.getPlot().getWorld().isWorldGenerated())) { - plotGenerator = new TutorialPlotGenerator(tutorialPlot, Builder.byUUID(playerUUID)); - try { - onPlotSchematicPaste(playerUUID, schematicId); - } catch (IOException ex) { - onException(ex); - 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())) { + return CompletableFuture.runAsync(() -> { + plotGenerator = new TutorialPlotLoader(tutorialPlot, Builder.byUUID(playerUUID)); + try { + int schematicId = ((AbstractPlotStage) currentStage).getInitSchematicId(); + tutorialPlot.setTutorialSchematic(schematicId); + onPlotSchematicPaste(playerUUID, schematicId); + } 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/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/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 bc7e7e10..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,10 +1,12 @@ 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.io.IOException; +import java.util.concurrent.CompletableFuture; public class PlotSchematicPasteTask extends AbstractTask { private final int schematicId; @@ -18,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 (IOException 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() diff --git a/src/main/java/com/alpsbte/plotsystem/utils/Utils.java b/src/main/java/com/alpsbte/plotsystem/utils/Utils.java index 32bf9812..cd3db3bf 100644 --- a/src/main/java/com/alpsbte/plotsystem/utils/Utils.java +++ b/src/main/java/com/alpsbte/plotsystem/utils/Utils.java @@ -40,6 +40,8 @@ import java.util.Objects; import java.util.Random; import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; 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 +104,40 @@ public static ItemStack getConfiguredItem(@NotNull String material, Object custo return builder.build(); } + public static CompletableFuture runSync(Callable task) { + CompletableFuture future = new CompletableFuture<>(); + 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<>(); + 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 class SoundUtils { private SoundUtils() {} @@ -252,4 +288,4 @@ public static boolean isOwnerOrReviewer(CommandSender sender, @Nullable Player p } return hasPermission; } -} \ No newline at end of file +} 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 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: