diff --git a/pom.xml b/pom.xml index 680e26dd..ca5832c8 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,11 @@ dmulloy2-repo https://repo.dmulloy2.net/repository/public/ + + + sonatype + https://oss.sonatype.org/content/groups/public/ + @@ -132,6 +137,13 @@ 2.8.12 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 011bf148..fc6310d4 100644 --- a/src/main/java/com/alpsbte/plotsystem/PlotSystem.java +++ b/src/main/java/com/alpsbte/plotsystem/PlotSystem.java @@ -42,6 +42,7 @@ import com.alpsbte.plotsystem.core.system.tutorial.TutorialEventListener; import com.alpsbte.plotsystem.core.system.tutorial.utils.TutorialNPCTurnTracker; import com.alpsbte.plotsystem.core.system.tutorial.utils.TutorialUtils; +import com.alpsbte.plotsystem.utils.DiscordUtil; import com.alpsbte.plotsystem.utils.PacketListener; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.io.ConfigPaths; @@ -61,6 +62,7 @@ import org.bukkit.plugin.java.JavaPlugin; import org.ipvp.canvas.MenuFunctionListener; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.io.InputStream; @@ -208,6 +210,12 @@ public void onEnable() { Bukkit.getScheduler().runTaskTimerAsynchronously(FancyNpcsPlugin.get().getPlugin(), new TutorialNPCTurnTracker(), 0, 1L); } + // Register discord Integration + org.bukkit.plugin.Plugin discordPlugin = PlotSystem.DependencyManager.getDiscordPlotSystemPlugin(); + if(discordPlugin != null) { + DiscordUtil.init(discordPlugin); + } + pluginEnabled = true; Bukkit.getConsoleSender().sendMessage(empty()); Bukkit.getConsoleSender().sendMessage(text("Enabled Plot-System plugin.", DARK_GREEN)); @@ -335,6 +343,10 @@ public static boolean isWorldGuardExtraFlagsEnabled() { return plugin.getServer().getPluginManager().isPluginEnabled("WorldGuardExtraFlags"); } + public static @Nullable org.bukkit.plugin.Plugin getDiscordPlotSystemPlugin() { + return plugin.getServer().getPluginManager().getPlugin("DiscordPlotSystem"); + } + /** * @param worldName Name of the world * @return Config path for the world @@ -375,7 +387,9 @@ public static String getWorldGuardConfigPath(String worldName) { /** * @return Protocol Lib Instance */ - public static ProtocolManager getProtocolManager() {return ProtocolLibrary.getProtocolManager();} + public static ProtocolManager getProtocolManager() { + return ProtocolLibrary.getProtocolManager(); + } } public static class UpdateChecker { 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 75b4490d..c9e5752c 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Abandon.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_Abandon.java @@ -32,6 +32,7 @@ import com.alpsbte.plotsystem.core.system.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; @@ -77,7 +78,7 @@ public void onCommand(CommandSender sender, String[] args) { } if (Objects.requireNonNull(plot).getStatus() == Status.unfinished) { - if (Utils.isOwnerOrReviewer(sender, player, plot) && PlotUtils.Actions.abandonPlot(plot)) { + if (Utils.isOwnerOrReviewer(sender, player, plot) && PlotUtils.Actions.abandonPlot(plot, DiscordUtil.AbandonType.MANUALLY)) { sender.sendMessage(Utils.ChatUtils.getInfoFormat(langUtil.get(sender, LangPaths.Message.Info.ABANDONED_PLOT, plot.getID() + ""))); if (player != null) 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 a284bb2e..a870d3c6 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_UndoSubmit.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/plot/CMD_Plot_UndoSubmit.java @@ -32,6 +32,7 @@ import com.alpsbte.plotsystem.core.system.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; @@ -79,6 +80,8 @@ public void onCommand(CommandSender sender, String[] args) { if (Objects.requireNonNull(plot).getStatus() == Status.unreviewed) { if (Utils.isOwnerOrReviewer(sender, player, plot)) { 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() + ""))); if (player != null) player.playSound(player.getLocation(), Utils.SoundUtils.FINISH_PLOT_SOUND, 1, 1); } diff --git a/src/main/java/com/alpsbte/plotsystem/commands/review/CMD_UndoReview.java b/src/main/java/com/alpsbte/plotsystem/commands/review/CMD_UndoReview.java index 1224e274..b08fd659 100644 --- a/src/main/java/com/alpsbte/plotsystem/commands/review/CMD_UndoReview.java +++ b/src/main/java/com/alpsbte/plotsystem/commands/review/CMD_UndoReview.java @@ -30,6 +30,7 @@ import com.alpsbte.plotsystem.core.system.Review; 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.io.LangPaths; import com.alpsbte.plotsystem.utils.io.LangUtil; @@ -72,6 +73,8 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N return true; } + DiscordUtil.getOpt(plot.getID()).ifPresent(DiscordUtil.PlotEventAction::onPlotUndoReview); + Review.undoReview(plot.getReview()); sender.sendMessage(Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(sender, LangPaths.Message.Info.UNDID_REVIEW, plot.getID() + "", plot.getPlotOwner().getName()))); } catch (SQLException ex) { diff --git a/src/main/java/com/alpsbte/plotsystem/core/menus/ReviewPlotMenu.java b/src/main/java/com/alpsbte/plotsystem/core/menus/ReviewPlotMenu.java index e6a5b291..f6af7385 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/menus/ReviewPlotMenu.java +++ b/src/main/java/com/alpsbte/plotsystem/core/menus/ReviewPlotMenu.java @@ -32,6 +32,7 @@ import com.alpsbte.plotsystem.core.system.Review; 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.chat.ChatInput; import com.alpsbte.plotsystem.utils.chat.PlayerFeedbackChatInput; @@ -225,6 +226,8 @@ protected void setItemClickEventsAsync() { plot.getReview().setFeedback("No Feedback"); plot.getPlotOwner().addCompletedBuild(1); + DiscordUtil.getOpt(plot.getID()).ifPresent(DiscordUtil.PlotEventAction::onPlotApprove); + // Remove Plot from Owner plot.getPlotOwner().removePlot(plot.getSlot()); @@ -272,6 +275,8 @@ protected void setItemClickEventsAsync() { reviewerConfirmationMessage = Utils.ChatUtils.getInfoFormat(LangUtil.getInstance().get(getMenuPlayer(), LangPaths.Message.Info.PLOT_REJECTED, Integer.toString(plot.getID()), sb.toString())); } + DiscordUtil.getOpt(plot.getID()).ifPresent(DiscordUtil.PlotEventAction::onPlotReject); + PlotUtils.Actions.undoSubmit(plot); } 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 new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/com/alpsbte/plotsystem/core/system/Review.java b/src/main/java/com/alpsbte/plotsystem/core/system/Review.java index 14ac2d58..c485a16f 100644 --- a/src/main/java/com/alpsbte/plotsystem/core/system/Review.java +++ b/src/main/java/com/alpsbte/plotsystem/core/system/Review.java @@ -27,6 +27,7 @@ import com.alpsbte.plotsystem.PlotSystem; import com.alpsbte.plotsystem.core.database.DatabaseConnection; import com.alpsbte.plotsystem.core.system.plot.Plot; +import com.alpsbte.plotsystem.utils.DiscordUtil; import com.alpsbte.plotsystem.utils.enums.Category; import com.alpsbte.plotsystem.utils.enums.Status; import com.alpsbte.plotsystem.utils.io.FTPManager; @@ -45,13 +46,16 @@ public class Review { private final int reviewID; + private final Integer plotID; public Review(int reviewID) { this.reviewID = reviewID; + this.plotID = null; } public Review(int plotID, UUID reviewer, String rating) throws SQLException { this.reviewID = DatabaseConnection.getTableID("plotsystem_reviews"); + this.plotID = plotID; DatabaseConnection.createStatement("INSERT INTO plotsystem_reviews (id, reviewer_uuid, rating, review_date, feedback) VALUES (?, ?, ?, ?, ?)") .setValue(this.reviewID) @@ -173,6 +177,9 @@ public void setRating(String ratingFormat) throws SQLException { public void setFeedback(String feedback) throws SQLException { DatabaseConnection.createStatement("UPDATE plotsystem_reviews SET feedback = ? WHERE id = ?") .setValue(feedback).setValue(this.reviewID).executeUpdate(); + + DiscordUtil.getOpt(this.plotID == null? this.getPlotID() : this.plotID) + .ifPresent(event -> event.onPlotFeedback(feedback)); } public void setFeedbackSent(boolean isSent) throws SQLException { 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 236e1be3..929ace20 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 @@ -33,6 +33,7 @@ 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.DiscordUtil; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.io.ConfigPaths; import com.alpsbte.plotsystem.utils.io.ConfigUtil; @@ -142,7 +143,7 @@ private AbstractPlotGenerator(@NotNull AbstractPlot plot, @NotNull Builder build } 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 e13114df..e218d3aa 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 @@ -31,6 +31,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; @@ -133,6 +134,9 @@ protected void onComplete(boolean failed, boolean unloadWorld) throws SQLExcepti plot.getWorld().teleportPlayer(getBuilder().getPlayer()); LangUtil.getInstance().broadcast(LangPaths.Message.Info.CREATED_NEW_PLOT, plot.getPlotOwner().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 c33d6210..24cee777 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 @@ -36,6 +36,7 @@ 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.DiscordUtil; import com.alpsbte.plotsystem.utils.ShortLink; import com.alpsbte.plotsystem.utils.Utils; import com.alpsbte.plotsystem.utils.enums.Status; @@ -86,6 +87,10 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.text.DecimalFormat; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -313,18 +318,43 @@ public static void checkPlotsForLastActivity() { try { List plots = Plot.getPlots(Status.unfinished); FileConfiguration config = PlotSystem.getPlugin().getConfig(); - long millisInDays = config.getLong(ConfigPaths.INACTIVITY_INTERVAL) * 24 * 60 * 60 * 1000; // Remove all plots which have no activity for the last x days + long inactivityIntervalDays = config.getLong(ConfigPaths.INACTIVITY_INTERVAL, -2); + int inactivityNotificationDays = config.getInt(ConfigPaths.INACTIVITY_NOTIFICATION_DAYS, 0); + int inactivityNotificationTime = config.getInt(ConfigPaths.INACTIVITY_NOTIFICATION_TIME, 16); + if (inactivityIntervalDays == -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) { - if (plot.getLastActivity() != null && plot.getLastActivity().getTime() < (new Date().getTime() - millisInDays)) { - 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!")); - } - }); + LocalDate lastActivity = switch (plot.getLastActivity()) { + case java.sql.Date date -> date.toLocalDate(); + case java.util.Date date -> date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + }; + + if(lastActivity == null) continue; + + LocalDate abandonDate = lastActivity.plusDays(inactivityIntervalDays); + + // 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(abandonDate.isAfter(LocalDate.now())) continue; + + Bukkit.getScheduler().runTask(PlotSystem.getPlugin(), () -> { + 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!")); + } + }); } } catch (SQLException ex) { Utils.logSqlException(ex); @@ -384,6 +414,8 @@ public static void submitPlot(@NotNull Plot plot) throws SQLException { plot.getPermissions().removeBuilderPerms(builder.getUUID()); } } + + DiscordUtil.getOpt(plot.getID()).ifPresent(DiscordUtil.PlotEventAction::onPlotSubmit); } public static void undoSubmit(@NotNull Plot plot) throws SQLException { @@ -397,7 +429,7 @@ public static void undoSubmit(@NotNull Plot plot) throws SQLException { } } - 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 +463,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 (SQLException | IOException | WorldEditException ex) { PlotSystem.getPlugin().getComponentLogger().error(text("Failed to abandon plot with the ID " + plot.getID() + "!"), ex); return false; @@ -478,7 +512,7 @@ private static boolean updateDatabase(@NotNull AbstractPlot plot) { } public static boolean deletePlot(Plot plot) { - if (abandonPlot(plot)) { + if (abandonPlot(plot, DiscordUtil.AbandonType.COMMANDS)) { try { CompletableFuture.runAsync(() -> { try { 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..db7c6363 --- /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.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.sql.SQLException; +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 = new Plot(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; + + try { + CityProject cityProject = ((Plot) plot).getCity(); + + 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.getName(); + + String countryCode = cityProject.getCountry().getName(); + + PlotCreateData.PlotStatus status = PlotCreateData.prepareStatus(plot.getStatus().name()); + return new PlotCreateData(plotID, ownerUUID.toString(), status, cityProjectID, countryCode, geoCoordinates); + } + catch (IllegalArgumentException | SQLException 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(new Plot(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 0cf83800..e1bd34b7 100644 --- a/src/main/java/com/alpsbte/plotsystem/utils/io/ConfigPaths.java +++ b/src/main/java/com/alpsbte/plotsystem/utils/io/ConfigPaths.java @@ -32,6 +32,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 ENABLE_GROUP_SUPPORT = "enable-group-support"; private static final String SYNC_FTP_FILES = "sync-ftp-files."; diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 69f4eeb9..1a5868f0 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -30,6 +30,13 @@ sync-ftp-files: # How many days of inactivity it will take before a claimed plot is automatically abandoned 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 + # Enable or disable the Group System, that allows users to invite other Builders as members of their plot, # allowing them to build together. # NOTE: Score will be split by all participating members @@ -39,7 +46,7 @@ enable-group-support: true # | Supported databases: MariaDB & MySQL # ----------------------------------------------------- database: - url: jdbc:mariadb://adress:3306/ + url: jdbc:mariadb://address:3306/ dbname: plotsystem username: plotsystem password: minecraft diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 0e8923cf..870f390d 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -4,7 +4,7 @@ api-version: "1.21" name: Plot-System author: R3tuxn & Cinnazeyy softdepend: [ LangLibs, FancyNpcs, VoidGen, FastAsyncWorldEdit, Multiverse-Core, WorldEdit, - DecentHolograms, WorldGuard, WorldGuardExtraFlags, HeadDatabase, ProtocolLib, ParticleNativeAPI ] + DecentHolograms, WorldGuard, WorldGuardExtraFlags, HeadDatabase, ProtocolLib, ParticleNativeAPI, DiscordPlotSystem ] commands: cancelchat: