From c22a7d6e2436ef295f8dc289fd06df479d4fabde Mon Sep 17 00:00:00 2001 From: Xelara Networks Date: Wed, 24 Jun 2026 00:15:56 -0400 Subject: [PATCH] Fresh upload --- README.md | 0 pom.xml | 74 +++ .../com/dirtsmp/dirtsmp/DirtSMPPlugin.java | 180 ++++++ .../dirtsmp/command/DirtSMPCommand.java | 592 ++++++++++++++++++ .../dirtsmp/dirtsmp/config/ConfigManager.java | 482 ++++++++++++++ .../dirtsmp/config/MessageManager.java | 207 ++++++ .../dirtsmp/dirtsmp/gui/AdminGuiManager.java | 202 ++++++ .../hook/DirtSMPPlaceholderExpansion.java | 117 ++++ .../dirtsmp/dirtsmp/hook/PlaceholderHook.java | 26 + .../dirtsmp/listener/PlayerListener.java | 47 ++ .../com/dirtsmp/dirtsmp/model/BorderMode.java | 17 + .../dirtsmp/dirtsmp/model/CatchUpMode.java | 18 + .../dirtsmp/model/ExpansionReason.java | 22 + .../dirtsmp/model/ExpansionResult.java | 17 + .../com/dirtsmp/dirtsmp/model/GrowthMode.java | 17 + .../dirtsmp/model/LegacyAccessReport.java | 11 + .../dirtsmp/model/PhaseDefinition.java | 11 + .../dirtsmp/model/TriggerDecision.java | 11 + .../dirtsmp/dirtsmp/model/TriggerMode.java | 36 ++ .../dirtsmp/model/TriggerSettings.java | 17 + .../com/dirtsmp/dirtsmp/model/WorldRule.java | 180 ++++++ .../dirtsmp/service/BorderManager.java | 408 ++++++++++++ .../dirtsmp/service/CommandHookManager.java | 77 +++ .../dirtsmp/service/HistoryLogger.java | 48 ++ .../dirtsmp/service/SoftBorderManager.java | 434 +++++++++++++ .../com/dirtsmp/dirtsmp/service/TimeUtil.java | 92 +++ .../dirtsmp/service/TriggerEvaluator.java | 139 ++++ .../dirtsmp/service/WebhookNotifier.java | 67 ++ .../dirtsmp/state/PlayerStatsManager.java | 144 +++++ .../dirtsmp/dirtsmp/state/StateManager.java | 205 ++++++ .../dirtsmp/dirtsmp/state/WorldProgress.java | 87 +++ .../dirtsmp/dirtsmp/task/ScheduleManager.java | 144 +++++ src/main/resources/config.yml | 414 ++++++++++++ src/main/resources/messages.yml | 76 +++ src/main/resources/plugin.yml | 65 ++ target/DirtSMP-1.0.0.jar | Bin 0 -> 122733 bytes .../com/dirtsmp/dirtsmp/DirtSMPPlugin.class | Bin 0 -> 8787 bytes .../dirtsmp/command/DirtSMPCommand.class | Bin 0 -> 27531 bytes .../dirtsmp/config/ConfigManager.class | Bin 0 -> 23575 bytes .../dirtsmp/config/MessageManager.class | Bin 0 -> 14038 bytes .../gui/AdminGuiManager$GuiHolder.class | Bin 0 -> 837 bytes .../dirtsmp/dirtsmp/gui/AdminGuiManager.class | Bin 0 -> 12102 bytes .../hook/DirtSMPPlaceholderExpansion.class | Bin 0 -> 6660 bytes .../dirtsmp/hook/PlaceholderHook.class | Bin 0 -> 1088 bytes .../dirtsmp/listener/PlayerListener.class | Bin 0 -> 3493 bytes .../dirtsmp/dirtsmp/model/BorderMode.class | Bin 0 -> 1528 bytes .../dirtsmp/dirtsmp/model/CatchUpMode.class | Bin 0 -> 1569 bytes .../dirtsmp/model/ExpansionReason.class | Bin 0 -> 1728 bytes .../dirtsmp/model/ExpansionResult.class | Bin 0 -> 2474 bytes .../dirtsmp/dirtsmp/model/GrowthMode.class | Bin 0 -> 1521 bytes .../dirtsmp/model/LegacyAccessReport.class | Bin 0 -> 1925 bytes .../dirtsmp/model/PhaseDefinition.class | Bin 0 -> 2156 bytes .../dirtsmp/model/TriggerDecision.class | Bin 0 -> 2081 bytes .../dirtsmp/dirtsmp/model/TriggerMode.class | Bin 0 -> 2267 bytes .../dirtsmp/model/TriggerSettings.class | Bin 0 -> 3092 bytes .../WorldRule$AnnouncementSettings.class | Bin 0 -> 2743 bytes .../WorldRule$LegacyAccessSettings.class | Bin 0 -> 3634 bytes .../model/WorldRule$LegacyLocation.class | Bin 0 -> 1824 bytes .../WorldRule$MilestoneRewardSettings.class | Bin 0 -> 3402 bytes .../model/WorldRule$ReminderSettings.class | Bin 0 -> 2748 bytes .../model/WorldRule$SoftBorderSettings.class | Bin 0 -> 5841 bytes .../com/dirtsmp/dirtsmp/model/WorldRule.class | Bin 0 -> 8838 bytes .../BorderManager$LegacyAccumulator.class | Bin 0 -> 823 bytes .../dirtsmp/service/BorderManager.class | Bin 0 -> 19497 bytes .../dirtsmp/service/CommandHookManager.class | Bin 0 -> 6247 bytes .../dirtsmp/service/HistoryLogger.class | Bin 0 -> 4009 bytes .../service/SoftBorderManager$Bounds.class | Bin 0 -> 1942 bytes .../dirtsmp/service/SoftBorderManager.class | Bin 0 -> 18523 bytes .../dirtsmp/dirtsmp/service/TimeUtil.class | Bin 0 -> 4083 bytes .../dirtsmp/service/TriggerEvaluator$1.class | Bin 0 -> 1339 bytes .../dirtsmp/service/TriggerEvaluator.class | Bin 0 -> 7474 bytes .../dirtsmp/service/WebhookNotifier.class | Bin 0 -> 6250 bytes .../PlayerStatsManager$PlayerRecord.class | Bin 0 -> 669 bytes .../dirtsmp/state/PlayerStatsManager.class | Bin 0 -> 7766 bytes .../state/StateManager$LegacyUserRecord.class | Bin 0 -> 2038 bytes .../dirtsmp/dirtsmp/state/StateManager.class | Bin 0 -> 12682 bytes .../dirtsmp/dirtsmp/state/WorldProgress.class | Bin 0 -> 2427 bytes .../dirtsmp/task/ScheduleManager.class | Bin 0 -> 7814 bytes target/classes/config.yml | 414 ++++++++++++ target/classes/messages.yml | 76 +++ target/classes/plugin.yml | 65 ++ target/maven-archiver/pom.properties | 3 + .../compile/default-compile/createdFiles.lst | 42 ++ .../compile/default-compile/inputFiles.lst | 30 + 84 files changed, 5314 insertions(+) create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/com/dirtsmp/dirtsmp/DirtSMPPlugin.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/command/DirtSMPCommand.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/config/ConfigManager.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/config/MessageManager.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/gui/AdminGuiManager.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/hook/DirtSMPPlaceholderExpansion.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/hook/PlaceholderHook.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/listener/PlayerListener.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/model/BorderMode.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/model/CatchUpMode.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/model/ExpansionReason.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/model/ExpansionResult.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/model/GrowthMode.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/model/LegacyAccessReport.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/model/PhaseDefinition.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/model/TriggerDecision.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/model/TriggerMode.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/model/TriggerSettings.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/model/WorldRule.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/service/BorderManager.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/service/CommandHookManager.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/service/HistoryLogger.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/service/SoftBorderManager.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/service/TimeUtil.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/service/TriggerEvaluator.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/service/WebhookNotifier.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/state/PlayerStatsManager.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/state/StateManager.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/state/WorldProgress.java create mode 100644 src/main/java/com/dirtsmp/dirtsmp/task/ScheduleManager.java create mode 100644 src/main/resources/config.yml create mode 100644 src/main/resources/messages.yml create mode 100644 src/main/resources/plugin.yml create mode 100644 target/DirtSMP-1.0.0.jar create mode 100644 target/classes/com/dirtsmp/dirtsmp/DirtSMPPlugin.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/command/DirtSMPCommand.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/config/ConfigManager.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/config/MessageManager.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/gui/AdminGuiManager$GuiHolder.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/gui/AdminGuiManager.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/hook/DirtSMPPlaceholderExpansion.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/hook/PlaceholderHook.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/listener/PlayerListener.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/model/BorderMode.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/model/CatchUpMode.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/model/ExpansionReason.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/model/ExpansionResult.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/model/GrowthMode.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/model/LegacyAccessReport.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/model/PhaseDefinition.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/model/TriggerDecision.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/model/TriggerMode.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/model/TriggerSettings.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/model/WorldRule$AnnouncementSettings.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/model/WorldRule$LegacyAccessSettings.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/model/WorldRule$LegacyLocation.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/model/WorldRule$MilestoneRewardSettings.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/model/WorldRule$ReminderSettings.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/model/WorldRule$SoftBorderSettings.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/model/WorldRule.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/service/BorderManager$LegacyAccumulator.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/service/BorderManager.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/service/CommandHookManager.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/service/HistoryLogger.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/service/SoftBorderManager$Bounds.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/service/SoftBorderManager.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/service/TimeUtil.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/service/TriggerEvaluator$1.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/service/TriggerEvaluator.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/service/WebhookNotifier.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/state/PlayerStatsManager$PlayerRecord.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/state/PlayerStatsManager.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/state/StateManager$LegacyUserRecord.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/state/StateManager.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/state/WorldProgress.class create mode 100644 target/classes/com/dirtsmp/dirtsmp/task/ScheduleManager.class create mode 100644 target/classes/config.yml create mode 100644 target/classes/messages.yml create mode 100644 target/classes/plugin.yml create mode 100644 target/maven-archiver/pom.properties create mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst create mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..96dcbab --- /dev/null +++ b/pom.xml @@ -0,0 +1,74 @@ + + 4.0.0 + + com.dirtsmp + DirtSMP + 1.0.0 + jar + + DirtSMP + DirtbagMC-themed configurable world-border progression for Paper SMP servers. + + + 21 + 1.21.8-R0.1-SNAPSHOT + 2.11.6 + UTF-8 + + + + + papermc + https://repo.papermc.io/repository/maven-public/ + + + placeholderapi + https://repo.helpch.at/releases/ + + + + + + io.papermc.paper + paper-api + ${paper.version} + provided + + + me.clip + placeholderapi + ${placeholderapi.version} + provided + true + + + + + DirtSMP-${project.version} + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + ${java.version} + true + + + + org.apache.maven.plugins + maven-jar-plugin + 3.4.2 + + + + true + + + + + + + diff --git a/src/main/java/com/dirtsmp/dirtsmp/DirtSMPPlugin.java b/src/main/java/com/dirtsmp/dirtsmp/DirtSMPPlugin.java new file mode 100644 index 0000000..9de2580 --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/DirtSMPPlugin.java @@ -0,0 +1,180 @@ +package com.dirtsmp.dirtsmp; + +import com.dirtsmp.dirtsmp.command.DirtSMPCommand; +import com.dirtsmp.dirtsmp.config.ConfigManager; +import com.dirtsmp.dirtsmp.config.MessageManager; +import com.dirtsmp.dirtsmp.gui.AdminGuiManager; +import com.dirtsmp.dirtsmp.hook.PlaceholderHook; +import com.dirtsmp.dirtsmp.listener.PlayerListener; +import com.dirtsmp.dirtsmp.service.BorderManager; +import com.dirtsmp.dirtsmp.service.CommandHookManager; +import com.dirtsmp.dirtsmp.service.HistoryLogger; +import com.dirtsmp.dirtsmp.service.SoftBorderManager; +import com.dirtsmp.dirtsmp.service.TriggerEvaluator; +import com.dirtsmp.dirtsmp.service.WebhookNotifier; +import com.dirtsmp.dirtsmp.state.PlayerStatsManager; +import com.dirtsmp.dirtsmp.state.StateManager; +import com.dirtsmp.dirtsmp.task.ScheduleManager; +import java.io.IOException; +import java.util.logging.Level; +import org.bukkit.Bukkit; +import org.bukkit.command.PluginCommand; +import org.bukkit.event.HandlerList; +import org.bukkit.plugin.java.JavaPlugin; + +public final class DirtSMPPlugin extends JavaPlugin { + private ConfigManager configManager; + private MessageManager messageManager; + private StateManager stateManager; + private PlayerStatsManager playerStatsManager; + private TriggerEvaluator triggerEvaluator; + private BorderManager borderManager; + private ScheduleManager scheduleManager; + private AdminGuiManager adminGuiManager; + private SoftBorderManager softBorderManager; + private PlaceholderHook placeholderHook; + + @Override + public void onEnable() { + try { + bootstrap(); + getLogger().info("DirtSMP enabled with " + configManager.worlds().size() + " configured world(s)."); + } catch (RuntimeException ex) { + getLogger().log(Level.SEVERE, "DirtSMP could not enable cleanly.", ex); + getServer().getPluginManager().disablePlugin(this); + } + } + + @Override + public void onDisable() { + if (scheduleManager != null) { + scheduleManager.stop(); + } + if (softBorderManager != null) { + softBorderManager.stop(); + } + Bukkit.getScheduler().cancelTasks(this); + if (adminGuiManager != null) { + adminGuiManager.closeOpenInventories(); + } + if (placeholderHook != null) { + placeholderHook.unregister(); + } + HandlerList.unregisterAll(this); + + if (stateManager != null) { + stateManager.saveQuietly(); + stateManager.clearRuntimeState(); + } + if (playerStatsManager != null) { + playerStatsManager.saveQuietly(); + playerStatsManager.clearRuntimeState(); + } + getLogger().info("DirtSMP disabled."); + } + + private void bootstrap() { + configManager = new ConfigManager(this); + configManager.load(); + messageManager = new MessageManager(configManager); + stateManager = new StateManager(this, configManager); + playerStatsManager = new PlayerStatsManager(this); + stateManager.load(); + playerStatsManager.load(); + + CommandHookManager commandHookManager = new CommandHookManager(configManager, messageManager); + WebhookNotifier webhookNotifier = new WebhookNotifier(this, configManager, messageManager); + HistoryLogger historyLogger = new HistoryLogger(this, configManager); + triggerEvaluator = new TriggerEvaluator(playerStatsManager); + borderManager = new BorderManager(this, configManager, messageManager, stateManager, playerStatsManager, commandHookManager, webhookNotifier, historyLogger); + scheduleManager = new ScheduleManager(this, configManager, stateManager, playerStatsManager, borderManager, triggerEvaluator, messageManager); + adminGuiManager = new AdminGuiManager(this, configManager, messageManager, stateManager, borderManager, triggerEvaluator); + softBorderManager = new SoftBorderManager(this, configManager, messageManager, stateManager); + + registerCommands(); + Bukkit.getPluginManager().registerEvents(new PlayerListener(this, playerStatsManager, borderManager, configManager), this); + Bukkit.getPluginManager().registerEvents(adminGuiManager, this); + Bukkit.getPluginManager().registerEvents(softBorderManager, this); + registerPlaceholders(); + + borderManager.applyStartupRules(); + softBorderManager.start(); + scheduleManager.start(); + } + + private void registerCommands() { + PluginCommand command = getCommand("dirtsmp"); + if (command == null) { + throw new IllegalStateException("plugin.yml is missing the dirtsmp command."); + } + DirtSMPCommand commandHandler = new DirtSMPCommand(this); + command.setExecutor(commandHandler); + command.setTabCompleter(commandHandler); + } + + private void registerPlaceholders() { + if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { + placeholderHook = new PlaceholderHook(this); + placeholderHook.register(); + } + } + + public void reloadDirtSMP() { + if (scheduleManager != null) { + scheduleManager.stop(); + } + if (softBorderManager != null) { + softBorderManager.stop(); + } + if (stateManager != null) { + stateManager.saveQuietly(); + } + if (playerStatsManager != null) { + playerStatsManager.saveQuietly(); + } + + configManager.load(); + stateManager.load(); + playerStatsManager.load(); + borderManager.applyStartupRules(); + softBorderManager.start(); + scheduleManager.start(); + } + + public void saveAll() { + try { + stateManager.save(); + playerStatsManager.save(); + } catch (IOException ex) { + getLogger().log(Level.WARNING, "Could not save all DirtSMP data.", ex); + } + } + + public ConfigManager configManager() { + return configManager; + } + + public MessageManager messageManager() { + return messageManager; + } + + public StateManager stateManager() { + return stateManager; + } + + public PlayerStatsManager playerStatsManager() { + return playerStatsManager; + } + + public TriggerEvaluator triggerEvaluator() { + return triggerEvaluator; + } + + public BorderManager borderManager() { + return borderManager; + } + + public AdminGuiManager adminGuiManager() { + return adminGuiManager; + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/command/DirtSMPCommand.java b/src/main/java/com/dirtsmp/dirtsmp/command/DirtSMPCommand.java new file mode 100644 index 0000000..f5ea7a3 --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/command/DirtSMPCommand.java @@ -0,0 +1,592 @@ +package com.dirtsmp.dirtsmp.command; + +import com.dirtsmp.dirtsmp.DirtSMPPlugin; +import com.dirtsmp.dirtsmp.config.MessageManager; +import com.dirtsmp.dirtsmp.model.ExpansionReason; +import com.dirtsmp.dirtsmp.model.ExpansionResult; +import com.dirtsmp.dirtsmp.model.LegacyAccessReport; +import com.dirtsmp.dirtsmp.model.PhaseDefinition; +import com.dirtsmp.dirtsmp.model.WorldRule; +import com.dirtsmp.dirtsmp.service.TimeUtil; +import com.dirtsmp.dirtsmp.state.WorldProgress; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.OptionalDouble; +import java.util.UUID; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.Statistic; +import org.bukkit.World; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class DirtSMPCommand implements CommandExecutor, TabCompleter { + private static final List SUBCOMMANDS = List.of("help", "reload", "status", "next", "legacy", "legacycheck", "tpborder", "tpborderside", "add", "expand", "setsize", "pause", "resume", "reset", "gui", "save"); + private static final List BORDER_SIDES = List.of("north", "south", "east", "west"); + + private final DirtSMPPlugin plugin; + + public DirtSMPCommand(DirtSMPPlugin plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (args.length == 0 || args[0].equalsIgnoreCase("help")) { + plugin.messageManager().sendList(sender, "commands.help", Map.of()); + return true; + } + + String sub = args[0].toLowerCase(Locale.ROOT); + switch (sub) { + case "reload" -> reload(sender); + case "status" -> status(sender, args); + case "next" -> next(sender, args); + case "legacy" -> legacy(sender, args); + case "legacycheck" -> legacyCheck(sender, args); + case "tpborder", "tpborderside" -> teleportBorder(sender, args); + case "add" -> add(sender, args); + case "expand" -> expand(sender, args); + case "setsize" -> setSize(sender, args); + case "pause" -> pause(sender, args); + case "resume" -> resume(sender, args); + case "reset" -> reset(sender, args); + case "gui" -> gui(sender); + case "save" -> save(sender); + default -> plugin.messageManager().sendList(sender, "commands.help", Map.of()); + } + return true; + } + + private void add(CommandSender sender, String[] args) { + if (args.length >= 4 + && args[1].equalsIgnoreCase("legacy") + && (args[2].equalsIgnoreCase("users") || args[2].equalsIgnoreCase("user")) + && args[3].equalsIgnoreCase("scan")) { + scanLegacyUsers(sender, args.length >= 5 ? args[4] : null); + return; + } + plugin.messageManager().sendList(sender, "commands.help", Map.of()); + } + + private void legacy(CommandSender sender, String[] args) { + if (args.length < 2) { + plugin.messageManager().sendList(sender, "commands.help", Map.of()); + return; + } + + switch (args[1].toLowerCase(Locale.ROOT)) { + case "scan" -> scanLegacyUsers(sender, args.length >= 3 ? args[2] : null); + case "list" -> listLegacyUsers(sender); + case "add" -> addLegacyUser(sender, args); + case "remove" -> removeLegacyUser(sender, args); + default -> plugin.messageManager().sendList(sender, "commands.help", Map.of()); + } + } + + private void scanLegacyUsers(CommandSender sender, String rawMinimum) { + if (!has(sender, "dirtsmp.legacy")) { + plugin.messageManager().send(sender, "commands.no-permission"); + return; + } + + long minimumMillis = rawMinimum == null || rawMinimum.isBlank() + ? plugin.configManager().legacyUsersMinimumPlaytimeMillis() + : TimeUtil.parseDurationMillis(rawMinimum, plugin.configManager().legacyUsersMinimumPlaytimeMillis()); + long minimumTicks = Math.max(1L, minimumMillis / 50L); + + int scanned = 0; + int matched = 0; + int added = 0; + for (OfflinePlayer player : Bukkit.getOfflinePlayers()) { + if (!player.hasPlayedBefore()) { + continue; + } + scanned++; + long playtimeTicks = playtimeTicks(player); + if (playtimeTicks < minimumTicks) { + continue; + } + matched++; + if (plugin.stateManager().addLegacyUser(player.getUniqueId(), player.getName(), playtimeTicks, "scan:" + TimeUtil.formatDuration(minimumMillis))) { + added++; + } + } + + plugin.stateManager().saveQuietly(); + plugin.messageManager().send(sender, "commands.legacy-scan", Map.of( + "scanned", String.valueOf(scanned), + "matched", String.valueOf(matched), + "added", String.valueOf(added), + "total", String.valueOf(plugin.stateManager().legacyUsers().size()), + "minimum", TimeUtil.formatDuration(minimumMillis) + )); + } + + private void listLegacyUsers(CommandSender sender) { + if (!has(sender, "dirtsmp.legacy")) { + plugin.messageManager().send(sender, "commands.no-permission"); + return; + } + + plugin.messageManager().send(sender, "commands.legacy-list-header", Map.of( + "count", String.valueOf(plugin.stateManager().legacyUsers().size()) + )); + plugin.stateManager().legacyUsers().entrySet().stream() + .sorted(Comparator.comparing(entry -> entry.getValue().name().toLowerCase(Locale.ROOT))) + .limit(20) + .forEach(entry -> plugin.messageManager().send(sender, "commands.legacy-list-line", Map.of( + "name", entry.getValue().name(), + "uuid", entry.getKey().toString(), + "playtime", TimeUtil.formatDuration(entry.getValue().playtimeTicks() * 50L), + "source", entry.getValue().source() + ))); + } + + @SuppressWarnings("deprecation") + private void addLegacyUser(CommandSender sender, String[] args) { + if (!has(sender, "dirtsmp.legacy")) { + plugin.messageManager().send(sender, "commands.no-permission"); + return; + } + if (args.length < 3) { + plugin.messageManager().sendList(sender, "commands.help", Map.of()); + return; + } + + OfflinePlayer player = Bukkit.getOfflinePlayer(args[2]); + long playtimeTicks = playtimeTicks(player); + boolean added = plugin.stateManager().addLegacyUser(player.getUniqueId(), player.getName(), playtimeTicks, "manual:" + sender.getName()); + plugin.stateManager().saveQuietly(); + plugin.messageManager().send(sender, "commands.legacy-add", Map.of( + "name", player.getName() == null ? args[2] : player.getName(), + "status", added ? "added" : "updated" + )); + } + + @SuppressWarnings("deprecation") + private void removeLegacyUser(CommandSender sender, String[] args) { + if (!has(sender, "dirtsmp.legacy")) { + plugin.messageManager().send(sender, "commands.no-permission"); + return; + } + if (args.length < 3) { + plugin.messageManager().sendList(sender, "commands.help", Map.of()); + return; + } + + UUID uuid = parseUuid(args[2]); + OfflinePlayer player = uuid == null ? Bukkit.getOfflinePlayer(args[2]) : Bukkit.getOfflinePlayer(uuid); + boolean removed = plugin.stateManager().removeLegacyUser(player.getUniqueId()); + plugin.stateManager().saveQuietly(); + plugin.messageManager().send(sender, "commands.legacy-remove", Map.of( + "name", player.getName() == null ? args[2] : player.getName(), + "status", removed ? "removed" : "not listed" + )); + } + + private long playtimeTicks(OfflinePlayer player) { + try { + return Math.max(0L, player.getStatistic(Statistic.PLAY_ONE_MINUTE)); + } catch (RuntimeException ex) { + return 0L; + } + } + + private UUID parseUuid(String value) { + try { + return UUID.fromString(value); + } catch (IllegalArgumentException ex) { + return null; + } + } + + private void reload(CommandSender sender) { + if (!has(sender, "dirtsmp.reload")) { + plugin.messageManager().send(sender, "commands.no-permission"); + return; + } + plugin.reloadDirtSMP(); + plugin.messageManager().send(sender, "commands.reload"); + } + + private void save(CommandSender sender) { + if (!has(sender, "dirtsmp.admin")) { + plugin.messageManager().send(sender, "commands.no-permission"); + return; + } + plugin.saveAll(); + plugin.messageManager().send(sender, "commands.saved"); + } + + private void status(CommandSender sender, String[] args) { + if (!has(sender, "dirtsmp.status")) { + plugin.messageManager().send(sender, "commands.no-permission"); + return; + } + if (args.length >= 2) { + WorldRule rule = rule(sender, args[1]); + if (rule != null) { + plugin.messageManager().send(sender, "commands.status-line", placeholders(rule)); + } + return; + } + + plugin.messageManager().send(sender, "commands.status-header", Map.of("count", String.valueOf(plugin.configManager().worlds().size()))); + for (WorldRule rule : plugin.configManager().worlds().values()) { + plugin.messageManager().send(sender, "commands.status-line", placeholders(rule)); + } + } + + private void next(CommandSender sender, String[] args) { + if (!has(sender, "dirtsmp.status")) { + plugin.messageManager().send(sender, "commands.no-permission"); + return; + } + if (args.length >= 2) { + WorldRule rule = rule(sender, args[1]); + if (rule != null) { + plugin.messageManager().send(sender, "commands.next-line", placeholders(rule)); + } + return; + } + for (WorldRule rule : plugin.configManager().worlds().values()) { + plugin.messageManager().send(sender, "commands.next-line", placeholders(rule)); + } + } + + private void legacyCheck(CommandSender sender, String[] args) { + if (!has(sender, "dirtsmp.status")) { + plugin.messageManager().send(sender, "commands.no-permission"); + return; + } + if (args.length < 2) { + plugin.messageManager().sendList(sender, "commands.help", Map.of()); + return; + } + WorldRule rule = rule(sender, args[1]); + if (rule == null) { + return; + } + + LegacyAccessReport report = plugin.borderManager().legacyAccessReport(rule); + double recommended = Math.max(rule.initialSize(), report.requiredSize()); + plugin.messageManager().send(sender, "commands.legacy-check", Map.of( + "world", rule.worldName(), + "locations", String.valueOf(report.includedLocations()), + "required_size", TimeUtil.formatSize(report.requiredSize()), + "recommended_size", TimeUtil.formatSize(recommended), + "initial_size", TimeUtil.formatSize(rule.initialSize()), + "farthest", report.farthestLocationName() + )); + } + + private void teleportBorder(CommandSender sender, String[] args) { + if (!has(sender, "dirtsmp.tpborder")) { + plugin.messageManager().send(sender, "commands.no-permission"); + return; + } + if (!(sender instanceof Player player)) { + plugin.messageManager().send(sender, "commands.player-only"); + return; + } + if (args.length < 2) { + plugin.messageManager().sendList(sender, "commands.help", Map.of()); + return; + } + + WorldRule rule = rule(sender, args[1]); + if (rule == null) { + return; + } + World world = Bukkit.getWorld(rule.worldName()); + if (world == null) { + plugin.messageManager().send(sender, "commands.tp-border-failed", Map.of( + "world", rule.worldName(), + "reason", "world is not loaded" + )); + return; + } + + String side = args.length >= 3 ? args[2].toLowerCase(Locale.ROOT) : nearestSide(player, rule); + if (!BORDER_SIDES.contains(side)) { + plugin.messageManager().send(sender, "commands.tp-border-failed", Map.of( + "world", rule.worldName(), + "reason", "side must be north, south, east, or west" + )); + return; + } + + Location destination = borderPreviewLocation(player, world, rule, side); + destination.setY(safeY(world, destination)); + player.teleport(destination); + plugin.messageManager().send(player, "commands.tp-border", Map.of( + "world", rule.worldName(), + "side", side, + "size", TimeUtil.formatSize(currentSize(rule)) + )); + } + + private void expand(CommandSender sender, String[] args) { + if (!has(sender, "dirtsmp.expand")) { + plugin.messageManager().send(sender, "commands.no-permission"); + return; + } + if (args.length < 2) { + plugin.messageManager().sendList(sender, "commands.help", Map.of()); + return; + } + WorldRule rule = rule(sender, args[1]); + if (rule == null) { + return; + } + ExpansionResult result = plugin.borderManager().expand(rule, ExpansionReason.MANUAL, sender.getName(), true); + if (result.success()) { + plugin.messageManager().send(sender, "commands.expanded", Map.of( + "world", rule.worldName(), + "old_size", TimeUtil.formatSize(result.oldSize()), + "new_size", TimeUtil.formatSize(result.newSize()) + )); + } else { + plugin.messageManager().send(sender, "commands.expand-failed", Map.of("world", rule.worldName(), "reason", result.reason())); + } + } + + private void setSize(CommandSender sender, String[] args) { + if (!has(sender, "dirtsmp.setsize")) { + plugin.messageManager().send(sender, "commands.no-permission"); + return; + } + if (args.length < 3) { + plugin.messageManager().sendList(sender, "commands.help", Map.of()); + return; + } + WorldRule rule = rule(sender, args[1]); + if (rule == null) { + return; + } + double size; + try { + size = Double.parseDouble(args[2].replace(",", "")); + } catch (NumberFormatException ex) { + plugin.messageManager().send(sender, "commands.invalid-number"); + return; + } + boolean force = args.length >= 4 && args[3].equalsIgnoreCase("force"); + ExpansionResult result = plugin.borderManager().setSize(rule, size, sender.getName(), force); + if (result.success()) { + plugin.messageManager().send(sender, "commands.set-size", Map.of("world", rule.worldName(), "new_size", TimeUtil.formatSize(result.newSize()))); + } else { + plugin.messageManager().send(sender, "commands.set-size-failed", Map.of("world", rule.worldName(), "reason", result.reason())); + } + } + + private void pause(CommandSender sender, String[] args) { + if (!has(sender, "dirtsmp.pause")) { + plugin.messageManager().send(sender, "commands.no-permission"); + return; + } + WorldRule rule = args.length >= 2 ? rule(sender, args[1]) : null; + if (rule == null) { + return; + } + plugin.borderManager().pause(rule); + plugin.messageManager().send(sender, "commands.paused", Map.of("world", rule.worldName())); + } + + private void resume(CommandSender sender, String[] args) { + if (!has(sender, "dirtsmp.resume")) { + plugin.messageManager().send(sender, "commands.no-permission"); + return; + } + WorldRule rule = args.length >= 2 ? rule(sender, args[1]) : null; + if (rule == null) { + return; + } + plugin.borderManager().resume(rule); + plugin.messageManager().send(sender, "commands.resumed", Map.of("world", rule.worldName())); + } + + private void reset(CommandSender sender, String[] args) { + if (!has(sender, "dirtsmp.reset")) { + plugin.messageManager().send(sender, "commands.no-permission"); + return; + } + WorldRule rule = args.length >= 2 ? rule(sender, args[1]) : null; + if (rule == null) { + return; + } + boolean force = args.length >= 3 && args[2].equalsIgnoreCase("force"); + ExpansionResult result = plugin.borderManager().reset(rule, sender.getName(), force); + if (result.success()) { + plugin.messageManager().send(sender, "commands.reset", Map.of("world", rule.worldName())); + } else { + plugin.messageManager().send(sender, "commands.set-size-failed", Map.of("world", rule.worldName(), "reason", result.reason())); + } + } + + private void gui(CommandSender sender) { + if (!has(sender, "dirtsmp.gui")) { + plugin.messageManager().send(sender, "commands.no-permission"); + return; + } + if (!(sender instanceof Player player)) { + plugin.messageManager().send(sender, "commands.player-only"); + return; + } + if (!plugin.configManager().guiEnabled()) { + plugin.messageManager().send(sender, "commands.gui-disabled"); + return; + } + plugin.adminGuiManager().open(player); + } + + private WorldRule rule(CommandSender sender, String key) { + WorldRule rule = plugin.configManager().world(key); + if (rule == null) { + plugin.messageManager().send(sender, "commands.unknown-world", Map.of("world", key)); + } + return rule; + } + + private Map placeholders(WorldRule rule) { + WorldProgress progress = plugin.stateManager().progress(rule.key()); + OptionalDouble nextSize = plugin.borderManager().nextSize(rule, progress); + PhaseDefinition phase = plugin.borderManager().currentPhase(rule, progress); + String nextTime = "manual"; + if (rule.triggers().mode().usesTime() && progress.lastExpansionAt() > 0L) { + long next = progress.lastExpansionAt() + rule.triggers().timeIntervalMillis(); + nextTime = next <= System.currentTimeMillis() ? "due" : TimeUtil.formatDuration(next - System.currentTimeMillis()); + } + return Map.ofEntries( + Map.entry("key", rule.key()), + Map.entry("world", rule.worldName()), + Map.entry("enabled", String.valueOf(rule.enabled())), + Map.entry("size", TimeUtil.formatSize(plugin.borderManager().displaySize(rule, progress))), + Map.entry("next_size", nextSize.isPresent() ? TimeUtil.formatSize(nextSize.getAsDouble()) : "max"), + Map.entry("border_mode", rule.borderMode().name()), + Map.entry("phase", phase == null ? "none" : phase.name()), + Map.entry("paused", String.valueOf(progress.paused())), + Map.entry("trigger", plugin.triggerEvaluator().describeTrigger(rule, progress)), + Map.entry("next_time", nextTime), + Map.entry("expansion_count", String.valueOf(progress.expansionCount())), + Map.entry("unique_progress", plugin.playerStatsManager().uniquePlayerCount() + "/" + (progress.uniquePlayersAtLastExpansion() + rule.triggers().uniquePlayersEvery())) + ); + } + + private Location borderPreviewLocation(Player player, World world, WorldRule rule, String side) { + double half = currentSize(rule) / 2.0; + double inset = Math.max(3.0, rule.softBorder().insideBuffer() + 1.0); + double x = clamp(player.getLocation().getX(), rule.centerX() - half + inset, rule.centerX() + half - inset); + double z = clamp(player.getLocation().getZ(), rule.centerZ() - half + inset, rule.centerZ() + half - inset); + + switch (side) { + case "north" -> z = rule.centerZ() - half + inset; + case "south" -> z = rule.centerZ() + half - inset; + case "east" -> x = rule.centerX() + half - inset; + case "west" -> x = rule.centerX() - half + inset; + default -> { + } + } + return new Location(world, x, player.getLocation().getY(), z, player.getLocation().getYaw(), player.getLocation().getPitch()); + } + + private String nearestSide(Player player, WorldRule rule) { + double half = currentSize(rule) / 2.0; + double north = Math.abs(player.getLocation().getZ() - (rule.centerZ() - half)); + double south = Math.abs(player.getLocation().getZ() - (rule.centerZ() + half)); + double east = Math.abs(player.getLocation().getX() - (rule.centerX() + half)); + double west = Math.abs(player.getLocation().getX() - (rule.centerX() - half)); + double min = Math.min(Math.min(north, south), Math.min(east, west)); + if (min == north) { + return "north"; + } + if (min == south) { + return "south"; + } + return min == east ? "east" : "west"; + } + + private double currentSize(WorldRule rule) { + WorldProgress progress = plugin.stateManager().progress(rule.key()); + return progress.currentSize() > 0.0 ? progress.currentSize() : rule.initialSize(); + } + + private double safeY(World world, Location location) { + int x = location.getBlockX(); + int z = location.getBlockZ(); + int highest = world.getHighestBlockYAt(x, z); + return Math.max(world.getMinHeight() + 1.0, Math.min(world.getMaxHeight() - 2.0, highest + 1.0)); + } + + private double clamp(double value, double min, double max) { + return Math.max(min, Math.min(max, value)); + } + + private boolean has(CommandSender sender, String permission) { + return sender.hasPermission("dirtsmp.admin") || sender.hasPermission(permission); + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (args.length == 1) { + return filter(SUBCOMMANDS, args[0]); + } + + String sub = args[0].toLowerCase(Locale.ROOT); + if (args.length == 2 && List.of("status", "next", "legacycheck", "tpborder", "tpborderside", "expand", "setsize", "pause", "resume", "reset").contains(sub)) { + return filter(new ArrayList<>(plugin.configManager().worlds().keySet()), args[1]); + } + if (args.length == 3 && List.of("tpborder", "tpborderside").contains(sub)) { + return filter(BORDER_SIDES, args[2]); + } + if (sub.equals("legacy")) { + if (args.length == 2) { + return filter(List.of("scan", "list", "add", "remove"), args[1]); + } + if (args.length == 3 && args[1].equalsIgnoreCase("scan")) { + return filter(List.of("1h", "30m", "2h"), args[2]); + } + if (args.length == 3 && List.of("add", "remove").contains(args[1].toLowerCase(Locale.ROOT))) { + return filter(Bukkit.getOnlinePlayers().stream().map(Player::getName).toList(), args[2]); + } + } + if (sub.equals("add")) { + if (args.length == 2) { + return filter(List.of("legacy"), args[1]); + } + if (args.length == 3 && args[1].equalsIgnoreCase("legacy")) { + return filter(List.of("users"), args[2]); + } + if (args.length == 4 && args[1].equalsIgnoreCase("legacy") && args[2].equalsIgnoreCase("users")) { + return filter(List.of("scan"), args[3]); + } + if (args.length == 5 && args[1].equalsIgnoreCase("legacy") && args[2].equalsIgnoreCase("users") && args[3].equalsIgnoreCase("scan")) { + return filter(List.of("1h", "30m", "2h"), args[4]); + } + } + if (args.length == 4 && sub.equals("setsize")) { + return filter(List.of("force"), args[3]); + } + if (args.length == 3 && sub.equals("reset")) { + return filter(List.of("force"), args[2]); + } + return List.of(); + } + + private List filter(List values, String prefix) { + String lower = prefix.toLowerCase(Locale.ROOT); + return values.stream() + .filter(value -> value.toLowerCase(Locale.ROOT).startsWith(lower)) + .toList(); + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/config/ConfigManager.java b/src/main/java/com/dirtsmp/dirtsmp/config/ConfigManager.java new file mode 100644 index 0000000..b82b64f --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/config/ConfigManager.java @@ -0,0 +1,482 @@ +package com.dirtsmp.dirtsmp.config; + +import com.dirtsmp.dirtsmp.DirtSMPPlugin; +import com.dirtsmp.dirtsmp.model.BorderMode; +import com.dirtsmp.dirtsmp.model.CatchUpMode; +import com.dirtsmp.dirtsmp.model.GrowthMode; +import com.dirtsmp.dirtsmp.model.PhaseDefinition; +import com.dirtsmp.dirtsmp.model.TriggerMode; +import com.dirtsmp.dirtsmp.model.TriggerSettings; +import com.dirtsmp.dirtsmp.model.WorldRule; +import com.dirtsmp.dirtsmp.service.TimeUtil; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.logging.Level; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; + +public final class ConfigManager { + private final DirtSMPPlugin plugin; + private FileConfiguration config; + private YamlConfiguration messages; + private Map worlds = new LinkedHashMap<>(); + private WorldRule.MilestoneRewardSettings globalMilestoneRewards = WorldRule.MilestoneRewardSettings.empty(); + + public ConfigManager(DirtSMPPlugin plugin) { + this.plugin = plugin; + } + + public void load() { + plugin.getDataFolder().mkdirs(); + plugin.saveDefaultConfig(); + saveBundledResource("messages.yml"); + + plugin.reloadConfig(); + config = plugin.getConfig(); + messages = YamlConfiguration.loadConfiguration(new File(plugin.getDataFolder(), "messages.yml")); + globalMilestoneRewards = readMilestoneRewards(config.getConfigurationSection("milestone-rewards")); + worlds = loadWorldRules(); + } + + private void saveBundledResource(String name) { + File target = new File(plugin.getDataFolder(), name); + if (!target.exists()) { + plugin.saveResource(name, false); + } + } + + private Map loadWorldRules() { + ConfigurationSection section = config.getConfigurationSection("worlds"); + if (section == null) { + plugin.getLogger().warning("No worlds section found in config.yml. DirtSMP will not manage any borders."); + return Collections.emptyMap(); + } + + Map loaded = new LinkedHashMap<>(); + for (String key : section.getKeys(false)) { + ConfigurationSection worldSection = section.getConfigurationSection(key); + if (worldSection == null) { + continue; + } + WorldRule rule = readWorldRule(key, worldSection); + loaded.put(key.toLowerCase(Locale.ROOT), rule); + } + return Collections.unmodifiableMap(loaded); + } + + private WorldRule readWorldRule(String key, ConfigurationSection section) { + boolean enabled = section.getBoolean("enabled", true); + String worldName = nonBlank(section.getString("world"), key); + double centerX = section.getDouble("center-x", 0.0); + double centerZ = section.getDouble("center-z", 0.0); + double startingSize = positiveDouble(section, "starting-size", 1000.0); + BorderMode borderMode = BorderMode.from(section.getString("border-mode", "WORLD_BORDER")); + GrowthMode growthMode = GrowthMode.from(section.getString("growth-mode", "INCREMENTAL")); + double growthAmount = positiveDouble(section, "growth-amount", 1000.0); + double maxSize = positiveDouble(section, "max-size", 59_999_968.0); + long transitionSeconds = Math.max(0L, section.getLong("transition-seconds", 60L)); + boolean manualOnly = section.getBoolean("manual-only", false); + boolean importCurrentBorder = section.getBoolean("import-current-border", false); + boolean noShrinkProtection = section.getBoolean("no-shrink-protection", true); + boolean enforceMaxSize = section.getBoolean("enforce-max-size", true); + + WorldRule.AnnouncementSettings announcements = readAnnouncements(section.getConfigurationSection("broadcasts")); + WorldRule.ReminderSettings reminders = readReminders(section.getConfigurationSection("reminders")); + TriggerSettings triggers = readTriggers(section.getConfigurationSection("triggers")); + List phases = readPhases(section); + WorldRule.MilestoneRewardSettings milestoneRewards = readMilestoneRewards(section.getConfigurationSection("milestone-rewards")); + WorldRule.LegacyAccessSettings legacyAccess = readLegacyAccess(section.getConfigurationSection("legacy-access")); + WorldRule.SoftBorderSettings softBorder = readSoftBorder(section.getConfigurationSection("soft-border")); + + if (growthMode == GrowthMode.PHASE && phases.size() < 2) { + plugin.getLogger().warning("World '" + key + "' is in PHASE mode but has fewer than two phases. It can initialize, but it has no scripted expansion path."); + } + + return new WorldRule( + key.toLowerCase(Locale.ROOT), + enabled, + worldName, + centerX, + centerZ, + startingSize, + borderMode, + growthMode, + growthAmount, + maxSize, + transitionSeconds, + manualOnly, + importCurrentBorder, + noShrinkProtection, + enforceMaxSize, + announcements, + reminders, + triggers, + phases, + milestoneRewards, + legacyAccess, + softBorder, + stringList(section, "command-hooks.before-expansion"), + stringList(section, "command-hooks.after-expansion") + ); + } + + private WorldRule.AnnouncementSettings readAnnouncements(ConfigurationSection section) { + if (section == null) { + return new WorldRule.AnnouncementSettings(true, false, "", "", "", "", "", 1.0f, 1.0f); + } + return new WorldRule.AnnouncementSettings( + section.getBoolean("enabled", true), + section.getBoolean("world-only", false), + section.getString("message", ""), + section.getString("title", ""), + section.getString("subtitle", ""), + section.getString("action-bar", ""), + section.getString("sound", ""), + (float) section.getDouble("volume", 1.0), + (float) section.getDouble("pitch", 1.0) + ); + } + + private WorldRule.ReminderSettings readReminders(ConfigurationSection section) { + if (section == null) { + return new WorldRule.ReminderSettings(false, List.of(), "", "", "", 1.0f, 1.0f); + } + List reminders = new ArrayList<>(); + for (String raw : section.getStringList("before")) { + long millis = TimeUtil.parseDurationMillis(raw, -1L); + if (millis > 0L) { + reminders.add(millis); + } else { + plugin.getLogger().warning("Ignoring invalid reminder duration '" + raw + "'."); + } + } + reminders.sort(Collections.reverseOrder()); + return new WorldRule.ReminderSettings( + section.getBoolean("enabled", false), + List.copyOf(reminders), + section.getString("message", ""), + section.getString("action-bar", ""), + section.getString("sound", ""), + (float) section.getDouble("volume", 1.0), + (float) section.getDouble("pitch", 1.0) + ); + } + + private TriggerSettings readTriggers(ConfigurationSection section) { + if (section == null) { + return new TriggerSettings(TriggerMode.MANUAL, 0L, 0, 0, 0, 0L, true, 0L, CatchUpMode.NONE); + } + + TriggerMode mode = TriggerMode.from(section.getString("mode", "MANUAL")); + long interval = TimeUtil.parseDurationMillis(section.getString("time-interval", "7d"), 604_800_000L); + long activeWindow = TimeUtil.parseDurationMillis(section.getString("active-window", "7d"), 604_800_000L); + long cooldown = TimeUtil.parseDurationMillis(section.getString("player-trigger-cooldown", "12h"), 43_200_000L); + + return new TriggerSettings( + mode, + interval, + Math.max(0, section.getInt("unique-players-every", 0)), + Math.max(0, section.getInt("online-players-threshold", 0)), + Math.max(0, section.getInt("active-players-threshold", 0)), + activeWindow, + section.getBoolean("exclude-vanished", true), + cooldown, + CatchUpMode.from(section.getString("catch-up", "ONE")) + ); + } + + private List readPhases(ConfigurationSection section) { + List phases = new ArrayList<>(); + int index = 0; + for (Map phaseMap : section.getMapList("phases")) { + Object sizeValue = phaseMap.get("size"); + double size = parseDouble(sizeValue, -1.0); + if (size <= 0.0) { + plugin.getLogger().warning("Ignoring phase with invalid size under world '" + section.getName() + "'."); + continue; + } + phases.add(new PhaseDefinition( + index, + mapString(phaseMap, "name", "Phase " + index), + size, + mapString(phaseMap, "message", ""), + mapString(phaseMap, "title", ""), + mapString(phaseMap, "subtitle", "") + )); + index++; + } + phases.sort((left, right) -> Integer.compare(left.index(), right.index())); + return List.copyOf(phases); + } + + private WorldRule.MilestoneRewardSettings readMilestoneRewards(ConfigurationSection section) { + if (section == null) { + return WorldRule.MilestoneRewardSettings.empty(); + } + + return new WorldRule.MilestoneRewardSettings( + section.getBoolean("enabled", false), + stringList(section, "every-expansion"), + integerCommandMap(section.getConfigurationSection("by-expansion-count")), + integerCommandMap(section.getConfigurationSection("by-phase-index")), + stringCommandMap(section.getConfigurationSection("by-phase-name")) + ); + } + + private WorldRule.LegacyAccessSettings readLegacyAccess(ConfigurationSection section) { + if (section == null) { + return WorldRule.LegacyAccessSettings.disabled(); + } + + List locations = new ArrayList<>(); + for (Map locationMap : section.getMapList("locations")) { + double x = parseDouble(locationMap.get("x"), Double.NaN); + double z = parseDouble(locationMap.get("z"), Double.NaN); + if (Double.isNaN(x) || Double.isNaN(z)) { + plugin.getLogger().warning("Ignoring legacy-access location with invalid x/z under world '" + section.getParent().getName() + "'."); + continue; + } + locations.add(new WorldRule.LegacyLocation( + mapString(locationMap, "name", "legacy location"), + x, + z + )); + } + + return new WorldRule.LegacyAccessSettings( + section.getBoolean("enabled", false), + section.getBoolean("include-current-border", false), + section.getBoolean("include-online-players", true), + section.getBoolean("include-offline-last-locations", true), + section.getBoolean("include-respawn-locations", true), + section.getBoolean("player-locations-require-legacy-user", false), + section.getBoolean("reconcile-on-startup", true), + section.getBoolean("allow-start-above-max-size", true), + Math.max(0.0, section.getDouble("padding", 1024.0)), + List.copyOf(locations) + ); + } + + private WorldRule.SoftBorderSettings readSoftBorder(ConfigurationSection section) { + WorldRule.SoftBorderSettings defaults = WorldRule.SoftBorderSettings.defaults(); + if (section == null) { + return defaults; + } + + return new WorldRule.SoftBorderSettings( + section.getBoolean("release-vanilla-border", defaults.releaseVanillaBorder()), + positiveDouble(section, "vanilla-border-size", defaults.vanillaBorderSize()), + section.getBoolean("ignore-creative", defaults.ignoreCreative()), + section.getBoolean("ignore-spectator", defaults.ignoreSpectator()), + nonBlank(section.getString("bypass-permission"), defaults.bypassPermission()), + Math.max(0.1, section.getDouble("inside-buffer", defaults.insideBuffer())), + Math.max(0.0, section.getDouble("bounce-strength", defaults.bounceStrength())), + Math.max(0.0, section.getDouble("vertical-boost", defaults.verticalBoost())), + section.getBoolean("protect-mounted-entities", defaults.protectMountedEntities()), + Math.max(0.0, section.getDouble("mounted-bounce-strength", defaults.mountedBounceStrength())), + Math.max(0.0, section.getDouble("mounted-vertical-boost", defaults.mountedVerticalBoost())), + Math.max(0.0, section.getDouble("max-outside-distance-before-teleport", defaults.maxOutsideDistanceBeforeTeleport())), + Math.max(0L, TimeUtil.parseDurationMillis(section.getString("cooldown", "900ms"), defaults.cooldownMillis())), + section.getString("message", defaults.message()), + section.getString("action-bar", defaults.actionBar()), + section.getString("sound", defaults.sound()), + (float) section.getDouble("volume", defaults.volume()), + (float) section.getDouble("pitch", defaults.pitch()), + section.getBoolean("particles.enabled", defaults.particlesEnabled()), + section.getString("particles.particle", defaults.particle()), + Math.max(0, section.getInt("particles.count", defaults.particleCount())), + Math.max(0.0, section.getDouble("particles.offset", defaults.particleOffset())), + Math.max(0.0, section.getDouble("particles.speed", defaults.particleSpeed())), + Math.max(1L, section.getLong("particles.interval-ticks", defaults.particleIntervalTicks())), + Math.max(8.0, section.getDouble("particles.view-distance", defaults.particleViewDistance())), + Math.max(1.0, section.getDouble("particles.spacing", defaults.particleSpacing())) + ); + } + + private Map> integerCommandMap(ConfigurationSection section) { + if (section == null) { + return Map.of(); + } + + Map> commands = new LinkedHashMap<>(); + for (String key : section.getKeys(false)) { + try { + int milestone = Integer.parseInt(key); + commands.put(milestone, List.copyOf(section.getStringList(key))); + } catch (NumberFormatException ex) { + plugin.getLogger().warning("Ignoring non-numeric milestone key '" + key + "' at " + section.getCurrentPath() + "."); + } + } + return Collections.unmodifiableMap(commands); + } + + private Map> stringCommandMap(ConfigurationSection section) { + if (section == null) { + return Map.of(); + } + + Map> commands = new LinkedHashMap<>(); + for (String key : section.getKeys(false)) { + commands.put(key.toLowerCase(Locale.ROOT), List.copyOf(section.getStringList(key))); + } + return Collections.unmodifiableMap(commands); + } + + private double positiveDouble(ConfigurationSection section, String path, double fallback) { + double value = section.getDouble(path, fallback); + if (value <= 0.0) { + plugin.getLogger().warning("Invalid non-positive value at " + section.getCurrentPath() + "." + path + "; using " + fallback + "."); + return fallback; + } + return value; + } + + private double parseDouble(Object value, double fallback) { + if (value instanceof Number number) { + return number.doubleValue(); + } + if (value == null) { + return fallback; + } + try { + return Double.parseDouble(String.valueOf(value)); + } catch (NumberFormatException ex) { + return fallback; + } + } + + private String mapString(Map values, String key, String fallback) { + Object value = values.get(key); + return value == null ? fallback : String.valueOf(value); + } + + private String nonBlank(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } + + private List stringList(ConfigurationSection section, String path) { + if (section == null) { + return List.of(); + } + return List.copyOf(section.getStringList(path)); + } + + public FileConfiguration config() { + return config; + } + + public YamlConfiguration messages() { + return messages; + } + + public Map worlds() { + return worlds; + } + + public WorldRule world(String key) { + if (key == null) { + return null; + } + return worlds.get(key.toLowerCase(Locale.ROOT)); + } + + public boolean dryRun() { + return config.getBoolean("settings.dry-run", false); + } + + public boolean debug() { + return config.getBoolean("settings.debug", false); + } + + public boolean applyBordersOnStartup() { + return config.getBoolean("settings.apply-borders-on-startup", true); + } + + public long pollIntervalTicks() { + return Math.max(1L, config.getLong("settings.poll-interval-seconds", 30L)) * 20L; + } + + public int maxCatchupExpansionsPerWorld() { + return Math.max(1, config.getInt("settings.max-catchup-expansions-per-world", 20)); + } + + public String stateFileName() { + return config.getString("state.file-name", "state.yml"); + } + + public long saveIntervalTicks() { + return Math.max(20L, config.getLong("state.save-interval-seconds", 300L) * 20L); + } + + public long legacyUsersMinimumPlaytimeMillis() { + return TimeUtil.parseDurationMillis(config.getString("legacy-users.minimum-playtime", "1h"), 3_600_000L); + } + + public boolean backupStateOnSave() { + return config.getBoolean("state.backup-on-save", true); + } + + public int backupKeep() { + return Math.max(0, config.getInt("state.backup-keep", 10)); + } + + public boolean historyEnabled() { + return config.getBoolean("history.enabled", true); + } + + public String historyFileName() { + return config.getString("history.file-name", "history.log"); + } + + public boolean guiEnabled() { + return config.getBoolean("gui.enabled", true); + } + + public String guiTitle() { + return config.getString("gui.title", "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛA4A2A&lʙB6B35&lᴀ&#A8873F&lɢ&#D4AF37&lᴍ&#B9C63F&lᴄ &8| &#D4AF37&lBorders"); + } + + public boolean hooksEnabled() { + return config.getBoolean("command-hooks.enabled", true); + } + + public WorldRule.MilestoneRewardSettings globalMilestoneRewards() { + return globalMilestoneRewards; + } + + public List globalBeforeCommands() { + return config.getStringList("command-hooks.before-expansion"); + } + + public List globalAfterCommands() { + return config.getStringList("command-hooks.after-expansion"); + } + + public boolean webhookEnabled() { + return config.getBoolean("webhook.enabled", false); + } + + public String webhookUrl() { + return config.getString("webhook.url", ""); + } + + public String webhookContent() { + return config.getString("webhook.content", ""); + } + + public int webhookTimeoutSeconds() { + return Math.max(1, config.getInt("webhook.timeout-seconds", 8)); + } + + public void debug(String message) { + if (debug()) { + plugin.getLogger().log(Level.INFO, "[debug] " + message); + } + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/config/MessageManager.java b/src/main/java/com/dirtsmp/dirtsmp/config/MessageManager.java new file mode 100644 index 0000000..f1763eb --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/config/MessageManager.java @@ -0,0 +1,207 @@ +package com.dirtsmp.dirtsmp.config; + +import com.dirtsmp.dirtsmp.model.ExpansionReason; +import com.dirtsmp.dirtsmp.model.PhaseDefinition; +import com.dirtsmp.dirtsmp.model.WorldRule; +import com.dirtsmp.dirtsmp.service.TimeUtil; +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.kyori.adventure.title.Title; +import org.bukkit.Bukkit; +import org.bukkit.Sound; +import org.bukkit.SoundCategory; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public final class MessageManager { + private final ConfigManager configManager; + private final MiniMessage miniMessage = MiniMessage.miniMessage(); + private final LegacyComponentSerializer legacySerializer = LegacyComponentSerializer.builder() + .character('&') + .hexColors() + .build(); + + public MessageManager(ConfigManager configManager) { + this.configManager = configManager; + } + + public void send(CommandSender sender, String path) { + send(sender, path, Map.of()); + } + + public void send(CommandSender sender, String path, Map placeholders) { + String message = configManager.messages().getString(path, ""); + if (message == null || message.isBlank()) { + return; + } + sender.sendMessage(component(message, placeholders)); + } + + public void sendRaw(CommandSender sender, String raw, Map placeholders) { + if (raw == null || raw.isBlank()) { + return; + } + sender.sendMessage(component(raw, placeholders)); + } + + public void sendList(CommandSender sender, String path, Map placeholders) { + for (String line : configManager.messages().getStringList(path)) { + sender.sendMessage(component(line, placeholders)); + } + } + + public Component component(String raw) { + return component(raw, Map.of()); + } + + public Component component(String raw, Map placeholders) { + String rendered = applyPlaceholders(raw, placeholders); + try { + if (usesLegacyFormatting(rendered)) { + return legacySerializer.deserialize(rendered); + } + return miniMessage.deserialize(rendered); + } catch (RuntimeException ex) { + return Component.text(rendered.replaceAll("<[^>]*>", "").replaceAll("&[#0-9A-Fa-fK-Ok-orR]", "")); + } + } + + public String applyPlaceholders(String raw, Map placeholders) { + String rendered = raw == null ? "" : raw; + rendered = rendered.replace("{prefix}", configManager.messages().getString("prefix", "")); + for (Map.Entry entry : placeholders.entrySet()) { + rendered = rendered.replace("{" + entry.getKey() + "}", entry.getValue() == null ? "" : entry.getValue()); + } + return rendered; + } + + public void announceExpansion(WorldRule rule, double oldSize, double newSize, ExpansionReason reason, String actor, PhaseDefinition phase) { + if (!rule.announcements().enabled()) { + return; + } + + Map placeholders = basePlaceholders(rule, oldSize, newSize, reason, actor, phase); + String message = firstNonBlank( + phase == null ? "" : phase.message(), + rule.announcements().message(), + configManager.messages().getString("expansion.default-message", "") + ); + String title = firstNonBlank( + phase == null ? "" : phase.title(), + rule.announcements().title(), + configManager.messages().getString("expansion.default-title", "") + ); + String subtitle = firstNonBlank( + phase == null ? "" : phase.subtitle(), + rule.announcements().subtitle(), + configManager.messages().getString("expansion.default-subtitle", "") + ); + String actionBar = firstNonBlank( + rule.announcements().actionBar(), + configManager.messages().getString("expansion.default-action-bar", "") + ); + + List recipients = Bukkit.getOnlinePlayers().stream() + .filter(player -> !rule.announcements().worldOnly() || player.getWorld().getName().equals(rule.worldName())) + .toList(); + + Component chat = component(message, placeholders); + for (Player player : recipients) { + player.sendMessage(chat); + if (!title.isBlank() || !subtitle.isBlank()) { + player.showTitle(Title.title( + component(title, placeholders), + component(subtitle, placeholders), + Title.Times.times(Duration.ofMillis(500), Duration.ofSeconds(4), Duration.ofMillis(800)) + )); + } + if (!actionBar.isBlank()) { + player.sendActionBar(component(actionBar, placeholders)); + } + playSound(player, rule.announcements().sound(), rule.announcements().volume(), rule.announcements().pitch()); + } + Bukkit.getConsoleSender().sendMessage(chat); + } + + public void announceReminder(WorldRule rule, long timeLeftMillis) { + if (!rule.reminders().enabled()) { + return; + } + + Map placeholders = new HashMap<>(); + placeholders.put("key", rule.key()); + placeholders.put("world", rule.worldName()); + placeholders.put("time_left", TimeUtil.formatDuration(timeLeftMillis)); + + String message = firstNonBlank( + rule.reminders().message(), + configManager.messages().getString("reminders.default-message", "") + ); + String actionBar = firstNonBlank( + rule.reminders().actionBar(), + configManager.messages().getString("reminders.default-action-bar", "") + ); + + Component chat = component(message, placeholders); + for (Player player : Bukkit.getOnlinePlayers()) { + player.sendMessage(chat); + if (!actionBar.isBlank()) { + player.sendActionBar(component(actionBar, placeholders)); + } + playSound(player, rule.reminders().sound(), rule.reminders().volume(), rule.reminders().pitch()); + } + Bukkit.getConsoleSender().sendMessage(chat); + } + + public Map basePlaceholders(WorldRule rule, double oldSize, double newSize, ExpansionReason reason, String actor, PhaseDefinition phase) { + Map placeholders = new HashMap<>(); + placeholders.put("key", rule.key()); + placeholders.put("world", rule.worldName()); + placeholders.put("old_size", TimeUtil.formatSize(oldSize)); + placeholders.put("new_size", TimeUtil.formatSize(newSize)); + placeholders.put("max_size", TimeUtil.formatSize(rule.maxSize())); + placeholders.put("reason", reason.name().toLowerCase(Locale.ROOT).replace('_', ' ')); + placeholders.put("actor", actor == null ? "console" : actor); + placeholders.put("phase", phase == null ? "none" : String.valueOf(phase.index())); + placeholders.put("phase_name", phase == null ? "none" : phase.name()); + return placeholders; + } + + public Map withBasePlaceholders(WorldRule rule, double oldSize, double newSize, ExpansionReason reason, String actor, PhaseDefinition phase, Map extra) { + Map placeholders = basePlaceholders(rule, oldSize, newSize, reason, actor, phase); + placeholders.putAll(extra); + return placeholders; + } + + @SuppressWarnings({"deprecation", "removal"}) + private void playSound(Player player, String soundName, float volume, float pitch) { + if (soundName == null || soundName.isBlank()) { + return; + } + try { + Sound sound = Sound.valueOf(soundName.trim().toUpperCase(Locale.ROOT)); + player.playSound(player.getLocation(), sound, SoundCategory.MASTER, volume, pitch); + } catch (IllegalArgumentException ignored) { + // Invalid sounds are config mistakes; they are non-fatal and already visible in config. + } + } + + private String firstNonBlank(String... values) { + for (String value : values) { + if (value != null && !value.isBlank()) { + return value; + } + } + return ""; + } + + private boolean usesLegacyFormatting(String rendered) { + return rendered.matches("(?s).*&(?:#[0-9A-Fa-f]{6}|[0-9A-Fa-fK-Ok-orR]).*"); + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/gui/AdminGuiManager.java b/src/main/java/com/dirtsmp/dirtsmp/gui/AdminGuiManager.java new file mode 100644 index 0000000..7dbdfd2 --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/gui/AdminGuiManager.java @@ -0,0 +1,202 @@ +package com.dirtsmp.dirtsmp.gui; + +import com.dirtsmp.dirtsmp.DirtSMPPlugin; +import com.dirtsmp.dirtsmp.config.ConfigManager; +import com.dirtsmp.dirtsmp.config.MessageManager; +import com.dirtsmp.dirtsmp.model.ExpansionReason; +import com.dirtsmp.dirtsmp.model.ExpansionResult; +import com.dirtsmp.dirtsmp.model.PhaseDefinition; +import com.dirtsmp.dirtsmp.model.WorldRule; +import com.dirtsmp.dirtsmp.service.BorderManager; +import com.dirtsmp.dirtsmp.service.TimeUtil; +import com.dirtsmp.dirtsmp.service.TriggerEvaluator; +import com.dirtsmp.dirtsmp.state.StateManager; +import com.dirtsmp.dirtsmp.state.WorldProgress; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.OptionalDouble; +import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +public final class AdminGuiManager implements Listener { + private final DirtSMPPlugin plugin; + private final ConfigManager configManager; + private final MessageManager messageManager; + private final StateManager stateManager; + private final BorderManager borderManager; + private final TriggerEvaluator triggerEvaluator; + + public AdminGuiManager(DirtSMPPlugin plugin, ConfigManager configManager, MessageManager messageManager, StateManager stateManager, BorderManager borderManager, TriggerEvaluator triggerEvaluator) { + this.plugin = plugin; + this.configManager = configManager; + this.messageManager = messageManager; + this.stateManager = stateManager; + this.borderManager = borderManager; + this.triggerEvaluator = triggerEvaluator; + } + + public void open(Player player) { + GuiHolder holder = new GuiHolder(); + Inventory inventory = Bukkit.createInventory(holder, 27, messageManager.component(configManager.guiTitle())); + holder.inventory = inventory; + + int[] slots = {10, 11, 12, 13, 14, 15, 16}; + int index = 0; + for (WorldRule rule : configManager.worlds().values()) { + if (index >= slots.length) { + break; + } + int slot = slots[index++]; + inventory.setItem(slot, worldItem(rule)); + holder.worldBySlot.put(slot, rule.key()); + } + + inventory.setItem(22, simpleItem(Material.COMPASS, configManager.messages().getString("gui.refresh", "&#B9C63FRefresh"))); + inventory.setItem(26, simpleItem(Material.BARRIER, configManager.messages().getString("gui.close", "&cClose"))); + player.openInventory(inventory); + } + + public void closeOpenInventories() { + for (Player player : Bukkit.getOnlinePlayers()) { + if (player.getOpenInventory().getTopInventory().getHolder() instanceof GuiHolder) { + player.closeInventory(); + } + } + } + + @EventHandler + public void onClick(InventoryClickEvent event) { + if (!(event.getInventory().getHolder() instanceof GuiHolder holder)) { + return; + } + event.setCancelled(true); + if (!(event.getWhoClicked() instanceof Player player)) { + return; + } + + int slot = event.getRawSlot(); + if (slot == 26) { + player.closeInventory(); + return; + } + if (slot == 22) { + open(player); + return; + } + + String key = holder.worldBySlot.get(slot); + if (key == null) { + return; + } + WorldRule rule = configManager.world(key); + if (rule == null) { + return; + } + + if (event.isLeftClick()) { + if (!player.hasPermission("dirtsmp.admin") && !player.hasPermission("dirtsmp.expand")) { + messageManager.send(player, "commands.no-permission"); + return; + } + ExpansionResult result = borderManager.expand(rule, ExpansionReason.MANUAL, player.getName(), true); + if (!result.success()) { + messageManager.send(player, "commands.expand-failed", Map.of("world", rule.worldName(), "reason", result.reason())); + } + open(player); + return; + } + + if (event.isRightClick()) { + WorldProgress progress = stateManager.progress(rule.key()); + if (progress.paused()) { + if (!player.hasPermission("dirtsmp.admin") && !player.hasPermission("dirtsmp.resume")) { + messageManager.send(player, "commands.no-permission"); + return; + } + borderManager.resume(rule); + messageManager.send(player, "commands.resumed", Map.of("world", rule.worldName())); + } else { + if (!player.hasPermission("dirtsmp.admin") && !player.hasPermission("dirtsmp.pause")) { + messageManager.send(player, "commands.no-permission"); + return; + } + borderManager.pause(rule); + messageManager.send(player, "commands.paused", Map.of("world", rule.worldName())); + } + open(player); + } + } + + private ItemStack worldItem(WorldRule rule) { + WorldProgress progress = stateManager.progress(rule.key()); + Material material = materialFor(rule, progress); + ItemStack item = new ItemStack(material); + ItemMeta meta = item.getItemMeta(); + Map placeholders = placeholders(rule, progress); + meta.displayName(messageManager.component(configManager.messages().getString("gui.world-name", "&#D4AF37&l{key}"), placeholders)); + List lore = configManager.messages().getStringList("gui.world-lore").stream() + .map(line -> messageManager.component(line, placeholders)) + .toList(); + meta.lore(lore); + item.setItemMeta(meta); + return item; + } + + private ItemStack simpleItem(Material material, String name) { + ItemStack item = new ItemStack(material); + ItemMeta meta = item.getItemMeta(); + meta.displayName(messageManager.component(name)); + item.setItemMeta(meta); + return item; + } + + private Material materialFor(WorldRule rule, WorldProgress progress) { + if (progress.paused()) { + return Material.REDSTONE_BLOCK; + } + String worldName = rule.worldName().toLowerCase(); + if (worldName.contains("the_end") || worldName.contains("end")) { + return Material.END_STONE; + } + if (worldName.contains("nether")) { + return Material.NETHERRACK; + } + return Material.GRASS_BLOCK; + } + + private Map placeholders(WorldRule rule, WorldProgress progress) { + OptionalDouble next = borderManager.nextSize(rule, progress); + PhaseDefinition phase = borderManager.currentPhase(rule, progress); + return Map.of( + "key", rule.key(), + "world", rule.worldName(), + "enabled", String.valueOf(rule.enabled()), + "size", TimeUtil.formatSize(borderManager.displaySize(rule, progress)), + "next_size", next.isPresent() ? TimeUtil.formatSize(next.getAsDouble()) : "max", + "border_mode", rule.borderMode().name(), + "trigger", triggerEvaluator.describeTrigger(rule, progress), + "phase", phase == null ? "none" : phase.name(), + "paused", String.valueOf(progress.paused()) + ); + } + + private static final class GuiHolder implements InventoryHolder { + private final Map worldBySlot = new HashMap<>(); + private Inventory inventory; + + @Override + public Inventory getInventory() { + return inventory; + } + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/hook/DirtSMPPlaceholderExpansion.java b/src/main/java/com/dirtsmp/dirtsmp/hook/DirtSMPPlaceholderExpansion.java new file mode 100644 index 0000000..f26526e --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/hook/DirtSMPPlaceholderExpansion.java @@ -0,0 +1,117 @@ +package com.dirtsmp.dirtsmp.hook; + +import com.dirtsmp.dirtsmp.DirtSMPPlugin; +import com.dirtsmp.dirtsmp.model.PhaseDefinition; +import com.dirtsmp.dirtsmp.model.WorldRule; +import com.dirtsmp.dirtsmp.service.TimeUtil; +import com.dirtsmp.dirtsmp.state.WorldProgress; +import java.util.Locale; +import java.util.OptionalDouble; +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@SuppressWarnings("deprecation") +public final class DirtSMPPlaceholderExpansion extends PlaceholderExpansion { + private final DirtSMPPlugin plugin; + + public DirtSMPPlaceholderExpansion(DirtSMPPlugin plugin) { + this.plugin = plugin; + } + + @Override + public @NotNull String getIdentifier() { + return "dirtsmp"; + } + + @Override + public @NotNull String getAuthor() { + return "DirtSMP"; + } + + @Override + public @NotNull String getVersion() { + return plugin.getDescription().getVersion(); + } + + @Override + public boolean persist() { + return true; + } + + @Override + public @Nullable String onRequest(OfflinePlayer player, @NotNull String params) { + String lower = params.toLowerCase(Locale.ROOT); + if (lower.startsWith("current_size")) { + WorldRule rule = ruleFor(lower, "current_size"); + return rule == null ? "" : TimeUtil.formatSize(plugin.borderManager().displaySize(rule, progress(rule))); + } + if (lower.startsWith("next_size")) { + WorldRule rule = ruleFor(lower, "next_size"); + if (rule == null) { + return ""; + } + OptionalDouble next = plugin.borderManager().nextSize(rule, progress(rule)); + return next.isPresent() ? TimeUtil.formatSize(next.getAsDouble()) : "max"; + } + if (lower.startsWith("next_expansion_time") || lower.startsWith("next_time")) { + String prefix = lower.startsWith("next_expansion_time") ? "next_expansion_time" : "next_time"; + WorldRule rule = ruleFor(lower, prefix); + if (rule == null) { + return ""; + } + WorldProgress progress = progress(rule); + if (!rule.triggers().mode().usesTime() || progress.lastExpansionAt() <= 0L) { + return "manual"; + } + long next = progress.lastExpansionAt() + rule.triggers().timeIntervalMillis(); + return next <= System.currentTimeMillis() ? "due" : TimeUtil.formatDuration(next - System.currentTimeMillis()); + } + if (lower.startsWith("phase")) { + WorldRule rule = ruleFor(lower, "phase"); + if (rule == null) { + return ""; + } + PhaseDefinition phase = plugin.borderManager().currentPhase(rule, progress(rule)); + return phase == null ? "none" : phase.name(); + } + if (lower.startsWith("expansion_count")) { + WorldRule rule = ruleFor(lower, "expansion_count"); + return rule == null ? "" : String.valueOf(progress(rule).expansionCount()); + } + if (lower.startsWith("unique_progress")) { + WorldRule rule = ruleFor(lower, "unique_progress"); + if (rule == null) { + return ""; + } + WorldProgress progress = progress(rule); + int needed = progress.uniquePlayersAtLastExpansion() + rule.triggers().uniquePlayersEvery(); + return plugin.playerStatsManager().uniquePlayerCount() + "/" + needed; + } + if (lower.startsWith("paused")) { + WorldRule rule = ruleFor(lower, "paused"); + return rule == null ? "" : String.valueOf(progress(rule).paused()); + } + if (lower.startsWith("border_mode")) { + WorldRule rule = ruleFor(lower, "border_mode"); + return rule == null ? "" : rule.borderMode().name(); + } + return null; + } + + private WorldRule ruleFor(String params, String prefix) { + if (params.equals(prefix)) { + return plugin.configManager().worlds().values().stream().findFirst().orElse(null); + } + String key = params.substring(prefix.length()); + if (key.startsWith("_")) { + key = key.substring(1); + } + return plugin.configManager().world(key); + } + + private WorldProgress progress(WorldRule rule) { + return plugin.stateManager().progress(rule.key()); + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/hook/PlaceholderHook.java b/src/main/java/com/dirtsmp/dirtsmp/hook/PlaceholderHook.java new file mode 100644 index 0000000..5f7c1b5 --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/hook/PlaceholderHook.java @@ -0,0 +1,26 @@ +package com.dirtsmp.dirtsmp.hook; + +import com.dirtsmp.dirtsmp.DirtSMPPlugin; + +public final class PlaceholderHook { + private final DirtSMPPlugin plugin; + private DirtSMPPlaceholderExpansion expansion; + + public PlaceholderHook(DirtSMPPlugin plugin) { + this.plugin = plugin; + } + + public void register() { + expansion = new DirtSMPPlaceholderExpansion(plugin); + if (expansion.register()) { + plugin.getLogger().info("Registered PlaceholderAPI expansion."); + } + } + + public void unregister() { + if (expansion != null) { + expansion.unregister(); + expansion = null; + } + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/listener/PlayerListener.java b/src/main/java/com/dirtsmp/dirtsmp/listener/PlayerListener.java new file mode 100644 index 0000000..47efd23 --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/listener/PlayerListener.java @@ -0,0 +1,47 @@ +package com.dirtsmp.dirtsmp.listener; + +import com.dirtsmp.dirtsmp.DirtSMPPlugin; +import com.dirtsmp.dirtsmp.config.ConfigManager; +import com.dirtsmp.dirtsmp.model.WorldRule; +import com.dirtsmp.dirtsmp.service.BorderManager; +import com.dirtsmp.dirtsmp.state.PlayerStatsManager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.world.WorldLoadEvent; + +public final class PlayerListener implements Listener { + private final DirtSMPPlugin plugin; + private final PlayerStatsManager playerStatsManager; + private final BorderManager borderManager; + private final ConfigManager configManager; + + public PlayerListener(DirtSMPPlugin plugin, PlayerStatsManager playerStatsManager, BorderManager borderManager, ConfigManager configManager) { + this.plugin = plugin; + this.playerStatsManager = playerStatsManager; + this.borderManager = borderManager; + this.configManager = configManager; + } + + @EventHandler + public void onJoin(PlayerJoinEvent event) { + playerStatsManager.markSeen(event.getPlayer()); + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + playerStatsManager.markSeen(event.getPlayer()); + playerStatsManager.saveQuietly(); + } + + @EventHandler + public void onWorldLoad(WorldLoadEvent event) { + for (WorldRule rule : configManager.worlds().values()) { + if (rule.enabled() && rule.worldName().equals(event.getWorld().getName())) { + plugin.getLogger().info("Applying DirtSMP border settings to newly loaded world '" + rule.worldName() + "'."); + borderManager.initializeRule(rule); + } + } + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/model/BorderMode.java b/src/main/java/com/dirtsmp/dirtsmp/model/BorderMode.java new file mode 100644 index 0000000..60c4676 --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/model/BorderMode.java @@ -0,0 +1,17 @@ +package com.dirtsmp.dirtsmp.model; + +public enum BorderMode { + WORLD_BORDER, + SOFT_BORDER; + + public static BorderMode from(String value) { + if (value == null || value.isBlank()) { + return WORLD_BORDER; + } + try { + return BorderMode.valueOf(value.trim().toUpperCase()); + } catch (IllegalArgumentException ignored) { + return WORLD_BORDER; + } + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/model/CatchUpMode.java b/src/main/java/com/dirtsmp/dirtsmp/model/CatchUpMode.java new file mode 100644 index 0000000..e1f28c3 --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/model/CatchUpMode.java @@ -0,0 +1,18 @@ +package com.dirtsmp.dirtsmp.model; + +public enum CatchUpMode { + NONE, + ONE, + ALL; + + public static CatchUpMode from(String value) { + if (value == null || value.isBlank()) { + return ONE; + } + try { + return CatchUpMode.valueOf(value.trim().toUpperCase()); + } catch (IllegalArgumentException ignored) { + return ONE; + } + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/model/ExpansionReason.java b/src/main/java/com/dirtsmp/dirtsmp/model/ExpansionReason.java new file mode 100644 index 0000000..4c30547 --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/model/ExpansionReason.java @@ -0,0 +1,22 @@ +package com.dirtsmp.dirtsmp.model; + +public enum ExpansionReason { + TIME, + UNIQUE_PLAYERS, + ONLINE_PLAYERS, + ACTIVE_PLAYERS, + HYBRID, + CATCH_UP, + MANUAL, + SET_SIZE, + RESET; + + public boolean automatic() { + return this == TIME + || this == UNIQUE_PLAYERS + || this == ONLINE_PLAYERS + || this == ACTIVE_PLAYERS + || this == HYBRID + || this == CATCH_UP; + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/model/ExpansionResult.java b/src/main/java/com/dirtsmp/dirtsmp/model/ExpansionResult.java new file mode 100644 index 0000000..ba41f31 --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/model/ExpansionResult.java @@ -0,0 +1,17 @@ +package com.dirtsmp.dirtsmp.model; + +public record ExpansionResult( + boolean success, + String reason, + double oldSize, + double newSize, + PhaseDefinition phase +) { + public static ExpansionResult success(double oldSize, double newSize, PhaseDefinition phase) { + return new ExpansionResult(true, "", oldSize, newSize, phase); + } + + public static ExpansionResult failure(String reason) { + return new ExpansionResult(false, reason, 0.0, 0.0, null); + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/model/GrowthMode.java b/src/main/java/com/dirtsmp/dirtsmp/model/GrowthMode.java new file mode 100644 index 0000000..b21ed23 --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/model/GrowthMode.java @@ -0,0 +1,17 @@ +package com.dirtsmp.dirtsmp.model; + +public enum GrowthMode { + INCREMENTAL, + PHASE; + + public static GrowthMode from(String value) { + if (value == null || value.isBlank()) { + return INCREMENTAL; + } + try { + return GrowthMode.valueOf(value.trim().toUpperCase()); + } catch (IllegalArgumentException ignored) { + return INCREMENTAL; + } + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/model/LegacyAccessReport.java b/src/main/java/com/dirtsmp/dirtsmp/model/LegacyAccessReport.java new file mode 100644 index 0000000..62f99d3 --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/model/LegacyAccessReport.java @@ -0,0 +1,11 @@ +package com.dirtsmp.dirtsmp.model; + +public record LegacyAccessReport( + double requiredSize, + int includedLocations, + String farthestLocationName +) { + public boolean hasLocations() { + return includedLocations > 0; + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/model/PhaseDefinition.java b/src/main/java/com/dirtsmp/dirtsmp/model/PhaseDefinition.java new file mode 100644 index 0000000..1df1475 --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/model/PhaseDefinition.java @@ -0,0 +1,11 @@ +package com.dirtsmp.dirtsmp.model; + +public record PhaseDefinition( + int index, + String name, + double size, + String message, + String title, + String subtitle +) { +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/model/TriggerDecision.java b/src/main/java/com/dirtsmp/dirtsmp/model/TriggerDecision.java new file mode 100644 index 0000000..1ada472 --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/model/TriggerDecision.java @@ -0,0 +1,11 @@ +package com.dirtsmp.dirtsmp.model; + +public record TriggerDecision( + boolean due, + ExpansionReason reason, + String description +) { + public static TriggerDecision none(String description) { + return new TriggerDecision(false, ExpansionReason.MANUAL, description); + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/model/TriggerMode.java b/src/main/java/com/dirtsmp/dirtsmp/model/TriggerMode.java new file mode 100644 index 0000000..8d8fd1b --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/model/TriggerMode.java @@ -0,0 +1,36 @@ +package com.dirtsmp.dirtsmp.model; + +public enum TriggerMode { + MANUAL, + TIME, + UNIQUE_PLAYERS, + ONLINE_PLAYERS, + ACTIVE_PLAYERS, + TIME_OR_UNIQUE_PLAYERS, + TIME_AND_UNIQUE_PLAYERS, + TIME_OR_ONLINE_PLAYERS, + TIME_AND_ONLINE_PLAYERS, + TIME_OR_ACTIVE_PLAYERS, + TIME_AND_ACTIVE_PLAYERS; + + public static TriggerMode from(String value) { + if (value == null || value.isBlank()) { + return MANUAL; + } + try { + return TriggerMode.valueOf(value.trim().toUpperCase()); + } catch (IllegalArgumentException ignored) { + return MANUAL; + } + } + + public boolean usesTime() { + return this == TIME + || this == TIME_OR_UNIQUE_PLAYERS + || this == TIME_AND_UNIQUE_PLAYERS + || this == TIME_OR_ONLINE_PLAYERS + || this == TIME_AND_ONLINE_PLAYERS + || this == TIME_OR_ACTIVE_PLAYERS + || this == TIME_AND_ACTIVE_PLAYERS; + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/model/TriggerSettings.java b/src/main/java/com/dirtsmp/dirtsmp/model/TriggerSettings.java new file mode 100644 index 0000000..b1f9d26 --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/model/TriggerSettings.java @@ -0,0 +1,17 @@ +package com.dirtsmp.dirtsmp.model; + +public record TriggerSettings( + TriggerMode mode, + long timeIntervalMillis, + int uniquePlayersEvery, + int onlinePlayersThreshold, + int activePlayersThreshold, + long activeWindowMillis, + boolean excludeVanished, + long playerTriggerCooldownMillis, + CatchUpMode catchUpMode +) { + public boolean manualOnly() { + return mode == TriggerMode.MANUAL; + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/model/WorldRule.java b/src/main/java/com/dirtsmp/dirtsmp/model/WorldRule.java new file mode 100644 index 0000000..ceb98df --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/model/WorldRule.java @@ -0,0 +1,180 @@ +package com.dirtsmp.dirtsmp.model; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public record WorldRule( + String key, + boolean enabled, + String worldName, + double centerX, + double centerZ, + double startingSize, + BorderMode borderMode, + GrowthMode growthMode, + double growthAmount, + double maxSize, + long transitionSeconds, + boolean manualOnly, + boolean importCurrentBorder, + boolean noShrinkProtection, + boolean enforceMaxSize, + AnnouncementSettings announcements, + ReminderSettings reminders, + TriggerSettings triggers, + List phases, + MilestoneRewardSettings milestoneRewards, + LegacyAccessSettings legacyAccess, + SoftBorderSettings softBorder, + List beforeCommands, + List afterCommands +) { + public double initialSize() { + if (growthMode == GrowthMode.PHASE && !phases.isEmpty()) { + return phases.getFirst().size(); + } + return startingSize; + } + + public Optional phase(int index) { + if (index < 0 || index >= phases.size()) { + return Optional.empty(); + } + return Optional.of(phases.get(index)); + } + + public Optional nextPhase(int currentIndex) { + return phase(currentIndex + 1); + } + + public int phaseIndexForSize(double size) { + int index = phases.isEmpty() ? -1 : 0; + for (PhaseDefinition phase : phases) { + if (size + 0.0001 >= phase.size()) { + index = phase.index(); + } + } + return index; + } + + public record AnnouncementSettings( + boolean enabled, + boolean worldOnly, + String message, + String title, + String subtitle, + String actionBar, + String sound, + float volume, + float pitch + ) { + } + + public record ReminderSettings( + boolean enabled, + List beforeMillis, + String message, + String actionBar, + String sound, + float volume, + float pitch + ) { + } + + public record MilestoneRewardSettings( + boolean enabled, + List everyExpansionCommands, + Map> expansionCountCommands, + Map> phaseIndexCommands, + Map> phaseNameCommands + ) { + public static MilestoneRewardSettings empty() { + return new MilestoneRewardSettings(false, List.of(), Map.of(), Map.of(), Map.of()); + } + } + + public record LegacyAccessSettings( + boolean enabled, + boolean includeCurrentBorder, + boolean includeOnlinePlayers, + boolean includeOfflineLastLocations, + boolean includeRespawnLocations, + boolean playerLocationsRequireLegacyUser, + boolean reconcileOnStartup, + boolean allowStartAboveMaxSize, + double padding, + List locations + ) { + public static LegacyAccessSettings disabled() { + return new LegacyAccessSettings(false, false, false, false, false, false, false, false, 0.0, List.of()); + } + } + + public record LegacyLocation( + String name, + double x, + double z + ) { + } + + public record SoftBorderSettings( + boolean releaseVanillaBorder, + double vanillaBorderSize, + boolean ignoreCreative, + boolean ignoreSpectator, + String bypassPermission, + double insideBuffer, + double bounceStrength, + double verticalBoost, + boolean protectMountedEntities, + double mountedBounceStrength, + double mountedVerticalBoost, + double maxOutsideDistanceBeforeTeleport, + long cooldownMillis, + String message, + String actionBar, + String sound, + float volume, + float pitch, + boolean particlesEnabled, + String particle, + int particleCount, + double particleOffset, + double particleSpeed, + long particleIntervalTicks, + double particleViewDistance, + double particleSpacing + ) { + public static SoftBorderSettings defaults() { + return new SoftBorderSettings( + true, + 59_999_968.0, + false, + true, + "dirtsmp.bypass.softborder", + 1.5, + 1.85, + 0.35, + true, + 1.15, + 0.12, + 24.0, + 900L, + "{prefix}&#D4AF37&lThe DirtbagMC border &7throws you back.", + "&#D4AF37&lBorder reached &8| &7turn back", + "ENTITY_SLIME_JUMP", + 0.65f, + 0.65f, + true, + "DUST", + 3, + 0.05, + 0.0, + 10L, + 96.0, + 3.0 + ); + } + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/service/BorderManager.java b/src/main/java/com/dirtsmp/dirtsmp/service/BorderManager.java new file mode 100644 index 0000000..68f426f --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/service/BorderManager.java @@ -0,0 +1,408 @@ +package com.dirtsmp.dirtsmp.service; + +import com.dirtsmp.dirtsmp.DirtSMPPlugin; +import com.dirtsmp.dirtsmp.config.ConfigManager; +import com.dirtsmp.dirtsmp.config.MessageManager; +import com.dirtsmp.dirtsmp.model.BorderMode; +import com.dirtsmp.dirtsmp.model.ExpansionReason; +import com.dirtsmp.dirtsmp.model.ExpansionResult; +import com.dirtsmp.dirtsmp.model.GrowthMode; +import com.dirtsmp.dirtsmp.model.LegacyAccessReport; +import com.dirtsmp.dirtsmp.model.PhaseDefinition; +import com.dirtsmp.dirtsmp.model.WorldRule; +import com.dirtsmp.dirtsmp.state.PlayerStatsManager; +import com.dirtsmp.dirtsmp.state.StateManager; +import com.dirtsmp.dirtsmp.state.WorldProgress; +import java.util.OptionalDouble; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.World; +import org.bukkit.WorldBorder; + +public final class BorderManager { + private static final double EPSILON = 0.0001; + + private final DirtSMPPlugin plugin; + private final ConfigManager configManager; + private final MessageManager messageManager; + private final StateManager stateManager; + private final PlayerStatsManager playerStatsManager; + private final CommandHookManager commandHookManager; + private final WebhookNotifier webhookNotifier; + private final HistoryLogger historyLogger; + + public BorderManager( + DirtSMPPlugin plugin, + ConfigManager configManager, + MessageManager messageManager, + StateManager stateManager, + PlayerStatsManager playerStatsManager, + CommandHookManager commandHookManager, + WebhookNotifier webhookNotifier, + HistoryLogger historyLogger + ) { + this.plugin = plugin; + this.configManager = configManager; + this.messageManager = messageManager; + this.stateManager = stateManager; + this.playerStatsManager = playerStatsManager; + this.commandHookManager = commandHookManager; + this.webhookNotifier = webhookNotifier; + this.historyLogger = historyLogger; + } + + public void applyStartupRules() { + if (!configManager.applyBordersOnStartup()) { + return; + } + for (WorldRule rule : configManager.worlds().values()) { + if (rule.enabled()) { + initializeRule(rule); + } + } + stateManager.saveQuietly(); + } + + public ExpansionResult initializeRule(WorldRule rule) { + World world = Bukkit.getWorld(rule.worldName()); + if (world == null) { + plugin.getLogger().warning("Configured world '" + rule.worldName() + "' for key '" + rule.key() + "' is not loaded. Skipping border initialization."); + return ExpansionResult.failure("missing world"); + } + + WorldProgress progress = stateManager.progress(rule.key()); + WorldBorder border = world.getWorldBorder(); + if (rule.borderMode() == BorderMode.WORLD_BORDER) { + border.setCenter(rule.centerX(), rule.centerZ()); + } else if (rule.softBorder().releaseVanillaBorder()) { + releaseVanillaBorder(world, rule); + } + + if (!progress.initialized()) { + double size = rule.importCurrentBorder() ? border.getSize() : rule.initialSize(); + LegacyAccessReport legacyReport = legacyAccessReport(rule); + if (legacyReport.requiredSize() > size + EPSILON) { + plugin.getLogger().warning("Legacy access increased initial border for '" + rule.key() + "' from " + TimeUtil.formatSize(size) + " to " + TimeUtil.formatSize(legacyReport.requiredSize()) + " blocks to include " + legacyReport.includedLocations() + " known beta location(s). Farthest: " + legacyReport.farthestLocationName() + "."); + size = legacyReport.requiredSize(); + } + size = enforceInitialMax(rule, size, legacyReport); + if (legacyReport.requiredSize() > size + EPSILON) { + plugin.getLogger().warning("Legacy access for '" + rule.key() + "' needs " + TimeUtil.formatSize(legacyReport.requiredSize()) + " blocks, but max-size enforcement capped startup at " + TimeUtil.formatSize(size) + ". Increase max-size or set legacy-access.allow-start-above-max-size: true."); + } + progress.initialized(true); + progress.currentSize(size); + progress.currentPhaseIndex(rule.phaseIndexForSize(size)); + progress.expansionCount(0); + progress.lastExpansionAt(System.currentTimeMillis()); + progress.uniquePlayersAtLastExpansion(playerStatsManager.uniquePlayerCount()); + progress.clearReminders(); + applyBorder(world, rule, size, 0L); + plugin.getLogger().info("Initialized '" + rule.key() + "' border at " + TimeUtil.formatSize(size) + " blocks."); + return ExpansionResult.success(border.getSize(), size, currentPhase(rule, progress)); + } + + double target = progress.currentSize() > 0.0 ? progress.currentSize() : rule.initialSize(); + target = enforceMax(rule, target); + if (rule.borderMode() == BorderMode.WORLD_BORDER && rule.noShrinkProtection() && border.getSize() > target + EPSILON) { + plugin.getLogger().warning("No-shrink protection kept '" + rule.key() + "' at current server border size " + TimeUtil.formatSize(border.getSize()) + " instead of saved size " + TimeUtil.formatSize(target) + "."); + target = border.getSize(); + progress.currentSize(target); + progress.currentPhaseIndex(rule.phaseIndexForSize(target)); + } + if (rule.legacyAccess().enabled() && rule.legacyAccess().reconcileOnStartup()) { + LegacyAccessReport legacyReport = legacyAccessReport(rule); + if (legacyReport.requiredSize() > target + EPSILON) { + double adjusted = enforceInitialMax(rule, legacyReport.requiredSize(), legacyReport); + if (adjusted > target + EPSILON) { + plugin.getLogger().warning("Legacy access reconciled saved border for '" + rule.key() + "' from " + TimeUtil.formatSize(target) + " to " + TimeUtil.formatSize(adjusted) + " blocks to include known beta locations. Farthest: " + legacyReport.farthestLocationName() + "."); + target = adjusted; + progress.currentSize(target); + progress.currentPhaseIndex(rule.phaseIndexForSize(target)); + } + } + } + applyBorder(world, rule, target, 0L); + return ExpansionResult.success(border.getSize(), target, currentPhase(rule, progress)); + } + + public ExpansionResult expand(WorldRule rule, ExpansionReason reason, String actor, boolean force) { + if (!rule.enabled()) { + return ExpansionResult.failure("world disabled"); + } + + World world = Bukkit.getWorld(rule.worldName()); + if (world == null) { + return ExpansionResult.failure(configManager.messages().getString("expansion.missing-world", "missing world")); + } + + WorldProgress progress = stateManager.progress(rule.key()); + if (!progress.initialized()) { + initializeRule(rule); + } + if (reason.automatic() && progress.paused()) { + return ExpansionResult.failure(configManager.messages().getString("expansion.paused", "paused")); + } + if (reason.automatic() && (rule.manualOnly() || rule.triggers().manualOnly())) { + return ExpansionResult.failure(configManager.messages().getString("expansion.manual-only", "manual only")); + } + if (configManager.dryRun()) { + return ExpansionResult.failure(configManager.messages().getString("expansion.dry-run", "dry-run")); + } + + double oldSize = progress.currentSize() > 0.0 ? progress.currentSize() : world.getWorldBorder().getSize(); + OptionalDouble next = nextSize(rule, progress); + if (next.isEmpty()) { + return ExpansionResult.failure(configManager.messages().getString("expansion.max-reached", "max reached")); + } + double newSize = enforceMax(rule, next.getAsDouble()); + if (newSize <= oldSize + EPSILON && !force) { + return ExpansionResult.failure(configManager.messages().getString("expansion.max-reached", "max reached")); + } + + PhaseDefinition phase = phaseAfterExpansion(rule, progress, newSize); + commandHookManager.runBefore(rule, oldSize, newSize, reason, actor, phase); + applyBorder(world, rule, newSize, rule.transitionSeconds()); + updateProgressAfterSizeChange(rule, progress, newSize, reason); + stateManager.saveQuietly(); + historyLogger.record(rule, oldSize, newSize, reason, actor, phase); + messageManager.announceExpansion(rule, oldSize, newSize, reason, actor, phase); + webhookNotifier.notifyExpansion(rule, oldSize, newSize, reason, actor, phase); + commandHookManager.runAfter(rule, oldSize, newSize, reason, actor, phase); + commandHookManager.runMilestoneRewards(rule, oldSize, newSize, reason, actor, phase, progress.expansionCount()); + return ExpansionResult.success(oldSize, newSize, phase); + } + + public ExpansionResult setSize(WorldRule rule, double size, String actor, boolean force) { + if (size <= 0.0) { + return ExpansionResult.failure("invalid size"); + } + World world = Bukkit.getWorld(rule.worldName()); + if (world == null) { + return ExpansionResult.failure(configManager.messages().getString("expansion.missing-world", "missing world")); + } + if (configManager.dryRun()) { + return ExpansionResult.failure(configManager.messages().getString("expansion.dry-run", "dry-run")); + } + + WorldProgress progress = stateManager.progress(rule.key()); + if (!progress.initialized()) { + initializeRule(rule); + } + double oldSize = progress.currentSize() > 0.0 ? progress.currentSize() : world.getWorldBorder().getSize(); + double newSize = force ? size : enforceMax(rule, size); + if (rule.noShrinkProtection() && newSize + EPSILON < oldSize && !force) { + return ExpansionResult.failure(configManager.messages().getString("expansion.no-shrink", "no shrink")); + } + + PhaseDefinition phase = phaseForSize(rule, newSize); + applyBorder(world, rule, newSize, rule.transitionSeconds()); + updateProgressAfterSizeChange(rule, progress, newSize, ExpansionReason.SET_SIZE); + stateManager.saveQuietly(); + historyLogger.record(rule, oldSize, newSize, ExpansionReason.SET_SIZE, actor, phase); + return ExpansionResult.success(oldSize, newSize, phase); + } + + public ExpansionResult reset(WorldRule rule, String actor, boolean force) { + WorldProgress progress = stateManager.progress(rule.key()); + double oldSize = progress.currentSize(); + double target = rule.initialSize(); + if (rule.noShrinkProtection() && target + EPSILON < oldSize && !force) { + return ExpansionResult.failure(configManager.messages().getString("expansion.no-shrink", "no shrink")); + } + + ExpansionResult result = setSize(rule, target, actor, true); + if (!result.success()) { + return result; + } + + progress.initialized(true); + progress.currentSize(target); + progress.currentPhaseIndex(rule.phaseIndexForSize(target)); + progress.expansionCount(0); + progress.paused(false); + progress.lastExpansionAt(System.currentTimeMillis()); + progress.uniquePlayersAtLastExpansion(playerStatsManager.uniquePlayerCount()); + progress.clearReminders(); + stateManager.saveQuietly(); + historyLogger.record(rule, oldSize, target, ExpansionReason.RESET, actor, currentPhase(rule, progress)); + return ExpansionResult.success(oldSize, target, currentPhase(rule, progress)); + } + + public void pause(WorldRule rule) { + WorldProgress progress = stateManager.progress(rule.key()); + progress.paused(true); + stateManager.saveQuietly(); + } + + public void resume(WorldRule rule) { + WorldProgress progress = stateManager.progress(rule.key()); + progress.paused(false); + stateManager.saveQuietly(); + } + + public OptionalDouble nextSize(WorldRule rule, WorldProgress progress) { + double currentSize = progress.currentSize() > 0.0 ? progress.currentSize() : rule.initialSize(); + if (rule.growthMode() == GrowthMode.PHASE) { + return rule.nextPhase(progress.currentPhaseIndex()).map(PhaseDefinition::size).map(OptionalDouble::of).orElseGet(OptionalDouble::empty); + } + return OptionalDouble.of(currentSize + rule.growthAmount()); + } + + public double displaySize(WorldRule rule, WorldProgress progress) { + return progress.currentSize() > 0.0 ? progress.currentSize() : rule.initialSize(); + } + + public String nextSizeDisplay(WorldRule rule, WorldProgress progress) { + OptionalDouble next = nextSize(rule, progress); + if (next.isEmpty()) { + return "max"; + } + return TimeUtil.formatSize(enforceMax(rule, next.getAsDouble())); + } + + public PhaseDefinition currentPhase(WorldRule rule, WorldProgress progress) { + return rule.phase(progress.currentPhaseIndex()).orElse(null); + } + + public PhaseDefinition phaseForSize(WorldRule rule, double size) { + return rule.phase(rule.phaseIndexForSize(size)).orElse(null); + } + + public LegacyAccessReport legacyAccessReport(WorldRule rule) { + World world = Bukkit.getWorld(rule.worldName()); + if (world == null || !rule.legacyAccess().enabled()) { + return new LegacyAccessReport(0.0, 0, "none"); + } + + LegacyAccumulator accumulator = new LegacyAccumulator(); + WorldRule.LegacyAccessSettings access = rule.legacyAccess(); + if (access.includeCurrentBorder()) { + accumulator.include(world.getWorldBorder().getSize(), "current world border"); + } + if (access.includeOnlinePlayers()) { + for (org.bukkit.entity.Player player : Bukkit.getOnlinePlayers()) { + if (!includePlayerLocation(rule, player)) { + continue; + } + includeLocation(rule, accumulator, player.getName() + " online location", player.getLocation()); + } + } + if (access.includeOfflineLastLocations() || access.includeRespawnLocations()) { + for (OfflinePlayer player : Bukkit.getOfflinePlayers()) { + if (!includePlayerLocation(rule, player)) { + continue; + } + String name = player.getName() == null ? player.getUniqueId().toString() : player.getName(); + if (access.includeOfflineLastLocations()) { + includeLocation(rule, accumulator, name + " last location", player.getLocation()); + } + if (access.includeRespawnLocations()) { + includeLocation(rule, accumulator, name + " respawn location", player.getRespawnLocation()); + } + } + } + for (WorldRule.LegacyLocation location : access.locations()) { + includeCoordinate(rule, accumulator, location.name(), location.x(), location.z()); + } + return new LegacyAccessReport(accumulator.requiredSize, accumulator.includedLocations, accumulator.farthestLocationName); + } + + private PhaseDefinition phaseAfterExpansion(WorldRule rule, WorldProgress progress, double newSize) { + if (rule.growthMode() == GrowthMode.PHASE) { + return rule.nextPhase(progress.currentPhaseIndex()).orElse(phaseForSize(rule, newSize)); + } + return phaseForSize(rule, newSize); + } + + private void updateProgressAfterSizeChange(WorldRule rule, WorldProgress progress, double newSize, ExpansionReason reason) { + progress.initialized(true); + progress.currentSize(newSize); + progress.currentPhaseIndex(rule.phaseIndexForSize(newSize)); + if (reason != ExpansionReason.SET_SIZE) { + progress.expansionCount(progress.expansionCount() + 1); + } + progress.lastExpansionAt(System.currentTimeMillis()); + progress.uniquePlayersAtLastExpansion(playerStatsManager.uniquePlayerCount()); + progress.clearReminders(); + } + + private double enforceMax(WorldRule rule, double size) { + if (!rule.enforceMaxSize()) { + return size; + } + return Math.min(size, rule.maxSize()); + } + + private double enforceInitialMax(WorldRule rule, double size, LegacyAccessReport legacyReport) { + if (!rule.enforceMaxSize()) { + return size; + } + if (rule.legacyAccess().enabled() + && rule.legacyAccess().allowStartAboveMaxSize() + && legacyReport.requiredSize() > rule.maxSize() + && size >= legacyReport.requiredSize() - EPSILON) { + return size; + } + return Math.min(size, rule.maxSize()); + } + + private void includeLocation(WorldRule rule, LegacyAccumulator accumulator, String name, Location location) { + if (location == null || location.getWorld() == null || !location.getWorld().getName().equals(rule.worldName())) { + return; + } + includeCoordinate(rule, accumulator, name, location.getX(), location.getZ()); + } + + private boolean includePlayerLocation(WorldRule rule, OfflinePlayer player) { + return !rule.legacyAccess().playerLocationsRequireLegacyUser() || stateManager.isLegacyUser(player.getUniqueId()); + } + + private void includeCoordinate(WorldRule rule, LegacyAccumulator accumulator, String name, double x, double z) { + double halfSize = Math.max(Math.abs(x - rule.centerX()), Math.abs(z - rule.centerZ())) + rule.legacyAccess().padding(); + accumulator.include(halfSize * 2.0, name); + } + + private void applyBorder(World world, WorldRule rule, double size, long transitionSeconds) { + if (configManager.dryRun()) { + plugin.getLogger().info("[dry-run] Would set '" + rule.key() + "' border to " + TimeUtil.formatSize(size) + " blocks."); + return; + } + if (rule.borderMode() == BorderMode.SOFT_BORDER) { + if (rule.softBorder().releaseVanillaBorder()) { + releaseVanillaBorder(world, rule); + } + plugin.getLogger().info("Set soft border state for '" + rule.key() + "' to " + TimeUtil.formatSize(size) + " blocks. Vanilla world border was not used for enforcement."); + return; + } + WorldBorder border = world.getWorldBorder(); + border.setCenter(rule.centerX(), rule.centerZ()); + if (transitionSeconds > 0L) { + border.setSize(size, transitionSeconds); + } else { + border.setSize(size); + } + } + + private void releaseVanillaBorder(World world, WorldRule rule) { + WorldBorder border = world.getWorldBorder(); + border.setCenter(rule.centerX(), rule.centerZ()); + if (border.getSize() < rule.softBorder().vanillaBorderSize() - EPSILON) { + border.setSize(rule.softBorder().vanillaBorderSize()); + } + } + + private static final class LegacyAccumulator { + private double requiredSize; + private int includedLocations; + private String farthestLocationName = "none"; + + private void include(double requiredSize, String name) { + includedLocations++; + if (requiredSize > this.requiredSize) { + this.requiredSize = requiredSize; + this.farthestLocationName = name; + } + } + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/service/CommandHookManager.java b/src/main/java/com/dirtsmp/dirtsmp/service/CommandHookManager.java new file mode 100644 index 0000000..214175d --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/service/CommandHookManager.java @@ -0,0 +1,77 @@ +package com.dirtsmp.dirtsmp.service; + +import com.dirtsmp.dirtsmp.config.ConfigManager; +import com.dirtsmp.dirtsmp.config.MessageManager; +import com.dirtsmp.dirtsmp.model.ExpansionReason; +import com.dirtsmp.dirtsmp.model.PhaseDefinition; +import com.dirtsmp.dirtsmp.model.WorldRule; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Locale; +import org.bukkit.Bukkit; + +public final class CommandHookManager { + private final ConfigManager configManager; + private final MessageManager messageManager; + + public CommandHookManager(ConfigManager configManager, MessageManager messageManager) { + this.configManager = configManager; + this.messageManager = messageManager; + } + + public void runBefore(WorldRule rule, double oldSize, double newSize, ExpansionReason reason, String actor, PhaseDefinition phase) { + runCommands(commands(configManager.globalBeforeCommands(), rule.beforeCommands()), rule, oldSize, newSize, reason, actor, phase, Map.of()); + } + + public void runAfter(WorldRule rule, double oldSize, double newSize, ExpansionReason reason, String actor, PhaseDefinition phase) { + runCommands(commands(configManager.globalAfterCommands(), rule.afterCommands()), rule, oldSize, newSize, reason, actor, phase, Map.of()); + } + + public void runMilestoneRewards(WorldRule rule, double oldSize, double newSize, ExpansionReason reason, String actor, PhaseDefinition phase, int expansionCount) { + List commands = new ArrayList<>(); + commands.addAll(rewardCommands(configManager.globalMilestoneRewards(), expansionCount, phase)); + commands.addAll(rewardCommands(rule.milestoneRewards(), expansionCount, phase)); + + runCommands(commands, rule, oldSize, newSize, reason, actor, phase, Map.of( + "expansion_count", String.valueOf(expansionCount) + )); + } + + private List commands(List global, List worldSpecific) { + List commands = new ArrayList<>(); + commands.addAll(global); + commands.addAll(worldSpecific); + return commands; + } + + private List rewardCommands(WorldRule.MilestoneRewardSettings settings, int expansionCount, PhaseDefinition phase) { + if (!settings.enabled()) { + return List.of(); + } + + List commands = new ArrayList<>(); + commands.addAll(settings.everyExpansionCommands()); + commands.addAll(settings.expansionCountCommands().getOrDefault(expansionCount, List.of())); + if (phase != null) { + commands.addAll(settings.phaseIndexCommands().getOrDefault(phase.index(), List.of())); + commands.addAll(settings.phaseNameCommands().getOrDefault(phase.name().toLowerCase(Locale.ROOT), List.of())); + } + return commands; + } + + private void runCommands(List commands, WorldRule rule, double oldSize, double newSize, ExpansionReason reason, String actor, PhaseDefinition phase, Map extraPlaceholders) { + if (!configManager.hooksEnabled() || commands.isEmpty()) { + return; + } + + Map placeholders = messageManager.withBasePlaceholders(rule, oldSize, newSize, reason, actor, phase, extraPlaceholders); + for (String command : commands) { + if (command == null || command.isBlank()) { + continue; + } + String rendered = messageManager.applyPlaceholders(command, placeholders); + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), rendered.startsWith("/") ? rendered.substring(1) : rendered); + } + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/service/HistoryLogger.java b/src/main/java/com/dirtsmp/dirtsmp/service/HistoryLogger.java new file mode 100644 index 0000000..d769263 --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/service/HistoryLogger.java @@ -0,0 +1,48 @@ +package com.dirtsmp.dirtsmp.service; + +import com.dirtsmp.dirtsmp.DirtSMPPlugin; +import com.dirtsmp.dirtsmp.config.ConfigManager; +import com.dirtsmp.dirtsmp.model.ExpansionReason; +import com.dirtsmp.dirtsmp.model.PhaseDefinition; +import com.dirtsmp.dirtsmp.model.WorldRule; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.time.Instant; +import java.util.logging.Level; + +public final class HistoryLogger { + private final DirtSMPPlugin plugin; + private final ConfigManager configManager; + + public HistoryLogger(DirtSMPPlugin plugin, ConfigManager configManager) { + this.plugin = plugin; + this.configManager = configManager; + } + + public void record(WorldRule rule, double oldSize, double newSize, ExpansionReason reason, String actor, PhaseDefinition phase) { + if (!configManager.historyEnabled()) { + return; + } + + File file = new File(plugin.getDataFolder(), configManager.historyFileName()); + String line = String.join(" | ", + Instant.now().toString(), + "world=" + rule.worldName(), + "key=" + rule.key(), + "old=" + TimeUtil.formatSize(oldSize), + "new=" + TimeUtil.formatSize(newSize), + "reason=" + reason.name(), + "actor=" + (actor == null ? "system" : actor), + "phase=" + (phase == null ? "none" : phase.name()) + ) + System.lineSeparator(); + + try { + Files.writeString(file.toPath(), line, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND); + } catch (IOException ex) { + plugin.getLogger().log(Level.WARNING, "Could not append to expansion history.", ex); + } + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/service/SoftBorderManager.java b/src/main/java/com/dirtsmp/dirtsmp/service/SoftBorderManager.java new file mode 100644 index 0000000..af96ff2 --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/service/SoftBorderManager.java @@ -0,0 +1,434 @@ +package com.dirtsmp.dirtsmp.service; + +import com.dirtsmp.dirtsmp.DirtSMPPlugin; +import com.dirtsmp.dirtsmp.config.ConfigManager; +import com.dirtsmp.dirtsmp.config.MessageManager; +import com.dirtsmp.dirtsmp.model.BorderMode; +import com.dirtsmp.dirtsmp.model.WorldRule; +import com.dirtsmp.dirtsmp.state.StateManager; +import com.dirtsmp.dirtsmp.state.WorldProgress; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.SoundCategory; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityTeleportEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.event.vehicle.VehicleMoveEvent; +import org.bukkit.scheduler.BukkitTask; +import org.bukkit.util.Vector; + +public final class SoftBorderManager implements Listener { + private static final double EPSILON = 0.0001; + private static final long TASK_PERIOD_TICKS = 5L; + + private final DirtSMPPlugin plugin; + private final ConfigManager configManager; + private final MessageManager messageManager; + private final StateManager stateManager; + private final Map feedbackCooldowns = new HashMap<>(); + private final Set correctingEntities = new HashSet<>(); + private BukkitTask task; + private long tickCounter; + + public SoftBorderManager(DirtSMPPlugin plugin, ConfigManager configManager, MessageManager messageManager, StateManager stateManager) { + this.plugin = plugin; + this.configManager = configManager; + this.messageManager = messageManager; + this.stateManager = stateManager; + } + + public void start() { + stop(); + task = Bukkit.getScheduler().runTaskTimer(plugin, this::tick, TASK_PERIOD_TICKS, TASK_PERIOD_TICKS); + } + + public void stop() { + if (task != null) { + task.cancel(); + task = null; + } + feedbackCooldowns.clear(); + correctingEntities.clear(); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onMove(PlayerMoveEvent event) { + Location to = event.getTo(); + if (!movedHorizontally(event.getFrom(), to)) { + return; + } + WorldRule rule = softRule(to.getWorld()); + if (rule == null || exempt(event.getPlayer(), rule)) { + return; + } + Entity target = protectedEntity(event.getPlayer(), rule); + Location targetLocation = target.getLocation(); + if (!outside(rule, targetLocation)) { + return; + } + + double outsideDistance = outsideDistance(rule, targetLocation); + if (outsideDistance >= rule.softBorder().maxOutsideDistanceBeforeTeleport()) { + teleportInside(target, event.getPlayer(), rule, targetLocation); + return; + } + bounce(target, event.getPlayer(), rule, targetLocation); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onTeleport(PlayerTeleportEvent event) { + if (correctingEntities.remove(event.getPlayer().getUniqueId())) { + return; + } + + Location to = event.getTo(); + if (to == null) { + return; + } + WorldRule rule = softRule(to.getWorld()); + if (rule == null || exempt(event.getPlayer(), rule) || !outside(rule, to)) { + return; + } + + Location corrected = closestInside(rule, to); + event.setTo(corrected); + feedback(event.getPlayer(), rule, true); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onEntityTeleport(EntityTeleportEvent event) { + Entity entity = event.getEntity(); + if (entity instanceof Player || correctingEntities.remove(entity.getUniqueId())) { + return; + } + + Location to = event.getTo(); + WorldRule rule = to == null ? null : softRule(to.getWorld()); + if (rule == null || !rule.softBorder().protectMountedEntities() || !outside(rule, to)) { + return; + } + Player rider = firstNonExemptPassenger(entity, rule); + if (rider == null) { + return; + } + + event.setTo(closestInside(rule, to)); + feedback(rider, rule, true); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onVehicleMove(VehicleMoveEvent event) { + if (!movedHorizontally(event.getFrom(), event.getTo())) { + return; + } + + WorldRule rule = softRule(event.getTo().getWorld()); + if (rule == null || !rule.softBorder().protectMountedEntities() || !outside(rule, event.getTo())) { + return; + } + Player rider = firstNonExemptPassenger(event.getVehicle(), rule); + if (rider == null) { + return; + } + + double outsideDistance = outsideDistance(rule, event.getTo()); + if (outsideDistance >= rule.softBorder().maxOutsideDistanceBeforeTeleport()) { + teleportInside(event.getVehicle(), rider, rule, event.getTo()); + return; + } + bounce(event.getVehicle(), rider, rule, event.getTo()); + } + + private void tick() { + tickCounter += TASK_PERIOD_TICKS; + for (Player player : Bukkit.getOnlinePlayers()) { + WorldRule rule = softRule(player.getWorld()); + if (rule == null || exempt(player, rule)) { + continue; + } + Entity target = protectedEntity(player, rule); + Location targetLocation = target.getLocation(); + if (outside(rule, targetLocation)) { + teleportInside(target, player, rule, targetLocation); + continue; + } + if (rule.softBorder().particlesEnabled() && tickCounter % rule.softBorder().particleIntervalTicks() == 0L) { + renderParticles(player, rule); + } + } + } + + private WorldRule softRule(World world) { + if (world == null) { + return null; + } + for (WorldRule rule : configManager.worlds().values()) { + if (rule.enabled() && rule.borderMode() == BorderMode.SOFT_BORDER && rule.worldName().equals(world.getName())) { + return rule; + } + } + return null; + } + + private boolean exempt(Player player, WorldRule rule) { + WorldRule.SoftBorderSettings settings = rule.softBorder(); + if (!settings.bypassPermission().isBlank() && player.hasPermission(settings.bypassPermission())) { + return true; + } + if (settings.ignoreCreative() && player.getGameMode() == GameMode.CREATIVE) { + return true; + } + return settings.ignoreSpectator() && player.getGameMode() == GameMode.SPECTATOR; + } + + private boolean movedHorizontally(Location from, Location to) { + return to != null + && from.getWorld() == to.getWorld() + && (Math.abs(from.getX() - to.getX()) > EPSILON || Math.abs(from.getZ() - to.getZ()) > EPSILON); + } + + private Entity protectedEntity(Player player, WorldRule rule) { + if (!rule.softBorder().protectMountedEntities() || !player.isInsideVehicle()) { + return player; + } + Entity vehicle = player.getVehicle(); + if (vehicle == null || vehicle.getWorld() != player.getWorld()) { + return player; + } + return vehicle; + } + + private Player firstNonExemptPassenger(Entity entity, WorldRule rule) { + for (Entity passenger : entity.getPassengers()) { + if (passenger instanceof Player player && !exempt(player, rule)) { + return player; + } + Player nested = firstNonExemptPassenger(passenger, rule); + if (nested != null) { + return nested; + } + } + return null; + } + + private boolean outside(WorldRule rule, Location location) { + Bounds bounds = bounds(rule); + return location.getX() < bounds.minX + || location.getX() > bounds.maxX + || location.getZ() < bounds.minZ + || location.getZ() > bounds.maxZ; + } + + private double outsideDistance(WorldRule rule, Location location) { + Bounds bounds = bounds(rule); + double outsideX = Math.max(bounds.minX - location.getX(), location.getX() - bounds.maxX); + double outsideZ = Math.max(bounds.minZ - location.getZ(), location.getZ() - bounds.maxZ); + return Math.max(Math.max(0.0, outsideX), Math.max(0.0, outsideZ)); + } + + private void bounce(Entity target, Player player, WorldRule rule, Location location) { + Bounds bounds = bounds(rule); + double pushX = 0.0; + double pushZ = 0.0; + if (location.getX() < bounds.minX) { + pushX = 1.0; + } else if (location.getX() > bounds.maxX) { + pushX = -1.0; + } + if (location.getZ() < bounds.minZ) { + pushZ = 1.0; + } else if (location.getZ() > bounds.maxZ) { + pushZ = -1.0; + } + + Vector velocity = new Vector(pushX, 0.0, pushZ); + if (velocity.lengthSquared() <= EPSILON) { + velocity = location.toVector().subtract(closestInside(rule, location).toVector()).multiply(-1.0); + } + velocity.setY(0.0); + if (velocity.lengthSquared() <= EPSILON) { + velocity = new Vector(rule.centerX() - location.getX(), 0.0, rule.centerZ() - location.getZ()); + } + double strength = target instanceof Player ? rule.softBorder().bounceStrength() : rule.softBorder().mountedBounceStrength(); + double vertical = target instanceof Player ? rule.softBorder().verticalBoost() : rule.softBorder().mountedVerticalBoost(); + velocity.normalize().multiply(strength).setY(vertical); + target.setVelocity(velocity); + feedback(player, rule, false); + } + + private void teleportInside(Entity target, Player player, WorldRule rule, Location from) { + Location corrected = closestInside(rule, from); + Set passengers = new HashSet<>(target.getPassengers()); + correctingEntities.add(target.getUniqueId()); + target.teleport(corrected); + restorePassengers(target, passengers); + feedback(player, rule, true); + } + + private void restorePassengers(Entity target, Set passengers) { + if (passengers.isEmpty()) { + return; + } + Bukkit.getScheduler().runTask(plugin, () -> { + if (!target.isValid()) { + return; + } + for (Entity passenger : passengers) { + if (!passenger.isValid() || target.getPassengers().contains(passenger)) { + continue; + } + correctingEntities.add(passenger.getUniqueId()); + passenger.teleport(target.getLocation()); + target.addPassenger(passenger); + } + }); + } + + private Location closestInside(WorldRule rule, Location location) { + Bounds bounds = bounds(rule); + double buffer = rule.softBorder().insideBuffer(); + double minX = bounds.minX + buffer; + double maxX = bounds.maxX - buffer; + double minZ = bounds.minZ + buffer; + double maxZ = bounds.maxZ - buffer; + double x = Math.max(minX, Math.min(maxX, location.getX())); + double z = Math.max(minZ, Math.min(maxZ, location.getZ())); + + Location corrected = location.clone(); + corrected.setX(x); + corrected.setZ(z); + return corrected; + } + + private Bounds bounds(WorldRule rule) { + WorldProgress progress = stateManager.progress(rule.key()); + double size = progress.currentSize() > 0.0 ? progress.currentSize() : rule.initialSize(); + double half = size / 2.0; + return new Bounds(rule.centerX() - half, rule.centerX() + half, rule.centerZ() - half, rule.centerZ() + half); + } + + private void feedback(Player player, WorldRule rule, boolean forcedTeleport) { + long now = System.currentTimeMillis(); + long last = feedbackCooldowns.getOrDefault(player.getUniqueId(), 0L); + if (now - last < rule.softBorder().cooldownMillis()) { + return; + } + feedbackCooldowns.put(player.getUniqueId(), now); + + Map placeholders = Map.of( + "world", rule.worldName(), + "key", rule.key(), + "mode", forcedTeleport ? "corrected" : "bounced" + ); + messageManager.sendRaw(player, rule.softBorder().message(), placeholders); + if (!rule.softBorder().actionBar().isBlank()) { + player.sendActionBar(messageManager.component(rule.softBorder().actionBar(), placeholders)); + } + playSound(player, rule); + } + + @SuppressWarnings({"deprecation", "removal"}) + private void playSound(Player player, WorldRule rule) { + String soundName = rule.softBorder().sound(); + if (soundName == null || soundName.isBlank()) { + return; + } + try { + Sound sound = Sound.valueOf(soundName.trim().toUpperCase(Locale.ROOT)); + player.playSound(player.getLocation(), sound, SoundCategory.MASTER, rule.softBorder().volume(), rule.softBorder().pitch()); + } catch (IllegalArgumentException ignored) { + player.playSound(player.getLocation(), soundName.toLowerCase(Locale.ROOT), SoundCategory.MASTER, rule.softBorder().volume(), rule.softBorder().pitch()); + } + } + + private void renderParticles(Player player, WorldRule rule) { + Bounds bounds = bounds(rule); + Location playerLocation = player.getLocation(); + double view = rule.softBorder().particleViewDistance(); + double spacing = rule.softBorder().particleSpacing(); + double y = playerLocation.getY() + 1.1; + + if (Math.abs(playerLocation.getX() - bounds.minX) <= view) { + renderLine(player, rule, bounds.minX, clamp(playerLocation.getZ() - view, bounds.minZ, bounds.maxZ), bounds.minX, clamp(playerLocation.getZ() + view, bounds.minZ, bounds.maxZ), y, spacing); + } + if (Math.abs(playerLocation.getX() - bounds.maxX) <= view) { + renderLine(player, rule, bounds.maxX, clamp(playerLocation.getZ() - view, bounds.minZ, bounds.maxZ), bounds.maxX, clamp(playerLocation.getZ() + view, bounds.minZ, bounds.maxZ), y, spacing); + } + if (Math.abs(playerLocation.getZ() - bounds.minZ) <= view) { + renderLine(player, rule, clamp(playerLocation.getX() - view, bounds.minX, bounds.maxX), bounds.minZ, clamp(playerLocation.getX() + view, bounds.minX, bounds.maxX), bounds.minZ, y, spacing); + } + if (Math.abs(playerLocation.getZ() - bounds.maxZ) <= view) { + renderLine(player, rule, clamp(playerLocation.getX() - view, bounds.minX, bounds.maxX), bounds.maxZ, clamp(playerLocation.getX() + view, bounds.minX, bounds.maxX), bounds.maxZ, y, spacing); + } + } + + private void renderLine(Player player, WorldRule rule, double startX, double startZ, double endX, double endZ, double y, double spacing) { + double distance = Math.hypot(endX - startX, endZ - startZ); + int steps = Math.max(1, (int) Math.floor(distance / spacing)); + for (int step = 0; step <= steps; step++) { + double t = (double) step / (double) steps; + double x = startX + ((endX - startX) * t); + double z = startZ + ((endZ - startZ) * t); + spawnParticle(player, rule, x, y, z); + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private void spawnParticle(Player player, WorldRule rule, double x, double y, double z) { + WorldRule.SoftBorderSettings settings = rule.softBorder(); + try { + Particle particle = Particle.valueOf(settings.particle().trim().toUpperCase(Locale.ROOT)); + if (particle == Particle.DUST) { + player.spawnParticle( + Particle.DUST, + x, + y, + z, + settings.particleCount(), + settings.particleOffset(), + settings.particleOffset(), + settings.particleOffset(), + settings.particleSpeed(), + new Particle.DustOptions(Color.fromRGB(212, 175, 55), 1.25f) + ); + return; + } + player.spawnParticle( + particle, + x, + y, + z, + settings.particleCount(), + settings.particleOffset(), + settings.particleOffset(), + settings.particleOffset(), + settings.particleSpeed() + ); + } catch (IllegalArgumentException ex) { + player.spawnParticle(Particle.END_ROD, x, y, z, 1, 0.02, 0.02, 0.02, 0.0); + } + } + + private double clamp(double value, double min, double max) { + return Math.max(min, Math.min(max, value)); + } + + private record Bounds(double minX, double maxX, double minZ, double maxZ) { + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/service/TimeUtil.java b/src/main/java/com/dirtsmp/dirtsmp/service/TimeUtil.java new file mode 100644 index 0000000..e9007cb --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/service/TimeUtil.java @@ -0,0 +1,92 @@ +package com.dirtsmp.dirtsmp.service; + +import java.text.DecimalFormat; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class TimeUtil { + private static final Pattern DURATION_PART = Pattern.compile("(\\d+(?:\\.\\d+)?)(ms|s|m|h|d|w)?", Pattern.CASE_INSENSITIVE); + private static final DecimalFormat SIZE_FORMAT = new DecimalFormat("#,##0.##"); + + private TimeUtil() { + } + + public static long parseDurationMillis(String input, long fallbackMillis) { + if (input == null || input.isBlank()) { + return fallbackMillis; + } + + String compact = input.trim().toLowerCase(Locale.ROOT).replace(" ", ""); + Matcher matcher = DURATION_PART.matcher(compact); + long total = 0L; + int matched = 0; + + while (matcher.find()) { + if (matcher.start() != matched) { + return fallbackMillis; + } + double amount = Double.parseDouble(matcher.group(1)); + String unit = matcher.group(2); + total += switch (unit == null ? "s" : unit) { + case "ms" -> Math.round(amount); + case "s" -> Math.round(amount * 1000.0); + case "m" -> Math.round(amount * 60_000.0); + case "h" -> Math.round(amount * 3_600_000.0); + case "d" -> Math.round(amount * 86_400_000.0); + case "w" -> Math.round(amount * 604_800_000.0); + default -> fallbackMillis; + }; + matched = matcher.end(); + } + + return matched == compact.length() && total > 0L ? total : fallbackMillis; + } + + public static String formatDuration(long millis) { + if (millis <= 0L) { + return "now"; + } + + long seconds = millis / 1000L; + long days = seconds / 86_400L; + seconds %= 86_400L; + long hours = seconds / 3_600L; + seconds %= 3_600L; + long minutes = seconds / 60L; + seconds %= 60L; + + StringBuilder builder = new StringBuilder(); + appendPart(builder, days, "d"); + appendPart(builder, hours, "h"); + appendPart(builder, minutes, "m"); + if (builder.isEmpty()) { + appendPart(builder, seconds, "s"); + } + return builder.toString().trim(); + } + + private static void appendPart(StringBuilder builder, long value, String suffix) { + if (value <= 0L) { + return; + } + if (!builder.isEmpty()) { + builder.append(' '); + } + builder.append(value).append(suffix); + } + + public static String formatSize(double size) { + return SIZE_FORMAT.format(size); + } + + public static String formatInstant(long epochMillis) { + if (epochMillis <= 0L) { + return "unknown"; + } + return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(Instant.ofEpochMilli(epochMillis).atZone(ZoneId.systemDefault())); + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/service/TriggerEvaluator.java b/src/main/java/com/dirtsmp/dirtsmp/service/TriggerEvaluator.java new file mode 100644 index 0000000..7300971 --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/service/TriggerEvaluator.java @@ -0,0 +1,139 @@ +package com.dirtsmp.dirtsmp.service; + +import com.dirtsmp.dirtsmp.model.ExpansionReason; +import com.dirtsmp.dirtsmp.model.TriggerDecision; +import com.dirtsmp.dirtsmp.model.TriggerMode; +import com.dirtsmp.dirtsmp.model.TriggerSettings; +import com.dirtsmp.dirtsmp.model.WorldRule; +import com.dirtsmp.dirtsmp.state.PlayerStatsManager; +import com.dirtsmp.dirtsmp.state.WorldProgress; + +public final class TriggerEvaluator { + private final PlayerStatsManager playerStatsManager; + + public TriggerEvaluator(PlayerStatsManager playerStatsManager) { + this.playerStatsManager = playerStatsManager; + } + + public TriggerDecision evaluate(WorldRule rule, WorldProgress progress) { + TriggerSettings settings = rule.triggers(); + if (!rule.enabled()) { + return TriggerDecision.none("world disabled"); + } + if (progress.paused()) { + return TriggerDecision.none("paused"); + } + if (rule.manualOnly() || settings.manualOnly()) { + return TriggerDecision.none("manual only"); + } + + boolean time = timeDue(settings, progress); + boolean unique = uniqueDue(settings, progress); + boolean online = onlineDue(settings, progress); + boolean active = activeDue(settings, progress); + + return switch (settings.mode()) { + case TIME -> decision(time, ExpansionReason.TIME, describeTrigger(rule, progress)); + case UNIQUE_PLAYERS -> decision(unique, ExpansionReason.UNIQUE_PLAYERS, describeTrigger(rule, progress)); + case ONLINE_PLAYERS -> decision(online, ExpansionReason.ONLINE_PLAYERS, describeTrigger(rule, progress)); + case ACTIVE_PLAYERS -> decision(active, ExpansionReason.ACTIVE_PLAYERS, describeTrigger(rule, progress)); + case TIME_OR_UNIQUE_PLAYERS -> decision(time || unique, time && unique ? ExpansionReason.HYBRID : (time ? ExpansionReason.TIME : ExpansionReason.UNIQUE_PLAYERS), describeTrigger(rule, progress)); + case TIME_AND_UNIQUE_PLAYERS -> decision(time && unique, ExpansionReason.HYBRID, describeTrigger(rule, progress)); + case TIME_OR_ONLINE_PLAYERS -> decision(time || online, time && online ? ExpansionReason.HYBRID : (time ? ExpansionReason.TIME : ExpansionReason.ONLINE_PLAYERS), describeTrigger(rule, progress)); + case TIME_AND_ONLINE_PLAYERS -> decision(time && online, ExpansionReason.HYBRID, describeTrigger(rule, progress)); + case TIME_OR_ACTIVE_PLAYERS -> decision(time || active, time && active ? ExpansionReason.HYBRID : (time ? ExpansionReason.TIME : ExpansionReason.ACTIVE_PLAYERS), describeTrigger(rule, progress)); + case TIME_AND_ACTIVE_PLAYERS -> decision(time && active, ExpansionReason.HYBRID, describeTrigger(rule, progress)); + case MANUAL -> TriggerDecision.none("manual only"); + }; + } + + public String describeTrigger(WorldRule rule, WorldProgress progress) { + TriggerSettings settings = rule.triggers(); + return switch (settings.mode()) { + case MANUAL -> "manual only"; + case TIME -> describeTime(settings, progress); + case UNIQUE_PLAYERS -> describeUnique(settings, progress); + case ONLINE_PLAYERS -> describeOnline(settings); + case ACTIVE_PLAYERS -> describeActive(settings); + case TIME_OR_UNIQUE_PLAYERS -> describeTime(settings, progress) + " or " + describeUnique(settings, progress); + case TIME_AND_UNIQUE_PLAYERS -> describeTime(settings, progress) + " and " + describeUnique(settings, progress); + case TIME_OR_ONLINE_PLAYERS -> describeTime(settings, progress) + " or " + describeOnline(settings); + case TIME_AND_ONLINE_PLAYERS -> describeTime(settings, progress) + " and " + describeOnline(settings); + case TIME_OR_ACTIVE_PLAYERS -> describeTime(settings, progress) + " or " + describeActive(settings); + case TIME_AND_ACTIVE_PLAYERS -> describeTime(settings, progress) + " and " + describeActive(settings); + }; + } + + public int missedTimeExpansions(WorldRule rule, WorldProgress progress) { + TriggerSettings settings = rule.triggers(); + if (!settings.mode().usesTime() || settings.timeIntervalMillis() <= 0L || progress.lastExpansionAt() <= 0L) { + return 0; + } + long elapsed = System.currentTimeMillis() - progress.lastExpansionAt(); + if (elapsed < settings.timeIntervalMillis()) { + return 0; + } + return (int) Math.max(1L, elapsed / settings.timeIntervalMillis()); + } + + private boolean timeDue(TriggerSettings settings, WorldProgress progress) { + return settings.timeIntervalMillis() > 0L + && progress.lastExpansionAt() > 0L + && System.currentTimeMillis() >= progress.lastExpansionAt() + settings.timeIntervalMillis(); + } + + private boolean uniqueDue(TriggerSettings settings, WorldProgress progress) { + if (settings.uniquePlayersEvery() <= 0) { + return false; + } + return playerStatsManager.uniquePlayerCount() >= progress.uniquePlayersAtLastExpansion() + settings.uniquePlayersEvery(); + } + + private boolean onlineDue(TriggerSettings settings, WorldProgress progress) { + if (settings.onlinePlayersThreshold() <= 0) { + return false; + } + return playerStatsManager.onlinePlayerCount(settings.excludeVanished()) >= settings.onlinePlayersThreshold() + && playerCooldownPassed(settings, progress); + } + + private boolean activeDue(TriggerSettings settings, WorldProgress progress) { + if (settings.activePlayersThreshold() <= 0) { + return false; + } + return playerStatsManager.activePlayerCount(settings.activeWindowMillis()) >= settings.activePlayersThreshold() + && playerCooldownPassed(settings, progress); + } + + private boolean playerCooldownPassed(TriggerSettings settings, WorldProgress progress) { + return settings.playerTriggerCooldownMillis() <= 0L + || progress.lastExpansionAt() <= 0L + || System.currentTimeMillis() >= progress.lastExpansionAt() + settings.playerTriggerCooldownMillis(); + } + + private TriggerDecision decision(boolean due, ExpansionReason reason, String description) { + return due ? new TriggerDecision(true, reason, description) : TriggerDecision.none(description); + } + + private String describeTime(TriggerSettings settings, WorldProgress progress) { + long next = progress.lastExpansionAt() + settings.timeIntervalMillis(); + long remaining = next - System.currentTimeMillis(); + return remaining <= 0L ? "time due" : "time in " + TimeUtil.formatDuration(remaining); + } + + private String describeUnique(TriggerSettings settings, WorldProgress progress) { + int current = playerStatsManager.uniquePlayerCount(); + int needed = progress.uniquePlayersAtLastExpansion() + settings.uniquePlayersEvery(); + return "unique players " + current + "/" + needed; + } + + private String describeOnline(TriggerSettings settings) { + int current = playerStatsManager.onlinePlayerCount(settings.excludeVanished()); + return "online players " + current + "/" + settings.onlinePlayersThreshold(); + } + + private String describeActive(TriggerSettings settings) { + int current = playerStatsManager.activePlayerCount(settings.activeWindowMillis()); + return "active players " + current + "/" + settings.activePlayersThreshold(); + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/service/WebhookNotifier.java b/src/main/java/com/dirtsmp/dirtsmp/service/WebhookNotifier.java new file mode 100644 index 0000000..e8f5505 --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/service/WebhookNotifier.java @@ -0,0 +1,67 @@ +package com.dirtsmp.dirtsmp.service; + +import com.dirtsmp.dirtsmp.DirtSMPPlugin; +import com.dirtsmp.dirtsmp.config.ConfigManager; +import com.dirtsmp.dirtsmp.config.MessageManager; +import com.dirtsmp.dirtsmp.model.ExpansionReason; +import com.dirtsmp.dirtsmp.model.PhaseDefinition; +import com.dirtsmp.dirtsmp.model.WorldRule; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.Map; +import java.util.logging.Level; +import org.bukkit.Bukkit; + +public final class WebhookNotifier { + private final DirtSMPPlugin plugin; + private final ConfigManager configManager; + private final MessageManager messageManager; + + public WebhookNotifier(DirtSMPPlugin plugin, ConfigManager configManager, MessageManager messageManager) { + this.plugin = plugin; + this.configManager = configManager; + this.messageManager = messageManager; + } + + public void notifyExpansion(WorldRule rule, double oldSize, double newSize, ExpansionReason reason, String actor, PhaseDefinition phase) { + if (!configManager.webhookEnabled() || configManager.webhookUrl().isBlank()) { + return; + } + + Map placeholders = messageManager.basePlaceholders(rule, oldSize, newSize, reason, actor, phase); + String content = messageManager.applyPlaceholders(configManager.webhookContent(), placeholders); + String body = "{\"content\":\"" + escapeJson(content) + "\"}"; + String url = configManager.webhookUrl(); + int timeoutSeconds = configManager.webhookTimeoutSeconds(); + + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + try { + HttpClient client = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(timeoutSeconds)) + .build(); + HttpRequest request = HttpRequest.newBuilder(URI.create(url)) + .timeout(Duration.ofSeconds(timeoutSeconds)) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(body)) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() < 200 || response.statusCode() >= 300) { + plugin.getLogger().warning("Webhook returned HTTP " + response.statusCode() + "."); + } + } catch (Exception ex) { + plugin.getLogger().log(Level.WARNING, "Could not send DirtSMP webhook.", ex); + } + }); + } + + private String escapeJson(String input) { + return input + .replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\n", "\\n") + .replace("\r", "\\r"); + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/state/PlayerStatsManager.java b/src/main/java/com/dirtsmp/dirtsmp/state/PlayerStatsManager.java new file mode 100644 index 0000000..b0f719b --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/state/PlayerStatsManager.java @@ -0,0 +1,144 @@ +package com.dirtsmp.dirtsmp.state; + +import com.dirtsmp.dirtsmp.DirtSMPPlugin; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.logging.Level; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; + +public final class PlayerStatsManager { + private final DirtSMPPlugin plugin; + private final Map players = new HashMap<>(); + private File file; + + public PlayerStatsManager(DirtSMPPlugin plugin) { + this.plugin = plugin; + } + + public void load() { + file = new File(plugin.getDataFolder(), "players.yml"); + if (!file.exists()) { + try { + file.createNewFile(); + } catch (IOException ex) { + plugin.getLogger().log(Level.WARNING, "Could not create players.yml.", ex); + } + } + + players.clear(); + YamlConfiguration config = YamlConfiguration.loadConfiguration(file); + ConfigurationSection section = config.getConfigurationSection("players"); + if (section != null) { + for (String rawUuid : section.getKeys(false)) { + try { + UUID uuid = UUID.fromString(rawUuid); + String path = "players." + rawUuid + "."; + players.put(uuid, new PlayerRecord( + config.getString(path + "name", "unknown"), + config.getLong(path + "first-seen", 0L), + config.getLong(path + "last-seen", 0L) + )); + } catch (IllegalArgumentException ignored) { + plugin.getLogger().warning("Ignoring invalid UUID in players.yml: " + rawUuid); + } + } + } + + Bukkit.getOnlinePlayers().forEach(this::markSeen); + } + + public void markSeen(Player player) { + long now = System.currentTimeMillis(); + players.compute(player.getUniqueId(), (uuid, existing) -> { + if (existing == null) { + return new PlayerRecord(player.getName(), now, now); + } + existing.name = player.getName(); + existing.lastSeen = now; + if (existing.firstSeen <= 0L) { + existing.firstSeen = now; + } + return existing; + }); + } + + public int uniquePlayerCount() { + return players.size(); + } + + public int onlinePlayerCount(boolean excludeVanished) { + int count = 0; + for (Player player : Bukkit.getOnlinePlayers()) { + if (!excludeVanished || !isVanished(player)) { + count++; + } + } + return count; + } + + public int activePlayerCount(long windowMillis) { + long cutoff = System.currentTimeMillis() - Math.max(0L, windowMillis); + int count = 0; + for (PlayerRecord record : players.values()) { + if (record.lastSeen >= cutoff) { + count++; + } + } + return count; + } + + private boolean isVanished(Player player) { + for (MetadataValue value : player.getMetadata("vanished")) { + if (value.asBoolean()) { + return true; + } + } + return false; + } + + public void saveQuietly() { + try { + save(); + } catch (IOException ex) { + plugin.getLogger().log(Level.WARNING, "Could not save player stats.", ex); + } + } + + public void save() throws IOException { + if (file == null) { + file = new File(plugin.getDataFolder(), "players.yml"); + } + YamlConfiguration config = new YamlConfiguration(); + for (Map.Entry entry : players.entrySet()) { + String path = "players." + entry.getKey() + "."; + PlayerRecord record = entry.getValue(); + config.set(path + "name", record.name); + config.set(path + "first-seen", record.firstSeen); + config.set(path + "last-seen", record.lastSeen); + } + config.save(file); + } + + public void clearRuntimeState() { + players.clear(); + } + + private static final class PlayerRecord { + private String name; + private long firstSeen; + private long lastSeen; + + private PlayerRecord(String name, long firstSeen, long lastSeen) { + this.name = name; + this.firstSeen = firstSeen; + this.lastSeen = lastSeen; + } + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/state/StateManager.java b/src/main/java/com/dirtsmp/dirtsmp/state/StateManager.java new file mode 100644 index 0000000..21fc51f --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/state/StateManager.java @@ -0,0 +1,205 @@ +package com.dirtsmp.dirtsmp.state; + +import com.dirtsmp.dirtsmp.DirtSMPPlugin; +import com.dirtsmp.dirtsmp.config.ConfigManager; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; +import java.util.logging.Level; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; + +public final class StateManager { + private static final DateTimeFormatter BACKUP_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss"); + + private final DirtSMPPlugin plugin; + private final ConfigManager configManager; + private final Map progressByWorld = new HashMap<>(); + private final Map legacyUsers = new LinkedHashMap<>(); + private File stateFile; + + public StateManager(DirtSMPPlugin plugin, ConfigManager configManager) { + this.plugin = plugin; + this.configManager = configManager; + } + + public void load() { + stateFile = new File(plugin.getDataFolder(), configManager.stateFileName()); + if (!stateFile.exists()) { + try { + stateFile.createNewFile(); + } catch (IOException ex) { + plugin.getLogger().log(Level.WARNING, "Could not create state file " + stateFile.getName(), ex); + } + } + + progressByWorld.clear(); + legacyUsers.clear(); + YamlConfiguration state = YamlConfiguration.loadConfiguration(stateFile); + ConfigurationSection worlds = state.getConfigurationSection("worlds"); + if (worlds != null) { + for (String key : worlds.getKeys(false)) { + ConfigurationSection section = worlds.getConfigurationSection(key); + if (section == null) { + continue; + } + WorldProgress progress = new WorldProgress(); + progress.initialized(section.getBoolean("initialized", false)); + progress.currentSize(section.getDouble("current-size", 0.0)); + progress.expansionCount(section.getInt("expansion-count", 0)); + progress.currentPhaseIndex(section.getInt("current-phase-index", 0)); + progress.paused(section.getBoolean("paused", false)); + progress.lastExpansionAt(section.getLong("last-expansion-at", 0L)); + progress.uniquePlayersAtLastExpansion(section.getInt("unique-players-at-last-expansion", 0)); + progress.sentReminders(new HashSet<>(section.getStringList("sent-reminders"))); + progressByWorld.put(key.toLowerCase(), progress); + } + } + + ConfigurationSection legacySection = state.getConfigurationSection("legacy-users"); + if (legacySection != null) { + for (String rawUuid : legacySection.getKeys(false)) { + try { + UUID uuid = UUID.fromString(rawUuid); + String path = "legacy-users." + rawUuid + "."; + legacyUsers.put(uuid, new LegacyUserRecord( + state.getString(path + "name", "unknown"), + state.getLong(path + "playtime-ticks", 0L), + state.getLong(path + "added-at", 0L), + state.getString(path + "source", "unknown") + )); + } catch (IllegalArgumentException ex) { + plugin.getLogger().warning("Ignoring invalid legacy user UUID in state.yml: " + rawUuid); + } + } + } + } + + public WorldProgress progress(String key) { + return progressByWorld.computeIfAbsent(key.toLowerCase(), ignored -> new WorldProgress()); + } + + public Map allProgress() { + return progressByWorld; + } + + public boolean isLegacyUser(UUID uuid) { + return legacyUsers.containsKey(uuid); + } + + public Map legacyUsers() { + return legacyUsers; + } + + public boolean addLegacyUser(UUID uuid, String name, long playtimeTicks, String source) { + boolean added = !legacyUsers.containsKey(uuid); + legacyUsers.put(uuid, new LegacyUserRecord( + name == null || name.isBlank() ? uuid.toString() : name, + Math.max(0L, playtimeTicks), + System.currentTimeMillis(), + source == null || source.isBlank() ? "manual" : source + )); + return added; + } + + public boolean removeLegacyUser(UUID uuid) { + return legacyUsers.remove(uuid) != null; + } + + public void saveQuietly() { + try { + save(); + } catch (IOException ex) { + plugin.getLogger().log(Level.WARNING, "Could not save DirtSMP state.", ex); + } + } + + public void save() throws IOException { + if (stateFile == null) { + stateFile = new File(plugin.getDataFolder(), configManager.stateFileName()); + } + if (configManager.backupStateOnSave() && stateFile.exists() && stateFile.length() > 0L) { + backupState(); + } + + YamlConfiguration state = new YamlConfiguration(); + for (Map.Entry entry : progressByWorld.entrySet()) { + String path = "worlds." + entry.getKey() + "."; + WorldProgress progress = entry.getValue(); + state.set(path + "initialized", progress.initialized()); + state.set(path + "current-size", progress.currentSize()); + state.set(path + "expansion-count", progress.expansionCount()); + state.set(path + "current-phase-index", progress.currentPhaseIndex()); + state.set(path + "paused", progress.paused()); + state.set(path + "last-expansion-at", progress.lastExpansionAt()); + state.set(path + "unique-players-at-last-expansion", progress.uniquePlayersAtLastExpansion()); + state.set(path + "sent-reminders", progress.sentReminders().stream().sorted().toList()); + } + for (Map.Entry entry : legacyUsers.entrySet()) { + String path = "legacy-users." + entry.getKey() + "."; + LegacyUserRecord record = entry.getValue(); + state.set(path + "name", record.name()); + state.set(path + "playtime-ticks", record.playtimeTicks()); + state.set(path + "added-at", record.addedAt()); + state.set(path + "source", record.source()); + } + state.save(stateFile); + } + + private void backupState() { + int keep = configManager.backupKeep(); + if (keep <= 0) { + return; + } + + File backupDirectory = new File(plugin.getDataFolder(), "backups"); + if (!backupDirectory.exists() && !backupDirectory.mkdirs()) { + plugin.getLogger().warning("Could not create backups directory for state file backups."); + return; + } + + File backup = new File(backupDirectory, "state-" + LocalDateTime.now().format(BACKUP_FORMAT) + ".yml"); + try { + Files.copy(stateFile.toPath(), backup.toPath(), StandardCopyOption.REPLACE_EXISTING); + pruneBackups(backupDirectory, keep); + } catch (IOException ex) { + plugin.getLogger().log(Level.WARNING, "Could not back up state file.", ex); + } + } + + private void pruneBackups(File backupDirectory, int keep) throws IOException { + File[] files = backupDirectory.listFiles((dir, name) -> name.startsWith("state-") && name.endsWith(".yml")); + if (files == null || files.length <= keep) { + return; + } + + for (File file : java.util.Arrays.stream(files) + .sorted(Comparator.comparingLong(File::lastModified).reversed()) + .skip(keep) + .toList()) { + Files.deleteIfExists(file.toPath()); + } + } + + public void clearRuntimeState() { + progressByWorld.clear(); + legacyUsers.clear(); + } + + public record LegacyUserRecord( + String name, + long playtimeTicks, + long addedAt, + String source + ) { + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/state/WorldProgress.java b/src/main/java/com/dirtsmp/dirtsmp/state/WorldProgress.java new file mode 100644 index 0000000..201bd96 --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/state/WorldProgress.java @@ -0,0 +1,87 @@ +package com.dirtsmp.dirtsmp.state; + +import java.util.HashSet; +import java.util.Set; + +public final class WorldProgress { + private boolean initialized; + private double currentSize; + private int expansionCount; + private int currentPhaseIndex; + private boolean paused; + private long lastExpansionAt; + private int uniquePlayersAtLastExpansion; + private Set sentReminders = new HashSet<>(); + + public boolean initialized() { + return initialized; + } + + public void initialized(boolean initialized) { + this.initialized = initialized; + } + + public double currentSize() { + return currentSize; + } + + public void currentSize(double currentSize) { + this.currentSize = currentSize; + } + + public int expansionCount() { + return expansionCount; + } + + public void expansionCount(int expansionCount) { + this.expansionCount = expansionCount; + } + + public int currentPhaseIndex() { + return currentPhaseIndex; + } + + public void currentPhaseIndex(int currentPhaseIndex) { + this.currentPhaseIndex = currentPhaseIndex; + } + + public boolean paused() { + return paused; + } + + public void paused(boolean paused) { + this.paused = paused; + } + + public long lastExpansionAt() { + return lastExpansionAt; + } + + public void lastExpansionAt(long lastExpansionAt) { + this.lastExpansionAt = lastExpansionAt; + } + + public int uniquePlayersAtLastExpansion() { + return uniquePlayersAtLastExpansion; + } + + public void uniquePlayersAtLastExpansion(int uniquePlayersAtLastExpansion) { + this.uniquePlayersAtLastExpansion = uniquePlayersAtLastExpansion; + } + + public Set sentReminders() { + return sentReminders; + } + + public void sentReminders(Set sentReminders) { + this.sentReminders = new HashSet<>(sentReminders); + } + + public boolean markReminderSent(String reminderKey) { + return sentReminders.add(reminderKey); + } + + public void clearReminders() { + sentReminders.clear(); + } +} diff --git a/src/main/java/com/dirtsmp/dirtsmp/task/ScheduleManager.java b/src/main/java/com/dirtsmp/dirtsmp/task/ScheduleManager.java new file mode 100644 index 0000000..7d2bafe --- /dev/null +++ b/src/main/java/com/dirtsmp/dirtsmp/task/ScheduleManager.java @@ -0,0 +1,144 @@ +package com.dirtsmp.dirtsmp.task; + +import com.dirtsmp.dirtsmp.DirtSMPPlugin; +import com.dirtsmp.dirtsmp.config.ConfigManager; +import com.dirtsmp.dirtsmp.config.MessageManager; +import com.dirtsmp.dirtsmp.model.CatchUpMode; +import com.dirtsmp.dirtsmp.model.ExpansionReason; +import com.dirtsmp.dirtsmp.model.TriggerDecision; +import com.dirtsmp.dirtsmp.model.TriggerMode; +import com.dirtsmp.dirtsmp.model.WorldRule; +import com.dirtsmp.dirtsmp.service.BorderManager; +import com.dirtsmp.dirtsmp.service.TriggerEvaluator; +import com.dirtsmp.dirtsmp.state.PlayerStatsManager; +import com.dirtsmp.dirtsmp.state.StateManager; +import com.dirtsmp.dirtsmp.state.WorldProgress; +import org.bukkit.Bukkit; +import org.bukkit.scheduler.BukkitTask; + +public final class ScheduleManager { + private final DirtSMPPlugin plugin; + private final ConfigManager configManager; + private final StateManager stateManager; + private final PlayerStatsManager playerStatsManager; + private final BorderManager borderManager; + private final TriggerEvaluator triggerEvaluator; + private final MessageManager messageManager; + private BukkitTask pollTask; + private BukkitTask saveTask; + + public ScheduleManager( + DirtSMPPlugin plugin, + ConfigManager configManager, + StateManager stateManager, + PlayerStatsManager playerStatsManager, + BorderManager borderManager, + TriggerEvaluator triggerEvaluator, + MessageManager messageManager + ) { + this.plugin = plugin; + this.configManager = configManager; + this.stateManager = stateManager; + this.playerStatsManager = playerStatsManager; + this.borderManager = borderManager; + this.triggerEvaluator = triggerEvaluator; + this.messageManager = messageManager; + } + + public void start() { + stop(); + applyCatchUp(); + pollTask = Bukkit.getScheduler().runTaskTimer(plugin, this::tick, configManager.pollIntervalTicks(), configManager.pollIntervalTicks()); + saveTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> { + stateManager.saveQuietly(); + playerStatsManager.saveQuietly(); + }, configManager.saveIntervalTicks(), configManager.saveIntervalTicks()); + } + + public void stop() { + if (pollTask != null) { + pollTask.cancel(); + pollTask = null; + } + if (saveTask != null) { + saveTask.cancel(); + saveTask = null; + } + } + + public void tick() { + Bukkit.getOnlinePlayers().forEach(playerStatsManager::markSeen); + handleReminders(); + handleTriggers(); + } + + private void handleTriggers() { + for (WorldRule rule : configManager.worlds().values()) { + WorldProgress progress = stateManager.progress(rule.key()); + TriggerDecision decision = triggerEvaluator.evaluate(rule, progress); + if (decision.due()) { + borderManager.expand(rule, decision.reason(), "automatic", false); + } + } + } + + private void handleReminders() { + long now = System.currentTimeMillis(); + for (WorldRule rule : configManager.worlds().values()) { + if (!rule.enabled() || !rule.reminders().enabled() || !rule.triggers().mode().usesTime()) { + continue; + } + WorldProgress progress = stateManager.progress(rule.key()); + if (progress.paused() || progress.lastExpansionAt() <= 0L || rule.triggers().timeIntervalMillis() <= 0L) { + continue; + } + + long next = progress.lastExpansionAt() + rule.triggers().timeIntervalMillis(); + if (now >= next) { + continue; + } + + for (long reminderMillis : rule.reminders().beforeMillis()) { + long reminderAt = next - reminderMillis; + String key = next + ":" + reminderMillis; + if (now >= reminderAt && progress.markReminderSent(key)) { + messageManager.announceReminder(rule, next - now); + stateManager.saveQuietly(); + } + } + } + } + + private void applyCatchUp() { + for (WorldRule rule : configManager.worlds().values()) { + WorldProgress progress = stateManager.progress(rule.key()); + if (!rule.enabled() || progress.paused() || rule.manualOnly() || rule.triggers().mode() == TriggerMode.MANUAL) { + continue; + } + if (rule.triggers().catchUpMode() == CatchUpMode.NONE) { + continue; + } + + TriggerDecision decision = triggerEvaluator.evaluate(rule, progress); + if (!decision.due() || !rule.triggers().mode().usesTime()) { + continue; + } + + int missed = triggerEvaluator.missedTimeExpansions(rule, progress); + if (missed <= 0) { + continue; + } + + int amount = 1; + if (rule.triggers().catchUpMode() == CatchUpMode.ALL && rule.triggers().mode() == TriggerMode.TIME) { + amount = Math.min(missed, configManager.maxCatchupExpansionsPerWorld()); + } + + for (int index = 0; index < amount; index++) { + if (!borderManager.expand(rule, ExpansionReason.CATCH_UP, "startup", false).success()) { + break; + } + } + } + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..6f8e428 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,414 @@ +# DirtbagMC progression configuration +# +# Size terminology: +# Every border size in this file is a FULL DIAMETER / WIDTH in blocks, matching Bukkit/Paper's WorldBorder size. +# Example: starting-size: 50000 creates a border that is 50,000 blocks wide, roughly 25,000 blocks from center to edge. +# +# Growth modes: +# INCREMENTAL - each expansion adds growth-amount to the current full diameter. +# PHASE - each expansion moves to the next exact size listed under phases. +# +# Border modes: +# WORLD_BORDER - use Minecraft/Paper's real WorldBorder. +# SOFT_BORDER - do not enforce the vanilla border; bounce players off an invisible DirtbagMC border with effects. +# +# Trigger modes: +# MANUAL, TIME, UNIQUE_PLAYERS, ONLINE_PLAYERS, ACTIVE_PLAYERS, +# TIME_OR_UNIQUE_PLAYERS, TIME_AND_UNIQUE_PLAYERS, +# TIME_OR_ONLINE_PLAYERS, TIME_AND_ONLINE_PLAYERS, +# TIME_OR_ACTIVE_PLAYERS, TIME_AND_ACTIVE_PLAYERS. + +settings: + dry-run: false + debug: false + poll-interval-seconds: 30 + apply-borders-on-startup: true + max-catchup-expansions-per-world: 20 + +state: + file-name: state.yml + save-interval-seconds: 300 + backup-on-save: true + backup-keep: 10 + +legacy-users: + # Admin scan command: + # /dirtsmp legacy scan + # Also supported: + # /dirtsmp add legacy users scan + # Players with at least this much Minecraft playtime are added to state.yml under legacy-users. + minimum-playtime: 1h + +history: + enabled: true + file-name: history.log + +gui: + enabled: true + title: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛA4A2A&lʙB6B35&lᴀ&#A8873F&lɢ&#D4AF37&lᴍ&#B9C63F&lᴄ &8| &#D4AF37&lBorders" + +webhook: + enabled: false + url: "" + timeout-seconds: 8 + content: "**DirtbagMC progression:** `{world}` expanded from `{old_size}` to `{new_size}` blocks. Reason: `{reason}`" + +command-hooks: + enabled: true + before-expansion: + # - "say Preparing expansion for {world} to {new_size}" + after-expansion: + # Useful for tools like Chunky: + # - "chunky world {world}" + # - "chunky border" + # - "chunky start" + +milestone-rewards: + enabled: true + # Commands run from console after an expansion reaches a configured milestone. + # Placeholders: {world}, {key}, {old_size}, {new_size}, {max_size}, {reason}, {actor}, {phase}, {phase_name}, {expansion_count} + every-expansion: [] + by-expansion-count: + # Example rewards: + # 1: + # - "broadcast DirtbagMC milestone 1 reached in {world}!" + # - "crate key giveall beta 1" + 1: [] + 3: [] + 5: [] + by-phase-index: + 1: [] + by-phase-name: + Frontier Era: [] + First Ring: [] + +worlds: + overworld: + enabled: true + world: world + center-x: 0.0 + center-z: 0.0 + starting-size: 50000 + border-mode: SOFT_BORDER + growth-mode: INCREMENTAL + growth-amount: 10000 + max-size: 200000 + transition-seconds: 300 + manual-only: false + import-current-border: false + no-shrink-protection: true + enforce-max-size: true + command-hooks: + before-expansion: [] + after-expansion: [] + milestone-rewards: + enabled: true + every-expansion: [] + by-expansion-count: + 1: [] + 2: [] + 3: [] + by-phase-index: {} + by-phase-name: {} + legacy-access: + enabled: true + # This protects beta builds from being stranded when progression starts after launch. + # The plugin expands the initial/saved border enough to include known locations plus padding. + include-current-border: false + include-online-players: true + include-offline-last-locations: true + include-respawn-locations: true + # When true, player last/respawn locations only count if the player is in the scanned legacy-users list. + player-locations-require-legacy-user: true + reconcile-on-startup: true + allow-start-above-max-size: true + padding: 1024 + # Add homes, towns, bases, or Essentials homes that cannot be discovered from Paper playerdata. + # Border math uses full diameter/width, centered on center-x/center-z. + locations: [] + # Example: + # locations: + # - name: "Beta town" + # x: 31500 + # z: -22400 + soft-border: + # Premium invisible border mode. Used only when border-mode is SOFT_BORDER. + # release-vanilla-border removes any old vanilla border enforcement by setting it to vanilla-border-size. + release-vanilla-border: true + vanilla-border-size: 59999968 + ignore-creative: false + ignore-spectator: true + bypass-permission: dirtsmp.bypass.softborder + inside-buffer: 1.5 + bounce-strength: 1.85 + vertical-boost: 0.35 + protect-mounted-entities: true + # Gentler values keep horses, camels, boats, and minecarts from taking weird launch angles. + mounted-bounce-strength: 1.15 + mounted-vertical-boost: 0.12 + max-outside-distance-before-teleport: 24 + cooldown: 900ms + message: "{prefix}&#D4AF37&lThe DirtbagMC border &7throws you back." + action-bar: "&#D4AF37&lBorder reached &8| &7turn back" + sound: ENTITY_SLIME_JUMP + volume: 0.65 + pitch: 0.65 + particles: + enabled: true + # DUST uses the DirtbagMC gold color. Other Bukkit particle names also work. + particle: DUST + count: 3 + offset: 0.05 + speed: 0.0 + interval-ticks: 10 + view-distance: 96 + spacing: 3 + broadcasts: + enabled: true + world-only: false + message: "{prefix}&#B9C63FThe dirt expands. &#D4AF37&l{world} &#B9C63Fhas grown from &#A8873F{old_size} &#B9C63Fto &#D4AF37&l{new_size} &#B9C63Fblocks." + title: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛA4A2A&lʙB6B35&lᴀ&#A8873F&lɢ&#D4AF37&lᴍ&#B9C63F&lᴄ" + subtitle: "&#B9C63F{world} &8| &#D4AF37{old_size} &7-> &#D4AF37{new_size}" + action-bar: "&#D4AF37&l{world} &7border is now &#B9C63F{new_size} &7blocks wide" + sound: ENTITY_ENDER_DRAGON_GROWL + volume: 0.7 + pitch: 1.25 + reminders: + enabled: true + before: + - 1d + - 12h + - 1h + - 10m + message: "{prefix}&#D4AF37&l{world} &7expands in &#B9C63F{time_left}&7." + action-bar: "&#D4AF37&l{world} &7expands in &#B9C63F{time_left}" + sound: BLOCK_NOTE_BLOCK_PLING + volume: 0.45 + pitch: 1.4 + triggers: + mode: TIME_OR_UNIQUE_PLAYERS + time-interval: 7d + unique-players-every: 50 + online-players-threshold: 35 + active-players-threshold: 120 + active-window: 7d + exclude-vanished: true + player-trigger-cooldown: 12h + catch-up: ONE + phases: + - name: Spawn Era + size: 50000 + message: "" + - name: Frontier Era + size: 75000 + message: "" + + end: + enabled: true + world: world_the_end + center-x: 0.0 + center-z: 0.0 + starting-size: 2000 + border-mode: SOFT_BORDER + growth-mode: PHASE + growth-amount: 2000 + max-size: 20000 + transition-seconds: 180 + manual-only: false + import-current-border: false + no-shrink-protection: true + enforce-max-size: true + command-hooks: + before-expansion: [] + after-expansion: [] + milestone-rewards: + enabled: true + every-expansion: [] + by-expansion-count: + 1: [] + 2: [] + by-phase-index: + 1: [] + 2: [] + by-phase-name: + First Ring: [] + Chorus Frontier: [] + legacy-access: + enabled: true + include-current-border: false + include-online-players: true + include-offline-last-locations: true + include-respawn-locations: true + player-locations-require-legacy-user: true + reconcile-on-startup: true + allow-start-above-max-size: true + padding: 512 + locations: [] + soft-border: + release-vanilla-border: true + vanilla-border-size: 59999968 + ignore-creative: false + ignore-spectator: true + bypass-permission: dirtsmp.bypass.softborder + inside-buffer: 1.5 + bounce-strength: 2.05 + vertical-boost: 0.45 + protect-mounted-entities: true + mounted-bounce-strength: 1.2 + mounted-vertical-boost: 0.12 + max-outside-distance-before-teleport: 18 + cooldown: 900ms + message: "{prefix}A2416&lThe End border &7snaps you back." + action-bar: "A2416&lThe End border &8| &7turn back" + sound: ENTITY_ENDERMAN_TELEPORT + volume: 0.55 + pitch: 0.8 + particles: + enabled: true + particle: DUST + count: 3 + offset: 0.05 + speed: 0.0 + interval-ticks: 10 + view-distance: 72 + spacing: 2.5 + broadcasts: + enabled: true + world-only: false + message: "{prefix}A2416&lThe End &#B9C63Fhas entered &#D4AF37&l{phase_name}&#B9C63F: &#D4AF37{new_size} &#B9C63Fblocks wide." + title: "A2416&lThe End Opens" + subtitle: "&#D4AF37{phase_name} &8| &#B9C63F{new_size} blocks" + action-bar: "A2416&lThe End &7border is now &#D4AF37{new_size}" + sound: ENTITY_ENDER_DRAGON_DEATH + volume: 0.8 + pitch: 1.0 + reminders: + enabled: true + before: + - 12h + - 1h + - 10m + message: "{prefix}A2416&lThe End &7expands in &#D4AF37{time_left}&7." + action-bar: "A2416&lThe End &7expands in &#D4AF37{time_left}" + sound: BLOCK_NOTE_BLOCK_PLING + volume: 0.45 + pitch: 1.6 + triggers: + mode: TIME_AND_UNIQUE_PLAYERS + time-interval: 10d + unique-players-every: 100 + online-players-threshold: 40 + active-players-threshold: 150 + active-window: 7d + exclude-vanished: true + player-trigger-cooldown: 12h + catch-up: ONE + phases: + - name: Outer Silence + size: 2000 + message: "" + - name: First Ring + size: 4000 + message: "{prefix}A2416&lThe End &#B9C63Fstirs. &#D4AF37The first outer ring &#B9C63Fis now reachable." + - name: Chorus Frontier + size: 6000 + message: "{prefix}A2416&lThe End &#B9C63Fgrows again. &#D4AF37New islands await." + - name: Dragon's Wake + size: 8000 + message: "{prefix}A2416&lThe End &#B9C63Fborder has expanded to &#D4AF37{new_size} &#B9C63Fblocks." + + nether: + enabled: false + world: world_nether + center-x: 0.0 + center-z: 0.0 + starting-size: 10000 + border-mode: SOFT_BORDER + growth-mode: INCREMENTAL + growth-amount: 2500 + max-size: 50000 + transition-seconds: 120 + manual-only: false + import-current-border: false + no-shrink-protection: true + enforce-max-size: true + command-hooks: + before-expansion: [] + after-expansion: [] + milestone-rewards: + enabled: true + every-expansion: [] + by-expansion-count: + 1: [] + by-phase-index: {} + by-phase-name: {} + legacy-access: + enabled: false + include-current-border: false + include-online-players: true + include-offline-last-locations: true + include-respawn-locations: true + player-locations-require-legacy-user: true + reconcile-on-startup: true + allow-start-above-max-size: true + padding: 512 + locations: [] + soft-border: + release-vanilla-border: true + vanilla-border-size: 59999968 + ignore-creative: false + ignore-spectator: true + bypass-permission: dirtsmp.bypass.softborder + inside-buffer: 1.5 + bounce-strength: 1.9 + vertical-boost: 0.35 + protect-mounted-entities: true + mounted-bounce-strength: 1.15 + mounted-vertical-boost: 0.12 + max-outside-distance-before-teleport: 18 + cooldown: 900ms + message: "{prefix}A4A2A&lThe Nether border &7throws you back." + action-bar: "A4A2A&lNether border &8| &7turn back" + sound: ENTITY_BLAZE_HURT + volume: 0.55 + pitch: 0.8 + particles: + enabled: true + particle: DUST + count: 3 + offset: 0.05 + speed: 0.0 + interval-ticks: 10 + view-distance: 72 + spacing: 2.5 + broadcasts: + enabled: true + world-only: false + message: "{prefix}A4A2A&lThe Nether &#B9C63Fhas expanded to &#D4AF37{new_size} &#B9C63Fblocks." + title: "A4A2A&lNether Expanded" + subtitle: "&#A8873F{old_size} &7-> &#D4AF37{new_size}" + action-bar: "A4A2A&lThe Nether &7border is now &#D4AF37{new_size}" + sound: ENTITY_WITHER_SPAWN + volume: 0.65 + pitch: 1.1 + reminders: + enabled: true + before: + - 1h + - 10m + message: "{prefix}A4A2A&lThe Nether &7expands in &#D4AF37{time_left}&7." + action-bar: "A4A2A&lThe Nether &7expands in &#D4AF37{time_left}" + sound: BLOCK_NOTE_BLOCK_PLING + volume: 0.45 + pitch: 1.2 + triggers: + mode: MANUAL + time-interval: 7d + unique-players-every: 50 + online-players-threshold: 30 + active-players-threshold: 100 + active-window: 7d + exclude-vanished: true + player-trigger-cooldown: 12h + catch-up: NONE + phases: [] diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml new file mode 100644 index 0000000..5dc69ed --- /dev/null +++ b/src/main/resources/messages.yml @@ -0,0 +1,76 @@ +prefix: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛA4A2A&lʙB6B35&lᴀ&#A8873F&lɢ&#D4AF37&lᴍ&#B9C63F&lᴄ &8| &7" + +commands: + no-permission: "{prefix}&cYou do not have permission to do that." + player-only: "{prefix}&cOnly players can use that command." + unknown-world: "{prefix}&cUnknown configured world: &#D4AF37{world}" + invalid-number: "{prefix}&cThat size must be a positive number." + reload: "{prefix}&#B9C63FConfiguration, messages, and DirtbagMC progression rules reloaded." + saved: "{prefix}&#B9C63FState saved." + help: + - "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛA4A2A&lʙB6B35&lᴀ&#A8873F&lɢ&#D4AF37&lᴍ&#B9C63F&lᴄ &8| &#D4AF37&lBorder Progression" + - "B6B35/dirtsmp status [world] &8- &7view progression" + - "B6B35/dirtsmp next [world] &8- &7view upcoming triggers" + - "B6B35/dirtsmp legacycheck &8- &7scan beta home safety" + - "B6B35/dirtsmp tpborder [side] &8- &7teleport near a border wall" + - "B6B35/dirtsmp legacy scan [1h] &8- &7mark legacy players by playtime" + - "B6B35/dirtsmp legacy list &8- &7show marked legacy players" + - "B6B35/dirtsmp expand &8- &7expand now" + - "B6B35/dirtsmp setsize [force] &8- &7set border size" + - "B6B35/dirtsmp pause &8- &7pause automatic progression" + - "B6B35/dirtsmp resume &8- &7resume automatic progression" + - "B6B35/dirtsmp reset [force] &8- &7reset progression" + - "B6B35/dirtsmp gui &8- &7open admin panel" + - "B6B35/dirtsmp reload &8- &7reload config" + status-header: "{prefix}&#B9C63FTracking &#D4AF37&l{count} &#B9C63Fconfigured world(s)." + status-line: "&8- &#D4AF37&l{key} &8(&7{world}&8) &7enabled: &#B9C63F{enabled} &7size: &#B9C63F{size} &7next: &#D4AF37{next_size} &7mode: &#A8873F{border_mode} &7phase: &#A8873F{phase} &7paused: &c{paused}" + next-line: "{prefix}&#D4AF37&l{world} &7next trigger: &#B9C63F{trigger}&7. Next size: &#D4AF37{next_size}&7." + legacy-check: "{prefix}&#D4AF37&l{world} &7legacy scan: &#B9C63F{locations} &7location(s), required size &#D4AF37{required_size}&7, recommended startup size &#B9C63F{recommended_size}&7. Farthest: &#A8873F{farthest}&7." + legacy-scan: "{prefix}&#B9C63FScanned &#D4AF37{scanned} &#B9C63Fplayer(s). Matched &#D4AF37{matched} &#B9C63Fat &#D4AF37{minimum}&#B9C63F+. Added &#D4AF37{added}&#B9C63F. Legacy total: &#D4AF37{total}&7." + legacy-list-header: "{prefix}&#B9C63FLegacy users: &#D4AF37{count}&7. Showing up to 20." + legacy-list-line: "&8- &#D4AF37{name} &7playtime: &#B9C63F{playtime} &7source: &#A8873F{source}" + legacy-add: "{prefix}&#D4AF37{name} &7was &#B9C63F{status} &7as a legacy user." + legacy-remove: "{prefix}&#D4AF37{name} &7legacy status: &#B9C63F{status}&7." + tp-border: "{prefix}&#B9C63FTeleported to the &#D4AF37{side} &#B9C63Fborder wall for &#D4AF37{world} &8(&7size {size}&8)&7." + tp-border-failed: "{prefix}&cCould not teleport to &#D4AF37{world}&c border: &7{reason}" + expanded: "{prefix}&#B9C63FExpanded &#D4AF37&l{world} &#B9C63Ffrom &#A8873F{old_size} &#B9C63Fto &#D4AF37{new_size}&7." + expand-failed: "{prefix}&cCould not expand &#D4AF37{world}&c: &7{reason}" + set-size: "{prefix}&#B9C63FSet &#D4AF37&l{world} &#B9C63Fborder size to &#D4AF37{new_size}&7." + set-size-failed: "{prefix}&cCould not set &#D4AF37{world}&c: &7{reason}" + paused: "{prefix}&#D4AF37&l{world} &7automatic progression is now paused." + resumed: "{prefix}&#B9C63F{world} automatic progression resumed." + reset: "{prefix}&#B9C63FReset progression for &#D4AF37&l{world}&7." + gui-disabled: "{prefix}&cThe admin GUI is disabled in config." + +expansion: + default-message: "{prefix}&#B9C63F{world} expanded from &#A8873F{old_size} &#B9C63Fto &#D4AF37&l{new_size} &#B9C63Fblocks!" + default-title: "A2416&lDirtbagMC" + default-subtitle: "&#B9C63F{world} &8| &#D4AF37{old_size} &7-> &#D4AF37{new_size}" + default-action-bar: "&#B9C63F{world} &7is now &#D4AF37&l{new_size} &#B9C63Fblocks wide" + max-reached: "The world is already at its configured maximum or final phase." + paused: "Automatic progression is paused." + manual-only: "This world is configured for manual-only progression." + missing-world: "The Bukkit world is not loaded or does not exist." + dry-run: "Dry-run mode is enabled; no border was changed." + no-shrink: "No-shrink protection blocked a smaller size. Add 'force' to the command to override." + +reminders: + default-message: "{prefix}&#D4AF37&l{world} &7expands in &#B9C63F{time_left}&7." + default-action-bar: "&#D4AF37&l{world} &7expands in &#B9C63F{time_left}" + +gui: + world-name: "&#D4AF37&l{key}" + world-lore: + - "&7World: &#D4AF37{world}" + - "&7Enabled: &#B9C63F{enabled}" + - "&7Size: &#B9C63F{size}" + - "&7Next: &#D4AF37{next_size}" + - "&7Mode: &#A8873F{border_mode}" + - "&7Trigger: B6B35{trigger}" + - "&7Phase: &#A8873F{phase}" + - "&7Paused: &c{paused}" + - "" + - "&#D4AF37Left-click &8- &7expand now" + - "&#D4AF37Right-click &8- &7pause/resume" + refresh: "&#B9C63FRefresh" + close: "&cClose" diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..28b4ec2 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,65 @@ +name: DirtSMP +version: 1.0.0 +main: com.dirtsmp.dirtsmp.DirtSMPPlugin +api-version: '1.21' +authors: + - DirtbagMC +description: DirtbagMC-themed configurable world-border progression for Paper SMP servers. +softdepend: + - PlaceholderAPI +commands: + dirtsmp: + description: Manage DirtbagMC world progression. + usage: /dirtsmp + aliases: + - dsmp + - dirtborder +permissions: + dirtsmp.admin: + description: Full access to every DirtbagMC progression command. + default: op + children: + dirtsmp.status: true + dirtsmp.expand: true + dirtsmp.setsize: true + dirtsmp.pause: true + dirtsmp.resume: true + dirtsmp.reload: true + dirtsmp.gui: true + dirtsmp.reset: true + dirtsmp.legacy: true + dirtsmp.tpborder: true + dirtsmp.bypass.softborder: true + dirtsmp.status: + description: View DirtbagMC progression status and legacy access scans. + default: true + dirtsmp.expand: + description: Manually expand a configured world border. + default: op + dirtsmp.setsize: + description: Set a configured world's border size. + default: op + dirtsmp.pause: + description: Pause automatic progression for a world. + default: op + dirtsmp.resume: + description: Resume automatic progression for a world. + default: op + dirtsmp.reload: + description: Reload DirtbagMC progression configuration. + default: op + dirtsmp.gui: + description: Open the DirtbagMC admin GUI. + default: op + dirtsmp.reset: + description: Reset a world's DirtbagMC progression state. + default: op + dirtsmp.legacy: + description: Scan, list, add, and remove DirtbagMC legacy users. + default: op + dirtsmp.tpborder: + description: Teleport near a configured world border for visual inspection. + default: op + dirtsmp.bypass.softborder: + description: Bypass DirtbagMC soft border bounce and correction. + default: op diff --git a/target/DirtSMP-1.0.0.jar b/target/DirtSMP-1.0.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..b957e8b13ed103602d727b0cc69468b2d76ded3a GIT binary patch literal 122733 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 z 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 J01zOc{{z(nPEY^< literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/DirtSMPPlugin.class b/target/classes/com/dirtsmp/dirtsmp/DirtSMPPlugin.class new file mode 100644 index 0000000000000000000000000000000000000000..431edcedda014306da7e7930f6610a3a7b955a7e GIT binary patch literal 8787 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 literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/config/ConfigManager.class b/target/classes/com/dirtsmp/dirtsmp/config/ConfigManager.class new file mode 100644 index 0000000000000000000000000000000000000000..fbf3938372397adaf9463bc79908ac9d0355a832 GIT binary patch literal 23575 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 Xnu_21@H+>;AEk0*zwtOe>X!c>JhzZo literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/config/MessageManager.class b/target/classes/com/dirtsmp/dirtsmp/config/MessageManager.class new file mode 100644 index 0000000000000000000000000000000000000000..1b07e0c985dded325326594918d20ad5be0fa8fa GIT binary patch literal 14038 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 literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/gui/AdminGuiManager$GuiHolder.class b/target/classes/com/dirtsmp/dirtsmp/gui/AdminGuiManager$GuiHolder.class new file mode 100644 index 0000000000000000000000000000000000000000..f4d425b6cb6a30d51ca2cf0ade8731df04fb9b30 GIT binary patch literal 837 zcmb7C%Wl&^6g^`nb?q3Eraa55gwRJ87=*;eRisi063H!#ph_$!$;h478CTClQf^1;K)kCKef7sg8s) zsXk^{j#3>5!~EbtnqZa>`e%sH!DWW_pFw95+<}Z0H@OxJPkt4!aT=D>=_?~he0VN3 zFj}fO?1l`Lt#nNF^++nQlTU_1zvRP&GR-I*@nn~4S;W(1#q7z9;c@ie6C)}=7H0NR zhSu8p-=@Lf4bohX#4}k)y7b$BJSc|FFux-*(@Qhs;2y*3KT0D#eWirnN_dtDdXzi+ zT|A`kvp|G&Hj5o0{|arPg{x&|vGlEC5$Wa{MP1nB0gcWHSmz_0S0`}a!+TpIuhUtq zFYp-^e8CcV{wW?eaFZg2Tev;#6_71@RXgv{ctehbqCr6gd_7lOofg};Q=*n}x3n#^ N$$EsZp@sWc`T>@%(6;~p literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/gui/AdminGuiManager.class b/target/classes/com/dirtsmp/dirtsmp/gui/AdminGuiManager.class new file mode 100644 index 0000000000000000000000000000000000000000..1115e21a2710392c809d53e6cb46528bfdcb5a98 GIT binary patch literal 12102 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 literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/hook/DirtSMPPlaceholderExpansion.class b/target/classes/com/dirtsmp/dirtsmp/hook/DirtSMPPlaceholderExpansion.class new file mode 100644 index 0000000000000000000000000000000000000000..275bd37f6ca4ea14a5d68aff2a81995636627d58 GIT binary patch literal 6660 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 literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/hook/PlaceholderHook.class b/target/classes/com/dirtsmp/dirtsmp/hook/PlaceholderHook.class new file mode 100644 index 0000000000000000000000000000000000000000..aa1d9a45f3a53ee2237d1f1d863eb4a567b3582a GIT binary patch literal 1088 zcma)*VQ_MD#cKKH%%@Z;x~ZvggiPelY#1u+d55NF67neR=*Hl4ol zvU|iWpCSHGIKqF#5UtiaNhFX|plVow#!$7~q0tkbKOTO0^Wr(<>4%Z&jD_o{NFgK5Tx6(J{{t0z zx}+h8JVTQI7Gij?wEJHhXviI`Ybc<|knnh4jD79}r}tLH6>KQDs^J>0Gi)w1IQJHw zRvsO*rA9r!Rf!PilMG-WeHNPL$A-6OS~y^WE>>x%Zp-t-!G!|e*xKrj@y=k+v?AN3ncoNg7Hu$geu zmU&6fR&Zj0IG#~_#BZAjVGHh4uvNu2Y-i}V>}-6tn9J#IoX>O9je8-NBROs9=5aC2 z(30h@msp0ak!aGt&~$aTR9-?J!q}-`mx|r!Wf%-f`D)o>XwGYPF2y=A+V-!q1@MctJ#2x3tcF;Q==jyBKf3wMoD2!j|>Fsveu`xzoZgoAQPg>}`kjf}&v zkrE;+xajIed`v4OTCoogDA=#!K^&kRtNBVR=QX3qY31gcl_Qp6Q1R=QNdOPy5e1K` zNZ=qtXBD7oMCsC9ZfmYZb=h5mbgDd0IEQdp!Q(1*-BBv(D$ZRK24zlj#`!hcW$Lx_ zV#P5P$1%##V9M#vyV$-CO(-}?Eftilyp`cbd|D)DvS{!SPT;hPCxm~6o7$|wGh%K` z#W>DTX~{H>Yk5v_)_6*}wr*yrq|RbeK}y9rJju}KuRU3`62Nnqw6sjQWSgmEB?%AK zw@S`ZYNqj&f~QrS7c@33Hz2lzAb5rF44zeRQN;|Nt5GYDA#EtXQq&CE(5{GA3I0q) z&xbIJw1SKZj!O(XgR)kmLteurt*q1>(Qws`Gia9568F9)+DTF8ROqIFAPr*KGwkQ$ka5ZUL#X2Yl!KOhHl!#8zMvPFqF|1GOCLb^6XSsb&v>5ez(n@Q_ zlxFMVy*%0A&goPRLxJR1IYJXu-?v<_C}kDxG#}MPDRk7{iH1d6la3v= zEZ1>utuV&jIVJk-&t@mOd{rXfk^jLXY>+~iq zD_1NeeDYeB8{bUL!gHkp4=}&rv-d8Wq#Y|rj%bFFqSKWsN-6?A zt!t!lOYAmSY+wOOY;XbVVnYkq80%?RK-bUowGPh<8#K{xCn0p91>I<)8)XM!_R!x8 z^ku(?gctD=y$O54gvlqtrA|HxHudsJ@DXfoNEU+4F|rX>YU~%R8(2ioB6d{$i+exl z?%9g%(qFe{Tk)sj6}(D#uaQ*~jjz+l@CM!_JArAE;42AMOh4KMmQRsi8UBRKhaPm(I!Jo%^d`K2` b_y`}<*i0)vqyJAR5+FoRU*lVRkDmVk)=l9* literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/model/BorderMode.class b/target/classes/com/dirtsmp/dirtsmp/model/BorderMode.class new file mode 100644 index 0000000000000000000000000000000000000000..004aae134731b2e12d5c529a9f2fe67186ca648d GIT binary patch literal 1528 zcma)6U2hXd6g}fzXYKVSPC}Xz^N}VE!8Qdql#~yLkS_;PIT*!|it>Oq_F@*+yViP* z+Bg0N{~`n_L?94PJn*Bco>_0P5g^%;edo@cd*|FU_xjVnZ{7n~#g>Mcz+BC17V5Si zw41HZw&~R^r?BSvb<5x8Itc;QKpaB?#-Ej)^2Wop%FaePV5A2qxt%6&e`Y!ztMW)-G20it?wIX%v2UXq__o_9_TL~A zg!wZDPT-`#(30)i!Lq<)f2t^QE4QoTG^REDWMBrf0;4@HT@2aS?KQH0DxhR@e@XnD zfisxrJMeze{j$a=wZvr&2+fh+tS3eObc**%Db_)D}hLLV3TBOLnxA@Q*<@K?EGbWym5 zBE2Pk;|zN)p&Sz^VX_5Ug-2Tc+{xFNddcPYGHJLjs|YhCV=RYjewV|L8>F~Girtd_ zp(O&-`H#@^hdA{D+5wb(E`L76*(2ol%hD$-jVWYso+M4JeWei4X$I4(N=|;-dZlz%inx-9{foT~?Y@dvAgImXr9H&~p z^>6H7P%6=?Ri}R1hyAE&=RLd;q*h9#3Hx0(VA_E5~aq~Dy}%C3nkm^JX3g*nU%6whio#jqN`cbnBeqHN{LuM)ptp^OS| zfakN^^F5}iCGM()FC=axiFUeOFK#$J&!o(YvhkWg{T(G$hSb}@lECa)wHra;wVhx+ zZudK0m^6QPy>8-1VIEm|=Om0_=$g8s+S8yrZ1G20Kl zt$t_Ui+^$U1MZaU$aR8UC-$YEZt9`aQL(d7^Srk5N>2R}J6F^LzmBt1FwX$mdB z9Xd%rCY9>%GVmadxR+-*xMg7?I?)p-I;=qetl>M$PQTIu{c71J1K3;&9FHu>(K%jV0zC0pw+`olo z;_LKgY30W}#^?z?c#F~zJ%JVe=2T20p2LKU!e6JA46`JvFEQ~JLgF*$@ZWLAIz?d> z-_u*;H%r=63FWJSrjjkuY780GtCwEj(lajKm&xG&7`srJ3K_P;HNUqNrNI*Ku*B1r zp3oA3nd)CKt0(yM4;aTVj=22%1g!eYBU}1}jbVztU1Jfl-w7%QrW%3C$S5NhFwK?h zXM!oGp`(ers$-9b<=n_B$9#!zo?}!Bc$%}M_&S@&vW*-T P*kUGC>pp%YE(7~t5aLXy literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/model/ExpansionReason.class b/target/classes/com/dirtsmp/dirtsmp/model/ExpansionReason.class new file mode 100644 index 0000000000000000000000000000000000000000..9e8f036864738bc410383bc42cf939ed9cda5618 GIT binary patch literal 1728 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 literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/model/LegacyAccessReport.class b/target/classes/com/dirtsmp/dirtsmp/model/LegacyAccessReport.class new file mode 100644 index 0000000000000000000000000000000000000000..a3d1342029d1014b8196d0dafd396295916d9a24 GIT binary patch literal 1925 zcmb7F+iuf95Ix&Cb(%V~2`#1E%ALeOL%Bl=lr~%a76V9`Ff61o$T0ebzvJZ= z_twhKgyYKw!{Dszx#})Ms#sbXLIy)7vKEG6F>_G(LSLT`y@tB1v94CoM7T%(K7xqY#i0{ej8{w!*=>@|?v06*q#F~^=7>p>| zk-F=NrEYsw$me{u6>3PLw^q1x^?Q71sHRInP47)KYNADqFTpfK~H6AwxFF985f5IMyFZ z6^jza3=N0@`j%2C=$S-M1wBu~ou&ru888}dCkSQ zY#{%QE(UOl)R!J8~ak8lPaKKGQ)e;dLnV8hOaYUTwmpF)%zw-9&Tke9Si!^O^Q01%t1I z+8KYuWX4Q+8eSK6F`oo0x zqjgD>F6Ng$VWeXkM)LNuh{z)2T)!N)jrVUUm^9A!)30Z2P|InVF5?RAV^q1Q?V_fO gTJ9>{j8a0ksb(2m!wuZTE&96!vUEC+Djs6;4`nT%&;S4c literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/model/PhaseDefinition.class b/target/classes/com/dirtsmp/dirtsmp/model/PhaseDefinition.class new file mode 100644 index 0000000000000000000000000000000000000000..eeb17c449650057f9c64621decdf57f1ffcc8f0b GIT binary patch literal 2156 zcmb7G-Ez}b5dKaQTaHnh*d!q)+%S0LrMP5E9xRN2W7I;JrrFyY#5DUh%A13wRa$KAHrDzD=xX2~jWK9ZHF zuyS6V&8R`DwZs|aMDdtJXFA2`f@_M2{9 z=5C}fu{Q%V_dG8QLf`I0$cl$$>4m= z^WJtlR-!<03nVl#@WqD?jcd^GolfZaNyu8u4uYg2_VMbVlVtR-hGoC_dewEMuUyJN zLrvg9Kj$GiXm}!Ua=0mduqa_%rIj#7Bb7#;7Zu&|yl#}Fg`y;>Y-w?)P51MhQhbg1nL(V9qdYCf`=^hr0g4V<07qT3~C3>dG3kZ9e zp6k%)S;R6-l6+%E{Jy9-G0!5eIkM*)^Gc~3GfS%*bJK8z?u9hLK|l}$WDHt-pj8Lt zXt!M@$4s0hhtP~&oCH48^&qo|g%&f*(GL4*0Q@ZQ$4KxJuEp>b3dU(=jHO-5=sWoN z$Y#GV8C=H=#rw!U5mWsJQpR77>453L)*X${VKq|c%K^PGUQ5kKJ8PWx#oGEIiZIvshW7#P3Qf9-{c6PWq&kLHR z89JpGtDJ#t35Y8)+R;JSCRZ)7IGjHpY2kB4)dj|y6Py(Q2^pP8GPD)BRWP-R?NB80 zHTmndsp&fu+>NUeuF2>TgiQq3(U7>6-0HhrK1y`G!k!xpxzyGrgq~HC-b|3~eKPuS ziy>?qu1bdKR1@4fE3@gdzykJ;jJp^h*j?4youf@)7`qhgvOu{fV+bj#o`0&UC5vI^ zG8jH(Z?^Y%!Oo_)7=o^v_PnO^)oOX0n;YtOiS#A%MnNrYs-`CXy~&`xs}XOrBT?@N z9o!Xe?;6Fos;QQ_%}oLb>V{4nWbK(#k-mi649Qdj$DMvyX6W+6`)X!}D6husEjejq z$tYo7{Kzvz*Nv)K;ES5DEm>Fpn4qC8J89XDS;Md`+f*yAD^?gU7`mwevu?v>eQndu zAkh<}C*B^yY!q{tm#`pX5ic3~YbBUf-PX!H}vClJZ;F78r zOWYdB8^(UMlJz;`b*gX1OWgQU;qzX98>T;HuB2L)f5OA~a2CYR{$J;uZiu|B>)do8 zxh3Hh!(grECN)UNGxRkNrA=`Q#yDLgE!1BDB*fiGUkPysIwM_WG={;%EkjXh`uj*@ zkjA9)6HMtnf~1TbA*PHTA$~~nEl}tkq8x!aOmD@F#|TF0k1`z|v7U681bb3kn-o7r z-?0D%fR;d$Km-}wCpbYqK|(}g>=(lC1K-g_BCda<2|sJZVU^uL2N#Ib%{K0Vh*5w@ z$}w&N_35r?H(l~X$LR)p&>;Ob@U@P;arzJ*dg+s-Ra6{ShL16LUVgV5bLv?bkMP*h z-G(O~QpWiQT9qG=n#pMOU>JEi(I7e2%z|2S=c$8!=J-XDgLvxL^bF5w6dUaxHTR&o f$1F=L9i%QtdmP3jrZ9~eEYWO~W|#3AE9m(L10C3D literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/model/TriggerMode.class b/target/classes/com/dirtsmp/dirtsmp/model/TriggerMode.class new file mode 100644 index 0000000000000000000000000000000000000000..c19ec4e504d6d1183f8614a03c50fb27095afacb GIT binary patch literal 2267 zcma)6T~ixn6n>Ts>}FYDp)KDQi%^o(7O5cCP$Z?PrY3WU9VCu9Q`i~rCldKyXm>PoZ}^^_@TgOq7z*Tx)PCOIuchf6TgscSQzUj{KD9` z8B0XPLAME09Q3A>u}A6X_If;$iEgCCRj-LYd9{{|$C7PV{U!$FRb+KDw$*lZ*~Arb zH7Fu%uWf9fcRXZbSU`p(B$B-U3e2d9u{Jm7NxWg=YP*~Bm^V#aYjbm+@LMLP+TEPT zTsJX;fC6o5D-usfQwjo4E?9K~A>7aqF!45SD(KASOGQV)L)gR;?kcd(xHK?i#ZTCVDgM@ET4CB zc0S_ess*Q5i9X9ZM-{hJ)X^zIF6i*Zm zK{g}dRQ5}|>$XQs#qmhVSJ`(d{3$nAv@2DQrtHcVFZR5W7#*IMJZD$IRLh{et~g;1 zs-&%;CsnbthY9kOl$2abQgQ=H$u%P-SBaF|7gBO3NXdr}Sfk=` zGlE9HkC9=#C)6e>#mA^)`UJt32pv;X@Hv0Gj z$&%tODK=92nvw`ig#LsPI>GzjL#snQ=HKNL+&e||I4*R;`Y?h~d`uD|-$7O$=tno# zejoSyC{M;P1d-1mU5>#A2YWKJ3FKsE`@GYkcB>CH`cdHGE`LR33$zfGS`5{p)o_#I zQVrKB25aP2$4Cvs6yr7YQ%uxIwvNdkq4|D-PyLyu4O)#hj7u7gvMx4@A%b^7ol{p7 z%Mj1E8QLLH@kv}8CN{>|5G^top zS=XgFvtU;-f_l;VO#i%Vajq6vhM#;y%Cyd))k6Y>Qs$dV(;r2L-F zv|rkprc+O6m}!4Ne^jS?lCV{9jq~BO=VSM|eRlVD`TL)>cL1x_RUXi12d(v&AcIN~Eapn+7E$F(e@6>Q$CEU1p!CMoBjeo$!PLS4l~@ zh7nS!oBE47+cwlwW;+`v%swTRJXaZ&aGk4+TBf0!=PJ7un>iKBD07u@2{$|yRde-| zwkkIz-12(rQ|1KRox3_^=V?~5#a0FR01AhFU&06YkYFpSQ(5;oExZF56 z=C@ncASRj z?KqLRx8qcNWKKrHDxGHH1P+1)0fI&#BE3J;S#O9;(j((LJ`|8yC!Id@46f3HppkSxfLtWWAw~HN3&5|my&M)F zNeqyNY4mKgF@F!>j$CcNga?873TgA0#^m`keDr_zqrOfvD=~bAho0a4$OlL{?>AB8 zq3Oh;FMy$cEl_A0`*G#Q1HW|O&@#T(3_7>88_PUQC? vzYF<2$nQXY|Jfqzo8-h(vX5aKkFbMX>|q~|@dRJu0EeWJAPo%+M;QMXNEL@D literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/model/WorldRule$AnnouncementSettings.class b/target/classes/com/dirtsmp/dirtsmp/model/WorldRule$AnnouncementSettings.class new file mode 100644 index 0000000000000000000000000000000000000000..e4363e6e5fe2d585456a7c2ddf413d84939fac86 GIT binary patch literal 2743 zcmb_eeN!7n9DcTtT!KdlkrG3p&=zWPwT<{1d$d}h5j2GgNR|3_BrCc0au@G5IQ>qJ zV`t2ChH?A=ekiBE-MhSWIGIfAAIooVv(N8s_j#6o{yq5{z+G5lIEN7pqdF2uGGrcG z$5zR)+@sPCuX%o*A-Q6^wz$JEQYh}HkwRL-n2tA~Gn8vyvsAZz5j0yp+w|((DLwRj zr@qs6_~KpH_1bQYH@Pczxe&H{6fkJqwGJIl0oZ}`dS1tbbeaBD0=He~sa&1Zk;R*| zyUByVI^qo3YCpwY;gd5{ZoHslT5e1TTR2> zt(SDnD{w%G*5%4&9anIbia++8c9X)gidzn6*ShUzYMouC(5RD`mT;;yhkfKF%>r^h_f`tX=G?j(a6!5p)p5efyUxQ5_@C#8v7a^>i7oV zGR*hM?YCWFH+d=SN|Yfn_pqBZ!?VH0+U~LUgqOmN8AKukF1froaq6hydxoWcD`K2? zOVjWp!=xaPi09jl_iB#mUK~B5<;@$ zAT`AMZmsR$%qOyD^FrsF%f>AY-H6+wb zR*_UQS%;=(vXYdV$y(C*gyu_Wat?AUTLXGiK(7es(EvRWphp4p0sxbw`nM=WS@bel znT%$f;BDX!nx3vf54S`$Op=_NG4SiaZ$rVCu@b>oD41+O%D8@l(tqKn!#%wgrl_@d z$OEHlKaD(<)HjeY{)Bc`8i~jaqc+8X$A<%FdmS8%oH?t1{m9d?;Hlxj`B-pnIPh#N zcxE{8d@OiwIPevG7IRb!zn^hq`VG4l3tk)soS=Uc<(*!|nzHMtkS!BqLJjFEh}4Hr zJwojWl_S)RP&Go$_?-65QYE{DPhkUJ;7e3-A6wYQ1AK)Y9MFy|MNDBGkBETZ;RpPL F{0rL%8hii% literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/model/WorldRule$LegacyAccessSettings.class b/target/classes/com/dirtsmp/dirtsmp/model/WorldRule$LegacyAccessSettings.class new file mode 100644 index 0000000000000000000000000000000000000000..74099198bbbe8c5853f1731f3e9504211a198474 GIT binary patch literal 3634 zcmbtWZC4XV6n=)6YzT`W5(Gh1M4PPERQqOKq*W+|Hi9LJ75la%1FUX#~2VKr4e(^-8-4N_s)HrXXfvJ_x=QM3(qp>Lbrw<9lb~~ z3_Z4Xt%75@+l4h=_WTM%YSwmbF~`uI%dht#jXn(-9sST5u9v-PpQ%T? zc;NX?Wv%Y;nIhk|%Fl0=%RC55TnO9U4j44*D>5Gu6nl*XKTuSb8U@3t=m2iL%JV?o}Jb)C54B5N|{@>9lq+8gyoBR zP5O0K$2qw?YB`SgOfB5n^mh3j>xYv4BbOrQb01odxGR%(hdv)_MFAFojLG>xw+pZ<*J~_O2Ac3>5k)^oL%sG>x zCrrR#k(zj=UftyWJ$YiNmQu`Fw|ravk0yJ>j!m)Mc#YlKk0k9l+HUxHEjB)Mhl?Gr za@X>$Di?I#7}j3dF-HtIzfN_p*g=E}v(&IhZZCq7xZhh_OkvnELpGOOM&`-n?n7Wy zVWmb@CIDPuSc)w-Pv8*)X2_Jhx?kptgcx*gn}~TudO*oo;elB40-@mn!}$)9K|LO$ z_Rww56M^upT3EUujYkZBMWAaYfowe42CU7=1Tc-)4g#q8f1qi;43uUvIGPb4noR&4 zB$Ol?AQ~b%K{QGWi%OA9#pX$r5%Y9{%(-|E}x_UL#7_N2DP0__2V-j^}XfB@6KwDhc&*>pK z(okoZI$V^RH%ut5(#6z8z@4EBpPm^s8kTPZ#pwy4INkJ$(@m~8-PVfJMXWg8xQf%Y zsyJPvVECBEUHF8oF?sKyo1Q?xKg6Zv@zb_z{MA2p&Pg9n)yh>b#s~LNE7m&hTEy7`zLVvX=OULOEP)tz&5csJh^_#d8sn618GR0}* z(jLrL>5qmo%_60-f>q^rKki1B3hF=5Yy1lBFgJQ5GmPq+q&(1>@^GT^P-n{7MCB8m zDUT&8k9MX!k*GZ0netSk@?>Yq=Mt6Abf!FmFPnAlqET4q>7Ng2xBWWjP@o2q$E3AM zE*{qEv2u9U*65&@)K>R!U-5N(MSPJUFa*FD{&boRp)Ul!5cWdQ3n4EAymq*INLo`A s)s_N{4SbDn@GZW>_XJX#D5C<7D#@H85K3bmF2>;ze@fhkz%IuB1EF0YBme*a literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/model/WorldRule$LegacyLocation.class b/target/classes/com/dirtsmp/dirtsmp/model/WorldRule$LegacyLocation.class new file mode 100644 index 0000000000000000000000000000000000000000..3d18e1ba2eda415e64591351128110b8fdec7c43 GIT binary patch literal 1824 zcmbVNZBNrs6n-v@bwGL9V7&MODxlj?3W%Z(QDGt^6AchdG=Au|8z^+Wv+W4*n@p5M zV!{W1fIrH3ZdaN*k)<1YVy%M z-_nZ*c~R6|Ls$$EOD}VVM7|zt-L(yCD=X1ERdk`7f$h_ilbVEz9#3Oe;(O%V|PE|=b<9{ZVhpIQB+|+GDeh12kyKPWv zGgoONapo;|w?*l_Zqr;{ZWB^(>YVO+3|e7GhO-V;!^Bxq1Y4e4j-hQ`RP7>PGGrN@ zHF`}*7SXs?x#KPi$5k-JFmee#&`eG#-n}4%>$tXF@k?=Hctntrr`i4NVHbl!PX|5m zt|*?hA&WT$&sEIh1;cQi$9C0njWXB#)B`Yv*1{=1!%?_;!`c(OT=NZ=b*p4@XEZOw zZncta9BoJwIBpzNc#bxwV2TcZ6P=-EB;<8NtAxB8JV`eKNd=7Vty^Suo4!7i zWt3zx^AjvHehej(Iz~rkAaacO5p6W#4!xt~1lSm%cLou9CozgKl5!;j$^E1kN`_C$ z>xb;_(z>IW3_x2TB@m~(=pLEN=u;%6qmns6FYuj~jq!YJC5RWNt0r}h`vCCuQtm@Y z2kvteOeP!4jGy4fzx?}rn_9UsYQVUccYCc4&HDygGC!bPR7Fbw!>AqigETY>Ae$ha zJHA~|L*2ROT<7>@oLlg~3o!xBlarVx*-JF=>Ee^cr^-Xx>89MC5@BMP!DGzg30{&m SK{>}TNq@sZEFgy^4E_a$e2%36 literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/model/WorldRule$MilestoneRewardSettings.class b/target/classes/com/dirtsmp/dirtsmp/model/WorldRule$MilestoneRewardSettings.class new file mode 100644 index 0000000000000000000000000000000000000000..101c59e4f0d1ae6da9f2e3a845ba948289725822 GIT binary patch literal 3402 zcmcImZBr9h6n-ucHo#&?ls9daRHlixrNnaH{EQg{HK1X zGi7veraz!Rs_k=kH_1jq2Ab(iHh1sd^E~I=^K#Gq>+gd<0o=w;FFMevp-V>u-2wwI zj9nvDFs$v=s?6C=UZ8u{v`lwSpmTC+qX$v+Xz10^2VEeYvx}*`>A2-$sWukvyey=i z*iIq8S}Dl!C9@#Qu5HOx`K#gN*QD#3)^=Gyla{enkklbI74az@F$@R{$zAE}-FsCs ztg>la3wE(+Sa|{lvYvSr*DR#6X4y?ER6@sToKdJcpkldoR4jyAGD;q3NXM`OB}zL+ zS!S%fd{qS~&QTp_)pFchUN(xdy4r^i@S%qDIxb*L;8deQ0v-0xWSyJ}>GUk^!^gO& zVO-%R1p4bZnnXnn2xVn2&Ge_3r!w?8O zp3LU{dj!+LF1>W|Dbq^PRUIjON=5YT&H}SRV7^U78O8gVj_dfG87Y6M7zO6TYJ04{ z(>&`}wqD4bo1WSf=vgzjEyJxiGcP=efx?hLOQtU0^RcXH$>mCMOFHYS+L)bLJ7*L&498US;A5A&W74BH+sX$PVI%im zsmedFB;6f5|HyDyOs;gO>z_ke9d=yJy`lZK<=g+C=R^UT+7O7yV#(bTxW(#krw#?q zRL_raEqX_GMqqH#-~0QSE^L(y^%FlR0uRPQxK!n=owrTnBk-_6u&p2oWLlA($io7? zYj(xS$@^?TJgRFQ>8jc?kEdm}rA52!YIr1Y@fZQ%(Npb;19xoOExV3U@)Nrp#k#;| z(9zP(J6O7Ti%K7ELjI?4B-V{*z4#u_HTOrCFBRm%llhcm;pcI}rk_!nJ)Fp^-33Y9Oz!!Y)z?Yo!LQvV6?5uhV-|mUVe=%BBeMyeI@;V%$qreulafau_o;`5TnK#jk@ADgo(1 zVo(IUJt1(%JbHM~yaVBFZWjYYMA YC-@FeVQ?iu2S%}iEsP=uiEWJj3&-<7-~a#s literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/model/WorldRule$ReminderSettings.class b/target/classes/com/dirtsmp/dirtsmp/model/WorldRule$ReminderSettings.class new file mode 100644 index 0000000000000000000000000000000000000000..647251a3b33a754d23fcefc1ae94c27ad12e93ce GIT binary patch literal 2748 zcmbVOZBrXn6n-v{Y=EUeTHcD3zEBO@w6TiSx`-B91R7{fkSf*cl3Zci&EB}XVaEUD zbnJ{7XZYX`@JBhGd+#PJ5Sa`g?%um+&w0-Kx% zT55QFuEcAuU*$oWN0ICAhYSYyojs2eINOzomrM*}gu&e7zleZux}N6}+elGa(}-NJ zP;|q{mb_ULmvMzKYCH^`ea?{8Y|7|z6p#wVIBsG>GWI)_$QAyw6G-tX6IUfQ6^e#m zm6Z2P%-|aNc__R_jqup2BwsgiLy}WwxK{;8n8H%~xQ*3sEElyrHl-1Sw9fm#~qv4|K^Q}g0j|bbbu4GAw zisS7#fh*r*VqbJ1tLnkUQpPZT67;uv3wUIcM+c(%+zFf-k9a^zUY&8YLp-{1^em5d z{YtJw4XGYoXBf*R#1i>p;$*kPpOhG-Guy&E?l^|IGsF&_D1=ppfwE`>6~5}qBwT8b zfP1o!sjRnn7_EsgGC;hyy67CL+}0QxSr#G+qrj=_XoneWPjSyNxv_@%-(VC<+O>1^Ijq(7t@vVU!6EE-`(T)dW(C{O-#tS-nu{G2T zdo2SQ-X#;@`iJ5TFKEU!$FF)koGS|Prcpn?`eVUus zWg`6@w4WG1?HVxGeV+#klFQ+HdTbl`f#LRX7&-vA+%oWr;oAAElv*7F+^1>VL&M5I zR^BRf%F62i4EN~Vg95$N$X9fl=xV1Ku6CN-YNt_6`yk1r@F98$6L?Fq8QSyKUtrdB z6NZ&ZA}PvvD%;DYuXp6qIK_{>|EWqx@&dM>WT literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/model/WorldRule$SoftBorderSettings.class b/target/classes/com/dirtsmp/dirtsmp/model/WorldRule$SoftBorderSettings.class new file mode 100644 index 0000000000000000000000000000000000000000..cc097603a8ba92b854f05b2f4c0ffe53870aa0d6 GIT binary patch literal 5841 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 Oj|5=;gg@i2==d+K2%fe8 literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/model/WorldRule.class b/target/classes/com/dirtsmp/dirtsmp/model/WorldRule.class new file mode 100644 index 0000000000000000000000000000000000000000..a84d9328e6d8869212cad59d373b3df6ed3f8aa9 GIT binary patch literal 8838 zcmeHLd3aRS75}{~Ob8E1LJ~5Bt%xys0fHb1ych{YBs2j^DAG_{CzD4qGE1D92-tnI zt6kE5wbkyn(pp=WIx4ia`(_ubR=aO@uiaN`tM+&9eVKWaybO>3`bYD9C+E(6_bm6E zbI(2Z-23FIdjNDBRn;g%c?cCIDlyAI&E=6RBOUQba-?IQ6HTRu4b19{C1cqx1Ldu4 z2WF!RvqPvhF$boBv!ba)$8ap2%_PS1IFTB5;vJWy((&Pa<8giP=22eRo{ za>N#H^GwV~je(Gpj10vc3N!{~zRtt~5i$FyFxnkSI5OK{q7e(ZI_e~|PWm8M_sD$M z#3IkU;;`7n5}7kI*+@D|c>}Sl6q98pmg8v#s)x8iC*4nd4Yc&lyq9hHbxP&4OsvG& zq#sGAj%G(sPk(WKouWU-M5|Iv(|0FQ>P zO9>O^!(nB&hiJgbieoa$EZruyVLLsZb`r599YdBI1F_ua5)`q!$ixm@Y@mvu}ob9Ouqj5MRk@Rd6m&&KPk;6Q)g)h&=Wg$dN49Qtq zIrHf&oE`&}dv|sZTr>y6a6&k2Vg#d_U#ao2HIxUWv>K6k8@a3A`eP65*Raa1=0p8GE6T@Vvk77 z%JjJU-CK>LI2OWj6IbDRJaGkm_lyaf2sNv8w8o8-YN`E9ez%3?3rxHaFCt5B!azf7 zug?ZV)Jsgf6fZMSmO5;pwRQR~UAkfk)pXS>Ok9muQW+KS1{UUJ6t5L+UTxwvcr8sD zqgNx@6ira9d#^iB-LJvxLwJLUH;TJF+n-!Qm(j4honu*9J7MC@;;f1!CuUu^XyYS~ zF5bRcY<;bXw~0|JB|jXi;}v$DiR2%E}KVe8vV2jnahmFyaD4HX3(RZJJr+EQ$oJy#w@X_aI)>5o?V0dE3r6lX#X1| z5aqOsq6F2{3t@`&>=L8}RcA`=frx$6cOXiiBt1?2LK0FpS=#{~I34fv`(jCF_jqE+ zN$;1MOlr4OG!j1$Nyp^3J6Vw(jWNE@4x$mmud$fN@v%uhHT_O@G&Q_8l4g;|oRKlg z`u`t;s)#;K44zpA2;!%e|FZ$xIXwk+*;9hrcZ~%09Ccqu;!;$z5E;luqDT58V{RZD zy#{*xZ@M$HF!QxsSt|LmWQ*#OyI1Zi7Vzp$RX=v5(pr;>)}A&BFIAUL&E!)H7dIVC zJ>apBTh6rc3Ncc0ehaDI{Unt^TZxeHWbo51^`voC@9Z+rS(0}lKl(lfSc(QDpO()J ztS(79^+3@yTt6ih8n|%UB$BGMd`P_t&(fTP82_-Gj`nb1hzBzx2s+JOtp)irt|q6 zAo-n*`-vNO-Y5>TvV7%LWS8ARG580h1u;> znC(4<*~U|t{W^u&oKu+HG=kIX(m!4-hXS)r55y468kdkhM66xz_3&YOSV<9O|v6SvfRWO;tHGTjA;) zmRjLCIjpdn%p6**rg=HES&cP0tg;&Fa#(9MEXZNK)li?qMysJAhs{>FF^5hoyfBBY zR=6pL9xEKqVW$;dltZ5tZq8w^6{gtzR(MGc2d(hZ91h(^t;%r||IebvKnNe^|NkJ& z{}Z?wA0d{JcuBrbC_jnsCZzLBS{aG2D%424HA{`eUsY-(9;;R(@!1?T60eD$C#08) zd1@q{6W>for^8AZ#Mogf7B1!VbbN!fwJo!U4h{;ZlMQ#9L|n+mPmeh7%4mG{qV1)*V=J zJDzhRmT|Q6B%%hc!vZ~y8Td33cOrEQe>aP^+RyXU&Q?&_v(Q3+h`+UO+g*R(uGShY zl)*k1Wr@r;x*xyY$SfSRqz~D^B8d@~K5eZ&GI6Qh822 z-VzO0(!SSTkJ&PKYuAaV9$s=Y`DsHhQoPS&AaB5WY(zKCXJ~Gwr?)6gElOX{kiHrx znJTD7UgthGMHf=XI<6GUlq<{Bpt5GG@dV}=Azah;IQL%5uHGqkFXs|VOoXgcR25T= zqtCVWbI27`%O2ji5BWR61;w`YC%*w7cgZ`cjrL9ZDaOPM^xE%U-+Tg}RC>?J@1sNg z1}f=+P^n#1y1Ou#kw#rLyu@=$?V&)tz*ll@Fv*SiBiX_2i}^+EdO2*~$n;vTAbF85 zaZ@njCBDS!n4ezqaz?&B0bYYG4Yuc%_>>RvieSX8zG}Aw+jF}waa%CrHOx$tdoH+Y zmE3QqH`qGg2wNL0!Z!F4uMbAN$(MLzFk;)6cyln~E_^0$Fr8W?wmXCQc^f|KL%cN@ z@eX{>hqxyg@h*Ix4wdZgjqRO5i1iim11NJ8Qctd>=o=kMLvs1V6Nrnxw{BO|7mulz4Yt73qs01NYq7xqI%pXXe}YFJA#{qHUvqVhNUmGngSP9ExMn z4TK(dAC3;C7ZYY~E3M+YgyKeLSVoy(=}^mA*qAH9aZtfLq34B@uCGiSO^$wJB+aq% zWcOZZd}(%t7Gr7F`*JM2=bN6FOp-vvp&=|j5hgy6QQQwb5vx$|iHRiC`+wvNVx#o9 zm$qGSP(z(iQ`!p>U;4lPiU_omRxCO=izdM_@>!yc^att%^U*eo-wJ(Bt9_;AUNRX; zvoA(L){J`xLt#|9&p%r6fnwG>{r?!RM=0e=3C)e|(?fNJsj1n>l|d}Lr@P`P7hn&< z#CY<)$|U|}-e^WOPcie+5LYA&Lc z(vN<|0z&CQAUlhD~qKpto3}cWBZ3tjj8k rXQsSMnx%uJB3F3L^XVndi=Y;VRZa`Ij4K(g;VPre#WvP(4a+|OS}VEH literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/service/BorderManager.class b/target/classes/com/dirtsmp/dirtsmp/service/BorderManager.class new file mode 100644 index 0000000000000000000000000000000000000000..efbaa5190dfc4d51a99ed6ee7f0ee2dc7c261de4 GIT binary patch literal 19497 zcmc&+34B!5)j#JZlQ)?>AS4DdC^Dd6wjqkBkl+Fd5Rd>SECOzmWD*7@GjSFct^0;m ztJPKoacQkmt5&2k0#<8VmsZ=VwN+cIcC)p1wY9C}q!6zUAt&x^*og0a>>B+wI#0Zv~y*%OK| z9kwLjTg`yd-nx9b9@HrFj5cWul`@U$j7GXcJsChXdEd7N)i*iEYA|IcmD52?qkDt# zI8e#JsT~eZYtCQ|Xq-vosglWzCjyBqn5w*B^qBf~$4Cuhf=LI{Axvd`;lS2lOrFOx z2u&Xb((*y$HM~PjnnXUp150}Yk*=0#bYlkIk@?}-;%OT6hK4xBq{HZNrZJm?9qR#Y zX*3b)4v7H$!-89#J6yw>X3`N<1qrSX#S_uk)+Ny%tqS=$pVpfe=io*U)ljWLbtd^~ zI#X3X-N`3=riwscUwEsfh4Ez(@H&=A_Gvv{D^QO#=_r~3#oQc?g}dTRW2>r{X#Gzn zLgD(>Kwo1C&7@-tI@YA)Xcp+md5SkT1;WW7-mJ`dvnd)5!*oK?2mmxtqd{{_I-X!c z2W0_d2Qj%riC`=M4l#|(fwaiJ4>%{$T!ZGB)FfOe%fd+yVKUYS;!A^D5;AtaNegHp zQ(*)*FmD>F!?JbweNjheruFA#}?g>AujGMs=Kv{Ac3D@Fg{4fF!tT<)|dVxdS6CR|6S8FacyXV96@ zjcBZ=z9YGDV<=HSPahz*o?t>V3HVf5nxpw^L#STjct?)|&;WH9)M-)|VdBa8sK{}I zKopat?dFiQ7`1|iVNJla-g*kyE!1PudZDghV{mI39Q+28Hd2^rd~aZ55U#N^kXRi` ztj8I66-hvIXXQaWA1PYloYZ+JLQ#YIOgfvOx0P9q%*6{TvpEop08vAleafWs=zNH}FBa{I zAto>#KVqFlYipk0 z2JIi&T%w`Znekx)1surL#6dbdE{#nezWNZ5_Tus1VXqtJa1eZ`=A zO}bBTJR%>Cd2|v^GV8$M*jwnUCOse!%0s<<(O9A>8H<6z7OoP z9x`dK+)KlllIYmgsgC|C(yZdZf7T~s4XaPnO6Sifnv!Zu#nXw^mT(CHR&(5^wbb`l1^Y&gnEN3 zkwQTGx}&k)faZ~Szh*nX$n~R-(05GwE2CB~f^41V6YNWZjE|pzoRV zeR1svbt8>gAB-a?SYsrHl@Zf(;_IJhIz5kn8tl;r-&r*HR$g@7^h1+gq#ps@U|hk&vY*i)H3_ zO!_PR4GDZW5Km;%sJXDV!m7nG@89Vk2L02d_eACL8i(a12A3R|I2j3@oeWx!Y-I1D z4@~-(&~b?KX?$*Ci4z0C?L(73lJsqKC!AocE!Z0Z_hNDQKiC~JD=^)WwKL86cfDaV zNG+oR+JHQTT_zVvl2RCg!pdZBlZ)AdIi1pBI(KBsn^$-9IM!hkT&)nz!EWt(Q7Ith zVbkPMJQ~3g%93at`2i7FCdqrMy1*RKimhLP!`bznQKPI=^;2Z1#Mb`CVAHuv^wA$`uGVN0Vp)GO(f*s{eR z3kEvZi)c|~gDqQp_6>m^Fu6nLl*lvZ#;s|E?kQKHN8q{LC?qn9#Pq5EVHO~3 z8``f7^uQio&mn_1n7ol;Mbq=Wna&fN26f<||B&;%Akr#i9M_MGkYOW4hA(ZXIuK?C zyP;1&oMaCz95Fd6dCAyBOj` z=Z%cGyx8Sa)ZxP9Kqi0=PVy#$H=Ddg#FW=VrNxxfkAyFh_z>&z@Hu?0!Jjhuydfc2 zezQKemkh=QA`#?yoxu!V9=?DtH25NuFBV|6!)eWcMk^A%h_q<8I)JU`OHIB^Ai1GV zn)TvMuaI=}8PU)yP5v}r1p~rk>RG+lpPOF-ov}45_?7v-edCD_#x2^giGzX zkqx01vpA-Es_DqpScWnsW%9$k50mPWglQ7;hHNzKh6U?nYk_@&fOvo(G5G5yKgy5g zH0je|QO@@Uumo|mokZ$62Lp0WlYSK#8JsGEYJAh=Z}GPQ3Q=)Lq}r2M{J^q}M(RnE zpW>%Mcr-RY91kv(_m0YoLgqDRt!%9Xf|)J+U6Y@Yh-_LipWCbJRfOvAnf!hJ0h5X% zE>EmVcRF&IYUd4ZMwbG7*dhRbX!4696PLD3;oy%=eu+`#7g@*-=&jqh-JQj+v3hL@zr*jD{Jylj3}9$=xr+H;Qik;pP>Rfg z%b-;X0rQc`A1gG-N&qH{rV`!Z+O*Kl5Mt&k1;gpA@ zb5+&rhL%@}D&_YKH8xjoa$sPHDmT?Zs=|`QO5I3W)CE78<5pL$T-4m?QR7vmp}eM= zAPO)#>viR^6mwBa=4IsJf6440l~IX4QLQi1?JzKdk-Bk7BPI-%5 z9c8K+`sy4}!kMNzMjZ=j*b-}s!V!cb$f=pO{=cZ9tgRID8)_CB>uNTzZkDS?G_Muv za$Y1?C!kHOP6YP{`z5%0HP=-0L=8u0+e}gv$fIUc%@^toLIn(D0djkQ8Vhp9S+KfXS_-y^L&STNCR5?23fB`OoFhpV8ex>b*)#qsO| zkmii++MKE}`$82m)dsZ@j0_Ej*i$*<-)E9ChX$Lz`r@UlCHh8>w(qvaoJ zm7UP6%{Wy7RhICmvpbi`!cuozA&;j{3>3n_65@ZESlf50m z*a~SkisiyFhF1k*A-T417ACNTXQ(PBjZsbt)F^eXY`_5rWZ#V}tcu#j^bV1!c}W}K zK>=bE-tNSXroJ>DQu#sSt@d$x88QFHDGQpjWo(THSa+anLlR3Vh(0de{M2M;cX(Ka zk90CGnsLzpYkN$(y#bg-R-%Rw=!}yZlEoMq175)P#aT$*is`S*ui^takrxkZkfVSb zqQmov>ObIy4xH}Ax&obgPm&Ams7`R5;rNVbSTBrXTY{q&N*5`1hL2&9;AYFZhbw#s zq~u_bD;7k-%M=_TTLvY3pPW|NUNn?u58|-iiv%=v=UKK{3?mpVdQp3AZ{TV?DZ7nI z(AXF6Zn5W-kwM5iO+G!WMs_qN-41THW@762pPprKbcfzpF-tVV5nOYM2S_;QVNQmK zhwgMz#xyf8$ph@duCvl%5)euPot^2L3ww3)0?F&I2A6DrRs?8z(@#TQK>4w;wm4+g z4-B;z>G?3pGcpDz!yLr8*9XGgmY(opG+NrDNf_b+H0n@T8h4KG=QxL z(|zq5Lw$X+HOq2vzRYG3z1B3{tsZBx6A+)pFAV7vCxRL5!RdEp8a-Vs+UsLqB8s05 z2z_H*&h1PW_clhNn~*cH#f zuJd&y6F%gXehD8wJNN8`YWg}6qktw%NrIFwkN{xcA{+heD?l55$!CQfzeVMAooM>? ztNwtbn*Q_@4ZcJy83cw;V27o>Jpwa#Y1(Zz%2YP(82A*^s-@`;rmruvatfjJCG=|; zpKc6@0taJho@$yH%tJs6borv)KH2kVHAyv_zQYvBWm9H}6QlH48>ck;x`!1{9^u4u zMN`Tzk8!DIJ^YILo~f>;>r2%S^oG2l>!yLRz}exk!^)T#apI-11tuBek(g3Wn{B|sanyX15@5T9&bOOMdr zUfZd><{a;{H!sea9&_S3>Nke^EjlXdEp%YBu@r6g=*A#+ zuggwYxCdf_Zx6KGOA8Vj2+Rx?|+}X z9HtGVJ?EjtQ73D19_x3Vb~2D9NR$^vBEeV_^fWHnciG^bjVOAZDRMe&LF+TOv+;Da zbuS&F4234()Im(nrw)dCkLl3iha%oux`TcBZtV9WDy9kQLN%LMU4$z|-#{sf4^MDj zCf|nWBR=lXM|_*1kNCbqA19GdAMrJVKH^IR9LJ+GoKf>IaZT-B zGHUAf(x@8$UK(5DE!;~LHD1?V^455Z_R_=}ud$aV*LdA~Y3hS`OQ}ooTSFfF)?tT> zAF!s=XsX8tCr6SOR7|27G?k8~T6{-v6dj{3!^qRD8P(K#=0pmkcmpN?kQ zPiW#SsohWL*KDVWI6?CGIJLCarD*YXDz3q?b@i@~e~yU!50$8b{fku*wOsXndgYO$zRSK-+xDlF-zu?C$q=>t4O zVnWL;#0%aANnolvzcxjy_R-qbx~ItpB#acDCAaX`eSktlDe5hY@1x{y!BFm|=-j1s z`{@Ggqbz(d zvC&=W&WK~SV|qPlrQ4Rpvj_MvJ$kGjXeHfBtLSN34NX`>KcThs7M5PzbUGGT z&){-=Su&B%;wcp1YU<#b)XB4`i|69wkTulJVG8lZw1Ka}n#cXvl=U3-@+;WS_&P=T zEjpX~DW(c2uF5E(e3V3Uc@uh|n^hZaQK!;Y)j{W}jdZ>?#co*C74)XM8t&8u%wAX5 zsLzmr_vWc+^%%7XaIc4JIbO|GH>l51A+YRM zpBKp5v}?%hr)q;P{E+Gmy2qfK#*A+&_>fxgrQsv9->1$oO$GhbQ}8d^3X8u{-DKIl z`Uh|vMZ`}+*%t30PdQH$$-?oNSLGw=7L8EY(o8tM-u`T7iI6D(2U*deatej7= zpUN!ShD!9=E*j%?X=kL}$!r74ixKS&#opo+uXX}Ct(?zDPf;sR&l02aRC+26F|V6% zr9&zTcR29ReW9q*lj6<>6XZ>-tu!hd>J?vr+3_UBeHbSG9EYin z(Ft6SbzE=7^<>BO`M7pMza-~cD7!qx+hlgHDGnuLJ{Rjqd@-E*C5VofBcfe_NVW|| zbrs^q)iA7UU`*G-K(3=+x}Gkh8xW5^Pj}Ld^f1m|#up$zr(0k;JE@;;gZbRfUb=(* zbSKQ^OWZBs_N+3R810Dp+|Ky1KM zH5yWf30&>i^YWHMLKH|b`4xUrkQO;6BM z_<-VR`YC;feuK{~{wTZO>Di2L1jjRq;mIh5NBc`r43Aa}Bw{!d#c*NvceP>^Va^A& zVi=h560I0+%p0@R0ka@3njJi7FHrJc@=q* z93{E%7D{#@zkG+c&`L7Ag+Rp4GJplr45uG)RT_Kvi*@iam;|bnsKg!nD#slxMRH5@ z93=QW4Y?6+LSSEoQXk0f;bbo6=2?dT3Use{Xs1)7jU>&$1ejIB-Izy zom8xi7wPy%G#^%50P$29_$-mtm(*RBfDrGk1l#KnP^<9x4x2OCDnWL@6Sm)(^fryf z_Z^k=2aUK-d<}Wis)sYnCz(J~f-Dkab@zWWp~z^63IB`<|AGnM!GwPu>4dVW*rsqd z(3X+|C4rqC%88vFE{68qwTJJyn~tc(?FUl)-~rw%`Q`ot{0;l~xE!B&oKM|djp5(v z+{e#mBZI}2zf%SM1GUmW@p1F}8p#^Xt=0@zTGIxJM9bnz1q}W*c-sOkreZ26m6T$- z7|<^DWgGi507lqbkq`R^@W85OP%q*9LkD{*GBa0XX0EVi*7LbYVLr@qkNS!|*F~61 zL_DcRc+tKTSu`TNQO?ip))}P)bR?t$XlE>yu%gjO%gfm1pnXyXgGm_-CfT$b5T?W& zi+0p}{Zxiff-r)QT5NQd+NxPtnk z*vo8E$JQbwz+3Q8z(+ZNkIeudn*l!7_O2|S2wK3^9#}G*9$#o@eXVs*>7-2RgB?`* z2tBf9bU``)^kM$l?nn5IH4pPImO`<-4camOaywP{tpZTzT;=>`is8mm{QK>+WFP-& zztp^vN!nHKEIE_1m)~h9vhuUP!87hc(Oc_BB;yDLd+|X zqkB7Dxlfe{%*T!`Dmd1tFe-|^NJ+1&!kE$E226K~HB4IPb?;YW7*VKH&dLPbxLxG2 zZ^JPQT*(VDckuyrFs8-0L-e%vTMt!IboXO|kl>eDh}@_scnhrlD!1ga__5HzI} zqH#*;vm;aL=v^QGSh(tcNF9g22K*f_%*~ib1TmI)EcShs(KysBhw(V7#iu?;aV41P zrIYz!YUe{Jj#}nCe2a4-`)E5)rk#8klCQ(*X`V_i@HF}nSJAtubNcZuODWfJCB8LG@CO)ymHxCMq<)lU=gi-+%#tDj2Hy!ZfZA0?k7*v4@I{H1uzX&L8O znRbpfwbe+5Y&*0rvRoc5o{G^@;n_5a8y&7-TADiW!%kRfnr)VtM`sz9D}YCu1Z1%P z-bUtOSgQ1i<{&81D|OX~@pVALitsLEV0Y1|I=HLL3(rEdud~8@9b$)-jVyEOuKyxmY^E^73n`kP}r#fCh$MHgH^v3Py?@XNW3Y7b5jQArgSDsO}a|Aw78H|@sV)o z*sf@!zZ3pb(4XlodSZ8MNo0-9HrnWN4vu0{rmAE?-7Mil{4VX?w=#h=1Hl`cw5aqMD z8rV+dlEu{7jWa@)fLwDAz!c(r4WUwphj8 z!8k*LeirPqyBgWV0Tq|@s_;SFxNMI~+)eN`na-x1-!l+TH!^FwsBPL z120aaI^AwEv}mzody02_Kh>bUWI&bhuP4|7z75cYyR$4Bm-=%k^y|a`w|1t~d1A9D zno4DeTo0ggoR|_{3bVb8D$rA?;Va=3U75+vR9$Ow8FklcRDNa~y88A=?U=b3Gjd3uDVu}MvnvSKhJs@e+(FkMG zCZ%!GC2iA&bRpeWx(CuAOwy)1ZPSJBJ5BfYoc`>e{zyx|_hz(O_HhqAO@Bx;^XATd z_kQ=g_ucoN{nt~^0N9Sd1mHuJ3crR~s8%rNh<;3OkL$_d_Q>E7BkCxq-fAXIXPbhm zmezx{s6nlYfQC9~3f4reM0?D%opd6VzorfQm>D(NcUXypo{a6XtfPDMq&{re3hJX) za>yLcKZp7%X>f(8phz|>rf$$M8*>yiB#d;L8uNPErqi>hL^vEkBj%}?uVDe26tv9L z0tNGjbyBRY9GdNfsPFH`#<4I2i8gsu(2~hwkGkJ()JGTgkBN zlw@MRW1Gq0@H8Lq8`0B7moX%mN)_J6G91E_)`KGEZ5mdIV9ax)xP8bm?3_;`UTZY8 zi}&?<@f#KE_(=h=?Y6Cti5Lg47F9y;1`Qi=lY%B!?;bO5q#Y}1>^DYrTSV*@LOTO| zmX&?kZ#WM9PRl@S*02Q~YX689eLQqim7 zHta5`ST0W#sK@kp#)y#A3tDrIA+*m`5eiGUx?hH;ZBj& zO?jMl2$%{YFUQ>~ULoMpndUM^(%asoW5}Z1REm-}9u_b~HN?QuqzxNR#Ab%+nK%Qx z`9DFe7+Mv>ETaXU6u2Nx zuIX-!mjjO21lCL`!8UvP!lNqQuHhX5 z#>N6ub0Wl+nK-$sS01l8XqTo)pN)-7X zJg(uasModW%wSqB=)&43G<*(EvdG8GbV_%kBi?ihHnmhXN*d=`Q)w;*w^wGUM{~MW zXh19X6rN;hLa>Qon0K&eqqTiz(%73x3>x+UQMyHu%fb=Ojz7+2@fhNV$jlFi>%=0X$X|xVolj$)31I$T_;Rj)>~#ByO z;&%#G&Tw5(@VA+9hmPmZ8FJ?i<>lsu+GfrWN0GLHek)@~jh)=@C|KyNc|`{$Ys6B| zxb-HJ><;64I&H87Ei9`_xeX%Lai{L%d5({*Ay}izRI>i~Parpc<^1z|t&M6ebqOP~cW@DS~psU9_AE z@)I`W3wVa=#n>0|B`MvEFY`_q+>Ec_tEiTDU&GhEZ!h5+#6`h3r#37^!&cA0uq8C? zy8pUi-@>yVVt;4Q>S%uCX{x)a$5M$d$>a8McR!jRsu7Y-jbpNCbnMlKvGK^K!laO%v^TBgRE_2?W zQmz)hy60i5RP7@5Gh)Q!LJz6u7~|O#8+VJqYfnRCP#;jZWAdNlSpBNjMYBXP2-XGz zS-ifZj<4UMphF947x9RY+b!mttPj>@@xG3R z34AEn(D?BzKFMi<4gM@Xdj_+D96vX3=IS|7XEvMzo4b>pIKck_ z^zyjlHg3*$<9_r>z)q00F)YE)Syvh;vjxB4UA^!t58}9lNic0(=8OPwT;`0Zw&OBq z1ZT!&&WOr3E_0?v-ie$M<#HUq#II;wEAGIrc~@7&QZa+eQXzFEFLi}dA{1$;^YBkB zQPHg8)bm)bqUS2}yk12O-%d92wC>86{`Ci5+Mb z*p_phX?teSaxBfLBlP=#DBWF6n~5WVT?TfW*n<{{`-zoW)!`O&8?xMh+ib~aw)TtBqn|MH( z5%b+Z(>NZ&2?K94@pg58C=9QHJ02JZ7NVL=Soe4u>gN3Ggvu zan`r<0`Y0rD_DNkewym;Y3nI_v|51#6YW$25hbD@?=tXi69YycTjBzxqv%54frpg}42 z1SSofGw~$eTT=@H=aK&fS7mT-HNe#&lXfd+MaX{y>>(;xbXjKv7`d z1}`mcxG0_BqH;tdJd_OhCg!mqkgBDt2;8Kn72Tt~U1N;Z)=in3=~iM|qFEh&A6ibs&g7_QI|wr#b&$}RCz+uw6b@YMEI4jut| z>U53YYEabSbsjVb>hLFlIw5v6;4g9f6@N4EchzOyVE5m|&UO~uImwEz=B;4r=+oSm z1zxCbMrFc4xI^Lo74Q*?9=Q{e--q;t_hsV_+mTXwXnRN`R zLvusYcmuJ-&?VF-jdhIZOCuRmm+!fRSTeSX>^jDIzlMo5yl)-nfz$D1<0@n_z5=`c z8r;dq3NBKY6)ckM>4{5dIo%X!|FIQ(D)ISb(<;7rndbfi|G>ZSEa6e`4Ph78{fOf( z0(1baxQ9b{3MX)oV{I#SJcM&N%<1O{N;rz=a4)_~nwQWp zliPLd#CJ&9q(I$p!VP>6-=_}h?j`&{N5BpIkUQnX8~71^%&AiCeuAIUC2DsGKVz)4 z;u?NViH-b!0>9*I9KYiG>oA&b;v!;(`u9+TKTP`#JYitWU?4>G_GJSvi<=Z1PE@~1 z#NACsR5)D?v*>MzZe{kM`t7?_GYWz*@T{8de?v3s@LQ;uet@R_nVPQX^%cFY>UB-8 Y>w5izUSHGeAL#-R41s^r5!aFYKj$r6;Q#;t literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/service/SoftBorderManager$Bounds.class b/target/classes/com/dirtsmp/dirtsmp/service/SoftBorderManager$Bounds.class new file mode 100644 index 0000000000000000000000000000000000000000..b2a60e963add3f732db1bd31dac5db04fca4dd15 GIT binary patch literal 1942 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 OAZ+tko=i_R75@*QIU&>l literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/service/TimeUtil.class b/target/classes/com/dirtsmp/dirtsmp/service/TimeUtil.class new file mode 100644 index 0000000000000000000000000000000000000000..4abc865e7d7480bd148ea7c3ceeace64733fdf62 GIT binary patch literal 4083 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 Bxr6`! literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/service/TriggerEvaluator$1.class b/target/classes/com/dirtsmp/dirtsmp/service/TriggerEvaluator$1.class new file mode 100644 index 0000000000000000000000000000000000000000..7b7fdb4a1da5f9d90a6fe6feabb3f35cb3b50cf9 GIT binary patch literal 1339 zcmb7^?M@Rx6o%iSrQ4PPD)J?Mp_EUN0xF6iu$B@V+bz<9G5o+~yD5v?U9!8yzb1MC zCVBxTn)p{G8e;6)kFY$~NGCYbEbJI|amXU@!?*Sm(g7^RZ#WC<8T`>I*dU52rd(ap@MCe1|9PNlP1PkeG ziedPa35&Do;5ds;2FZ4chNYSefsxU)gl>kuU3Fhgn5tDuD0aT0?W`EOSxh;O?TAHt zInIei+t!uzqxIC*YDRvNTFVR5d5#OdDW_!8N}cHv$7SCnCkyF~I@1-7tHRVRcCnRP z+iDCP=NJ?&J-&;qET7;r%rR0IrZI^zj%)Q{8hx&FOw@&GOn8!Gsyp^+A`2ba{t3%n$Kh1YmP z`WEl-p7aAg;ea%NyL2oe|8NE{NBt&x=jokA_hX4XvL`f(82e{~Any5r7O+Sf#1ggl Q{gEXkNu&PPp-wFP2b6L%SpWb4 literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/service/TriggerEvaluator.class b/target/classes/com/dirtsmp/dirtsmp/service/TriggerEvaluator.class new file mode 100644 index 0000000000000000000000000000000000000000..d1b909cadaca2abffd5187c1a18335e26b3a12ab GIT binary patch literal 7474 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 apZ~U>FAFJ_ygjPpFD8dXp$M{gI_|&a;!W`Y literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/service/WebhookNotifier.class b/target/classes/com/dirtsmp/dirtsmp/service/WebhookNotifier.class new file mode 100644 index 0000000000000000000000000000000000000000..3cecd8597e7ddd819ac803d5e724edfc6cc5c81b GIT binary patch literal 6250 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 literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/state/PlayerStatsManager.class b/target/classes/com/dirtsmp/dirtsmp/state/PlayerStatsManager.class new file mode 100644 index 0000000000000000000000000000000000000000..284d5159f6dd1a7940abd6d31dac507893cca585 GIT binary patch literal 7766 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 albFI&WHF5e&mSS*I9`$>7O{jJy8Zw!o4?-x literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/state/StateManager.class b/target/classes/com/dirtsmp/dirtsmp/state/StateManager.class new file mode 100644 index 0000000000000000000000000000000000000000..e80ab1e6db95843bd73aa83905d07c06fd8f65f0 GIT binary patch literal 12682 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 literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtsmp/dirtsmp/task/ScheduleManager.class b/target/classes/com/dirtsmp/dirtsmp/task/ScheduleManager.class new file mode 100644 index 0000000000000000000000000000000000000000..293cada9d0e193f27e7d2d281e749a97f3afa7ba GIT binary patch literal 7814 zcmcIp3v?V;dH#NBwIi*rC0ntSY;f#2u@h-6TO@Jp;B|s!#g1hpS+Qg}wnM{cHI_D- zePnmmi3o+n4Jm;@XmMJcgz#t^w@q8XjTYO5K-(1B00q)QTMD$av^0ICC4B}Z;=41m z4_Twd=WsYa^4z(P|Nf8ff6Twnzjp3f0KM{-2Ggb>pG`F=4KLd7(z{gQby!8w z#xuDID_179A()hvJC;mXJv*y6yL477tik29nv+XTQp>xJnRdZ+GF0~xT=}F%&)e@( zyF%egtP`}PtbCqE`!Q_!8XsJ+IUb?qu2Q&KgDI4EGFd&*r4Z8-%FJf%nLg7=Oda4t zH#QpBq|k%c3sz)ulRe{w>FK1?v(tTW|D@%VfJB)(JG%y|cIADD<^28So$)qe`&xzT zu$idM6$rW=!S)V9jd+Ta;@Hwi>uW;SJcf1n`3}NWroh+wM<0RK01B zCKJ@m%aFfCtu|u%#LL{R~D9$JyY&4nDUNzELtr5?~dVD7e5@YY)nkv+k zT`cTW=)=u|M$NpQ6|Akk>3rS02iqXHRx9cjg*{qJEAr+sYk}z6vG*#x$)&AV^}%Kg zV#vTgg<*}7H8nKhu@X+0>4ar#1npNC!6;pTP}!HZlWEKIIV$-Q!o{^@!AaVT47Sa9 zmdvDS%P|}@aGSy*9442QOa0?yw`~DX{HZpE)7ur^ zjGtk|GZP6b%LqCc3_dT|1cZocwvQIlF4H=)gS4ZKI;VZ4_p z3ZlF?>1e;pWhQeBt%4h?k>```3QiO{Z+R>Z`^U+^BY3}oUr_i(d_d40$iNa=foxb_ zMzI9<27~xKwZt!&v0xEAaz4avwi3y_=dh3Bmkj)}!UwgzyMlxWJW1P36lkkQ@tDHn zc!GG#S!O;%$Zn*$mZr1oo~)VHckg$%#~bmapvf#anUv`y6A^p_A2skXgqwsNjf-1|EB8lLJni?;A*1A&g8L=FP zK9nb8r}4Cb-xX|L+G0ze*Zj;X6!9qnnr^8pCHhru0QeBB;^8dL8TdVgPvbL7SbPBl zOD7p`^gNH$6yLvZkXtG5XYn}$zpwBITCeMu7P{&bWh>;Zyf#e)&*6^@d|u%T`ux>F z=e?lgDy4_ zH`$yR)0v|U_4sRrZ{gd5i&N&b#XOlXor6hdN@q;ROgpr7N2e})+&f%)<>>qz{zl<% z@m<0_W#* z>G)oqrXRwK3jeHAakDO+ZY?A&$DYyf{#S*6)25E3%yhxDSqRNE5kxO3{0IJ%b{*^; zI?y{nDAc&D3QI)bS-h<9U-)m{MRn?WT|@K7q1Z3&T@*LrfAN0?{$Jr0{IDj9t4`5d zhV~8Zs?BNDescNK1+jeqNfsW?zI0d5lS>ZfQTO6s&+^}PcF z)U||^G-zFiM*9bMu{IJzNux9gR;A2)T#gIb3RlBc&NVL2cXsthq*;_9%apW8>jK~p znhs02dRAkChR*(e-YqMn&5(xYNX<->vek3Bnm8u^ZAIZ#*2omS*jXwtC+=Qu}Cb~ zkPBM}foAmmkrAQNNvKt-j8u030x5dHSj^=TRD)`P+FN~hEPv7m?+y$!@vbrAu+R0^ z)&YJJ4HZ)3R&G>($TAKOWD=%5X6BOm-Jc9OQ%Sl?aBs!oH`@6PojCF6LV_hdwUwA= zHD9L1k6c2dhTazB9g=FC@Jqeg$njpBJ=|VQXSou{m3l5IXWUcD$7ry9SS^I2tY2Z`tGi`f>AEI%pWHcfNWXzC*tTW^` zwrUos#H%M5UJ|CWyQGv4{`A%0*D*(`)K&sS)ztYSu{7&Xr>$HcYde;Vgkh!b@k(w( zZl_Z%5;Mq%hOpJKy7pGOiDz+K!+t^?yK_xw(;Z}v+H{xKeX>pKKG|w@pKOu3PqsMS zC!3Re*6GHS`(zV|&qg+#8su__tmWIMx!%ZUckEM;*oIjcvF=$ci*206idZx>i}qNw zVHVL?G(3yTVo_rjYd^{Tb#ev&zlwb6|3mCwHlY(O=wkCYhA1|$(b|pm*vQc)xsto~ zd-upXSZbJQX@T2{pM{^KmzxtK+7)t+6YJ-fX(SdN|0Cx*YdLK z$mC|+gstq|ZDuF#23Jf)^^yr+Ox9&(2JfbbdQCIc`+2IDkF#Kr857OPGwAmw`^%H= z)%C8Xy{qG7U^{hnBih*nU4))IUE5C*gNV#y;=O5s>s~t8eum&a0@P_q^r?ib=ewU&N|)k@~`+Zjit?X zi1exQ>9fcPsWapcY&Z+%gOk|7!P^C&!|_3GTG3s^14X=Rh~vw;Hx}`}ZJ}tWh!ZEV zl2g%c{c^JHL-j>`_~5De&uHeVwK7004ALw^)XhF(d>99aw%ah`G8&@**AOiaOBZF| zj;O?B1LxMWiQX+6UECe2(pd}Y=g?+g=7-q%3RmlNqt6pfUgD^Qx4(7Wp`ETjrGew*!;@QU#J~jVEtph#z0F|(O-``5!}vy_p+(`c6KS>2b1ljadx6C8vH0fu_y5Y zrub2qkHSR@l|w$XxKUZ!^H79^cZ+<4$;sYT3mDQo2Rk) z+g_vI1Tv+K&);Euo@rJFOSWCg%1sa)A;wN zwCO(Uz5n1e{>Od)IB!|#4D(O;M@qPPxU^+%OGA5u3yqo3mT-xXusyZ9bc@tKg%#}$ zF6$CLBatoP`YlGg(H{O7)^ax5eOi`ZBpuqvigJm@C7K>EIwR)B7Dbf&1v|_u~mXfDhxHi~$eQ`VZl2csE-C z@4<^4zeF4U0FStq-^1wkJl4x~vYDf6@T~8%cM-Xs&z`^$xt^;Hq&F;EI0}=K=gWTl za+M!nkBH~TucD9Urmb7&aD{+Ur%ngFfMF$rSK9!tB=8Yxq&WBl2ds1JNG=+^NM) literal 0 HcmV?d00001 diff --git a/target/classes/config.yml b/target/classes/config.yml new file mode 100644 index 0000000..6f8e428 --- /dev/null +++ b/target/classes/config.yml @@ -0,0 +1,414 @@ +# DirtbagMC progression configuration +# +# Size terminology: +# Every border size in this file is a FULL DIAMETER / WIDTH in blocks, matching Bukkit/Paper's WorldBorder size. +# Example: starting-size: 50000 creates a border that is 50,000 blocks wide, roughly 25,000 blocks from center to edge. +# +# Growth modes: +# INCREMENTAL - each expansion adds growth-amount to the current full diameter. +# PHASE - each expansion moves to the next exact size listed under phases. +# +# Border modes: +# WORLD_BORDER - use Minecraft/Paper's real WorldBorder. +# SOFT_BORDER - do not enforce the vanilla border; bounce players off an invisible DirtbagMC border with effects. +# +# Trigger modes: +# MANUAL, TIME, UNIQUE_PLAYERS, ONLINE_PLAYERS, ACTIVE_PLAYERS, +# TIME_OR_UNIQUE_PLAYERS, TIME_AND_UNIQUE_PLAYERS, +# TIME_OR_ONLINE_PLAYERS, TIME_AND_ONLINE_PLAYERS, +# TIME_OR_ACTIVE_PLAYERS, TIME_AND_ACTIVE_PLAYERS. + +settings: + dry-run: false + debug: false + poll-interval-seconds: 30 + apply-borders-on-startup: true + max-catchup-expansions-per-world: 20 + +state: + file-name: state.yml + save-interval-seconds: 300 + backup-on-save: true + backup-keep: 10 + +legacy-users: + # Admin scan command: + # /dirtsmp legacy scan + # Also supported: + # /dirtsmp add legacy users scan + # Players with at least this much Minecraft playtime are added to state.yml under legacy-users. + minimum-playtime: 1h + +history: + enabled: true + file-name: history.log + +gui: + enabled: true + title: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛA4A2A&lʙB6B35&lᴀ&#A8873F&lɢ&#D4AF37&lᴍ&#B9C63F&lᴄ &8| &#D4AF37&lBorders" + +webhook: + enabled: false + url: "" + timeout-seconds: 8 + content: "**DirtbagMC progression:** `{world}` expanded from `{old_size}` to `{new_size}` blocks. Reason: `{reason}`" + +command-hooks: + enabled: true + before-expansion: + # - "say Preparing expansion for {world} to {new_size}" + after-expansion: + # Useful for tools like Chunky: + # - "chunky world {world}" + # - "chunky border" + # - "chunky start" + +milestone-rewards: + enabled: true + # Commands run from console after an expansion reaches a configured milestone. + # Placeholders: {world}, {key}, {old_size}, {new_size}, {max_size}, {reason}, {actor}, {phase}, {phase_name}, {expansion_count} + every-expansion: [] + by-expansion-count: + # Example rewards: + # 1: + # - "broadcast DirtbagMC milestone 1 reached in {world}!" + # - "crate key giveall beta 1" + 1: [] + 3: [] + 5: [] + by-phase-index: + 1: [] + by-phase-name: + Frontier Era: [] + First Ring: [] + +worlds: + overworld: + enabled: true + world: world + center-x: 0.0 + center-z: 0.0 + starting-size: 50000 + border-mode: SOFT_BORDER + growth-mode: INCREMENTAL + growth-amount: 10000 + max-size: 200000 + transition-seconds: 300 + manual-only: false + import-current-border: false + no-shrink-protection: true + enforce-max-size: true + command-hooks: + before-expansion: [] + after-expansion: [] + milestone-rewards: + enabled: true + every-expansion: [] + by-expansion-count: + 1: [] + 2: [] + 3: [] + by-phase-index: {} + by-phase-name: {} + legacy-access: + enabled: true + # This protects beta builds from being stranded when progression starts after launch. + # The plugin expands the initial/saved border enough to include known locations plus padding. + include-current-border: false + include-online-players: true + include-offline-last-locations: true + include-respawn-locations: true + # When true, player last/respawn locations only count if the player is in the scanned legacy-users list. + player-locations-require-legacy-user: true + reconcile-on-startup: true + allow-start-above-max-size: true + padding: 1024 + # Add homes, towns, bases, or Essentials homes that cannot be discovered from Paper playerdata. + # Border math uses full diameter/width, centered on center-x/center-z. + locations: [] + # Example: + # locations: + # - name: "Beta town" + # x: 31500 + # z: -22400 + soft-border: + # Premium invisible border mode. Used only when border-mode is SOFT_BORDER. + # release-vanilla-border removes any old vanilla border enforcement by setting it to vanilla-border-size. + release-vanilla-border: true + vanilla-border-size: 59999968 + ignore-creative: false + ignore-spectator: true + bypass-permission: dirtsmp.bypass.softborder + inside-buffer: 1.5 + bounce-strength: 1.85 + vertical-boost: 0.35 + protect-mounted-entities: true + # Gentler values keep horses, camels, boats, and minecarts from taking weird launch angles. + mounted-bounce-strength: 1.15 + mounted-vertical-boost: 0.12 + max-outside-distance-before-teleport: 24 + cooldown: 900ms + message: "{prefix}&#D4AF37&lThe DirtbagMC border &7throws you back." + action-bar: "&#D4AF37&lBorder reached &8| &7turn back" + sound: ENTITY_SLIME_JUMP + volume: 0.65 + pitch: 0.65 + particles: + enabled: true + # DUST uses the DirtbagMC gold color. Other Bukkit particle names also work. + particle: DUST + count: 3 + offset: 0.05 + speed: 0.0 + interval-ticks: 10 + view-distance: 96 + spacing: 3 + broadcasts: + enabled: true + world-only: false + message: "{prefix}&#B9C63FThe dirt expands. &#D4AF37&l{world} &#B9C63Fhas grown from &#A8873F{old_size} &#B9C63Fto &#D4AF37&l{new_size} &#B9C63Fblocks." + title: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛA4A2A&lʙB6B35&lᴀ&#A8873F&lɢ&#D4AF37&lᴍ&#B9C63F&lᴄ" + subtitle: "&#B9C63F{world} &8| &#D4AF37{old_size} &7-> &#D4AF37{new_size}" + action-bar: "&#D4AF37&l{world} &7border is now &#B9C63F{new_size} &7blocks wide" + sound: ENTITY_ENDER_DRAGON_GROWL + volume: 0.7 + pitch: 1.25 + reminders: + enabled: true + before: + - 1d + - 12h + - 1h + - 10m + message: "{prefix}&#D4AF37&l{world} &7expands in &#B9C63F{time_left}&7." + action-bar: "&#D4AF37&l{world} &7expands in &#B9C63F{time_left}" + sound: BLOCK_NOTE_BLOCK_PLING + volume: 0.45 + pitch: 1.4 + triggers: + mode: TIME_OR_UNIQUE_PLAYERS + time-interval: 7d + unique-players-every: 50 + online-players-threshold: 35 + active-players-threshold: 120 + active-window: 7d + exclude-vanished: true + player-trigger-cooldown: 12h + catch-up: ONE + phases: + - name: Spawn Era + size: 50000 + message: "" + - name: Frontier Era + size: 75000 + message: "" + + end: + enabled: true + world: world_the_end + center-x: 0.0 + center-z: 0.0 + starting-size: 2000 + border-mode: SOFT_BORDER + growth-mode: PHASE + growth-amount: 2000 + max-size: 20000 + transition-seconds: 180 + manual-only: false + import-current-border: false + no-shrink-protection: true + enforce-max-size: true + command-hooks: + before-expansion: [] + after-expansion: [] + milestone-rewards: + enabled: true + every-expansion: [] + by-expansion-count: + 1: [] + 2: [] + by-phase-index: + 1: [] + 2: [] + by-phase-name: + First Ring: [] + Chorus Frontier: [] + legacy-access: + enabled: true + include-current-border: false + include-online-players: true + include-offline-last-locations: true + include-respawn-locations: true + player-locations-require-legacy-user: true + reconcile-on-startup: true + allow-start-above-max-size: true + padding: 512 + locations: [] + soft-border: + release-vanilla-border: true + vanilla-border-size: 59999968 + ignore-creative: false + ignore-spectator: true + bypass-permission: dirtsmp.bypass.softborder + inside-buffer: 1.5 + bounce-strength: 2.05 + vertical-boost: 0.45 + protect-mounted-entities: true + mounted-bounce-strength: 1.2 + mounted-vertical-boost: 0.12 + max-outside-distance-before-teleport: 18 + cooldown: 900ms + message: "{prefix}A2416&lThe End border &7snaps you back." + action-bar: "A2416&lThe End border &8| &7turn back" + sound: ENTITY_ENDERMAN_TELEPORT + volume: 0.55 + pitch: 0.8 + particles: + enabled: true + particle: DUST + count: 3 + offset: 0.05 + speed: 0.0 + interval-ticks: 10 + view-distance: 72 + spacing: 2.5 + broadcasts: + enabled: true + world-only: false + message: "{prefix}A2416&lThe End &#B9C63Fhas entered &#D4AF37&l{phase_name}&#B9C63F: &#D4AF37{new_size} &#B9C63Fblocks wide." + title: "A2416&lThe End Opens" + subtitle: "&#D4AF37{phase_name} &8| &#B9C63F{new_size} blocks" + action-bar: "A2416&lThe End &7border is now &#D4AF37{new_size}" + sound: ENTITY_ENDER_DRAGON_DEATH + volume: 0.8 + pitch: 1.0 + reminders: + enabled: true + before: + - 12h + - 1h + - 10m + message: "{prefix}A2416&lThe End &7expands in &#D4AF37{time_left}&7." + action-bar: "A2416&lThe End &7expands in &#D4AF37{time_left}" + sound: BLOCK_NOTE_BLOCK_PLING + volume: 0.45 + pitch: 1.6 + triggers: + mode: TIME_AND_UNIQUE_PLAYERS + time-interval: 10d + unique-players-every: 100 + online-players-threshold: 40 + active-players-threshold: 150 + active-window: 7d + exclude-vanished: true + player-trigger-cooldown: 12h + catch-up: ONE + phases: + - name: Outer Silence + size: 2000 + message: "" + - name: First Ring + size: 4000 + message: "{prefix}A2416&lThe End &#B9C63Fstirs. &#D4AF37The first outer ring &#B9C63Fis now reachable." + - name: Chorus Frontier + size: 6000 + message: "{prefix}A2416&lThe End &#B9C63Fgrows again. &#D4AF37New islands await." + - name: Dragon's Wake + size: 8000 + message: "{prefix}A2416&lThe End &#B9C63Fborder has expanded to &#D4AF37{new_size} &#B9C63Fblocks." + + nether: + enabled: false + world: world_nether + center-x: 0.0 + center-z: 0.0 + starting-size: 10000 + border-mode: SOFT_BORDER + growth-mode: INCREMENTAL + growth-amount: 2500 + max-size: 50000 + transition-seconds: 120 + manual-only: false + import-current-border: false + no-shrink-protection: true + enforce-max-size: true + command-hooks: + before-expansion: [] + after-expansion: [] + milestone-rewards: + enabled: true + every-expansion: [] + by-expansion-count: + 1: [] + by-phase-index: {} + by-phase-name: {} + legacy-access: + enabled: false + include-current-border: false + include-online-players: true + include-offline-last-locations: true + include-respawn-locations: true + player-locations-require-legacy-user: true + reconcile-on-startup: true + allow-start-above-max-size: true + padding: 512 + locations: [] + soft-border: + release-vanilla-border: true + vanilla-border-size: 59999968 + ignore-creative: false + ignore-spectator: true + bypass-permission: dirtsmp.bypass.softborder + inside-buffer: 1.5 + bounce-strength: 1.9 + vertical-boost: 0.35 + protect-mounted-entities: true + mounted-bounce-strength: 1.15 + mounted-vertical-boost: 0.12 + max-outside-distance-before-teleport: 18 + cooldown: 900ms + message: "{prefix}A4A2A&lThe Nether border &7throws you back." + action-bar: "A4A2A&lNether border &8| &7turn back" + sound: ENTITY_BLAZE_HURT + volume: 0.55 + pitch: 0.8 + particles: + enabled: true + particle: DUST + count: 3 + offset: 0.05 + speed: 0.0 + interval-ticks: 10 + view-distance: 72 + spacing: 2.5 + broadcasts: + enabled: true + world-only: false + message: "{prefix}A4A2A&lThe Nether &#B9C63Fhas expanded to &#D4AF37{new_size} &#B9C63Fblocks." + title: "A4A2A&lNether Expanded" + subtitle: "&#A8873F{old_size} &7-> &#D4AF37{new_size}" + action-bar: "A4A2A&lThe Nether &7border is now &#D4AF37{new_size}" + sound: ENTITY_WITHER_SPAWN + volume: 0.65 + pitch: 1.1 + reminders: + enabled: true + before: + - 1h + - 10m + message: "{prefix}A4A2A&lThe Nether &7expands in &#D4AF37{time_left}&7." + action-bar: "A4A2A&lThe Nether &7expands in &#D4AF37{time_left}" + sound: BLOCK_NOTE_BLOCK_PLING + volume: 0.45 + pitch: 1.2 + triggers: + mode: MANUAL + time-interval: 7d + unique-players-every: 50 + online-players-threshold: 30 + active-players-threshold: 100 + active-window: 7d + exclude-vanished: true + player-trigger-cooldown: 12h + catch-up: NONE + phases: [] diff --git a/target/classes/messages.yml b/target/classes/messages.yml new file mode 100644 index 0000000..5dc69ed --- /dev/null +++ b/target/classes/messages.yml @@ -0,0 +1,76 @@ +prefix: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛA4A2A&lʙB6B35&lᴀ&#A8873F&lɢ&#D4AF37&lᴍ&#B9C63F&lᴄ &8| &7" + +commands: + no-permission: "{prefix}&cYou do not have permission to do that." + player-only: "{prefix}&cOnly players can use that command." + unknown-world: "{prefix}&cUnknown configured world: &#D4AF37{world}" + invalid-number: "{prefix}&cThat size must be a positive number." + reload: "{prefix}&#B9C63FConfiguration, messages, and DirtbagMC progression rules reloaded." + saved: "{prefix}&#B9C63FState saved." + help: + - "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛA4A2A&lʙB6B35&lᴀ&#A8873F&lɢ&#D4AF37&lᴍ&#B9C63F&lᴄ &8| &#D4AF37&lBorder Progression" + - "B6B35/dirtsmp status [world] &8- &7view progression" + - "B6B35/dirtsmp next [world] &8- &7view upcoming triggers" + - "B6B35/dirtsmp legacycheck &8- &7scan beta home safety" + - "B6B35/dirtsmp tpborder [side] &8- &7teleport near a border wall" + - "B6B35/dirtsmp legacy scan [1h] &8- &7mark legacy players by playtime" + - "B6B35/dirtsmp legacy list &8- &7show marked legacy players" + - "B6B35/dirtsmp expand &8- &7expand now" + - "B6B35/dirtsmp setsize [force] &8- &7set border size" + - "B6B35/dirtsmp pause &8- &7pause automatic progression" + - "B6B35/dirtsmp resume &8- &7resume automatic progression" + - "B6B35/dirtsmp reset [force] &8- &7reset progression" + - "B6B35/dirtsmp gui &8- &7open admin panel" + - "B6B35/dirtsmp reload &8- &7reload config" + status-header: "{prefix}&#B9C63FTracking &#D4AF37&l{count} &#B9C63Fconfigured world(s)." + status-line: "&8- &#D4AF37&l{key} &8(&7{world}&8) &7enabled: &#B9C63F{enabled} &7size: &#B9C63F{size} &7next: &#D4AF37{next_size} &7mode: &#A8873F{border_mode} &7phase: &#A8873F{phase} &7paused: &c{paused}" + next-line: "{prefix}&#D4AF37&l{world} &7next trigger: &#B9C63F{trigger}&7. Next size: &#D4AF37{next_size}&7." + legacy-check: "{prefix}&#D4AF37&l{world} &7legacy scan: &#B9C63F{locations} &7location(s), required size &#D4AF37{required_size}&7, recommended startup size &#B9C63F{recommended_size}&7. Farthest: &#A8873F{farthest}&7." + legacy-scan: "{prefix}&#B9C63FScanned &#D4AF37{scanned} &#B9C63Fplayer(s). Matched &#D4AF37{matched} &#B9C63Fat &#D4AF37{minimum}&#B9C63F+. Added &#D4AF37{added}&#B9C63F. Legacy total: &#D4AF37{total}&7." + legacy-list-header: "{prefix}&#B9C63FLegacy users: &#D4AF37{count}&7. Showing up to 20." + legacy-list-line: "&8- &#D4AF37{name} &7playtime: &#B9C63F{playtime} &7source: &#A8873F{source}" + legacy-add: "{prefix}&#D4AF37{name} &7was &#B9C63F{status} &7as a legacy user." + legacy-remove: "{prefix}&#D4AF37{name} &7legacy status: &#B9C63F{status}&7." + tp-border: "{prefix}&#B9C63FTeleported to the &#D4AF37{side} &#B9C63Fborder wall for &#D4AF37{world} &8(&7size {size}&8)&7." + tp-border-failed: "{prefix}&cCould not teleport to &#D4AF37{world}&c border: &7{reason}" + expanded: "{prefix}&#B9C63FExpanded &#D4AF37&l{world} &#B9C63Ffrom &#A8873F{old_size} &#B9C63Fto &#D4AF37{new_size}&7." + expand-failed: "{prefix}&cCould not expand &#D4AF37{world}&c: &7{reason}" + set-size: "{prefix}&#B9C63FSet &#D4AF37&l{world} &#B9C63Fborder size to &#D4AF37{new_size}&7." + set-size-failed: "{prefix}&cCould not set &#D4AF37{world}&c: &7{reason}" + paused: "{prefix}&#D4AF37&l{world} &7automatic progression is now paused." + resumed: "{prefix}&#B9C63F{world} automatic progression resumed." + reset: "{prefix}&#B9C63FReset progression for &#D4AF37&l{world}&7." + gui-disabled: "{prefix}&cThe admin GUI is disabled in config." + +expansion: + default-message: "{prefix}&#B9C63F{world} expanded from &#A8873F{old_size} &#B9C63Fto &#D4AF37&l{new_size} &#B9C63Fblocks!" + default-title: "A2416&lDirtbagMC" + default-subtitle: "&#B9C63F{world} &8| &#D4AF37{old_size} &7-> &#D4AF37{new_size}" + default-action-bar: "&#B9C63F{world} &7is now &#D4AF37&l{new_size} &#B9C63Fblocks wide" + max-reached: "The world is already at its configured maximum or final phase." + paused: "Automatic progression is paused." + manual-only: "This world is configured for manual-only progression." + missing-world: "The Bukkit world is not loaded or does not exist." + dry-run: "Dry-run mode is enabled; no border was changed." + no-shrink: "No-shrink protection blocked a smaller size. Add 'force' to the command to override." + +reminders: + default-message: "{prefix}&#D4AF37&l{world} &7expands in &#B9C63F{time_left}&7." + default-action-bar: "&#D4AF37&l{world} &7expands in &#B9C63F{time_left}" + +gui: + world-name: "&#D4AF37&l{key}" + world-lore: + - "&7World: &#D4AF37{world}" + - "&7Enabled: &#B9C63F{enabled}" + - "&7Size: &#B9C63F{size}" + - "&7Next: &#D4AF37{next_size}" + - "&7Mode: &#A8873F{border_mode}" + - "&7Trigger: B6B35{trigger}" + - "&7Phase: &#A8873F{phase}" + - "&7Paused: &c{paused}" + - "" + - "&#D4AF37Left-click &8- &7expand now" + - "&#D4AF37Right-click &8- &7pause/resume" + refresh: "&#B9C63FRefresh" + close: "&cClose" diff --git a/target/classes/plugin.yml b/target/classes/plugin.yml new file mode 100644 index 0000000..28b4ec2 --- /dev/null +++ b/target/classes/plugin.yml @@ -0,0 +1,65 @@ +name: DirtSMP +version: 1.0.0 +main: com.dirtsmp.dirtsmp.DirtSMPPlugin +api-version: '1.21' +authors: + - DirtbagMC +description: DirtbagMC-themed configurable world-border progression for Paper SMP servers. +softdepend: + - PlaceholderAPI +commands: + dirtsmp: + description: Manage DirtbagMC world progression. + usage: /dirtsmp + aliases: + - dsmp + - dirtborder +permissions: + dirtsmp.admin: + description: Full access to every DirtbagMC progression command. + default: op + children: + dirtsmp.status: true + dirtsmp.expand: true + dirtsmp.setsize: true + dirtsmp.pause: true + dirtsmp.resume: true + dirtsmp.reload: true + dirtsmp.gui: true + dirtsmp.reset: true + dirtsmp.legacy: true + dirtsmp.tpborder: true + dirtsmp.bypass.softborder: true + dirtsmp.status: + description: View DirtbagMC progression status and legacy access scans. + default: true + dirtsmp.expand: + description: Manually expand a configured world border. + default: op + dirtsmp.setsize: + description: Set a configured world's border size. + default: op + dirtsmp.pause: + description: Pause automatic progression for a world. + default: op + dirtsmp.resume: + description: Resume automatic progression for a world. + default: op + dirtsmp.reload: + description: Reload DirtbagMC progression configuration. + default: op + dirtsmp.gui: + description: Open the DirtbagMC admin GUI. + default: op + dirtsmp.reset: + description: Reset a world's DirtbagMC progression state. + default: op + dirtsmp.legacy: + description: Scan, list, add, and remove DirtbagMC legacy users. + default: op + dirtsmp.tpborder: + description: Teleport near a configured world border for visual inspection. + default: op + dirtsmp.bypass.softborder: + description: Bypass DirtbagMC soft border bounce and correction. + default: op diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties new file mode 100644 index 0000000..2479a7f --- /dev/null +++ b/target/maven-archiver/pom.properties @@ -0,0 +1,3 @@ +artifactId=DirtSMP +groupId=com.dirtsmp +version=1.0.0 diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..78c10b0 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,42 @@ +com/dirtsmp/dirtsmp/service/TimeUtil.class +com/dirtsmp/dirtsmp/model/TriggerMode.class +com/dirtsmp/dirtsmp/config/MessageManager.class +com/dirtsmp/dirtsmp/model/ExpansionReason.class +com/dirtsmp/dirtsmp/service/HistoryLogger.class +com/dirtsmp/dirtsmp/service/TriggerEvaluator$1.class +com/dirtsmp/dirtsmp/task/ScheduleManager.class +com/dirtsmp/dirtsmp/state/PlayerStatsManager.class +com/dirtsmp/dirtsmp/config/ConfigManager.class +com/dirtsmp/dirtsmp/service/SoftBorderManager.class +com/dirtsmp/dirtsmp/model/TriggerSettings.class +com/dirtsmp/dirtsmp/service/SoftBorderManager$Bounds.class +com/dirtsmp/dirtsmp/model/WorldRule$MilestoneRewardSettings.class +com/dirtsmp/dirtsmp/hook/PlaceholderHook.class +com/dirtsmp/dirtsmp/model/GrowthMode.class +com/dirtsmp/dirtsmp/model/WorldRule$LegacyLocation.class +com/dirtsmp/dirtsmp/service/TriggerEvaluator.class +com/dirtsmp/dirtsmp/state/StateManager.class +com/dirtsmp/dirtsmp/model/WorldRule.class +com/dirtsmp/dirtsmp/gui/AdminGuiManager.class +com/dirtsmp/dirtsmp/model/WorldRule$LegacyAccessSettings.class +com/dirtsmp/dirtsmp/model/WorldRule$AnnouncementSettings.class +com/dirtsmp/dirtsmp/state/StateManager$LegacyUserRecord.class +com/dirtsmp/dirtsmp/service/WebhookNotifier.class +com/dirtsmp/dirtsmp/listener/PlayerListener.class +com/dirtsmp/dirtsmp/hook/DirtSMPPlaceholderExpansion.class +com/dirtsmp/dirtsmp/command/DirtSMPCommand.class +com/dirtsmp/dirtsmp/DirtSMPPlugin.class +com/dirtsmp/dirtsmp/model/PhaseDefinition.class +com/dirtsmp/dirtsmp/service/BorderManager.class +com/dirtsmp/dirtsmp/model/WorldRule$ReminderSettings.class +com/dirtsmp/dirtsmp/model/ExpansionResult.class +com/dirtsmp/dirtsmp/model/LegacyAccessReport.class +com/dirtsmp/dirtsmp/state/PlayerStatsManager$PlayerRecord.class +com/dirtsmp/dirtsmp/state/WorldProgress.class +com/dirtsmp/dirtsmp/service/CommandHookManager.class +com/dirtsmp/dirtsmp/model/BorderMode.class +com/dirtsmp/dirtsmp/service/BorderManager$LegacyAccumulator.class +com/dirtsmp/dirtsmp/model/WorldRule$SoftBorderSettings.class +com/dirtsmp/dirtsmp/model/TriggerDecision.class +com/dirtsmp/dirtsmp/model/CatchUpMode.class +com/dirtsmp/dirtsmp/gui/AdminGuiManager$GuiHolder.class diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..6e072c4 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,30 @@ +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/DirtSMPPlugin.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/command/DirtSMPCommand.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/config/ConfigManager.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/config/MessageManager.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/gui/AdminGuiManager.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/hook/DirtSMPPlaceholderExpansion.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/hook/PlaceholderHook.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/listener/PlayerListener.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/model/BorderMode.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/model/CatchUpMode.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/model/ExpansionReason.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/model/ExpansionResult.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/model/GrowthMode.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/model/LegacyAccessReport.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/model/PhaseDefinition.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/model/TriggerDecision.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/model/TriggerMode.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/model/TriggerSettings.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/model/WorldRule.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/service/BorderManager.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/service/CommandHookManager.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/service/HistoryLogger.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/service/SoftBorderManager.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/service/TimeUtil.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/service/TriggerEvaluator.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/service/WebhookNotifier.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/state/PlayerStatsManager.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/state/StateManager.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/state/WorldProgress.java +/home/bitnix/Desktop/DirtSMP/src/main/java/com/dirtsmp/dirtsmp/task/ScheduleManager.java