diff --git a/build.gradle.kts b/build.gradle.kts index 6013616a..b0fc3a92 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -57,6 +57,7 @@ dependencies { compileOnly(libs.de.oliver.fancynpcs) compileOnly(libs.li.cinnazeyy.langlibs.api) compileOnly(libs.commons.io.commons.io) + compileOnly(libs.asia.buildtheearth.asean.discord.discord.plotsystem.api) } val versionDetails: groovy.lang.Closure by extra diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0af5093b..01de7e52 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,6 +18,7 @@ io-papermc-paper-paper-api = "1.21.8-R0.1-SNAPSHOT" li-cinnazeyy-langlibs-api = "1.5.1" org-mariadb-jdbc-mariadb-java-client = "2.7.4" com-intellectualsites-bom-bom-newest = "1.55" # Ref: https://github.com/IntellectualSites/bom +asia-buildtheearth-asean-discord-discord-plotsystem-api = "1.2.3" # Ref: https://github.com/ASEAN-Build-The-Earth/discordsrv-plotsystem [libraries] com-alpsbte-alpslib-alpslib-hologram = { module = "com.alpsbte.alpslib:alpslib-hologram", version.ref = "com-alpsbte-alpslib-alpslib-hologram" } @@ -35,4 +36,5 @@ de-oliver-fancynpcs = { module = "de.oliver:FancyNpcs", version.ref = "de-oliver io-papermc-paper-paper-api = { module = "io.papermc.paper:paper-api", version.ref = "io-papermc-paper-paper-api" } li-cinnazeyy-langlibs-api = { module = "li.cinnazeyy:LangLibs-API", version.ref = "li-cinnazeyy-langlibs-api" } org-mariadb-jdbc-mariadb-java-client = { module = "org.mariadb.jdbc:mariadb-java-client", version.ref = "org-mariadb-jdbc-mariadb-java-client" } -com-intellectualsites-bom-bom-newest = { module = "com.intellectualsites.bom:bom-newest", version.ref = "com-intellectualsites-bom-bom-newest" } \ No newline at end of file +com-intellectualsites-bom-bom-newest = { module = "com.intellectualsites.bom:bom-newest", version.ref = "com-intellectualsites-bom-bom-newest" } +asia-buildtheearth-asean-discord-discord-plotsystem-api = { module = "asia.buildtheearth.asean.discord:discord-plotsystem-api", version.ref = "asia-buildtheearth-asean-discord-discord-plotsystem-api" } \ No newline at end of file diff --git a/pom.xml b/pom.xml index bbcc0a99..026584f4 100644 --- a/pom.xml +++ b/pom.xml @@ -105,6 +105,13 @@ 2.9.2 provided + + + asia.buildtheearth.asean.discord + discord-plotsystem-api + 1.2.3 + provided + com.alpsbte.alpslib diff --git a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java index 201a5912..4174e785 100644 --- a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java +++ b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java @@ -19,6 +19,8 @@ import com.alpsbte.plotsystem.core.system.tutorial.TutorialEventListener; import com.alpsbte.plotsystem.core.system.tutorial.utils.TutorialNPCTurnTracker; import com.alpsbte.plotsystem.core.system.tutorial.utils.TutorialUtils; +import com.alpsbte.plotsystem.utils.DependencyManager; +import com.alpsbte.plotsystem.utils.DiscordUtil; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.io.ConfigPaths; import com.alpsbte.plotsystem.utils.io.ConfigUtil; @@ -127,6 +129,10 @@ public void onEnable() { Bukkit.getScheduler().runTaskTimerAsynchronously(FancyNpcsPlugin.get().getPlugin(), new TutorialNPCTurnTracker(), 0, 1L); } + // Register discord Integration + org.bukkit.plugin.Plugin discordPlugin = DependencyManager.getDiscordIntegration(); + if(discordPlugin != null) DiscordUtil.init(discordPlugin); + pluginEnabled = true; getComponentLogger().info(text("Enabled Plot-System plugin.", DARK_GREEN)); getComponentLogger().info(text("------------------------------------------------------", GOLD)); 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..50053760 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 @@ -9,6 +9,7 @@ import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; +import com.alpsbte.plotsystem.utils.DiscordUtil; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.Status; import com.alpsbte.plotsystem.utils.io.LangPaths; @@ -67,7 +68,7 @@ public void onCommand(CommandSender sender, String[] args) { } Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - if (PlotUtils.Actions.abandonPlot(plot)) { + if (PlotUtils.Actions.abandonPlot(plot, DiscordUtil.AbandonType.MANUALLY)) { 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_UndoSubmit.java b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_UndoSubmit.java index a9b89bb9..2041045c 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 @@ -9,6 +9,7 @@ import com.alpsbte.plotsystem.core.system.plot.AbstractPlot; import com.alpsbte.plotsystem.core.system.plot.Plot; import com.alpsbte.plotsystem.core.system.plot.utils.PlotUtils; +import com.alpsbte.plotsystem.utils.DiscordUtil; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.Status; import com.alpsbte.plotsystem.utils.io.LangPaths; @@ -70,6 +71,7 @@ public void onCommand(CommandSender sender, String[] args) { Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { PlotUtils.Actions.undoSubmit(plot); + DiscordUtil.getOpt(plot.getId()).ifPresent(DiscordUtil.PlotEventAction::onPlotUndoSubmit); sender.sendMessage(Utils.ChatUtils.getInfoFormat(langUtil.get(sender, LangPaths.Message.Info.UNDID_SUBMISSION, plot.getId() + ""))); player.playSound(player.getLocation(), Utils.SoundUtils.FINISH_PLOT_SOUND, 1, 1); diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/review/ReviewPlotTogglesMenu.java index 91e949de..498b19a4 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 @@ -11,6 +11,7 @@ import com.alpsbte.plotsystem.core.system.review.PlotReview; import com.alpsbte.plotsystem.core.system.review.ReviewRating; import com.alpsbte.plotsystem.core.system.review.ToggleCriteria; +import com.alpsbte.plotsystem.utils.DiscordUtil; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.chat.ChatInput; import com.alpsbte.plotsystem.utils.chat.PlayerFeedbackChatInput; @@ -129,9 +130,11 @@ private void submitReview() { if (!isRejected) { reviewerConfirmationMessage = Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Message.Info.PLOT_MARKED_REVIEWED, Integer.toString(plot.getId()), getParticipantsString())); if(!acceptPlot(review.getScore(), review.getSplitScore())) return; + DiscordUtil.getOpt(plot.getId()).ifPresent(DiscordUtil.PlotEventAction::onPlotApprove); } else { reviewerConfirmationMessage = Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Message.Info.PLOT_REJECTED, Integer.toString(plot.getId()), getParticipantsString())); PlotUtils.Actions.undoSubmit(plot); + DiscordUtil.getOpt(plot.getId()).ifPresent(DiscordUtil.PlotEventAction::onPlotReject); } Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java index 5b4ec0cd..2e1d2569 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/AbstractPlotGenerator.java @@ -10,6 +10,7 @@ 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.DiscordUtil; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.io.ConfigPaths; import com.alpsbte.plotsystem.utils.io.ConfigUtil; @@ -112,7 +113,7 @@ private AbstractPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder build this.onComplete(exception != null, false); if (exception != null) { - PlotUtils.Actions.abandonPlot(plot); + PlotUtils.Actions.abandonPlot(plot, DiscordUtil.AbandonType.SYSTEM); onException(exception); } } diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java index 617777f9..698caa48 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/plot/generator/DefaultPlotGenerator.java @@ -9,6 +9,7 @@ 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.DiscordUtil; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.PlotDifficulty; import com.alpsbte.plotsystem.utils.enums.Status; @@ -117,5 +118,8 @@ protected void onComplete(boolean failed, boolean unloadWorld) { PlotUtils.Cache.clearCache(getBuilder().getUUID()); plot.getWorld().teleportPlayer(getBuilder().getPlayer()); LangUtil.getInstance().broadcast(LangPaths.Message.Info.CREATED_NEW_PLOT, getBuilder().getName()); + + // Create the plot to discord forum + DiscordUtil.getOpt(plot.getId()).ifPresent(event -> event.onPlotCreate(this.plot)); } } 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 f76b001d..a3652c22 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 @@ -14,6 +14,7 @@ import com.alpsbte.plotsystem.core.system.review.PlotReview; import com.alpsbte.plotsystem.core.system.review.ReviewNotification; import com.alpsbte.plotsystem.utils.DependencyManager; +import com.alpsbte.plotsystem.utils.DiscordUtil; import com.alpsbte.plotsystem.utils.ShortLink; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.Status; @@ -74,6 +75,8 @@ import java.util.Objects; import java.util.Optional; import java.util.UUID; +import java.time.LocalTime; +import java.time.temporal.ChronoUnit; import java.util.concurrent.CompletableFuture; import static net.kyori.adventure.text.Component.empty; @@ -328,16 +331,36 @@ 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 inactivityIntervalDays = config.getLong(ConfigPaths.INACTIVITY_INTERVAL, -2); long rejectedInactivityIntervalDays = (config.getLong(ConfigPaths.REJECTED_INACTIVITY_INTERVAL) != -1) ? config.getLong(ConfigPaths.REJECTED_INACTIVITY_INTERVAL) : inactivityIntervalDays; + int inactivityNotificationDays = config.getInt(ConfigPaths.INACTIVITY_NOTIFICATION_DAYS, 0); + int inactivityNotificationTime = config.getInt(ConfigPaths.INACTIVITY_NOTIFICATION_TIME, 16); if (inactivityIntervalDays == -2 && rejectedInactivityIntervalDays == -2) return; + + // Determine if the current time is within the notification window. + // Run within a ±minute window around a set local time. + LocalTime now = LocalTime.now(); + LocalTime start = LocalTime.of(inactivityNotificationTime, 0).minusMinutes(30); + LocalTime end = LocalTime.of(inactivityNotificationTime, 0).plusMinutes(30); + boolean inNotificationWindow = inactivityNotificationDays > 0 && !now.isBefore(start) && !now.isAfter(end);; + for (Plot plot : plots) { LocalDate lastActivity = plot.getLastActivity(); + + if(lastActivity == null) continue; long interval = plot.isRejected() ? rejectedInactivityIntervalDays : inactivityIntervalDays; - if (interval == -2 || lastActivity == null || lastActivity.plusDays(interval).isAfter(LocalDate.now())) continue; + LocalDate abandonDate = lastActivity.plusDays(interval); + + // Check if today is within X days before the plot's abandon date + if(inNotificationWindow && LocalDate.now().isAfter(abandonDate.minusDays(inactivityNotificationDays))) { + // Notify the plot's owner on discord + DiscordUtil.getOpt(plot.getId()).ifPresent(event -> event.onPlotInactivity(abandonDate)); + } + + if (interval == -2 || abandonDate.isAfter(LocalDate.now())) continue; Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { - if (Actions.abandonPlot(plot)) { + if (Actions.abandonPlot(plot, DiscordUtil.AbandonType.INACTIVE)) { 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!")); @@ -384,6 +407,8 @@ public static void submitPlot(@NotNull Plot plot) { plot.getPermissions().removeBuilderPerms(builder.getUUID()); } } + + DiscordUtil.getOpt(plot.getId()).ifPresent(DiscordUtil.PlotEventAction::onPlotSubmit); } public static void undoSubmit(@NotNull Plot plot) { @@ -397,7 +422,7 @@ public static void undoSubmit(@NotNull Plot plot) { } } - public static boolean abandonPlot(@NotNull AbstractPlot plot) { + public static boolean abandonPlot(@NotNull AbstractPlot plot, @NotNull DiscordUtil.AbandonType type) { try { if (plot.getWorld() instanceof OnePlotWorld) { if (plot.getWorld().isWorldGenerated()) { @@ -431,6 +456,8 @@ public static boolean abandonPlot(@NotNull AbstractPlot plot) { if (plot.getWorld().isWorldLoaded()) plot.getWorld().unloadWorld(false); } } + // Send to discord + DiscordUtil.getOpt(plot.getId()).ifPresent(event -> event.onPlotAbandon(type)); } catch (IOException | WorldEditException ex) { PlotSystem.getPlugin().getComponentLogger().error(text("Failed to abandon plot with the ID " + plot.getId() + "!"), ex); return false; @@ -467,7 +494,7 @@ public static boolean abandonPlot(@NotNull AbstractPlot plot) { } public static boolean deletePlot(Plot plot) { - if (abandonPlot(plot)) { + if (abandonPlot(plot, DiscordUtil.AbandonType.COMMANDS)) { CompletableFuture.runAsync(() -> { if (DataProvider.PLOT.deletePlot(plot.getId())) return; PlotSystem.getPlugin().getComponentLogger().warn(text("Failed to abandon plot with the ID " + plot.getId() + "!")); 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..09279eb3 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 @@ -4,6 +4,7 @@ import com.alpsbte.plotsystem.core.database.DataProvider; import com.alpsbte.plotsystem.core.system.Builder; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.utils.DiscordUtil; import com.alpsbte.plotsystem.utils.enums.Slot; import com.alpsbte.plotsystem.utils.enums.Status; import org.jetbrains.annotations.Nullable; @@ -67,6 +68,7 @@ public UUID getReviewerUUID() { public boolean updateFeedback(String feedback) { if (DataProvider.REVIEW.updateFeedback(reviewId, feedback)) { this.feedback = feedback; + DiscordUtil.getOpt(this.plot.getId()).ifPresent(event -> event.onPlotFeedback(feedback)); return true; } return false; @@ -106,6 +108,9 @@ public boolean undoReview() { successful = false; PlotSystem.getPlugin().getComponentLogger().error("Failed to remove plot review with ID {} from database!", reviewId); } + else { + DiscordUtil.getOpt(this.plot.getId()).ifPresent(DiscordUtil.PlotEventAction::onPlotUndoReview); + } return successful; } diff --git a/src/main/java/com/alpsbte/plotsystem/utils/DependencyManager.java b/src/main/java/com/alpsbte/plotsystem/utils/DependencyManager.java index e348266e..4ce0007e 100644 --- a/src/main/java/com/alpsbte/plotsystem/utils/DependencyManager.java +++ b/src/main/java/com/alpsbte/plotsystem/utils/DependencyManager.java @@ -4,6 +4,7 @@ import com.sk89q.worldguard.bukkit.WorldGuardPlugin; import org.bukkit.Bukkit; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.mvplugins.multiverse.core.MultiverseCoreApi; import java.util.Objects; @@ -26,6 +27,10 @@ public static boolean isWorldGuardExtraFlagsEnabled() { return PlotSystem.getPlugin().getServer().getPluginManager().isPluginEnabled("WorldGuardExtraFlags"); } + public static @Nullable org.bukkit.plugin.Plugin getDiscordIntegration() { + return PlotSystem.getPlugin().getServer().getPluginManager().getPlugin("DiscordPlotSystem"); + } + /** * @param worldName Name of the world * @return Config path for the world diff --git a/src/main/java/com/alpsbte/plotsystem/utils/DiscordUtil.java b/src/main/java/com/alpsbte/plotsystem/utils/DiscordUtil.java new file mode 100644 index 00000000..ba82e6be --- /dev/null +++ b/src/main/java/com/alpsbte/plotsystem/utils/DiscordUtil.java @@ -0,0 +1,215 @@ +/* + * 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.utils; + +import asia.buildtheearth.asean.discord.plotsystem.api.DiscordPlotSystemAPI; +import asia.buildtheearth.asean.discord.plotsystem.api.PlotCreateData; +import asia.buildtheearth.asean.discord.plotsystem.api.PlotCreateProvider; +import asia.buildtheearth.asean.discord.plotsystem.api.events.*; +import com.alpsbte.plotsystem.PlotSystem; +import com.alpsbte.plotsystem.core.database.DataProvider; +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.utils.conversion.CoordinateConversion; +import com.alpsbte.plotsystem.utils.conversion.projection.OutOfProjectionBoundsException; +import com.sk89q.worldedit.math.BlockVector3; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.time.LocalDate; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.NamedTextColor.GREEN; + +/** + * Provides optional integration with the {@link DiscordPlotSystemAPI} plugin. + * + *

This singleton utility wraps interaction with the {@link asia.buildtheearth.asean.discord.plotsystem.api.DiscordPlotSystemAPI} + * and should only be initialized if the Discord plugin is available and enabled.

+ * + *

Submitting a plot:

+ *
{@snippet : + * AbstractPlot plot = DataProvider.PLOT.getPlotById(1); + * DiscordUtil.getOpt(plot.getID()).ifPresent(DiscordUtil.PlotEventAction::onPlotSubmit); + * }
+ * + *

Always check for availability using {@link #getOpt(int)} before calling integration methods, + * to avoid runtime errors if the plugin is not installed.

+ * + * @see #init(Plugin) + * @see #getOpt(int) + */ +public class DiscordUtil { + + private static @Nullable DiscordUtil instance; + + private final @NotNull DiscordPlotSystemAPI api; + + private DiscordUtil(@NotNull DiscordPlotSystemAPI api) { + this.api = api; + } + + public static Optional getOpt(int plotID) { + return Optional.ofNullable(instance).map(instance -> new PlotEventAction(plotID, instance)); + } + + public static void init(@Nullable org.bukkit.plugin.Plugin plugin) { + if(plugin instanceof DiscordPlotSystemAPI discordPlugin) { + DiscordUtil.instance = new DiscordUtil(discordPlugin); + DiscordPlotSystemAPI.registerProvider(new DiscordUtil.DiscordDataProvider()); + PlotSystem.getPlugin().getComponentLogger().info(text("Registered data provider to DiscordPlotSystem", GREEN)); + } + } + + public void callEvent(E event) { + this.api.callEvent(event); + } + + /** + * Wrapper for Discord API abandon types. + * + * @see asia.buildtheearth.asean.discord.plotsystem.api.events.AbandonType + */ + public enum AbandonType { + /** @see asia.buildtheearth.asean.discord.plotsystem.api.events.AbandonType#INACTIVE */ + INACTIVE, + /** @see asia.buildtheearth.asean.discord.plotsystem.api.events.AbandonType#MANUALLY */ + MANUALLY, + /** @see asia.buildtheearth.asean.discord.plotsystem.api.events.AbandonType#COMMANDS */ + COMMANDS, + /** @see asia.buildtheearth.asean.discord.plotsystem.api.events.AbandonType#SYSTEM */ + SYSTEM + } + + public static final class PlotEventAction { + + private final int plotID; + private final @NotNull DiscordUtil api; + + private PlotEventAction(int plotID, @NotNull DiscordUtil api) { + this.plotID = plotID; + this.api = api; + } + + public void onPlotCreate(@NotNull AbstractPlot plot) { + CompletableFuture.supplyAsync(() -> DiscordPlotSystemAPI.getDataProvider().getData(plot)) + .thenAccept(createData -> this.api.callEvent(new PlotCreateEvent(this.plotID, createData))); + } + + public void onPlotSubmit() { + this.api.callEvent(new PlotSubmitEvent(this.plotID)); + } + + public void onPlotAbandon(@NotNull AbandonType type) { + this.api.callEvent(new PlotAbandonedEvent(this.plotID, + asia.buildtheearth.asean.discord.plotsystem.api.events.AbandonType.valueOf(type.name())) + ); + } + + public void onPlotReject() { + this.api.callEvent(new PlotRejectedEvent(this.plotID)); + } + + public void onPlotFeedback(String feedback) { + this.api.callEvent(new PlotFeedbackEvent(this.plotID, feedback)); + } + + public void onPlotApprove() { + this.api.callEvent(new PlotApprovedEvent(this.plotID)); + } + + public void onPlotUndoReview() { + this.api.callEvent(new PlotUndoReviewEvent(this.plotID)); + } + + public void onPlotUndoSubmit() { + this.api.callEvent(new PlotUndoSubmitEvent(this.plotID)); + } + + public void onPlotInactivity(LocalDate abandonDate) { + this.api.callEvent(new InactivityNoticeEvent(this.plotID, abandonDate)); + } + } + + /** + * Data Provider for {@link DiscordPlotSystemAPI} + * registered during the plugin onEnabled. + * + * @see DiscordPlotSystemAPI#registerProvider(PlotCreateProvider) + */ + private static final class DiscordDataProvider implements PlotCreateProvider { + + public @Nullable PlotCreateData getData(AbstractPlot plot) { + if(plot == null) return null; + + CityProject cityProject = ((Plot) plot).getCityProject(); + + if(!cityProject.isVisible()) return null; + + // GeoCoordinate + double[] geoCoordinates = null; + try { + BlockVector3 mcCoordinates = plot.getCoordinates(); + + geoCoordinates = CoordinateConversion.convertToGeo(mcCoordinates.x(), mcCoordinates.z()); + } catch (IOException | OutOfProjectionBoundsException ignored) { } + + int plotID = plot.getId(); + + UUID ownerUUID = plot.getPlotOwner().getUUID(); + + String cityProjectID = cityProject.getId(); + + String countryCode = cityProject.getCountry().getCode(); + + try { + PlotCreateData.PlotStatus status = PlotCreateData.prepareStatus(plot.getStatus().name()); + return new PlotCreateData(plotID, ownerUUID.toString(), status, cityProjectID, countryCode, geoCoordinates); + } + catch (IllegalArgumentException ignored) { + return null; + } + } + + @Override + public PlotCreateData getData(Object rawData) { + if(rawData == null) return null; + if(rawData instanceof AbstractPlot plot) + return this.getData(plot); + else return null; + } + + @Override + public PlotCreateData getData(int plotID) { + return this.getData(DataProvider.PLOT.getPlotById(plotID)); + } + } +} 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..0ea2c3f7 100644 --- a/src/main/java/com/alpsbte/plotsystem/utils/io/ConfigPaths.java +++ b/src/main/java/com/alpsbte/plotsystem/utils/io/ConfigPaths.java @@ -9,6 +9,8 @@ public abstract class ConfigPaths { public static final String ENABLE_SCORE_REQUIREMENT = "enable-score-requirement"; public static final String DEV_MODE = "dev-mode"; public static final String INACTIVITY_INTERVAL = "inactivity-interval"; + public static final String INACTIVITY_NOTIFICATION_TIME = "inactivity-notification-time"; + public static final String INACTIVITY_NOTIFICATION_DAYS = "inactivity-notification-days"; public static final String REJECTED_INACTIVITY_INTERVAL = "rejected-inactivity-interval"; public static final String ENABLE_GROUP_SUPPORT = "enable-group-support"; public static final String UNFINISHED_REMINDER_INTERVAL = "unfinished-reminder-interval"; diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 1ef96243..f3345ae2 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -21,6 +21,13 @@ enable-score-requirement: true # How many days of inactivity it will take before a claimed plot is automatically abandoned, -2 disables it inactivity-interval: 14 +# How long left (Days) until the plot gets abandon due to inactivity and the system will start pinging owner on discord +# Be careful, if set >= inactivity-interval will ping the owner every day after plot is created +inactivity-notification-days: 5 + +# What local-time in a day to ping the owner discord for their inactive plot (24 Hour) +inactivity-notification-time: 16 + # How many days of inactivity it will take before a rejected plots are automatically abandoned # Use -1 to use the normal inactivity-interval, -2 disables it rejected-inactivity-interval: -1 diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index a9a177fd..b7127d69 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -6,7 +6,7 @@ description: "${description}" author: R3tuxn, Cinnazeyy & Zoriot depend: [ LangLibs, FancyNpcs, VoidGen, FastAsyncWorldEdit, Multiverse-Core, DecentHolograms, WorldGuard, HeadDatabase ] -softdepend: [ WorldGuardExtraFlags, ParticleNativeAPI ] +softdepend: [ WorldGuardExtraFlags, ParticleNativeAPI, DiscordPlotSystem ] commands: cancelchat: