From c5c83aa216428a867e138417c884a953e07d3f66 Mon Sep 17 00:00:00 2001 From: Xelara Networks Date: Wed, 24 Jun 2026 18:38:15 -0400 Subject: [PATCH] first --- README.md | 0 pom.xml | 82 +++ .../dirtbounties/DirtBountiesPlugin.java | 204 ++++++ .../command/BountyAdminCommand.java | 193 ++++++ .../dirtbounties/command/BountyCommand.java | 223 ++++++ .../dirtbounties/config/ConfigService.java | 62 ++ .../dirtbounties/economy/EconomyService.java | 148 ++++ .../dirtbagmc/dirtbounties/gui/GuiHolder.java | 40 ++ .../dirtbounties/gui/GuiManager.java | 589 ++++++++++++++++ .../dirtbagmc/dirtbounties/gui/GuiType.java | 11 + .../dirtbounties/gui/PendingBountyInput.java | 96 +++ .../hook/DirtBountiesExpansion.java | 95 +++ .../dirtbounties/listener/BountyListener.java | 89 +++ .../dirtbagmc/dirtbounties/model/Bounty.java | 96 +++ .../model/BountyContribution.java | 58 ++ .../model/BountyHistoryRecord.java | 64 ++ .../dirtbounties/model/ClaimValidation.java | 11 + .../model/SuspiciousActivity.java | 64 ++ .../service/AntiAbuseService.java | 78 +++ .../dirtbounties/service/BountyService.java | 641 ++++++++++++++++++ .../dirtbounties/service/CombatTracker.java | 112 +++ .../dirtbounties/service/HistoryService.java | 163 +++++ .../dirtbounties/service/MessageService.java | 103 +++ .../service/PlayerCacheService.java | 126 ++++ .../dirtbounties/storage/StorageManager.java | 296 ++++++++ .../dirtbounties/util/ColorUtil.java | 63 ++ .../dirtbounties/util/ItemBuilder.java | 86 +++ .../dirtbounties/util/NumberUtil.java | 21 + .../dirtbagmc/dirtbounties/util/TimeUtil.java | 79 +++ .../dirtbounties/webhook/WebhookService.java | 100 +++ src/main/resources/config.yml | 154 +++++ src/main/resources/gui.yml | 177 +++++ src/main/resources/messages.yml | 105 +++ src/main/resources/plugin.yml | 67 ++ target/DirtBounties-1.0.0.jar | Bin 0 -> 109916 bytes .../dirtbounties/DirtBountiesPlugin.class | Bin 0 -> 11504 bytes .../command/BountyAdminCommand.class | Bin 0 -> 11567 bytes .../command/BountyCommand$ReasonInput.class | Bin 0 -> 1790 bytes .../dirtbounties/command/BountyCommand.class | Bin 0 -> 12603 bytes .../dirtbounties/config/ConfigService.class | Bin 0 -> 2410 bytes .../EconomyService$EconomyResult.class | Bin 0 -> 2111 bytes .../dirtbounties/economy/EconomyService.class | Bin 0 -> 7927 bytes .../dirtbounties/gui/GuiHolder.class | Bin 0 -> 1176 bytes .../dirtbounties/gui/GuiManager$1.class | Bin 0 -> 1584 bytes .../dirtbounties/gui/GuiManager.class | Bin 0 -> 26697 bytes .../dirtbagmc/dirtbounties/gui/GuiType.class | Bin 0 -> 1434 bytes .../gui/PendingBountyInput$Stage.class | Bin 0 -> 1482 bytes .../dirtbounties/gui/PendingBountyInput.class | Bin 0 -> 2407 bytes .../hook/DirtBountiesExpansion.class | Bin 0 -> 5776 bytes .../listener/BountyListener.class | Bin 0 -> 5561 bytes .../dirtbagmc/dirtbounties/model/Bounty.class | Bin 0 -> 3426 bytes .../model/BountyContribution.class | Bin 0 -> 1409 bytes .../model/BountyHistoryRecord.class | Bin 0 -> 1562 bytes .../dirtbounties/model/ClaimValidation.class | Bin 0 -> 2092 bytes .../model/SuspiciousActivity.class | Bin 0 -> 1566 bytes .../service/AntiAbuseService.class | Bin 0 -> 4954 bytes .../service/BountyService$Fee.class | Bin 0 -> 1850 bytes .../dirtbounties/service/BountyService.class | Bin 0 -> 30590 bytes .../service/CombatTracker$CombatRecord.class | Bin 0 -> 1141 bytes .../dirtbounties/service/CombatTracker.class | Bin 0 -> 5250 bytes .../dirtbounties/service/HistoryService.class | Bin 0 -> 10269 bytes .../dirtbounties/service/MessageService.class | Bin 0 -> 7927 bytes .../PlayerCacheService$CacheEntry.class | Bin 0 -> 1701 bytes .../service/PlayerCacheService.class | Bin 0 -> 6357 bytes .../StorageManager$PlayerCacheData.class | Bin 0 -> 2578 bytes .../dirtbounties/storage/StorageManager.class | Bin 0 -> 15393 bytes .../dirtbounties/util/ColorUtil.class | Bin 0 -> 4511 bytes .../dirtbounties/util/ItemBuilder.class | Bin 0 -> 6389 bytes .../dirtbounties/util/NumberUtil.class | Bin 0 -> 922 bytes .../dirtbounties/util/TimeUtil.class | Bin 0 -> 4200 bytes .../dirtbounties/webhook/WebhookService.class | Bin 0 -> 7909 bytes target/classes/config.yml | 154 +++++ target/classes/gui.yml | 177 +++++ target/classes/messages.yml | 105 +++ target/classes/plugin.yml | 67 ++ target/maven-archiver/pom.properties | 3 + .../compile/default-compile/createdFiles.lst | 36 + .../compile/default-compile/inputFiles.lst | 28 + 78 files changed, 5066 insertions(+) create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/DirtBountiesPlugin.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/command/BountyAdminCommand.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/command/BountyCommand.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/config/ConfigService.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/economy/EconomyService.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/gui/GuiHolder.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/gui/GuiManager.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/gui/GuiType.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/gui/PendingBountyInput.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/hook/DirtBountiesExpansion.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/listener/BountyListener.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/model/Bounty.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/model/BountyContribution.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/model/BountyHistoryRecord.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/model/ClaimValidation.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/model/SuspiciousActivity.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/service/AntiAbuseService.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/service/BountyService.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/service/CombatTracker.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/service/HistoryService.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/service/MessageService.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/service/PlayerCacheService.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/storage/StorageManager.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/util/ColorUtil.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/util/ItemBuilder.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/util/NumberUtil.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/util/TimeUtil.java create mode 100644 src/main/java/com/dirtbagmc/dirtbounties/webhook/WebhookService.java create mode 100644 src/main/resources/config.yml create mode 100644 src/main/resources/gui.yml create mode 100644 src/main/resources/messages.yml create mode 100644 src/main/resources/plugin.yml create mode 100644 target/DirtBounties-1.0.0.jar create mode 100644 target/classes/com/dirtbagmc/dirtbounties/DirtBountiesPlugin.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/command/BountyAdminCommand.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/command/BountyCommand$ReasonInput.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/command/BountyCommand.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/config/ConfigService.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/economy/EconomyService$EconomyResult.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/economy/EconomyService.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/gui/GuiHolder.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/gui/GuiManager$1.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/gui/GuiManager.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/gui/GuiType.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/gui/PendingBountyInput$Stage.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/gui/PendingBountyInput.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/hook/DirtBountiesExpansion.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/listener/BountyListener.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/model/Bounty.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/model/BountyContribution.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/model/BountyHistoryRecord.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/model/ClaimValidation.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/model/SuspiciousActivity.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/service/AntiAbuseService.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/service/BountyService$Fee.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/service/BountyService.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/service/CombatTracker$CombatRecord.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/service/CombatTracker.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/service/HistoryService.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/service/MessageService.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/service/PlayerCacheService$CacheEntry.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/service/PlayerCacheService.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/storage/StorageManager$PlayerCacheData.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/storage/StorageManager.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/util/ColorUtil.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/util/ItemBuilder.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/util/NumberUtil.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/util/TimeUtil.class create mode 100644 target/classes/com/dirtbagmc/dirtbounties/webhook/WebhookService.class create mode 100644 target/classes/config.yml create mode 100644 target/classes/gui.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..9717efe --- /dev/null +++ b/pom.xml @@ -0,0 +1,82 @@ + + + 4.0.0 + + com.dirtbagmc + DirtBounties + 1.0.0 + jar + + DirtBounties + Premium Paper bounty system for DirtbagMC. + https://dirtbagmc.com + + + 21 + UTF-8 + 1.21.8-R0.1-SNAPSHOT + 2.11.6 + + + + + papermc + https://repo.papermc.io/repository/maven-public/ + + + placeholderapi + https://repo.extendedclip.com/content/repositories/placeholderapi/ + + + + + + io.papermc.paper + paper-api + ${paper.version} + provided + + + me.clip + placeholderapi + ${placeholderapi.version} + provided + true + + + + + DirtBounties-${project.version} + + + src/main/resources + false + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + ${java.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-jar-plugin + 3.4.2 + + + + true + + + + + + + diff --git a/src/main/java/com/dirtbagmc/dirtbounties/DirtBountiesPlugin.java b/src/main/java/com/dirtbagmc/dirtbounties/DirtBountiesPlugin.java new file mode 100644 index 0000000..d1b09f2 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/DirtBountiesPlugin.java @@ -0,0 +1,204 @@ +package com.dirtbagmc.dirtbounties; + +import com.dirtbagmc.dirtbounties.command.BountyAdminCommand; +import com.dirtbagmc.dirtbounties.command.BountyCommand; +import com.dirtbagmc.dirtbounties.config.ConfigService; +import com.dirtbagmc.dirtbounties.economy.EconomyService; +import com.dirtbagmc.dirtbounties.gui.GuiManager; +import com.dirtbagmc.dirtbounties.hook.DirtBountiesExpansion; +import com.dirtbagmc.dirtbounties.listener.BountyListener; +import com.dirtbagmc.dirtbounties.service.AntiAbuseService; +import com.dirtbagmc.dirtbounties.service.BountyService; +import com.dirtbagmc.dirtbounties.service.CombatTracker; +import com.dirtbagmc.dirtbounties.service.HistoryService; +import com.dirtbagmc.dirtbounties.service.MessageService; +import com.dirtbagmc.dirtbounties.service.PlayerCacheService; +import com.dirtbagmc.dirtbounties.storage.StorageManager; +import com.dirtbagmc.dirtbounties.util.TimeUtil; +import com.dirtbagmc.dirtbounties.webhook.WebhookService; +import org.bukkit.Bukkit; +import org.bukkit.command.PluginCommand; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitTask; + +import java.util.Objects; +import java.util.logging.Level; + +public final class DirtBountiesPlugin extends JavaPlugin { + private ConfigService configService; + private MessageService messageService; + private StorageManager storageManager; + private EconomyService economyService; + private HistoryService historyService; + private PlayerCacheService playerCacheService; + private CombatTracker combatTracker; + private AntiAbuseService antiAbuseService; + private WebhookService webhookService; + private BountyService bountyService; + private GuiManager guiManager; + private BukkitTask cleanupTask; + private BukkitTask autosaveTask; + private Object placeholderExpansion; + + @Override + public void onEnable() { + try { + bootstrap(); + registerCommands(); + registerEvents(); + registerPlaceholders(); + scheduleTasks(); + getLogger().info("DirtBounties enabled with " + bountyService.activeBounties().size() + " active bounties."); + } catch (RuntimeException ex) { + getLogger().log(Level.SEVERE, "DirtBounties failed to enable safely.", ex); + Bukkit.getPluginManager().disablePlugin(this); + } + } + + @Override + public void onDisable() { + Bukkit.getScheduler().cancelTasks(this); + if (bountyService != null) { + bountyService.save(); + } + if (historyService != null) { + historyService.save(); + } + if (playerCacheService != null) { + playerCacheService.save(); + } + if (guiManager != null) { + guiManager.clear(); + } + if (combatTracker != null) { + combatTracker.clear(); + } + if (antiAbuseService != null) { + antiAbuseService.clear(); + } + if (bountyService != null) { + bountyService.clearRuntimeState(); + } + cleanupTask = null; + autosaveTask = null; + getLogger().info("DirtBounties disabled cleanly."); + } + + public void reloadDirtBounties() { + Bukkit.getScheduler().cancelTasks(this); + if (bountyService != null) { + bountyService.save(); + } + if (historyService != null) { + historyService.save(); + } + if (playerCacheService != null) { + playerCacheService.save(); + } + + configService.load(); + economyService.initialize(); + webhookService.initialize(); + historyService.load(); + playerCacheService.load(); + bountyService.load(); + guiManager.clear(); + combatTracker.clear(); + antiAbuseService.clear(); + scheduleTasks(); + } + + public BountyService bountyService() { + return bountyService; + } + + public HistoryService historyService() { + return historyService; + } + + private void bootstrap() { + configService = new ConfigService(this); + configService.load(); + messageService = new MessageService(configService); + storageManager = new StorageManager(this); + storageManager.initialize(); + economyService = new EconomyService(this, configService); + economyService.initialize(); + if (!economyService.isReady() && configService.main().getBoolean("economy.fail-if-missing", false)) { + throw new IllegalStateException("Configured to fail when no Vault-compatible economy is available."); + } + + historyService = new HistoryService(configService, storageManager); + historyService.load(); + playerCacheService = new PlayerCacheService(configService, storageManager); + playerCacheService.load(); + combatTracker = new CombatTracker(configService); + antiAbuseService = new AntiAbuseService(configService, historyService); + webhookService = new WebhookService(this, configService, messageService); + webhookService.initialize(); + bountyService = new BountyService(this, configService, messageService, economyService, storageManager, + historyService, playerCacheService, combatTracker, antiAbuseService, webhookService); + bountyService.load(); + guiManager = new GuiManager(this, configService, messageService, bountyService, historyService); + } + + private void registerCommands() { + BountyCommand bountyCommand = new BountyCommand(configService, messageService, bountyService, guiManager); + PluginCommand bounty = Objects.requireNonNull(getCommand("bounty"), "bounty command missing from plugin.yml"); + bounty.setExecutor(bountyCommand); + bounty.setTabCompleter(bountyCommand); + + BountyAdminCommand adminCommand = new BountyAdminCommand(this, configService, messageService, + bountyService, historyService, guiManager); + PluginCommand admin = Objects.requireNonNull(getCommand("bountyadmin"), "bountyadmin command missing from plugin.yml"); + admin.setExecutor(adminCommand); + admin.setTabCompleter(adminCommand); + } + + private void registerEvents() { + Bukkit.getPluginManager().registerEvents( + new BountyListener(this, guiManager, bountyService, playerCacheService, combatTracker), this); + } + + private void registerPlaceholders() { + if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") == null) { + return; + } + try { + placeholderExpansion = new DirtBountiesExpansion(bountyService, historyService, getDescription().getVersion()); + ((DirtBountiesExpansion) placeholderExpansion).register(); + getLogger().info("Registered PlaceholderAPI placeholders."); + } catch (NoClassDefFoundError | RuntimeException ex) { + getLogger().log(Level.WARNING, "PlaceholderAPI was present but DirtBounties could not register placeholders.", ex); + } + } + + private void scheduleTasks() { + long cleanupTicks = ticks(configService.main().getString("storage.cleanup-expired-interval"), 600_000L); + if (cleanupTicks > 0L) { + cleanupTask = Bukkit.getScheduler().runTaskTimer(this, () -> { + int removed = bountyService.cleanupExpired(); + if (removed > 0 && configService.main().getBoolean("logging.console.admin-actions", true)) { + getLogger().info("Cleaned up " + removed + " expired bounties."); + } + }, cleanupTicks, cleanupTicks); + } + + long autosaveTicks = ticks(configService.main().getString("storage.autosave-interval"), 300_000L); + if (autosaveTicks > 0L) { + autosaveTask = Bukkit.getScheduler().runTaskTimer(this, () -> { + bountyService.save(); + historyService.save(); + playerCacheService.save(); + }, autosaveTicks, autosaveTicks); + } + } + + private long ticks(String duration, long fallbackMillis) { + long millis = TimeUtil.parseMillis(duration, fallbackMillis); + if (millis <= 0L) { + return 0L; + } + return Math.max(20L, millis / 50L); + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/command/BountyAdminCommand.java b/src/main/java/com/dirtbagmc/dirtbounties/command/BountyAdminCommand.java new file mode 100644 index 0000000..52a36d7 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/command/BountyAdminCommand.java @@ -0,0 +1,193 @@ +package com.dirtbagmc.dirtbounties.command; + +import com.dirtbagmc.dirtbounties.DirtBountiesPlugin; +import com.dirtbagmc.dirtbounties.config.ConfigService; +import com.dirtbagmc.dirtbounties.gui.GuiManager; +import com.dirtbagmc.dirtbounties.model.BountyHistoryRecord; +import com.dirtbagmc.dirtbounties.model.SuspiciousActivity; +import com.dirtbagmc.dirtbounties.service.BountyService; +import com.dirtbagmc.dirtbounties.service.HistoryService; +import com.dirtbagmc.dirtbounties.service.MessageService; +import com.dirtbagmc.dirtbounties.util.TimeUtil; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public final class BountyAdminCommand implements TabExecutor { + private final DirtBountiesPlugin plugin; + private final ConfigService configService; + private final MessageService messages; + private final BountyService bountyService; + private final HistoryService historyService; + private final GuiManager guiManager; + + public BountyAdminCommand(DirtBountiesPlugin plugin, ConfigService configService, MessageService messages, + BountyService bountyService, HistoryService historyService, GuiManager guiManager) { + this.plugin = plugin; + this.configService = configService; + this.messages = messages; + this.bountyService = bountyService; + this.historyService = historyService; + this.guiManager = guiManager; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!sender.hasPermission("dirtbounties.admin")) { + messages.send(sender, "no-permission"); + return true; + } + if (args.length == 0 || args[0].equalsIgnoreCase("help")) { + messages.sendLines(sender, "admin-help", Map.of()); + return true; + } + + switch (args[0].toLowerCase(Locale.ROOT)) { + case "reload" -> { + plugin.reloadDirtBounties(); + messages.send(sender, "reload-complete"); + } + case "remove" -> remove(sender, args); + case "clearall" -> clearAll(sender, args); + case "set" -> set(sender, args); + case "expire" -> expire(sender, args); + case "history" -> history(sender, args); + case "suspicious" -> suspicious(sender); + case "gui" -> { + if (sender instanceof Player player) { + guiManager.openAdminMain(player); + } else { + messages.send(sender, "player-only"); + } + } + default -> messages.sendLines(sender, "admin-help", Map.of()); + } + return true; + } + + private void remove(CommandSender sender, String[] args) { + if (args.length < 2) { + messages.send(sender, "invalid-player"); + return; + } + OfflinePlayer target = bountyService.resolveTarget(args[1]); + double refund = configService.main().getDouble("refunds.on-admin-remove-percent", 100.0); + bountyService.adminRemove(sender, target, "ADMIN_REMOVE", refund); + } + + private void clearAll(CommandSender sender, String[] args) { + if (args.length < 2 || !args[1].equalsIgnoreCase("confirm")) { + messages.send(sender, "bounty.clearall-warning"); + return; + } + int count = bountyService.clearAll(sender); + messages.send(sender, "bounty.clearall-done", Map.of("count", String.valueOf(count))); + } + + private void set(CommandSender sender, String[] args) { + if (args.length < 3) { + messages.send(sender, "invalid-number"); + return; + } + OfflinePlayer target = bountyService.resolveTarget(args[1]); + double amount; + try { + amount = Double.parseDouble(args[2].replace(",", "")); + } catch (NumberFormatException ex) { + messages.send(sender, "invalid-number"); + return; + } + bountyService.adminSet(sender, target, amount); + } + + private void expire(CommandSender sender, String[] args) { + if (args.length < 2) { + messages.send(sender, "invalid-player"); + return; + } + OfflinePlayer target = bountyService.resolveTarget(args[1]); + double refund = configService.main().getDouble("refunds.on-expire-percent", 50.0); + bountyService.adminRemove(sender, target, "EXPIRED", refund); + } + + private void history(CommandSender sender, String[] args) { + if (args.length < 2) { + messages.send(sender, "invalid-player"); + return; + } + OfflinePlayer target = bountyService.resolveTarget(args[1]); + if (target == null) { + messages.send(sender, "invalid-player"); + return; + } + int limit = configService.main().getInt("history.max-records-per-player-command", 10); + List records = historyService.forPlayer(target.getUniqueId(), limit); + if (records.isEmpty()) { + messages.send(sender, "admin.history-empty", Map.of("target", target.getName() == null ? args[1] : target.getName())); + return; + } + for (BountyHistoryRecord record : records) { + messages.send(sender, "admin.history-line", Map.of( + "time", TimeUtil.formatTimestamp(record.createdAt()), + "type", record.type(), + "amount", bountyService.formatAmount(record.amount()), + "note", record.note() + )); + } + } + + private void suspicious(CommandSender sender) { + List records = historyService.suspiciousRecent(10); + if (records.isEmpty()) { + messages.send(sender, "admin.suspicious-empty"); + return; + } + for (SuspiciousActivity record : records) { + messages.send(sender, "admin.suspicious-line", Map.of( + "time", TimeUtil.formatTimestamp(record.createdAt()), + "type", record.type(), + "details", record.details() + )); + } + } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + if (!sender.hasPermission("dirtbounties.admin")) { + return List.of(); + } + if (args.length == 1) { + return filter(args[0], List.of("reload", "remove", "clearall", "set", "expire", "history", "suspicious", "gui", "help")); + } + if (args.length == 2 && List.of("remove", "set", "expire", "history").contains(args[0].toLowerCase(Locale.ROOT))) { + List names = new ArrayList<>(); + Bukkit.getOnlinePlayers().forEach(player -> names.add(player.getName())); + bountyService.activeBounties().forEach(bounty -> names.add(bounty.targetName())); + return filter(args[1], names); + } + if (args.length == 2 && args[0].equalsIgnoreCase("clearall")) { + return filter(args[1], List.of("confirm")); + } + if (args.length == 3 && args[0].equalsIgnoreCase("set")) { + return filter(args[2], List.of("100", "1000", "5000", "10000")); + } + return List.of(); + } + + private List filter(String input, List options) { + String lower = input.toLowerCase(Locale.ROOT); + return options.stream() + .distinct() + .filter(option -> option.toLowerCase(Locale.ROOT).startsWith(lower)) + .sorted(String.CASE_INSENSITIVE_ORDER) + .toList(); + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/command/BountyCommand.java b/src/main/java/com/dirtbagmc/dirtbounties/command/BountyCommand.java new file mode 100644 index 0000000..cb416f3 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/command/BountyCommand.java @@ -0,0 +1,223 @@ +package com.dirtbagmc.dirtbounties.command; + +import com.dirtbagmc.dirtbounties.config.ConfigService; +import com.dirtbagmc.dirtbounties.gui.GuiManager; +import com.dirtbagmc.dirtbounties.model.Bounty; +import com.dirtbagmc.dirtbounties.service.BountyService; +import com.dirtbagmc.dirtbounties.service.MessageService; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public final class BountyCommand implements TabExecutor { + private final ConfigService configService; + private final MessageService messages; + private final BountyService bountyService; + private final GuiManager guiManager; + + public BountyCommand(ConfigService configService, MessageService messages, + BountyService bountyService, GuiManager guiManager) { + this.configService = configService; + this.messages = messages; + this.bountyService = bountyService; + this.guiManager = guiManager; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!sender.hasPermission("dirtbounties.use")) { + messages.send(sender, "no-permission"); + return true; + } + if (args.length == 0 || args[0].equalsIgnoreCase("gui")) { + if (!(sender instanceof Player player)) { + messages.send(sender, "player-only"); + return true; + } + guiManager.openMain(player, 0); + return true; + } + + switch (args[0].toLowerCase(Locale.ROOT)) { + case "help" -> messages.sendLines(sender, "help", Map.of()); + case "place" -> place(sender, args, false); + case "add", "increase" -> place(sender, args, true); + case "list" -> list(sender); + case "top" -> top(sender); + case "view" -> view(sender, args); + case "claiminfo" -> claimInfo(sender, args); + default -> messages.send(sender, "unknown-command"); + } + return true; + } + + private void place(CommandSender sender, String[] args, boolean increase) { + if (!(sender instanceof Player player)) { + messages.send(sender, "player-only"); + return; + } + if (!player.hasPermission(increase ? "dirtbounties.add" : "dirtbounties.place")) { + messages.send(player, "no-permission"); + return; + } + if (args.length < 3) { + messages.sendLines(player, "help", Map.of()); + return; + } + OfflinePlayer target = bountyService.resolveTarget(args[1]); + double amount; + try { + amount = Double.parseDouble(args[2].replace(",", "")); + } catch (NumberFormatException ex) { + messages.send(player, "invalid-number"); + return; + } + ReasonInput reason = parseReason(args, 3); + bountyService.placeBounty(player, target, amount, reason.reason(), reason.anonymous(), increase); + } + + private void list(CommandSender sender) { + if (!sender.hasPermission("dirtbounties.view")) { + messages.send(sender, "no-permission"); + return; + } + List bounties = bountyService.activeSorted(); + if (bounties.isEmpty()) { + messages.send(sender, "no-active-bounties"); + return; + } + int limit = Math.min(10, bounties.size()); + for (int i = 0; i < limit; i++) { + Bounty bounty = bounties.get(i); + messages.send(sender, "bounty.list-line", bountyService.bountyPlaceholders(bounty)); + } + } + + private void top(CommandSender sender) { + if (!sender.hasPermission("dirtbounties.top")) { + messages.send(sender, "no-permission"); + return; + } + List bounties = bountyService.topBounties(10); + if (bounties.isEmpty()) { + messages.send(sender, "no-active-bounties"); + return; + } + for (int i = 0; i < bounties.size(); i++) { + Bounty bounty = bounties.get(i); + Map placeholders = bountyService.bountyPlaceholders(bounty); + placeholders.put("rank", String.valueOf(i + 1)); + messages.send(sender, "bounty.top-line", placeholders); + } + } + + private void view(CommandSender sender, String[] args) { + if (!sender.hasPermission("dirtbounties.view")) { + messages.send(sender, "no-permission"); + return; + } + if (args.length < 2) { + messages.send(sender, "invalid-player"); + return; + } + OfflinePlayer target = bountyService.resolveTarget(args[1]); + if (target == null) { + messages.send(sender, "invalid-player"); + return; + } + Bounty bounty = bountyService.bounty(target.getUniqueId()).orElse(null); + if (bounty == null) { + messages.send(sender, "bounty-not-found", Map.of("target", target.getName() == null ? args[1] : target.getName())); + return; + } + messages.sendLines(sender, "bounty.view", bountyService.bountyPlaceholders(bounty)); + if (sender instanceof Player player) { + guiManager.openDetail(player, target.getUniqueId()); + } + } + + private void claimInfo(CommandSender sender, String[] args) { + if (!sender.hasPermission("dirtbounties.view")) { + messages.send(sender, "no-permission"); + return; + } + if (args.length < 2) { + messages.send(sender, "invalid-player"); + return; + } + OfflinePlayer target = bountyService.resolveTarget(args[1]); + if (target == null) { + messages.send(sender, "invalid-player"); + return; + } + Map placeholders = new HashMap<>(); + placeholders.put("target", target.getName() == null ? args[1] : target.getName()); + placeholders.put("pvp", yes(configService.main().getBoolean("claim-rules.require-pvp-kill", true))); + placeholders.put("same_ip", yes(configService.main().getBoolean("claim-rules.prevent-same-ip-claims", true))); + placeholders.put("worlds", configService.main().getString("claim-rules.worlds.mode", "blacklist") + + " " + configService.main().getStringList("claim-rules.worlds.list")); + placeholders.put("combat", yes(configService.main().getBoolean("claim-rules.combat.enabled", true))); + messages.sendLines(sender, "bounty.claiminfo", placeholders); + } + + private ReasonInput parseReason(String[] args, int start) { + boolean anonymous = false; + List parts = new ArrayList<>(); + for (int i = start; i < args.length; i++) { + if (args[i].equalsIgnoreCase("--anonymous") || args[i].equalsIgnoreCase("-a")) { + anonymous = true; + } else { + parts.add(args[i]); + } + } + String reason = parts.isEmpty() + ? configService.main().getString("bounties.default-reason", "No reason given.") + : String.join(" ", parts); + return new ReasonInput(reason, anonymous); + } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + if (args.length == 1) { + return filter(args[0], List.of("help", "place", "add", "list", "top", "view", "claiminfo", "gui")); + } + if (args.length == 2 && List.of("place", "add", "increase", "view", "claiminfo").contains(args[0].toLowerCase(Locale.ROOT))) { + List names = new ArrayList<>(); + Bukkit.getOnlinePlayers().forEach(player -> names.add(player.getName())); + bountyService.activeBounties().forEach(bounty -> names.add(bounty.targetName())); + return filter(args[1], names); + } + if (args.length == 3 && List.of("place", "add", "increase").contains(args[0].toLowerCase(Locale.ROOT))) { + return filter(args[2], List.of("100", "500", "1000", "5000", "10000")); + } + if (args.length >= 4 && List.of("place", "add", "increase").contains(args[0].toLowerCase(Locale.ROOT))) { + return filter(args[args.length - 1], List.of("--anonymous")); + } + return List.of(); + } + + private List filter(String input, List options) { + String lower = input.toLowerCase(Locale.ROOT); + return options.stream() + .distinct() + .filter(option -> option.toLowerCase(Locale.ROOT).startsWith(lower)) + .sorted(String.CASE_INSENSITIVE_ORDER) + .toList(); + } + + private String yes(boolean value) { + return value ? "yes" : "no"; + } + + private record ReasonInput(String reason, boolean anonymous) { + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/config/ConfigService.java b/src/main/java/com/dirtbagmc/dirtbounties/config/ConfigService.java new file mode 100644 index 0000000..9ccbbc5 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/config/ConfigService.java @@ -0,0 +1,62 @@ +package com.dirtbagmc.dirtbounties.config; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; + +public final class ConfigService { + private final JavaPlugin plugin; + private FileConfiguration mainConfig; + private YamlConfiguration messagesConfig; + private YamlConfiguration guiConfig; + private File messagesFile; + private File guiFile; + + public ConfigService(JavaPlugin plugin) { + this.plugin = plugin; + } + + public void load() { + plugin.getDataFolder().mkdirs(); + plugin.saveDefaultConfig(); + saveResourceIfMissing("messages.yml"); + saveResourceIfMissing("gui.yml"); + + plugin.reloadConfig(); + mainConfig = plugin.getConfig(); + messagesFile = new File(plugin.getDataFolder(), "messages.yml"); + guiFile = new File(plugin.getDataFolder(), "gui.yml"); + messagesConfig = YamlConfiguration.loadConfiguration(messagesFile); + guiConfig = YamlConfiguration.loadConfiguration(guiFile); + } + + public FileConfiguration main() { + return mainConfig; + } + + public YamlConfiguration messages() { + return messagesConfig; + } + + public YamlConfiguration gui() { + return guiConfig; + } + + public boolean debug() { + return mainConfig.getBoolean("server.debug", false); + } + + public ConfigurationSection guiSection(String path) { + return guiConfig.getConfigurationSection(path); + } + + private void saveResourceIfMissing(String name) { + File file = new File(plugin.getDataFolder(), name); + if (!file.exists()) { + plugin.saveResource(name, false); + } + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/economy/EconomyService.java b/src/main/java/com/dirtbagmc/dirtbounties/economy/EconomyService.java new file mode 100644 index 0000000..9464022 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/economy/EconomyService.java @@ -0,0 +1,148 @@ +package com.dirtbagmc.dirtbounties.economy; + +import com.dirtbagmc.dirtbounties.config.ConfigService; +import com.dirtbagmc.dirtbounties.util.NumberUtil; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.bukkit.plugin.java.JavaPlugin; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.logging.Level; + +public final class EconomyService { + private final JavaPlugin plugin; + private final ConfigService configService; + private Class economyClass; + private Object provider; + private boolean ready; + private String providerName = "None"; + + public EconomyService(JavaPlugin plugin, ConfigService configService) { + this.plugin = plugin; + this.configService = configService; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public void initialize() { + ready = false; + provider = null; + providerName = "None"; + if (!configService.main().getBoolean("economy.enabled", true)) { + plugin.getLogger().info("Economy integration is disabled in config.yml."); + return; + } + + try { + economyClass = Class.forName("net.milkbowl.vault.economy.Economy", false, plugin.getClass().getClassLoader()); + RegisteredServiceProvider registration = Bukkit.getServicesManager().getRegistration((Class) economyClass); + if (registration == null || registration.getProvider() == null) { + logMissingProvider(); + return; + } + provider = registration.getProvider(); + providerName = provider.getClass().getSimpleName(); + ready = true; + if (configService.main().getBoolean("economy.provider-log-on-enable", true)) { + plugin.getLogger().info("Hooked economy provider through Vault services: " + providerName); + } + } catch (ClassNotFoundException | LinkageError ex) { + logMissingProvider(); + } catch (RuntimeException ex) { + plugin.getLogger().log(Level.WARNING, "Could not hook an economy provider.", ex); + } + } + + public boolean isReady() { + return ready && provider != null; + } + + public String providerName() { + return providerName; + } + + public double balance(OfflinePlayer player) { + if (!isReady()) { + return 0.0; + } + Object result = invoke("getBalance", new Class[]{OfflinePlayer.class}, player); + return result instanceof Number number ? number.doubleValue() : 0.0; + } + + public EconomyResult withdraw(OfflinePlayer player, double amount) { + return transaction("withdrawPlayer", player, amount); + } + + public EconomyResult deposit(OfflinePlayer player, double amount) { + return transaction("depositPlayer", player, amount); + } + + public String format(double amount) { + double clamped = NumberUtil.clampMoney(amount); + if (isReady()) { + Object result = invoke("format", new Class[]{double.class}, clamped); + if (result instanceof String formatted && !formatted.isBlank()) { + return formatted; + } + } + return configService.main().getString("economy.currency-format", "${amount}") + .replace("{amount}", NumberUtil.compact(clamped)); + } + + private EconomyResult transaction(String method, OfflinePlayer player, double amount) { + if (!isReady()) { + return EconomyResult.failure("Economy provider is missing."); + } + if (amount <= 0.0) { + return EconomyResult.ok(); + } + Object response = invoke(method, new Class[]{OfflinePlayer.class, double.class}, player, NumberUtil.clampMoney(amount)); + if (response == null) { + return EconomyResult.failure("Economy provider returned no response."); + } + try { + Method success = response.getClass().getMethod("transactionSuccess"); + boolean ok = Boolean.TRUE.equals(success.invoke(response)); + if (ok) { + return EconomyResult.ok(); + } + Method errorMessage = response.getClass().getMethod("errorMessage"); + Object message = errorMessage.invoke(response); + return EconomyResult.failure(message == null ? "Economy transaction failed." : message.toString()); + } catch (ReflectiveOperationException ex) { + return EconomyResult.failure("Could not read economy transaction response."); + } + } + + private Object invoke(String methodName, Class[] parameterTypes, Object... args) { + try { + Method method = provider.getClass().getMethod(methodName, parameterTypes); + return method.invoke(provider, args); + } catch (NoSuchMethodException ex) { + plugin.getLogger().warning("Economy provider does not support method: " + methodName); + } catch (IllegalAccessException | InvocationTargetException ex) { + plugin.getLogger().log(Level.WARNING, "Economy method failed: " + methodName, ex); + } + return null; + } + + private void logMissingProvider() { + String message = "No Vault-compatible economy provider is registered. Bounty economy actions will be blocked."; + if (configService.main().getBoolean("economy.fail-if-missing", false)) { + plugin.getLogger().severe(message); + } else if (configService.main().getBoolean("logging.console.economy-status", true)) { + plugin.getLogger().warning(message + " This also covers missing CMI Vault injector setup."); + } + } + + public record EconomyResult(boolean success, String message) { + public static EconomyResult ok() { + return new EconomyResult(true, ""); + } + + public static EconomyResult failure(String message) { + return new EconomyResult(false, message); + } + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/gui/GuiHolder.java b/src/main/java/com/dirtbagmc/dirtbounties/gui/GuiHolder.java new file mode 100644 index 0000000..ff4a8dc --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/gui/GuiHolder.java @@ -0,0 +1,40 @@ +package com.dirtbagmc.dirtbounties.gui; + +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; + +import java.util.UUID; + +public final class GuiHolder implements InventoryHolder { + private final GuiType type; + private final int page; + private final UUID targetUuid; + private Inventory inventory; + + public GuiHolder(GuiType type, int page, UUID targetUuid) { + this.type = type; + this.page = page; + this.targetUuid = targetUuid; + } + + @Override + public Inventory getInventory() { + return inventory; + } + + public void inventory(Inventory inventory) { + this.inventory = inventory; + } + + public GuiType type() { + return type; + } + + public int page() { + return page; + } + + public UUID targetUuid() { + return targetUuid; + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/gui/GuiManager.java b/src/main/java/com/dirtbagmc/dirtbounties/gui/GuiManager.java new file mode 100644 index 0000000..95401c5 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/gui/GuiManager.java @@ -0,0 +1,589 @@ +package com.dirtbagmc.dirtbounties.gui; + +import com.dirtbagmc.dirtbounties.config.ConfigService; +import com.dirtbagmc.dirtbounties.model.Bounty; +import com.dirtbagmc.dirtbounties.model.BountyContribution; +import com.dirtbagmc.dirtbounties.model.BountyHistoryRecord; +import com.dirtbagmc.dirtbounties.model.SuspiciousActivity; +import com.dirtbagmc.dirtbounties.service.BountyService; +import com.dirtbagmc.dirtbounties.service.HistoryService; +import com.dirtbagmc.dirtbounties.service.MessageService; +import com.dirtbagmc.dirtbounties.util.ItemBuilder; +import com.dirtbagmc.dirtbounties.util.NumberUtil; +import com.dirtbagmc.dirtbounties.util.TimeUtil; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; + +public final class GuiManager { + private final JavaPlugin plugin; + private final ConfigService configService; + private final MessageService messages; + private final BountyService bountyService; + private final HistoryService historyService; + private final Map pendingInputs = new HashMap<>(); + + public GuiManager(JavaPlugin plugin, ConfigService configService, MessageService messages, + BountyService bountyService, HistoryService historyService) { + this.plugin = plugin; + this.configService = configService; + this.messages = messages; + this.bountyService = bountyService; + this.historyService = historyService; + } + + public void openMain(Player player, int page) { + if (!enabled(player)) { + return; + } + List bounties = bountyService.activeSorted(); + Inventory inventory = create(GuiType.MAIN, page, null, "titles.main", Map.of()); + decorate(inventory); + fillBounties(inventory, bounties, page, false); + addMainButtons(inventory, page, bounties.size(), GuiType.MAIN, player); + player.openInventory(inventory); + messages.play(player, "gui.open-sound"); + } + + public void openTop(Player player, int page) { + if (!enabled(player)) { + return; + } + List bounties = bountyService.activeSorted(); + Inventory inventory = create(GuiType.TOP, page, null, "titles.top", Map.of()); + decorate(inventory); + fillBounties(inventory, bounties, page, true); + addMainButtons(inventory, page, bounties.size(), GuiType.TOP, player); + player.openInventory(inventory); + messages.play(player, "gui.open-sound"); + } + + public void openDetail(Player player, UUID targetUuid) { + Bounty bounty = bountyService.bounty(targetUuid).orElse(null); + if (bounty == null) { + messages.send(player, "bounty-not-found", Map.of("target", "Unknown")); + openMain(player, 0); + return; + } + Map placeholders = bountyService.bountyPlaceholders(bounty); + placeholders.put("reason_lines", reasonLines(bounty)); + placeholders.putAll(claimRulePlaceholders()); + + Inventory inventory = create(GuiType.DETAIL, 0, targetUuid, "titles.detail", placeholders); + decorate(inventory); + OfflinePlayer owner = Bukkit.getOfflinePlayer(targetUuid); + inventory.setItem(13, item("items.detail-head", placeholders, owner)); + inventory.setItem(22, item("items.claim-info", placeholders, null)); + inventory.setItem(30, item("items.add", placeholders, null)); + inventory.setItem(slot("layout.back-slot", 46), item("items.back", placeholders, null)); + inventory.setItem(slot("layout.refresh-slot", 49), item("items.refresh", placeholders, null)); + player.openInventory(inventory); + messages.play(player, "gui.open-sound"); + } + + public void openConfirm(Player player, PendingBountyInput input) { + Map placeholders = confirmPlaceholders(input); + Inventory inventory = create(GuiType.CONFIRM, 0, input.targetUuid(), "titles.confirm", placeholders); + decorate(inventory); + OfflinePlayer owner = input.targetUuid() == null ? null : Bukkit.getOfflinePlayer(input.targetUuid()); + inventory.setItem(13, ItemBuilder.simple(Material.PLAYER_HEAD, "&c&l{target}", + List.of("&7Amount: &f{amount}", "&7Total cost: &f{cost}", "&7Reason: &f{reason}"), + messages, placeholders)); + if (owner != null && inventory.getItem(13) != null) { + inventory.setItem(13, item("items.bounty", placeholders, owner)); + } + inventory.setItem(29, item("items.confirm", placeholders, owner)); + inventory.setItem(31, anonymousToggle(input)); + inventory.setItem(33, item("items.cancel", placeholders, null)); + player.openInventory(inventory); + messages.play(player, "gui.open-sound"); + } + + public void openAdminMain(Player player) { + if (!player.hasPermission("dirtbounties.admin")) { + messages.send(player, "no-permission"); + return; + } + Inventory inventory = create(GuiType.ADMIN_MAIN, 0, null, "titles.admin-main", Map.of()); + decorate(inventory); + inventory.setItem(20, item("items.admin-active", Map.of(), null)); + inventory.setItem(22, item("items.admin-history", Map.of(), null)); + inventory.setItem(24, item("items.admin-suspicious", Map.of(), null)); + inventory.setItem(slot("layout.back-slot", 46), item("items.back", Map.of(), null)); + player.openInventory(inventory); + messages.send(player, "gui.admin-opened"); + messages.play(player, "gui.open-sound"); + } + + public void openAdminHistory(Player player, int page) { + Inventory inventory = create(GuiType.ADMIN_HISTORY, page, null, "titles.admin-history", Map.of()); + decorate(inventory); + List slots = contentSlots(); + List records = historyService.recent(500); + int start = page * slots.size(); + for (int i = 0; i < slots.size() && start + i < records.size(); i++) { + BountyHistoryRecord record = records.get(start + i); + Map placeholders = new HashMap<>(); + placeholders.put("type", record.type()); + placeholders.put("target", record.targetName()); + placeholders.put("actor", record.actorName()); + placeholders.put("amount", bountyService.formatAmount(record.amount())); + placeholders.put("time", TimeUtil.formatTimestamp(record.createdAt())); + placeholders.put("note", record.note()); + inventory.setItem(slots.get(i), ItemBuilder.simple(Material.PAPER, "&e{type} &8| &f{target}", + List.of("&7Actor: &f{actor}", "&7Amount: &f{amount}", "&7When: &f{time}", "&7Note: &f{note}"), + messages, placeholders)); + } + addPageButtons(inventory, page, records.size(), GuiType.ADMIN_HISTORY); + inventory.setItem(slot("layout.back-slot", 46), item("items.back", Map.of(), null)); + player.openInventory(inventory); + } + + public void openAdminSuspicious(Player player, int page) { + Inventory inventory = create(GuiType.ADMIN_SUSPICIOUS, page, null, "titles.admin-suspicious", Map.of()); + decorate(inventory); + List slots = contentSlots(); + List records = historyService.suspiciousRecent(500); + int start = page * slots.size(); + for (int i = 0; i < slots.size() && start + i < records.size(); i++) { + SuspiciousActivity record = records.get(start + i); + Map placeholders = new HashMap<>(); + placeholders.put("type", record.type()); + placeholders.put("killer", record.killerName()); + placeholders.put("target", record.targetName()); + placeholders.put("severity", String.valueOf(record.severity())); + placeholders.put("time", TimeUtil.formatTimestamp(record.createdAt())); + placeholders.put("details", record.details()); + inventory.setItem(slots.get(i), ItemBuilder.simple(Material.REDSTONE_TORCH, "&c{type} &8| &fSeverity {severity}", + List.of("&7Killer: &f{killer}", "&7Target: &f{target}", "&7When: &f{time}", "&7Details: &f{details}"), + messages, placeholders)); + } + addPageButtons(inventory, page, records.size(), GuiType.ADMIN_SUSPICIOUS); + inventory.setItem(slot("layout.back-slot", 46), item("items.back", Map.of(), null)); + player.openInventory(inventory); + } + + public void handleClick(InventoryClickEvent event) { + if (!(event.getInventory().getHolder() instanceof GuiHolder holder)) { + return; + } + event.setCancelled(true); + if (!(event.getWhoClicked() instanceof Player player)) { + return; + } + if (event.getRawSlot() >= event.getView().getTopInventory().getSize()) { + return; + } + messages.play(player, "gui.click-sound"); + int slot = event.getRawSlot(); + switch (holder.type()) { + case MAIN, TOP -> handleBountyListClick(player, holder, slot); + case DETAIL -> handleDetailClick(player, holder, slot); + case CONFIRM -> handleConfirmClick(player, slot); + case ADMIN_MAIN -> handleAdminMainClick(player, slot); + case ADMIN_HISTORY -> handlePagedAdminClick(player, holder, slot, GuiType.ADMIN_HISTORY); + case ADMIN_SUSPICIOUS -> handlePagedAdminClick(player, holder, slot, GuiType.ADMIN_SUSPICIOUS); + } + } + + public boolean hasPendingInput(UUID playerUuid) { + return pendingInputs.containsKey(playerUuid); + } + + public void startPlaceFlow(Player player) { + if (!enabled(player)) { + return; + } + PendingBountyInput input = new PendingBountyInput(player.getUniqueId(), PendingBountyInput.Stage.TARGET, inputExpiresAt()); + pendingInputs.put(player.getUniqueId(), input); + player.closeInventory(); + messages.send(player, "gui.prompt-target"); + } + + public void startAddFlow(Player player, Bounty bounty) { + PendingBountyInput input = new PendingBountyInput(player.getUniqueId(), PendingBountyInput.Stage.AMOUNT, inputExpiresAt()); + input.targetUuid(bounty.targetUuid()); + input.targetName(bounty.targetName()); + input.increase(true); + pendingInputs.put(player.getUniqueId(), input); + player.closeInventory(); + messages.send(player, "gui.prompt-amount", Map.of("target", bounty.targetName())); + } + + public void acceptChatInput(Player player, String message) { + PendingBountyInput input = pendingInputs.get(player.getUniqueId()); + if (input == null) { + return; + } + if (System.currentTimeMillis() > input.expiresAt()) { + pendingInputs.remove(player.getUniqueId()); + messages.send(player, "gui.input-expired"); + return; + } + if (message.equalsIgnoreCase("cancel")) { + pendingInputs.remove(player.getUniqueId()); + messages.send(player, "gui.input-cancelled"); + return; + } + + switch (input.stage()) { + case TARGET -> acceptTarget(player, input, message); + case AMOUNT -> acceptAmount(player, input, message); + case REASON -> acceptReason(player, input, message); + case CONFIRM -> openConfirm(player, input); + } + } + + public void clear() { + pendingInputs.clear(); + } + + private void acceptTarget(Player player, PendingBountyInput input, String message) { + OfflinePlayer target = bountyService.resolveTarget(message); + if (target == null) { + messages.send(player, "invalid-player"); + messages.send(player, "gui.prompt-target"); + return; + } + input.targetUuid(target.getUniqueId()); + input.targetName(target.getName() == null ? message : target.getName()); + input.stage(PendingBountyInput.Stage.AMOUNT); + input.expiresAt(inputExpiresAt()); + messages.send(player, "gui.prompt-amount", Map.of("target", input.targetName())); + } + + private void acceptAmount(Player player, PendingBountyInput input, String message) { + double amount; + try { + amount = NumberUtil.clampMoney(Double.parseDouble(message.replace(",", ""))); + } catch (NumberFormatException ex) { + messages.send(player, "invalid-number"); + messages.send(player, "gui.prompt-amount", Map.of("target", input.targetName())); + return; + } + input.amount(amount); + input.expiresAt(inputExpiresAt()); + if (configService.main().getBoolean("bounties.allow-reasons", true)) { + input.stage(PendingBountyInput.Stage.REASON); + messages.send(player, "gui.prompt-reason", Map.of("target", input.targetName())); + } else { + input.reason(configService.main().getString("bounties.default-reason", "No reason given.")); + input.stage(PendingBountyInput.Stage.CONFIRM); + openConfirm(player, input); + } + } + + private void acceptReason(Player player, PendingBountyInput input, String message) { + input.reason(message.equals("-") ? configService.main().getString("bounties.default-reason", "No reason given.") : message); + input.stage(PendingBountyInput.Stage.CONFIRM); + input.expiresAt(inputExpiresAt()); + messages.send(player, "gui.confirm-opened"); + openConfirm(player, input); + } + + private void handleBountyListClick(Player player, GuiHolder holder, int slot) { + if (slot == slot("layout.place-slot", 48)) { + startPlaceFlow(player); + return; + } + if (slot == slot("layout.top-slot", 50)) { + if (holder.type() == GuiType.TOP) { + openMain(player, 0); + } else { + openTop(player, 0); + } + return; + } + if (slot == slot("layout.refresh-slot", 49)) { + if (holder.type() == GuiType.TOP) { + openTop(player, holder.page()); + } else { + openMain(player, holder.page()); + } + return; + } + if (slot == slot("layout.previous-slot", 45) && holder.page() > 0) { + if (holder.type() == GuiType.TOP) { + openTop(player, holder.page() - 1); + } else { + openMain(player, holder.page() - 1); + } + return; + } + if (slot == slot("layout.next-slot", 53)) { + if (holder.type() == GuiType.TOP) { + openTop(player, holder.page() + 1); + } else { + openMain(player, holder.page() + 1); + } + return; + } + if (slot == slot("layout.admin-slot", 52) && player.hasPermission("dirtbounties.admin")) { + openAdminMain(player); + return; + } + Bounty clicked = bountyAt(holder.page(), slot); + if (clicked != null) { + openDetail(player, clicked.targetUuid()); + } + } + + private void handleDetailClick(Player player, GuiHolder holder, int slot) { + if (slot == slot("layout.back-slot", 46)) { + openMain(player, 0); + return; + } + if (slot == slot("layout.refresh-slot", 49)) { + openDetail(player, holder.targetUuid()); + return; + } + if (slot == 30) { + Bounty bounty = bountyService.bounty(holder.targetUuid()).orElse(null); + if (bounty != null) { + startAddFlow(player, bounty); + } + } + } + + private void handleConfirmClick(Player player, int slot) { + PendingBountyInput input = pendingInputs.get(player.getUniqueId()); + if (input == null) { + player.closeInventory(); + return; + } + if (slot == 31) { + if (!configService.main().getBoolean("bounties.allow-anonymous", true) + || (configService.main().getBoolean("bounties.anonymous-requires-permission", true) + && !player.hasPermission("dirtbounties.anonymous"))) { + messages.send(player, "no-permission"); + messages.play(player, "gui.error-sound"); + return; + } + input.anonymous(!input.anonymous()); + openConfirm(player, input); + return; + } + if (slot == 33) { + pendingInputs.remove(player.getUniqueId()); + messages.send(player, "gui.input-cancelled"); + player.closeInventory(); + return; + } + if (slot == 29) { + OfflinePlayer target = input.targetUuid() == null ? bountyService.resolveTarget(input.targetName()) : Bukkit.getOfflinePlayer(input.targetUuid()); + boolean success = bountyService.placeBounty(player, target, input.amount(), input.reason(), input.anonymous(), input.increase()); + if (success) { + pendingInputs.remove(player.getUniqueId()); + messages.play(player, "gui.success-sound"); + if (configService.main().getBoolean("gui.close-on-confirm", true)) { + player.closeInventory(); + } else { + openMain(player, 0); + } + } else { + messages.play(player, "gui.error-sound"); + } + } + } + + private void handleAdminMainClick(Player player, int slot) { + if (slot == 20) { + openMain(player, 0); + } else if (slot == 22) { + openAdminHistory(player, 0); + } else if (slot == 24) { + openAdminSuspicious(player, 0); + } else if (slot == slot("layout.back-slot", 46)) { + openMain(player, 0); + } + } + + private void handlePagedAdminClick(Player player, GuiHolder holder, int slot, GuiType type) { + if (slot == slot("layout.back-slot", 46)) { + openAdminMain(player); + return; + } + if (slot == slot("layout.previous-slot", 45) && holder.page() > 0) { + if (type == GuiType.ADMIN_HISTORY) { + openAdminHistory(player, holder.page() - 1); + } else { + openAdminSuspicious(player, holder.page() - 1); + } + return; + } + if (slot == slot("layout.next-slot", 53)) { + if (type == GuiType.ADMIN_HISTORY) { + openAdminHistory(player, holder.page() + 1); + } else { + openAdminSuspicious(player, holder.page() + 1); + } + } + } + + private void fillBounties(Inventory inventory, List bounties, int page, boolean top) { + List slots = contentSlots(); + int start = page * slots.size(); + if (bounties.isEmpty()) { + inventory.setItem(22, item("items.empty", Map.of(), null)); + return; + } + for (int i = 0; i < slots.size() && start + i < bounties.size(); i++) { + Bounty bounty = bounties.get(start + i); + Map placeholders = bountyService.bountyPlaceholders(bounty); + placeholders.put("rank", String.valueOf(start + i + 1)); + ConfigurationSection section = configService.guiSection(top ? "items.top-bounty" : "items.bounty"); + inventory.setItem(slots.get(i), ItemBuilder.fromSection(section, messages, placeholders, Bukkit.getOfflinePlayer(bounty.targetUuid()))); + } + } + + private Bounty bountyAt(int page, int slot) { + List slots = contentSlots(); + int index = slots.indexOf(slot); + if (index < 0) { + return null; + } + int global = page * slots.size() + index; + List bounties = bountyService.activeSorted(); + return global >= 0 && global < bounties.size() ? bounties.get(global) : null; + } + + private void addMainButtons(Inventory inventory, int page, int totalItems, GuiType type, Player player) { + addPageButtons(inventory, page, totalItems, type); + inventory.setItem(slot("layout.place-slot", 48), item("items.place", Map.of(), null)); + inventory.setItem(slot("layout.top-slot", 50), item(type == GuiType.TOP ? "items.back" : "items.top", Map.of(), null)); + inventory.setItem(slot("layout.refresh-slot", 49), item("items.refresh", Map.of(), null)); + if (player.hasPermission("dirtbounties.admin")) { + inventory.setItem(slot("layout.admin-slot", 52), item("items.admin-active", Map.of(), null)); + } + } + + private void addPageButtons(Inventory inventory, int page, int totalItems, GuiType type) { + List slots = contentSlots(); + if (page > 0) { + inventory.setItem(slot("layout.previous-slot", 45), item("items.previous", Map.of(), null)); + } + if ((page + 1) * slots.size() < totalItems) { + inventory.setItem(slot("layout.next-slot", 53), item("items.next", Map.of(), null)); + } + if (type == GuiType.ADMIN_HISTORY || type == GuiType.ADMIN_SUSPICIOUS) { + inventory.setItem(slot("layout.refresh-slot", 49), item("items.refresh", Map.of(), null)); + } + } + + private Inventory create(GuiType type, int page, UUID targetUuid, String titlePath, Map placeholders) { + int size = Math.max(9, configService.gui().getInt("layout.size", 54)); + size = Math.min(54, ((size + 8) / 9) * 9); + GuiHolder holder = new GuiHolder(type, Math.max(0, page), targetUuid); + String title = configService.gui().getString(titlePath, "DirtBounties"); + Inventory inventory = Bukkit.createInventory(holder, size, messages.legacy(title, placeholders)); + holder.inventory(inventory); + return inventory; + } + + private void decorate(Inventory inventory) { + ItemStack filler = item("items.filler", Map.of(), null); + ItemStack border = item("items.border", Map.of(), null); + for (int i = 0; i < inventory.getSize(); i++) { + inventory.setItem(i, filler); + } + for (int i = 0; i < inventory.getSize(); i++) { + boolean isBorder = i < 9 || i >= inventory.getSize() - 9 || i % 9 == 0 || i % 9 == 8; + if (isBorder) { + inventory.setItem(i, border); + } + } + } + + private ItemStack item(String path, Map placeholders, OfflinePlayer owner) { + return ItemBuilder.fromSection(configService.guiSection(path), messages, placeholders, owner); + } + + private ItemStack anonymousToggle(PendingBountyInput input) { + String state = input.anonymous() ? "&aEnabled" : "&cDisabled"; + return ItemBuilder.simple(Material.NAME_TAG, "&6Anonymous", List.of("&7Current: " + state, "", "&eClick to toggle."), + messages, Map.of()); + } + + private Map confirmPlaceholders(PendingBountyInput input) { + BountyService.Fee fee = bountyService.previewFee(input.amount(), input.increase()); + Map placeholders = new HashMap<>(); + placeholders.put("target", input.targetName() == null ? "Unknown" : input.targetName()); + placeholders.put("amount", bountyService.formatAmount(fee.bountyAmount())); + placeholders.put("fee", bountyService.formatAmount(fee.feeAmount())); + placeholders.put("cost", bountyService.formatAmount(fee.totalCost())); + placeholders.put("reason", input.reason() == null || input.reason().isBlank() + ? configService.main().getString("bounties.default-reason", "No reason given.") + : input.reason()); + return placeholders; + } + + private Map claimRulePlaceholders() { + Map placeholders = new HashMap<>(); + placeholders.put("pvp", bool(configService.main().getBoolean("claim-rules.require-pvp-kill", true))); + placeholders.put("same_ip", bool(configService.main().getBoolean("claim-rules.prevent-same-ip-claims", true))); + placeholders.put("world_mode", configService.main().getString("claim-rules.worlds.mode", "blacklist")); + placeholders.put("combat", bool(configService.main().getBoolean("claim-rules.combat.enabled", true))); + return placeholders; + } + + private String reasonLines(Bounty bounty) { + List lines = new ArrayList<>(); + List contributions = bounty.contributions(); + int start = Math.max(0, contributions.size() - 5); + for (int i = contributions.size() - 1; i >= start; i--) { + BountyContribution contribution = contributions.get(i); + String placer = contribution.anonymous() ? "Anonymous" : contribution.placerName(); + lines.add("&8- &7" + placer + ": &f" + contribution.reason()); + } + if (lines.isEmpty()) { + lines.add("&8- &7No contribution notes."); + } + return String.join("\n", lines); + } + + private List contentSlots() { + List slots = configService.gui().getIntegerList("layout.content-slots"); + if (!slots.isEmpty()) { + return slots; + } + return List.of(10, 11, 12, 13, 14, 15, 16, 19, 20, 21, 22, 23, 24, 25, 28, 29, 30, 31, 32, 33, 34, 37, 38, 39, 40, 41, 42, 43); + } + + private int slot(String path, int fallback) { + return configService.gui().getInt(path, fallback); + } + + private boolean enabled(Player player) { + if (!configService.main().getBoolean("gui.enabled", true)) { + messages.send(player, "gui.unavailable"); + return false; + } + if (!player.hasPermission("dirtbounties.use")) { + messages.send(player, "no-permission"); + return false; + } + return true; + } + + private String bool(boolean value) { + return value ? "yes" : "no"; + } + + private long inputExpiresAt() { + long timeout = TimeUtil.parseMillis(configService.main().getString("gui.chat-input-timeout"), 60_000L); + return System.currentTimeMillis() + Math.max(5_000L, timeout); + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/gui/GuiType.java b/src/main/java/com/dirtbagmc/dirtbounties/gui/GuiType.java new file mode 100644 index 0000000..0be474e --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/gui/GuiType.java @@ -0,0 +1,11 @@ +package com.dirtbagmc.dirtbounties.gui; + +public enum GuiType { + MAIN, + TOP, + DETAIL, + CONFIRM, + ADMIN_MAIN, + ADMIN_HISTORY, + ADMIN_SUSPICIOUS +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/gui/PendingBountyInput.java b/src/main/java/com/dirtbagmc/dirtbounties/gui/PendingBountyInput.java new file mode 100644 index 0000000..2e36131 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/gui/PendingBountyInput.java @@ -0,0 +1,96 @@ +package com.dirtbagmc.dirtbounties.gui; + +import java.util.UUID; + +public final class PendingBountyInput { + private final UUID playerUuid; + private UUID targetUuid; + private String targetName; + private double amount; + private String reason = ""; + private boolean anonymous; + private boolean increase; + private Stage stage; + private long expiresAt; + + public PendingBountyInput(UUID playerUuid, Stage stage, long expiresAt) { + this.playerUuid = playerUuid; + this.stage = stage; + this.expiresAt = expiresAt; + } + + public UUID playerUuid() { + return playerUuid; + } + + public UUID targetUuid() { + return targetUuid; + } + + public void targetUuid(UUID targetUuid) { + this.targetUuid = targetUuid; + } + + public String targetName() { + return targetName; + } + + public void targetName(String targetName) { + this.targetName = targetName; + } + + public double amount() { + return amount; + } + + public void amount(double amount) { + this.amount = amount; + } + + public String reason() { + return reason; + } + + public void reason(String reason) { + this.reason = reason; + } + + public boolean anonymous() { + return anonymous; + } + + public void anonymous(boolean anonymous) { + this.anonymous = anonymous; + } + + public boolean increase() { + return increase; + } + + public void increase(boolean increase) { + this.increase = increase; + } + + public Stage stage() { + return stage; + } + + public void stage(Stage stage) { + this.stage = stage; + } + + public long expiresAt() { + return expiresAt; + } + + public void expiresAt(long expiresAt) { + this.expiresAt = expiresAt; + } + + public enum Stage { + TARGET, + AMOUNT, + REASON, + CONFIRM + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/hook/DirtBountiesExpansion.java b/src/main/java/com/dirtbagmc/dirtbounties/hook/DirtBountiesExpansion.java new file mode 100644 index 0000000..41301dc --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/hook/DirtBountiesExpansion.java @@ -0,0 +1,95 @@ +package com.dirtbagmc.dirtbounties.hook; + +import com.dirtbagmc.dirtbounties.model.Bounty; +import com.dirtbagmc.dirtbounties.service.BountyService; +import com.dirtbagmc.dirtbounties.service.HistoryService; +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Comparator; + +public final class DirtBountiesExpansion extends PlaceholderExpansion { + private final BountyService bountyService; + private final HistoryService historyService; + private final String version; + + public DirtBountiesExpansion(BountyService bountyService, HistoryService historyService, String version) { + this.bountyService = bountyService; + this.historyService = historyService; + this.version = version; + } + + @Override + public @NotNull String getIdentifier() { + return "dirtbounties"; + } + + @Override + public @NotNull String getAuthor() { + return "DirtbagMC"; + } + + @Override + public @NotNull String getVersion() { + return version; + } + + @Override + public boolean persist() { + return true; + } + + @Override + public String onRequest(OfflinePlayer player, @NotNull String params) { + if (params.equalsIgnoreCase("total_active_bounties")) { + return String.valueOf(bountyService.activeBounties().size()); + } + if (params.equalsIgnoreCase("total_active_value")) { + return bountyService.formatAmount(bountyService.totalActiveValue()); + } + if (params.equalsIgnoreCase("top_target")) { + return bountyService.activeSorted().stream().findFirst().map(Bounty::targetName).orElse(""); + } + if (params.equalsIgnoreCase("top_amount")) { + return bountyService.activeSorted().stream().findFirst().map(bounty -> bountyService.formatAmount(bounty.amount())).orElse("0"); + } + if (player != null) { + if (params.equalsIgnoreCase("current_player_bounty") || params.equalsIgnoreCase("player_bounty")) { + return bountyService.bounty(player.getUniqueId()) + .map(bounty -> bountyService.formatAmount(bounty.amount())) + .orElse("0"); + } + if (params.equalsIgnoreCase("has_bounty")) { + return bountyService.bounty(player.getUniqueId()).isPresent() ? "yes" : "no"; + } + if (params.equalsIgnoreCase("claimed_count")) { + return String.valueOf(historyService.claimedCountBy(player.getUniqueId())); + } + } + if (params.startsWith("rank_")) { + int rank; + try { + rank = Integer.parseInt(params.substring("rank_".length())); + } catch (NumberFormatException ex) { + return ""; + } + if (rank <= 0) { + return ""; + } + return bountyService.activeSorted().stream() + .sorted(Comparator.comparingDouble(Bounty::amount).reversed()) + .skip(rank - 1L) + .findFirst() + .map(bounty -> bounty.targetName() + ":" + bountyService.formatAmount(bounty.amount())) + .orElse(""); + } + return null; + } + + @Override + public String onPlaceholderRequest(Player player, @NotNull String params) { + return onRequest(player, params); + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/listener/BountyListener.java b/src/main/java/com/dirtbagmc/dirtbounties/listener/BountyListener.java new file mode 100644 index 0000000..d0461f5 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/listener/BountyListener.java @@ -0,0 +1,89 @@ +package com.dirtbagmc.dirtbounties.listener; + +import com.dirtbagmc.dirtbounties.gui.GuiManager; +import com.dirtbagmc.dirtbounties.service.BountyService; +import com.dirtbagmc.dirtbounties.service.CombatTracker; +import com.dirtbagmc.dirtbounties.service.PlayerCacheService; +import io.papermc.paper.event.player.AsyncChatEvent; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.Bukkit; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.projectiles.ProjectileSource; + +public final class BountyListener implements Listener { + private final JavaPlugin plugin; + private final GuiManager guiManager; + private final BountyService bountyService; + private final PlayerCacheService playerCacheService; + private final CombatTracker combatTracker; + + public BountyListener(JavaPlugin plugin, GuiManager guiManager, BountyService bountyService, + PlayerCacheService playerCacheService, CombatTracker combatTracker) { + this.plugin = plugin; + this.guiManager = guiManager; + this.bountyService = bountyService; + this.playerCacheService = playerCacheService; + this.combatTracker = combatTracker; + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onJoin(PlayerJoinEvent event) { + playerCacheService.update(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onDamage(EntityDamageByEntityEvent event) { + if (!(event.getEntity() instanceof Player victim)) { + return; + } + Player attacker = attacker(event.getDamager()); + if (attacker == null) { + return; + } + combatTracker.record(attacker, victim, event.getFinalDamage()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onDeath(PlayerDeathEvent event) { + bountyService.handleDeath(event.getEntity(), event.getEntity().getKiller()); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onInventoryClick(InventoryClickEvent event) { + guiManager.handleClick(event); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onChat(AsyncChatEvent event) { + Player player = event.getPlayer(); + if (!guiManager.hasPendingInput(player.getUniqueId())) { + return; + } + String plain = PlainTextComponentSerializer.plainText().serialize(event.message()).trim(); + event.setCancelled(true); + Bukkit.getScheduler().runTask(plugin, () -> guiManager.acceptChatInput(player, plain)); + } + + private Player attacker(Entity damager) { + if (damager instanceof Player player) { + return player; + } + if (damager instanceof Projectile projectile) { + ProjectileSource shooter = projectile.getShooter(); + if (shooter instanceof Player player) { + return player; + } + } + return null; + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/model/Bounty.java b/src/main/java/com/dirtbagmc/dirtbounties/model/Bounty.java new file mode 100644 index 0000000..eb1a885 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/model/Bounty.java @@ -0,0 +1,96 @@ +package com.dirtbagmc.dirtbounties.model; + +import com.dirtbagmc.dirtbounties.util.NumberUtil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +public final class Bounty { + private final UUID id; + private final UUID targetUuid; + private String targetName; + private double amount; + private final long createdAt; + private long updatedAt; + private long expiresAt; + private final List contributions; + + public Bounty(UUID id, UUID targetUuid, String targetName, double amount, long createdAt, + long updatedAt, long expiresAt, List contributions) { + this.id = id; + this.targetUuid = targetUuid; + this.targetName = targetName; + this.amount = NumberUtil.clampMoney(amount); + this.createdAt = createdAt; + this.updatedAt = updatedAt; + this.expiresAt = expiresAt; + this.contributions = new ArrayList<>(contributions == null ? List.of() : contributions); + } + + public UUID id() { + return id; + } + + public UUID targetUuid() { + return targetUuid; + } + + public String targetName() { + return targetName; + } + + public void targetName(String targetName) { + this.targetName = targetName; + } + + public double amount() { + return amount; + } + + public long createdAt() { + return createdAt; + } + + public long updatedAt() { + return updatedAt; + } + + public long expiresAt() { + return expiresAt; + } + + public void expiresAt(long expiresAt) { + this.expiresAt = expiresAt; + } + + public List contributions() { + return Collections.unmodifiableList(contributions); + } + + public void addContribution(BountyContribution contribution) { + contributions.add(contribution); + amount = NumberUtil.clampMoney(amount + contribution.amount()); + updatedAt = System.currentTimeMillis(); + } + + public void setAmount(double amount) { + this.amount = NumberUtil.clampMoney(amount); + this.updatedAt = System.currentTimeMillis(); + } + + public boolean isExpired(long now) { + return expiresAt > 0L && now >= expiresAt; + } + + public String topReason(String fallback) { + for (int i = contributions.size() - 1; i >= 0; i--) { + String reason = contributions.get(i).reason(); + if (reason != null && !reason.isBlank()) { + return reason; + } + } + return fallback; + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/model/BountyContribution.java b/src/main/java/com/dirtbagmc/dirtbounties/model/BountyContribution.java new file mode 100644 index 0000000..94584f8 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/model/BountyContribution.java @@ -0,0 +1,58 @@ +package com.dirtbagmc.dirtbounties.model; + +import java.util.UUID; + +public final class BountyContribution { + private final UUID id; + private final UUID placerUuid; + private final String placerName; + private final double amount; + private final double fee; + private final String reason; + private final boolean anonymous; + private final long createdAt; + + public BountyContribution(UUID id, UUID placerUuid, String placerName, double amount, double fee, + String reason, boolean anonymous, long createdAt) { + this.id = id; + this.placerUuid = placerUuid; + this.placerName = placerName; + this.amount = amount; + this.fee = fee; + this.reason = reason; + this.anonymous = anonymous; + this.createdAt = createdAt; + } + + public UUID id() { + return id; + } + + public UUID placerUuid() { + return placerUuid; + } + + public String placerName() { + return placerName; + } + + public double amount() { + return amount; + } + + public double fee() { + return fee; + } + + public String reason() { + return reason; + } + + public boolean anonymous() { + return anonymous; + } + + public long createdAt() { + return createdAt; + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/model/BountyHistoryRecord.java b/src/main/java/com/dirtbagmc/dirtbounties/model/BountyHistoryRecord.java new file mode 100644 index 0000000..8eaa309 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/model/BountyHistoryRecord.java @@ -0,0 +1,64 @@ +package com.dirtbagmc.dirtbounties.model; + +import java.util.UUID; + +public final class BountyHistoryRecord { + private final UUID id; + private final String type; + private final UUID targetUuid; + private final String targetName; + private final UUID actorUuid; + private final String actorName; + private final double amount; + private final String note; + private final long createdAt; + + public BountyHistoryRecord(UUID id, String type, UUID targetUuid, String targetName, UUID actorUuid, + String actorName, double amount, String note, long createdAt) { + this.id = id; + this.type = type; + this.targetUuid = targetUuid; + this.targetName = targetName; + this.actorUuid = actorUuid; + this.actorName = actorName; + this.amount = amount; + this.note = note; + this.createdAt = createdAt; + } + + public UUID id() { + return id; + } + + public String type() { + return type; + } + + public UUID targetUuid() { + return targetUuid; + } + + public String targetName() { + return targetName; + } + + public UUID actorUuid() { + return actorUuid; + } + + public String actorName() { + return actorName; + } + + public double amount() { + return amount; + } + + public String note() { + return note; + } + + public long createdAt() { + return createdAt; + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/model/ClaimValidation.java b/src/main/java/com/dirtbagmc/dirtbounties/model/ClaimValidation.java new file mode 100644 index 0000000..8647b20 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/model/ClaimValidation.java @@ -0,0 +1,11 @@ +package com.dirtbagmc.dirtbounties.model; + +public record ClaimValidation(boolean allowed, String messageKey, String reason) { + public static ClaimValidation allow() { + return new ClaimValidation(true, "", ""); + } + + public static ClaimValidation denied(String messageKey, String reason) { + return new ClaimValidation(false, messageKey, reason); + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/model/SuspiciousActivity.java b/src/main/java/com/dirtbagmc/dirtbounties/model/SuspiciousActivity.java new file mode 100644 index 0000000..e02474a --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/model/SuspiciousActivity.java @@ -0,0 +1,64 @@ +package com.dirtbagmc.dirtbounties.model; + +import java.util.UUID; + +public final class SuspiciousActivity { + private final UUID id; + private final String type; + private final UUID killerUuid; + private final String killerName; + private final UUID targetUuid; + private final String targetName; + private final String details; + private final int severity; + private final long createdAt; + + public SuspiciousActivity(UUID id, String type, UUID killerUuid, String killerName, UUID targetUuid, + String targetName, String details, int severity, long createdAt) { + this.id = id; + this.type = type; + this.killerUuid = killerUuid; + this.killerName = killerName; + this.targetUuid = targetUuid; + this.targetName = targetName; + this.details = details; + this.severity = severity; + this.createdAt = createdAt; + } + + public UUID id() { + return id; + } + + public String type() { + return type; + } + + public UUID killerUuid() { + return killerUuid; + } + + public String killerName() { + return killerName; + } + + public UUID targetUuid() { + return targetUuid; + } + + public String targetName() { + return targetName; + } + + public String details() { + return details; + } + + public int severity() { + return severity; + } + + public long createdAt() { + return createdAt; + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/service/AntiAbuseService.java b/src/main/java/com/dirtbagmc/dirtbounties/service/AntiAbuseService.java new file mode 100644 index 0000000..07b8d95 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/service/AntiAbuseService.java @@ -0,0 +1,78 @@ +package com.dirtbagmc.dirtbounties.service; + +import com.dirtbagmc.dirtbounties.config.ConfigService; +import com.dirtbagmc.dirtbounties.model.ClaimValidation; +import com.dirtbagmc.dirtbounties.util.TimeUtil; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public final class AntiAbuseService { + private final ConfigService configService; + private final HistoryService historyService; + private final Map runtimeKillerClaims = new HashMap<>(); + private final Map runtimeTargetClaims = new HashMap<>(); + private final Map runtimePairClaims = new HashMap<>(); + + public AntiAbuseService(ConfigService configService, HistoryService historyService) { + this.configService = configService; + this.historyService = historyService; + } + + public ClaimValidation validate(Player killer, Player target) { + if (!configService.main().getBoolean("anti-abuse.enabled", true) + || killer.hasPermission("dirtbounties.bypass.cooldowns")) { + return ClaimValidation.allow(); + } + long now = System.currentTimeMillis(); + long killerCooldown = TimeUtil.parseMillis(configService.main().getString("anti-abuse.killer-claim-cooldown"), 120_000L); + long targetCooldown = TimeUtil.parseMillis(configService.main().getString("anti-abuse.target-claim-cooldown"), 120_000L); + long pairCooldown = TimeUtil.parseMillis(configService.main().getString("anti-abuse.killer-target-pair-cooldown"), 43_200_000L); + + long lastKiller = Math.max(runtimeKillerClaims.getOrDefault(killer.getUniqueId(), 0L), + historyService.lastClaimByKiller(killer.getUniqueId())); + if (killerCooldown > 0L && now - lastKiller < killerCooldown) { + return ClaimValidation.denied("claim-denied.cooldown", "Killer claim cooldown"); + } + + long lastTarget = Math.max(runtimeTargetClaims.getOrDefault(target.getUniqueId(), 0L), + historyService.lastClaimOnTarget(target.getUniqueId())); + if (targetCooldown > 0L && now - lastTarget < targetCooldown) { + return ClaimValidation.denied("claim-denied.cooldown", "Target claim cooldown"); + } + + String pair = pair(killer.getUniqueId(), target.getUniqueId()); + long lastPair = Math.max(runtimePairClaims.getOrDefault(pair, 0L), + historyService.lastPairClaim(killer.getUniqueId(), target.getUniqueId())); + if (pairCooldown > 0L && now - lastPair < pairCooldown) { + return ClaimValidation.denied("claim-denied.cooldown", "Killer-target pair cooldown"); + } + + long window = TimeUtil.parseMillis(configService.main().getString("anti-abuse.pair-window"), 604_800_000L); + int max = configService.main().getInt("anti-abuse.max-pair-claims-in-window", 2); + if (max > 0 && historyService.claimCount(killer.getUniqueId(), target.getUniqueId(), now - window) >= max) { + return ClaimValidation.denied("claim-denied.pair-limit", "Killer-target pair claim limit"); + } + + return ClaimValidation.allow(); + } + + public void recordClaim(Player killer, Player target) { + long now = System.currentTimeMillis(); + runtimeKillerClaims.put(killer.getUniqueId(), now); + runtimeTargetClaims.put(target.getUniqueId(), now); + runtimePairClaims.put(pair(killer.getUniqueId(), target.getUniqueId()), now); + } + + public void clear() { + runtimeKillerClaims.clear(); + runtimeTargetClaims.clear(); + runtimePairClaims.clear(); + } + + private String pair(UUID killer, UUID target) { + return killer + ":" + target; + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/service/BountyService.java b/src/main/java/com/dirtbagmc/dirtbounties/service/BountyService.java new file mode 100644 index 0000000..3a071ae --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/service/BountyService.java @@ -0,0 +1,641 @@ +package com.dirtbagmc.dirtbounties.service; + +import com.dirtbagmc.dirtbounties.config.ConfigService; +import com.dirtbagmc.dirtbounties.economy.EconomyService; +import com.dirtbagmc.dirtbounties.model.Bounty; +import com.dirtbagmc.dirtbounties.model.BountyContribution; +import com.dirtbagmc.dirtbounties.model.ClaimValidation; +import com.dirtbagmc.dirtbounties.storage.StorageManager; +import com.dirtbagmc.dirtbounties.util.NumberUtil; +import com.dirtbagmc.dirtbounties.util.TimeUtil; +import com.dirtbagmc.dirtbounties.webhook.WebhookService; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.OfflinePlayer; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.regex.Pattern; + +public final class BountyService { + private static final Pattern VALID_PLAYER_NAME = Pattern.compile("^[A-Za-z0-9_]{3,16}$"); + + private final JavaPlugin plugin; + private final ConfigService configService; + private final MessageService messages; + private final EconomyService economy; + private final StorageManager storage; + private final HistoryService history; + private final PlayerCacheService playerCache; + private final CombatTracker combatTracker; + private final AntiAbuseService antiAbuse; + private final WebhookService webhooks; + private final Map bounties = new HashMap<>(); + private final Map placementCooldowns = new HashMap<>(); + + public BountyService(JavaPlugin plugin, ConfigService configService, MessageService messages, EconomyService economy, + StorageManager storage, HistoryService history, PlayerCacheService playerCache, + CombatTracker combatTracker, AntiAbuseService antiAbuse, WebhookService webhooks) { + this.plugin = plugin; + this.configService = configService; + this.messages = messages; + this.economy = economy; + this.storage = storage; + this.history = history; + this.playerCache = playerCache; + this.combatTracker = combatTracker; + this.antiAbuse = antiAbuse; + this.webhooks = webhooks; + } + + public void load() { + bounties.clear(); + bounties.putAll(storage.loadBounties()); + } + + public void save() { + storage.saveBounties(bounties.values()); + } + + public void clearRuntimeState() { + placementCooldowns.clear(); + bounties.clear(); + } + + public Optional bounty(UUID targetUuid) { + return Optional.ofNullable(bounties.get(targetUuid)); + } + + public Collection activeBounties() { + return List.copyOf(bounties.values()); + } + + public List activeSorted() { + return bounties.values().stream() + .sorted(Comparator.comparingDouble(Bounty::amount).reversed() + .thenComparing(Bounty::updatedAt, Comparator.reverseOrder())) + .toList(); + } + + public List topBounties(int limit) { + return activeSorted().stream().limit(limit).toList(); + } + + public double totalActiveValue() { + return bounties.values().stream().mapToDouble(Bounty::amount).sum(); + } + + public OfflinePlayer resolveTarget(String name) { + if (name == null || !VALID_PLAYER_NAME.matcher(name).matches()) { + return null; + } + + Player exact = Bukkit.getPlayerExact(name); + if (exact != null) { + return exact; + } + for (Player online : Bukkit.getOnlinePlayers()) { + if (online.getName().equalsIgnoreCase(name)) { + return online; + } + } + for (OfflinePlayer offline : Bukkit.getOfflinePlayers()) { + if (offline.getName() != null && offline.getName().equalsIgnoreCase(name)) { + return offline; + } + } + if (configService.main().getBoolean("bounties.allow-never-joined-targets", false)) { + return Bukkit.getOfflinePlayer(name); + } + return null; + } + + public boolean placeBounty(Player placer, OfflinePlayer target, double requestedAmount, String reason, + boolean anonymous, boolean increaseOnly) { + String permission = increaseOnly ? "dirtbounties.add" : "dirtbounties.place"; + if (!placer.hasPermission(permission)) { + messages.send(placer, "no-permission"); + return false; + } + if (!validateTargetForPlacement(placer, target)) { + return false; + } + if (!economy.isReady()) { + messages.send(placer, "economy-missing"); + return false; + } + + requestedAmount = NumberUtil.clampMoney(requestedAmount); + double min = configService.main().getDouble("bounties.min-amount", 100.0); + double max = configService.main().getDouble("bounties.max-amount", 1_000_000.0); + if (requestedAmount < min) { + messages.send(placer, "amount-too-low", Map.of("min", economy.format(min))); + return false; + } + if (requestedAmount > max) { + messages.send(placer, "amount-too-high", Map.of("max", economy.format(max))); + return false; + } + + long cooldown = TimeUtil.parseMillis(configService.main().getString("bounties.placement-cooldown"), 30_000L); + long lastPlacement = placementCooldowns.getOrDefault(placer.getUniqueId(), 0L); + if (cooldown > 0L && !placer.hasPermission("dirtbounties.bypass.cooldowns") + && System.currentTimeMillis() - lastPlacement < cooldown) { + messages.send(placer, "cooldown", Map.of("time", TimeUtil.formatDuration(cooldown - (System.currentTimeMillis() - lastPlacement)))); + return false; + } + + reason = cleanReason(placer, reason); + if (reason == null) { + return false; + } + anonymous = sanitizeAnonymous(placer, anonymous); + + Bounty existing = bounties.get(target.getUniqueId()); + if (increaseOnly && existing == null) { + messages.send(placer, "bounty-not-found", Map.of("target", safeName(target))); + return false; + } + if (existing != null && !configService.main().getBoolean("bounties.stack-existing", true)) { + messages.send(placer, "amount-would-exceed-max", Map.of("max", economy.format(max))); + return false; + } + + Fee fee = calculateFee(requestedAmount, increaseOnly); + double previousTotal = existing == null ? 0.0 : existing.amount(); + if (previousTotal + fee.bountyAmount() > max) { + messages.send(placer, "amount-would-exceed-max", Map.of("max", economy.format(max))); + return false; + } + + double minimumBalanceAfter = configService.main().getDouble("economy.minimum-balance-after-withdraw", 0.0); + if (economy.balance(placer) - fee.totalCost() < minimumBalanceAfter) { + messages.send(placer, "not-enough-money", Map.of("cost", economy.format(fee.totalCost()))); + return false; + } + EconomyService.EconomyResult withdraw = economy.withdraw(placer, fee.totalCost()); + if (!withdraw.success()) { + messages.send(placer, "not-enough-money", Map.of("cost", economy.format(fee.totalCost()))); + return false; + } + + long now = System.currentTimeMillis(); + String targetName = safeName(target); + Bounty bounty = existing == null + ? new Bounty(UUID.randomUUID(), target.getUniqueId(), targetName, 0.0, now, now, defaultExpiresAt(now), List.of()) + : existing; + bounty.targetName(targetName); + bounty.addContribution(new BountyContribution(UUID.randomUUID(), placer.getUniqueId(), placer.getName(), + fee.bountyAmount(), fee.feeAmount(), reason, anonymous, now)); + bounties.put(target.getUniqueId(), bounty); + placementCooldowns.put(placer.getUniqueId(), now); + save(); + + String type = existing == null ? "PLACED" : "INCREASED"; + history.record(type, target.getUniqueId(), targetName, placer.getUniqueId(), placer.getName(), + fee.bountyAmount(), reason + (anonymous ? " (anonymous)" : "")); + + Map placeholders = bountyPlaceholders(bounty); + placeholders.put("placer", anonymous ? "Anonymous" : placer.getName()); + placeholders.put("amount", economy.format(fee.bountyAmount())); + placeholders.put("fee", economy.format(fee.feeAmount())); + placeholders.put("cost", economy.format(fee.totalCost())); + + messages.send(placer, existing == null ? "bounty.placed" : "bounty.added", placeholders); + maybeBroadcastPlacement(existing == null, previousTotal, bounty, placeholders); + if (configService.main().getBoolean("logging.console.placements", true)) { + plugin.getLogger().info(placer.getName() + " placed/increased bounty on " + targetName + " for " + fee.bountyAmount()); + } + webhooks.placement(placer.getName(), targetName, bounty.amount(), reason, existing != null); + return true; + } + + public boolean adminSet(CommandSender sender, OfflinePlayer target, double amount) { + if (target == null) { + messages.send(sender, "invalid-player"); + return false; + } + amount = NumberUtil.clampMoney(amount); + if (amount <= 0.0) { + messages.send(sender, "invalid-number"); + return false; + } + long now = System.currentTimeMillis(); + Bounty bounty = new Bounty(UUID.randomUUID(), target.getUniqueId(), safeName(target), amount, + now, now, defaultExpiresAt(now), List.of()); + bounties.put(target.getUniqueId(), bounty); + save(); + history.record("ADMIN_SET", target.getUniqueId(), safeName(target), actorUuid(sender), sender.getName(), amount, "Admin set"); + messages.send(sender, "bounty.set", bountyPlaceholders(bounty)); + webhooks.admin(sender.getName(), "Set bounty on " + safeName(target) + " to " + economy.format(amount)); + return true; + } + + public boolean adminRemove(CommandSender sender, OfflinePlayer target, String type, double refundPercent) { + if (target == null) { + messages.send(sender, "invalid-player"); + return false; + } + Bounty bounty = bounties.remove(target.getUniqueId()); + if (bounty == null) { + messages.send(sender, "bounty-not-found", Map.of("target", safeName(target))); + return false; + } + refundContributions(bounty, refundPercent, configService.main().getBoolean("refunds.refund-fees", false), sender); + save(); + history.record(type, bounty.targetUuid(), bounty.targetName(), actorUuid(sender), sender.getName(), bounty.amount(), "Admin action"); + messages.send(sender, "bounty.removed", bountyPlaceholders(bounty)); + webhooks.admin(sender.getName(), type + " bounty on " + bounty.targetName() + " (" + economy.format(bounty.amount()) + ")"); + return true; + } + + public int clearAll(CommandSender sender) { + int count = bounties.size(); + double refund = configService.main().getDouble("refunds.on-admin-remove-percent", 100.0); + for (Bounty bounty : new ArrayList<>(bounties.values())) { + refundContributions(bounty, refund, configService.main().getBoolean("refunds.refund-fees", false), sender); + history.record("ADMIN_REMOVE", bounty.targetUuid(), bounty.targetName(), actorUuid(sender), sender.getName(), bounty.amount(), "Clear all"); + } + bounties.clear(); + save(); + webhooks.admin(sender.getName(), "Cleared all active bounties: " + count); + return count; + } + + public int cleanupExpired() { + if (!configService.main().getBoolean("expiration.enabled", true)) { + return 0; + } + long now = System.currentTimeMillis(); + int removed = 0; + Iterator> iterator = bounties.entrySet().iterator(); + while (iterator.hasNext()) { + Bounty bounty = iterator.next().getValue(); + if (bounty.isExpired(now) || shouldExpireForTargetState(bounty)) { + iterator.remove(); + double refund = configService.main().getDouble("refunds.on-expire-percent", 50.0); + refundContributions(bounty, refund, configService.main().getBoolean("refunds.refund-fees", false), Bukkit.getConsoleSender()); + history.record("EXPIRED", bounty.targetUuid(), bounty.targetName(), null, "Console", bounty.amount(), "Expired"); + if (configService.main().getBoolean("logging.console.admin-actions", true)) { + plugin.getLogger().info("Expired bounty on " + bounty.targetName() + " worth " + bounty.amount()); + } + removed++; + } + } + if (removed > 0) { + save(); + } + return removed; + } + + public void handleDeath(Player victim, Player killer) { + Bounty bounty = bounties.get(victim.getUniqueId()); + if (bounty == null) { + return; + } + if (killer == null) { + if (configService.main().getBoolean("claim-rules.block-environmental-deaths", true) + && configService.main().getBoolean("anti-abuse.log-failed-claims", true)) { + history.suspicious("ENVIRONMENTAL_DEATH", null, victim, "Bounty target died without a player killer.", 1); + } + return; + } + + ClaimValidation validation = validateClaim(killer, victim, bounty); + if (!validation.allowed()) { + Map placeholders = new HashMap<>(); + placeholders.put("reason", messages.raw(validation.messageKey(), validation.reason())); + messages.send(killer, "claim-denied-format", placeholders); + if (configService.main().getBoolean("anti-abuse.log-failed-claims", true)) { + history.suspicious("CLAIM_DENIED", killer, victim, validation.reason(), severity(validation.reason())); + runSuspiciousCommands(killer, victim, validation.reason()); + } + return; + } + + if (!economy.isReady()) { + messages.send(killer, "economy-missing"); + plugin.getLogger().warning("Could not pay bounty claim because economy is missing."); + return; + } + + double payout = claimPayout(bounty.amount()); + EconomyService.EconomyResult deposit = economy.deposit(killer, payout); + if (!deposit.success()) { + plugin.getLogger().warning("Could not pay bounty claim to " + killer.getName() + ": " + deposit.message()); + return; + } + + bounties.remove(victim.getUniqueId()); + save(); + antiAbuse.recordClaim(killer, victim); + history.record("CLAIMED", victim.getUniqueId(), victim.getName(), killer.getUniqueId(), killer.getName(), + payout, "Killed bounty target"); + + Map placeholders = bountyPlaceholders(bounty); + placeholders.put("killer", killer.getName()); + placeholders.put("payout", economy.format(payout)); + messages.send(killer, "bounty.claimed", placeholders); + if (configService.main().getBoolean("economy.broadcasts.enabled", true) + && payout >= configService.main().getDouble("economy.broadcasts.claimed-threshold", 5000.0)) { + messages.broadcast("bounty.claim-broadcast", placeholders); + } + if (configService.main().getBoolean("logging.console.claims", true)) { + plugin.getLogger().info(killer.getName() + " claimed bounty on " + victim.getName() + " for " + payout); + } + webhooks.claim(killer.getName(), victim.getName(), payout); + } + + public ClaimValidation validateClaim(Player killer, Player target, Bounty bounty) { + if (configService.main().getBoolean("claim-rules.require-permission", true) + && !killer.hasPermission("dirtbounties.claim")) { + return ClaimValidation.denied("claim-denied.no-permission", "No claim permission"); + } + if (killer.getUniqueId().equals(target.getUniqueId())) { + return ClaimValidation.denied("claim-denied.self", "Self claim"); + } + + boolean bypass = configService.main().getBoolean("claim-rules.allow-bypass-permission", true) + && killer.hasPermission("dirtbounties.bypass.claimrules"); + if (bypass) { + return ClaimValidation.allow(); + } + + if (blockedWorld(target.getWorld())) { + return ClaimValidation.denied("claim-denied.world", "World blocked"); + } + if (blockedGameMode(killer.getGameMode())) { + return ClaimValidation.denied("claim-denied.gamemode", "Blocked game mode"); + } + if (configService.main().getBoolean("claim-rules.prevent-same-ip-claims", true) + && playerCache.sameLastIp(killer.getUniqueId(), target.getUniqueId())) { + return ClaimValidation.denied("claim-denied.same-ip", "Same last IP"); + } + if (configService.main().getBoolean("claim-rules.prevent-shared-known-ip-claims", true) + && playerCache.sharedKnownIp(killer.getUniqueId(), target.getUniqueId())) { + return ClaimValidation.denied("claim-denied.shared-known-ip", "Shared known IP"); + } + if (!combatTracker.meetsRequirement(killer, target)) { + return ClaimValidation.denied("claim-denied.combat", "Combat requirement failed"); + } + + ClaimValidation abuse = antiAbuse.validate(killer, target); + if (!abuse.allowed()) { + return abuse; + } + + return bounty == null + ? ClaimValidation.denied("claim-denied.no-bounty", "No bounty") + : ClaimValidation.allow(); + } + + public Map bountyPlaceholders(Bounty bounty) { + Map placeholders = new HashMap<>(); + placeholders.put("target", bounty.targetName()); + placeholders.put("amount", economy.format(bounty.amount())); + placeholders.put("total", economy.format(bounty.amount())); + placeholders.put("contributors", String.valueOf(bounty.contributions().size())); + placeholders.put("reason", bounty.topReason(configService.main().getString("bounties.default-reason", "No reason given."))); + placeholders.put("expires", expiresText(bounty)); + return placeholders; + } + + public String expiresText(Bounty bounty) { + if (bounty.expiresAt() <= 0L) { + return "never"; + } + long remaining = bounty.expiresAt() - System.currentTimeMillis(); + return remaining <= 0L ? "expired" : TimeUtil.formatDuration(remaining); + } + + public String formatAmount(double amount) { + return economy.format(amount); + } + + public Fee previewFee(double amount, boolean increase) { + return calculateFee(amount, increase); + } + + private boolean validateTargetForPlacement(Player placer, OfflinePlayer target) { + if (target == null) { + messages.send(placer, "invalid-player"); + return false; + } + if (!configService.main().getBoolean("bounties.allow-offline-targets", true) && !target.isOnline()) { + messages.send(placer, "invalid-player"); + return false; + } + if (!configService.main().getBoolean("bounties.allow-never-joined-targets", false) + && !target.hasPlayedBefore() + && !target.isOnline()) { + messages.send(placer, "invalid-player"); + return false; + } + if (!configService.main().getBoolean("bounties.allow-self-target", false) + && placer.getUniqueId().equals(target.getUniqueId())) { + messages.send(placer, "target-self"); + return false; + } + if (!configService.main().getBoolean("bounties.allow-banned-targets", false) && target.isBanned()) { + messages.send(placer, "target-banned"); + return false; + } + return true; + } + + private String cleanReason(Player player, String reason) { + if (!configService.main().getBoolean("bounties.allow-reasons", true)) { + return configService.main().getString("bounties.default-reason", "No reason given."); + } + String cleaned = reason == null || reason.isBlank() + ? configService.main().getString("bounties.default-reason", "No reason given.") + : reason.trim(); + int max = configService.main().getInt("bounties.max-reason-length", 80); + if (cleaned.length() > max) { + messages.send(player, "reason-too-long", Map.of("max", String.valueOf(max))); + return null; + } + return cleaned; + } + + private boolean sanitizeAnonymous(Player player, boolean requested) { + if (!requested || !configService.main().getBoolean("bounties.allow-anonymous", true)) { + return false; + } + if (configService.main().getBoolean("bounties.anonymous-requires-permission", true) + && !player.hasPermission("dirtbounties.anonymous")) { + return false; + } + return true; + } + + private Fee calculateFee(double amount, boolean increase) { + double percent = configService.main().getDouble(increase ? "economy.fees.add-percent" : "economy.fees.placement-percent", 5.0); + String mode = configService.main().getString(increase ? "economy.fees.add-mode" : "economy.fees.placement-mode", "extra"); + double fee = NumberUtil.clampMoney(amount * Math.max(0.0, percent) / 100.0); + double bountyAmount = "deduct".equalsIgnoreCase(mode) ? NumberUtil.clampMoney(amount - fee) : amount; + bountyAmount = Math.max(0.0, bountyAmount); + double totalCost = "deduct".equalsIgnoreCase(mode) ? amount : NumberUtil.clampMoney(amount + fee); + return new Fee(NumberUtil.clampMoney(bountyAmount), fee, NumberUtil.clampMoney(totalCost)); + } + + private void maybeBroadcastPlacement(boolean newBounty, double previousTotal, Bounty bounty, Map placeholders) { + if (!configService.main().getBoolean("economy.broadcasts.enabled", true)) { + return; + } + double threshold = configService.main().getDouble("economy.broadcasts.placed-threshold", 5000.0); + if (bounty.amount() >= threshold) { + messages.broadcast(newBounty ? "bounty.broadcast-placed" : "bounty.broadcast-added", placeholders); + } + for (Object object : configService.main().getList("economy.broadcasts.milestone-thresholds", List.of())) { + double milestone = parseDouble(object); + if (milestone > 0.0 && previousTotal < milestone && bounty.amount() >= milestone) { + messages.broadcast("bounty.milestone", placeholders); + } + } + } + + private void refundContributions(Bounty bounty, double percent, boolean includeFees, CommandSender actor) { + if (!economy.isReady() || percent <= 0.0) { + return; + } + boolean refundOffline = configService.main().getBoolean("refunds.refund-offline-players", true); + double minimumRefund = configService.main().getDouble("refunds.minimum-refund", 0.01); + for (BountyContribution contribution : bounty.contributions()) { + if (contribution.placerUuid() == null) { + continue; + } + OfflinePlayer player = Bukkit.getOfflinePlayer(contribution.placerUuid()); + if (!refundOffline && !player.isOnline()) { + continue; + } + double refund = NumberUtil.clampMoney((contribution.amount() + (includeFees ? contribution.fee() : 0.0)) * percent / 100.0); + if (refund < minimumRefund) { + continue; + } + EconomyService.EconomyResult result = economy.deposit(player, refund); + if (result.success()) { + history.record("REFUND", bounty.targetUuid(), bounty.targetName(), contribution.placerUuid(), + player.getName() == null ? contribution.placerName() : player.getName(), refund, "Refunded by " + actor.getName()); + } else { + plugin.getLogger().warning("Failed bounty refund to " + contribution.placerName() + ": " + result.message()); + } + } + } + + private long defaultExpiresAt(long now) { + if (!configService.main().getBoolean("expiration.enabled", true)) { + return 0L; + } + long duration = TimeUtil.parseMillis(configService.main().getString("expiration.default-duration"), 1_209_600_000L); + long max = TimeUtil.parseMillis(configService.main().getString("expiration.max-duration"), 2_592_000_000L); + if (max > 0L) { + duration = Math.min(duration, max); + } + return duration <= 0L ? 0L : now + duration; + } + + private boolean shouldExpireForTargetState(Bounty bounty) { + OfflinePlayer target = Bukkit.getOfflinePlayer(bounty.targetUuid()); + if ("expire".equalsIgnoreCase(configService.main().getString("expiration.banned-player-action", "keep")) + && target.isBanned()) { + return true; + } + return "expire".equalsIgnoreCase(configService.main().getString("expiration.deleted-player-action", "keep")) + && !target.hasPlayedBefore() + && !target.isOnline(); + } + + private boolean blockedWorld(World world) { + String mode = configService.main().getString("claim-rules.worlds.mode", "blacklist"); + if (mode == null || mode.equalsIgnoreCase("disabled")) { + return false; + } + boolean listed = configService.main().getStringList("claim-rules.worlds.list").stream() + .anyMatch(name -> name.equalsIgnoreCase(world.getName())); + if (mode.equalsIgnoreCase("whitelist")) { + return !listed; + } + if (mode.equalsIgnoreCase("blacklist")) { + return listed; + } + return false; + } + + private boolean blockedGameMode(GameMode mode) { + for (String configured : configService.main().getStringList("claim-rules.blocked-killer-game-modes")) { + if (configured.equalsIgnoreCase(mode.name())) { + return true; + } + } + return false; + } + + private double claimPayout(double amount) { + double tax = Math.max(0.0, configService.main().getDouble("economy.fees.claim-tax-percent", 0.0)); + double sink = Math.max(0.0, configService.main().getDouble("economy.fees.claim-sink-percent", 0.0)); + double removed = Math.min(100.0, tax + sink); + return NumberUtil.clampMoney(amount * (100.0 - removed) / 100.0); + } + + private int severity(String reason) { + String lower = reason == null ? "" : reason.toLowerCase(Locale.ROOT); + if (lower.contains("ip")) { + return 5; + } + if (lower.contains("cooldown") || lower.contains("limit")) { + return 3; + } + return 1; + } + + private void runSuspiciousCommands(Player killer, Player target, String reason) { + if (!configService.main().getBoolean("anti-abuse.run-console-commands-on-suspicious", false)) { + return; + } + for (String command : configService.main().getStringList("anti-abuse.suspicious-commands")) { + String parsed = command + .replace("{killer}", killer.getName()) + .replace("{target}", target.getName()) + .replace("{reason}", reason == null ? "" : reason); + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), parsed); + } + } + + private UUID actorUuid(CommandSender sender) { + return sender instanceof Player player ? player.getUniqueId() : null; + } + + private String safeName(OfflinePlayer player) { + return player.getName() == null ? player.getUniqueId().toString() : player.getName(); + } + + private double parseDouble(Object object) { + if (object instanceof Number number) { + return number.doubleValue(); + } + if (object instanceof String string) { + try { + return Double.parseDouble(string); + } catch (NumberFormatException ignored) { + return 0.0; + } + } + return 0.0; + } + + public record Fee(double bountyAmount, double feeAmount, double totalCost) { + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/service/CombatTracker.java b/src/main/java/com/dirtbagmc/dirtbounties/service/CombatTracker.java new file mode 100644 index 0000000..2f9714d --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/service/CombatTracker.java @@ -0,0 +1,112 @@ +package com.dirtbagmc.dirtbounties.service; + +import com.dirtbagmc.dirtbounties.config.ConfigService; +import com.dirtbagmc.dirtbounties.util.TimeUtil; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; + +public final class CombatTracker { + private final ConfigService configService; + private final Map records = new HashMap<>(); + + public CombatTracker(ConfigService configService) { + this.configService = configService; + } + + public void record(Player attacker, Player victim, double damage) { + if (attacker.getUniqueId().equals(victim.getUniqueId())) { + return; + } + long now = System.currentTimeMillis(); + String key = key(attacker.getUniqueId(), victim.getUniqueId()); + CombatRecord record = records.computeIfAbsent(key, ignored -> new CombatRecord(now)); + record.lastAt(now); + record.damage(record.damage() + Math.max(0.0, damage)); + record.hits(record.hits() + 1); + cleanup(now); + } + + public boolean meetsRequirement(Player attacker, Player victim) { + if (!configService.main().getBoolean("claim-rules.combat.enabled", true)) { + return true; + } + long now = System.currentTimeMillis(); + cleanup(now); + CombatRecord record = records.get(key(attacker.getUniqueId(), victim.getUniqueId())); + if (record == null) { + return false; + } + long window = TimeUtil.parseMillis(configService.main().getString("claim-rules.combat.window"), 30_000L); + if (now - record.lastAt() > window) { + return false; + } + double minDamage = configService.main().getDouble("claim-rules.combat.min-damage", 4.0); + int minHits = configService.main().getInt("claim-rules.combat.min-hits", 1); + long minDuration = TimeUtil.parseMillis(configService.main().getString("claim-rules.combat.min-combat-duration"), 0L); + return record.damage() >= minDamage + && record.hits() >= minHits + && record.lastAt() - record.firstAt() >= minDuration; + } + + public void clear() { + records.clear(); + } + + private void cleanup(long now) { + long window = TimeUtil.parseMillis(configService.main().getString("claim-rules.combat.window"), 30_000L); + Iterator> iterator = records.entrySet().iterator(); + while (iterator.hasNext()) { + if (now - iterator.next().getValue().lastAt() > window) { + iterator.remove(); + } + } + } + + private String key(UUID attacker, UUID victim) { + return attacker + ":" + victim; + } + + private static final class CombatRecord { + private final long firstAt; + private long lastAt; + private double damage; + private int hits; + + private CombatRecord(long firstAt) { + this.firstAt = firstAt; + this.lastAt = firstAt; + } + + public long firstAt() { + return firstAt; + } + + public long lastAt() { + return lastAt; + } + + public void lastAt(long lastAt) { + this.lastAt = lastAt; + } + + public double damage() { + return damage; + } + + public void damage(double damage) { + this.damage = damage; + } + + public int hits() { + return hits; + } + + public void hits(int hits) { + this.hits = hits; + } + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/service/HistoryService.java b/src/main/java/com/dirtbagmc/dirtbounties/service/HistoryService.java new file mode 100644 index 0000000..24eb4d1 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/service/HistoryService.java @@ -0,0 +1,163 @@ +package com.dirtbagmc.dirtbounties.service; + +import com.dirtbagmc.dirtbounties.config.ConfigService; +import com.dirtbagmc.dirtbounties.model.BountyHistoryRecord; +import com.dirtbagmc.dirtbounties.model.SuspiciousActivity; +import com.dirtbagmc.dirtbounties.storage.StorageManager; +import com.dirtbagmc.dirtbounties.util.TimeUtil; +import org.bukkit.OfflinePlayer; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; + +public final class HistoryService { + private final ConfigService configService; + private final StorageManager storageManager; + private final List records = new ArrayList<>(); + private final List suspicious = new ArrayList<>(); + + public HistoryService(ConfigService configService, StorageManager storageManager) { + this.configService = configService; + this.storageManager = storageManager; + } + + public void load() { + records.clear(); + suspicious.clear(); + records.addAll(storageManager.loadHistory()); + suspicious.addAll(storageManager.loadSuspicious()); + prune(); + } + + public void save() { + storageManager.saveHistory(records); + storageManager.saveSuspicious(suspicious); + } + + public void record(String type, UUID targetUuid, String targetName, UUID actorUuid, String actorName, + double amount, String note) { + if (!configService.main().getBoolean("history.enabled", true)) { + return; + } + records.add(0, new BountyHistoryRecord(UUID.randomUUID(), type, targetUuid, targetName, + actorUuid, actorName, amount, note, System.currentTimeMillis())); + prune(); + if (configService.main().getBoolean("storage.write-history-to-disk-immediately", true)) { + storageManager.saveHistory(records); + } + } + + public void suspicious(String type, OfflinePlayer killer, OfflinePlayer target, String details, int severity) { + suspicious.add(0, new SuspiciousActivity(UUID.randomUUID(), type, + killer == null ? null : killer.getUniqueId(), + killer == null ? "Unknown" : safeName(killer), + target == null ? null : target.getUniqueId(), + target == null ? "Unknown" : safeName(target), + details, + severity, + System.currentTimeMillis())); + prune(); + if (configService.main().getBoolean("storage.write-history-to-disk-immediately", true)) { + storageManager.saveSuspicious(suspicious); + } + } + + public List forPlayer(UUID uuid, int limit) { + return records.stream() + .filter(record -> uuid.equals(record.targetUuid()) || uuid.equals(record.actorUuid())) + .sorted(Comparator.comparingLong(BountyHistoryRecord::createdAt).reversed()) + .limit(limit) + .toList(); + } + + public List recent(int limit) { + return records.stream() + .sorted(Comparator.comparingLong(BountyHistoryRecord::createdAt).reversed()) + .limit(limit) + .toList(); + } + + public List suspiciousRecent(int limit) { + return suspicious.stream() + .sorted(Comparator.comparingLong(SuspiciousActivity::createdAt).reversed()) + .limit(limit) + .toList(); + } + + public int claimCount(UUID killer, UUID target, long since) { + int count = 0; + for (BountyHistoryRecord record : records) { + if ("CLAIMED".equalsIgnoreCase(record.type()) + && killer.equals(record.actorUuid()) + && target.equals(record.targetUuid()) + && record.createdAt() >= since) { + count++; + } + } + return count; + } + + public long lastClaimByKiller(UUID killer) { + return records.stream() + .filter(record -> "CLAIMED".equalsIgnoreCase(record.type()) && killer.equals(record.actorUuid())) + .mapToLong(BountyHistoryRecord::createdAt) + .max() + .orElse(0L); + } + + public long lastClaimOnTarget(UUID target) { + return records.stream() + .filter(record -> "CLAIMED".equalsIgnoreCase(record.type()) && target.equals(record.targetUuid())) + .mapToLong(BountyHistoryRecord::createdAt) + .max() + .orElse(0L); + } + + public long lastPairClaim(UUID killer, UUID target) { + return records.stream() + .filter(record -> "CLAIMED".equalsIgnoreCase(record.type()) + && killer.equals(record.actorUuid()) + && target.equals(record.targetUuid())) + .mapToLong(BountyHistoryRecord::createdAt) + .max() + .orElse(0L); + } + + public long claimedCountBy(UUID killer) { + return records.stream() + .filter(record -> "CLAIMED".equalsIgnoreCase(record.type()) && killer.equals(record.actorUuid())) + .count(); + } + + private void prune() { + long olderThan = TimeUtil.parseMillis(configService.main().getString("history.prune-older-than"), 7_776_000_000L); + if (olderThan > 0L) { + long cutoff = System.currentTimeMillis() - olderThan; + records.removeIf(record -> record.createdAt() < cutoff); + suspicious.removeIf(record -> record.createdAt() < cutoff); + } + + int maxRecords = configService.main().getInt("history.max-records", 2000); + trim(records, maxRecords); + + int maxSuspicious = configService.main().getInt("anti-abuse.suspicious-history-limit", 500); + trim(suspicious, maxSuspicious); + } + + private void trim(List list, int max) { + if (max <= 0) { + list.clear(); + return; + } + while (list.size() > max) { + list.remove(list.size() - 1); + } + } + + private String safeName(OfflinePlayer player) { + return player.getName() == null ? player.getUniqueId().toString() : player.getName(); + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/service/MessageService.java b/src/main/java/com/dirtbagmc/dirtbounties/service/MessageService.java new file mode 100644 index 0000000..ad2d7e4 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/service/MessageService.java @@ -0,0 +1,103 @@ +package com.dirtbagmc.dirtbounties.service; + +import com.dirtbagmc.dirtbounties.config.ConfigService; +import com.dirtbagmc.dirtbounties.util.ColorUtil; +import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.Sound; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public final class MessageService { + private final ConfigService configService; + + public MessageService(ConfigService configService) { + this.configService = configService; + } + + public void send(CommandSender sender, String path) { + send(sender, path, Map.of()); + } + + public void send(CommandSender sender, String path, Map placeholders) { + String raw = configService.messages().getString(path); + if (raw == null || raw.isBlank()) { + return; + } + sender.sendMessage(component(raw, placeholders)); + } + + public void sendLines(CommandSender sender, String path, Map placeholders) { + List lines = configService.messages().getStringList(path); + if (lines.isEmpty()) { + send(sender, path, placeholders); + return; + } + for (String line : lines) { + sender.sendMessage(component(line, placeholders)); + } + } + + public void broadcast(String path, Map placeholders) { + String raw = configService.messages().getString(path); + if (raw == null || raw.isBlank()) { + return; + } + Component component = component(raw, placeholders); + Bukkit.getOnlinePlayers().forEach(player -> player.sendMessage(component)); + Bukkit.getConsoleSender().sendMessage(component); + } + + public Component component(String raw, Map placeholders) { + return ColorUtil.component(apply(raw, placeholders)); + } + + public String legacy(String raw, Map placeholders) { + return ColorUtil.legacySection(apply(raw, placeholders)); + } + + public List components(List lines, Map placeholders) { + return lines.stream().map(line -> component(line, placeholders)).toList(); + } + + public String raw(String path, String fallback) { + return configService.messages().getString(path, fallback); + } + + public void play(Player player, String configPath) { + String soundName = configService.main().getString(configPath, ""); + if (soundName == null || soundName.isBlank() || soundName.equalsIgnoreCase("none")) { + return; + } + Sound sound = resolveSound(soundName); + if (sound != null) { + player.playSound(player.getLocation(), sound, 1.0f, 1.0f); + } else if (configService.debug()) { + Bukkit.getLogger().warning("[DirtBounties] Unknown sound in config: " + soundName); + } + } + + private Sound resolveSound(String soundName) { + String normalized = soundName.toLowerCase(Locale.ROOT); + NamespacedKey key = normalized.contains(":") + ? NamespacedKey.fromString(normalized) + : NamespacedKey.minecraft(normalized.replace('_', '.')); + return key == null ? null : Registry.SOUNDS.get(key); + } + + private String apply(String raw, Map placeholders) { + Map merged = new HashMap<>(); + merged.put("prefix", configService.messages().getString("prefix", "")); + if (placeholders != null) { + merged.putAll(placeholders); + } + return ColorUtil.replace(raw, merged); + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/service/PlayerCacheService.java b/src/main/java/com/dirtbagmc/dirtbounties/service/PlayerCacheService.java new file mode 100644 index 0000000..ecc2060 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/service/PlayerCacheService.java @@ -0,0 +1,126 @@ +package com.dirtbagmc.dirtbounties.service; + +import com.dirtbagmc.dirtbounties.config.ConfigService; +import com.dirtbagmc.dirtbounties.storage.StorageManager; +import org.bukkit.entity.Player; + +import java.net.InetSocketAddress; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public final class PlayerCacheService { + private final ConfigService configService; + private final StorageManager storageManager; + private final Map cache = new HashMap<>(); + + public PlayerCacheService(ConfigService configService, StorageManager storageManager) { + this.configService = configService; + this.storageManager = storageManager; + } + + public void load() { + cache.clear(); + storageManager.loadPlayerCache().forEach((uuid, data) -> + cache.put(uuid, new CacheEntry(data.name(), data.lastIp(), new HashSet<>(data.knownIps()), data.lastSeen()))); + } + + public void save() { + if (!configService.main().getBoolean("storage.save-player-ip-cache", true)) { + return; + } + Map data = new HashMap<>(); + cache.forEach((uuid, entry) -> + data.put(uuid, new StorageManager.PlayerCacheData(entry.name(), entry.lastIp(), entry.knownIps(), entry.lastSeen()))); + storageManager.savePlayerCache(data); + } + + public void update(Player player) { + String ip = ip(player); + CacheEntry entry = cache.computeIfAbsent(player.getUniqueId(), + ignored -> new CacheEntry(player.getName(), "", new HashSet<>(), 0L)); + entry.name(player.getName()); + entry.lastSeen(System.currentTimeMillis()); + if (!ip.isBlank()) { + entry.lastIp(ip); + entry.knownIps().add(ip); + } + } + + public boolean sameLastIp(UUID first, UUID second) { + CacheEntry left = cache.get(first); + CacheEntry right = cache.get(second); + return left != null && right != null && !left.lastIp().isBlank() && left.lastIp().equals(right.lastIp()); + } + + public boolean sharedKnownIp(UUID first, UUID second) { + CacheEntry left = cache.get(first); + CacheEntry right = cache.get(second); + if (left == null || right == null) { + return false; + } + for (String ip : left.knownIps()) { + if (!ip.isBlank() && right.knownIps().contains(ip)) { + return true; + } + } + return false; + } + + public String cachedName(UUID uuid, String fallback) { + CacheEntry entry = cache.get(uuid); + return entry == null || entry.name().isBlank() ? fallback : entry.name(); + } + + private String ip(Player player) { + InetSocketAddress address = player.getAddress(); + if (address == null || address.getAddress() == null) { + return ""; + } + return address.getAddress().getHostAddress(); + } + + private static final class CacheEntry { + private String name; + private String lastIp; + private final Set knownIps; + private long lastSeen; + + private CacheEntry(String name, String lastIp, Set knownIps, long lastSeen) { + this.name = name; + this.lastIp = lastIp == null ? "" : lastIp; + this.knownIps = knownIps; + this.lastSeen = lastSeen; + } + + public String name() { + return name; + } + + public void name(String name) { + this.name = name; + } + + public String lastIp() { + return lastIp; + } + + public void lastIp(String lastIp) { + this.lastIp = lastIp; + } + + public Set knownIps() { + return knownIps; + } + + public long lastSeen() { + return lastSeen; + } + + public void lastSeen(long lastSeen) { + this.lastSeen = lastSeen; + } + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/storage/StorageManager.java b/src/main/java/com/dirtbagmc/dirtbounties/storage/StorageManager.java new file mode 100644 index 0000000..3c424bf --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/storage/StorageManager.java @@ -0,0 +1,296 @@ +package com.dirtbagmc.dirtbounties.storage; + +import com.dirtbagmc.dirtbounties.model.Bounty; +import com.dirtbagmc.dirtbounties.model.BountyContribution; +import com.dirtbagmc.dirtbounties.model.BountyHistoryRecord; +import com.dirtbagmc.dirtbounties.model.SuspiciousActivity; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; + +public final class StorageManager { + private final JavaPlugin plugin; + private File bountiesFile; + private File historyFile; + private File suspiciousFile; + private File playerCacheFile; + + public StorageManager(JavaPlugin plugin) { + this.plugin = plugin; + } + + public void initialize() { + plugin.getDataFolder().mkdirs(); + bountiesFile = ensureFile("bounties.yml"); + historyFile = ensureFile("history.yml"); + suspiciousFile = ensureFile("suspicious.yml"); + playerCacheFile = ensureFile("player-cache.yml"); + } + + public Map loadBounties() { + Map loaded = new HashMap<>(); + YamlConfiguration yaml = YamlConfiguration.loadConfiguration(bountiesFile); + ConfigurationSection root = yaml.getConfigurationSection("bounties"); + if (root == null) { + return loaded; + } + + for (String key : root.getKeys(false)) { + ConfigurationSection section = root.getConfigurationSection(key); + if (section == null) { + continue; + } + try { + UUID targetUuid = UUID.fromString(section.getString("target-uuid", key)); + UUID bountyId = UUID.fromString(section.getString("id", UUID.randomUUID().toString())); + List contributions = new ArrayList<>(); + ConfigurationSection contributionsSection = section.getConfigurationSection("contributions"); + if (contributionsSection != null) { + for (String contributionKey : contributionsSection.getKeys(false)) { + ConfigurationSection contribution = contributionsSection.getConfigurationSection(contributionKey); + if (contribution == null) { + continue; + } + UUID placerUuid = uuidOrNull(contribution.getString("placer-uuid")); + contributions.add(new BountyContribution( + UUID.fromString(contribution.getString("id", contributionKey)), + placerUuid, + contribution.getString("placer-name", "Console"), + contribution.getDouble("amount"), + contribution.getDouble("fee"), + contribution.getString("reason", ""), + contribution.getBoolean("anonymous"), + contribution.getLong("created-at") + )); + } + } + Bounty bounty = new Bounty( + bountyId, + targetUuid, + section.getString("target-name", "Unknown"), + section.getDouble("amount"), + section.getLong("created-at"), + section.getLong("updated-at"), + section.getLong("expires-at"), + contributions + ); + loaded.put(targetUuid, bounty); + } catch (RuntimeException ex) { + plugin.getLogger().log(Level.WARNING, "Skipping invalid bounty entry " + key, ex); + } + } + return loaded; + } + + public void saveBounties(Collection bounties) { + YamlConfiguration yaml = new YamlConfiguration(); + for (Bounty bounty : bounties) { + String path = "bounties." + bounty.targetUuid(); + yaml.set(path + ".id", bounty.id().toString()); + yaml.set(path + ".target-uuid", bounty.targetUuid().toString()); + yaml.set(path + ".target-name", bounty.targetName()); + yaml.set(path + ".amount", bounty.amount()); + yaml.set(path + ".created-at", bounty.createdAt()); + yaml.set(path + ".updated-at", bounty.updatedAt()); + yaml.set(path + ".expires-at", bounty.expiresAt()); + for (BountyContribution contribution : bounty.contributions()) { + String contributionPath = path + ".contributions." + contribution.id(); + yaml.set(contributionPath + ".id", contribution.id().toString()); + yaml.set(contributionPath + ".placer-uuid", contribution.placerUuid() == null ? null : contribution.placerUuid().toString()); + yaml.set(contributionPath + ".placer-name", contribution.placerName()); + yaml.set(contributionPath + ".amount", contribution.amount()); + yaml.set(contributionPath + ".fee", contribution.fee()); + yaml.set(contributionPath + ".reason", contribution.reason()); + yaml.set(contributionPath + ".anonymous", contribution.anonymous()); + yaml.set(contributionPath + ".created-at", contribution.createdAt()); + } + } + save(yaml, bountiesFile); + } + + public List loadHistory() { + List records = new ArrayList<>(); + YamlConfiguration yaml = YamlConfiguration.loadConfiguration(historyFile); + ConfigurationSection root = yaml.getConfigurationSection("records"); + if (root == null) { + return records; + } + for (String key : root.getKeys(false)) { + ConfigurationSection section = root.getConfigurationSection(key); + if (section == null) { + continue; + } + try { + records.add(new BountyHistoryRecord( + UUID.fromString(section.getString("id", key)), + section.getString("type", "UNKNOWN"), + uuidOrNull(section.getString("target-uuid")), + section.getString("target-name", "Unknown"), + uuidOrNull(section.getString("actor-uuid")), + section.getString("actor-name", "Console"), + section.getDouble("amount"), + section.getString("note", ""), + section.getLong("created-at") + )); + } catch (RuntimeException ex) { + plugin.getLogger().log(Level.WARNING, "Skipping invalid history entry " + key, ex); + } + } + records.sort((left, right) -> Long.compare(right.createdAt(), left.createdAt())); + return records; + } + + public void saveHistory(Collection records) { + YamlConfiguration yaml = new YamlConfiguration(); + for (BountyHistoryRecord record : records) { + String path = "records." + record.id(); + yaml.set(path + ".id", record.id().toString()); + yaml.set(path + ".type", record.type()); + yaml.set(path + ".target-uuid", record.targetUuid() == null ? null : record.targetUuid().toString()); + yaml.set(path + ".target-name", record.targetName()); + yaml.set(path + ".actor-uuid", record.actorUuid() == null ? null : record.actorUuid().toString()); + yaml.set(path + ".actor-name", record.actorName()); + yaml.set(path + ".amount", record.amount()); + yaml.set(path + ".note", record.note()); + yaml.set(path + ".created-at", record.createdAt()); + } + save(yaml, historyFile); + } + + public List loadSuspicious() { + List records = new ArrayList<>(); + YamlConfiguration yaml = YamlConfiguration.loadConfiguration(suspiciousFile); + ConfigurationSection root = yaml.getConfigurationSection("records"); + if (root == null) { + return records; + } + for (String key : root.getKeys(false)) { + ConfigurationSection section = root.getConfigurationSection(key); + if (section == null) { + continue; + } + try { + records.add(new SuspiciousActivity( + UUID.fromString(section.getString("id", key)), + section.getString("type", "UNKNOWN"), + uuidOrNull(section.getString("killer-uuid")), + section.getString("killer-name", "Unknown"), + uuidOrNull(section.getString("target-uuid")), + section.getString("target-name", "Unknown"), + section.getString("details", ""), + section.getInt("severity", 1), + section.getLong("created-at") + )); + } catch (RuntimeException ex) { + plugin.getLogger().log(Level.WARNING, "Skipping invalid suspicious entry " + key, ex); + } + } + records.sort((left, right) -> Long.compare(right.createdAt(), left.createdAt())); + return records; + } + + public void saveSuspicious(Collection records) { + YamlConfiguration yaml = new YamlConfiguration(); + for (SuspiciousActivity record : records) { + String path = "records." + record.id(); + yaml.set(path + ".id", record.id().toString()); + yaml.set(path + ".type", record.type()); + yaml.set(path + ".killer-uuid", record.killerUuid() == null ? null : record.killerUuid().toString()); + yaml.set(path + ".killer-name", record.killerName()); + yaml.set(path + ".target-uuid", record.targetUuid() == null ? null : record.targetUuid().toString()); + yaml.set(path + ".target-name", record.targetName()); + yaml.set(path + ".details", record.details()); + yaml.set(path + ".severity", record.severity()); + yaml.set(path + ".created-at", record.createdAt()); + } + save(yaml, suspiciousFile); + } + + public Map loadPlayerCache() { + Map cache = new HashMap<>(); + YamlConfiguration yaml = YamlConfiguration.loadConfiguration(playerCacheFile); + ConfigurationSection root = yaml.getConfigurationSection("players"); + if (root == null) { + return cache; + } + for (String key : root.getKeys(false)) { + ConfigurationSection section = root.getConfigurationSection(key); + if (section == null) { + continue; + } + try { + UUID uuid = UUID.fromString(key); + cache.put(uuid, new PlayerCacheData( + section.getString("name", "Unknown"), + section.getString("last-ip", ""), + new HashSet<>(section.getStringList("known-ips")), + section.getLong("last-seen") + )); + } catch (RuntimeException ex) { + plugin.getLogger().log(Level.WARNING, "Skipping invalid player cache entry " + key, ex); + } + } + return cache; + } + + public void savePlayerCache(Map cache) { + YamlConfiguration yaml = new YamlConfiguration(); + for (Map.Entry entry : cache.entrySet()) { + String path = "players." + entry.getKey(); + PlayerCacheData data = entry.getValue(); + yaml.set(path + ".name", data.name()); + yaml.set(path + ".last-ip", data.lastIp()); + yaml.set(path + ".known-ips", new ArrayList<>(data.knownIps())); + yaml.set(path + ".last-seen", data.lastSeen()); + } + save(yaml, playerCacheFile); + } + + private File ensureFile(String name) { + File file = new File(plugin.getDataFolder(), name); + if (!file.exists()) { + try { + if (file.createNewFile()) { + plugin.getLogger().fine("Created " + name); + } + } catch (IOException ex) { + plugin.getLogger().log(Level.SEVERE, "Could not create " + name, ex); + } + } + return file; + } + + private void save(YamlConfiguration yaml, File file) { + try { + yaml.save(file); + } catch (IOException ex) { + plugin.getLogger().log(Level.SEVERE, "Could not save " + file.getName(), ex); + } + } + + private UUID uuidOrNull(String input) { + if (input == null || input.isBlank()) { + return null; + } + return UUID.fromString(input); + } + + public record PlayerCacheData(String name, String lastIp, Set knownIps, long lastSeen) { + public PlayerCacheData { + knownIps = knownIps == null ? Set.of() : Set.copyOf(knownIps); + } + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/util/ColorUtil.java b/src/main/java/com/dirtbagmc/dirtbounties/util/ColorUtil.java new file mode 100644 index 0000000..049d026 --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/util/ColorUtil.java @@ -0,0 +1,63 @@ +package com.dirtbagmc.dirtbounties.util; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class ColorUtil { + private static final Pattern HEX_PATTERN = Pattern.compile("&#([A-Fa-f0-9]{6})"); + private static final LegacyComponentSerializer LEGACY = LegacyComponentSerializer.builder() + .character('&') + .hexColors() + .build(); + + private ColorUtil() { + } + + public static Component component(String input) { + return LEGACY.deserialize(translateHex(input == null ? "" : input)); + } + + public static String legacySection(String input) { + String translated = translateHex(input == null ? "" : input); + StringBuilder builder = new StringBuilder(translated.length()); + for (int i = 0; i < translated.length(); i++) { + char current = translated.charAt(i); + if (current == '&' && i + 1 < translated.length()) { + builder.append('§'); + } else { + builder.append(current); + } + } + return builder.toString(); + } + + public static String replace(String input, Map placeholders) { + String value = input == null ? "" : input; + if (placeholders == null || placeholders.isEmpty()) { + return value; + } + for (Map.Entry entry : placeholders.entrySet()) { + value = value.replace("{" + entry.getKey() + "}", entry.getValue() == null ? "" : entry.getValue()); + } + return value; + } + + private static String translateHex(String input) { + Matcher matcher = HEX_PATTERN.matcher(input); + StringBuilder builder = new StringBuilder(); + while (matcher.find()) { + String hex = matcher.group(1); + StringBuilder replacement = new StringBuilder("&x"); + for (char c : hex.toCharArray()) { + replacement.append('&').append(c); + } + matcher.appendReplacement(builder, Matcher.quoteReplacement(replacement.toString())); + } + matcher.appendTail(builder); + return builder.toString(); + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/util/ItemBuilder.java b/src/main/java/com/dirtbagmc/dirtbounties/util/ItemBuilder.java new file mode 100644 index 0000000..62543ad --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/util/ItemBuilder.java @@ -0,0 +1,86 @@ +package com.dirtbagmc.dirtbounties.util; + +import com.dirtbagmc.dirtbounties.service.MessageService; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public final class ItemBuilder { + private ItemBuilder() { + } + + public static ItemStack fromSection(ConfigurationSection section, MessageService messages, + Map placeholders, OfflinePlayer skullOwner) { + if (section == null) { + return new ItemStack(Material.STONE); + } + Material material = material(section.getString("material", "STONE")); + ItemStack item = new ItemStack(material); + ItemMeta meta = item.getItemMeta(); + if (meta == null) { + return item; + } + + if (meta instanceof SkullMeta skullMeta && skullOwner != null) { + skullMeta.setOwningPlayer(skullOwner); + } + + String name = section.getString("name", ""); + if (!name.isBlank()) { + meta.displayName(messages.component(name, placeholders)); + } + + List lore = new ArrayList<>(); + for (String line : section.getStringList("lore")) { + String expanded = ColorUtil.replace(line, placeholders); + if (expanded.contains("\n")) { + lore.addAll(List.of(expanded.split("\\n", -1))); + } else { + lore.add(expanded); + } + } + if (!lore.isEmpty()) { + meta.lore(lore.stream().map(ColorUtil::component).toList()); + } + + meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ENCHANTS, ItemFlag.HIDE_ADDITIONAL_TOOLTIP); + item.setItemMeta(meta); + return item; + } + + public static ItemStack simple(Material material, String name, List lore, MessageService messages, + Map placeholders) { + ItemStack item = new ItemStack(material); + ItemMeta meta = item.getItemMeta(); + if (meta == null) { + return item; + } + meta.displayName(messages.component(name, placeholders)); + meta.lore(lore.stream().map(line -> messages.component(line, placeholders)).toList()); + meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ENCHANTS, ItemFlag.HIDE_ADDITIONAL_TOOLTIP); + item.setItemMeta(meta); + return item; + } + + private static Material material(String name) { + if (name == null || name.isBlank()) { + return Material.STONE; + } + Material material = Material.matchMaterial(name.toUpperCase(Locale.ROOT)); + if (material == null) { + Bukkit.getLogger().warning("[DirtBounties] Unknown material in GUI config: " + name); + return Material.STONE; + } + return material; + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/util/NumberUtil.java b/src/main/java/com/dirtbagmc/dirtbounties/util/NumberUtil.java new file mode 100644 index 0000000..386564e --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/util/NumberUtil.java @@ -0,0 +1,21 @@ +package com.dirtbagmc.dirtbounties.util; + +import java.text.DecimalFormat; + +public final class NumberUtil { + private static final DecimalFormat MONEY = new DecimalFormat("#,##0.##"); + + private NumberUtil() { + } + + public static double clampMoney(double value) { + if (Double.isNaN(value) || Double.isInfinite(value)) { + return 0.0; + } + return Math.round(value * 100.0) / 100.0; + } + + public static String compact(double value) { + return MONEY.format(clampMoney(value)); + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/util/TimeUtil.java b/src/main/java/com/dirtbagmc/dirtbounties/util/TimeUtil.java new file mode 100644 index 0000000..483adfa --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/util/TimeUtil.java @@ -0,0 +1,79 @@ +package com.dirtbagmc.dirtbounties.util; + +import java.time.Duration; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class TimeUtil { + private static final Pattern TOKEN = Pattern.compile("(\\d+(?:\\.\\d+)?)(ms|s|m|h|d|w)", Pattern.CASE_INSENSITIVE); + + private TimeUtil() { + } + + public static long parseMillis(String input, long fallbackMillis) { + if (input == null || input.isBlank()) { + return fallbackMillis; + } + String normalized = input.trim().toLowerCase(Locale.ROOT); + if (normalized.equals("0") || normalized.equals("none") || normalized.equals("off") || normalized.equals("disabled")) { + return 0L; + } + if (normalized.matches("\\d+")) { + return Long.parseLong(normalized) * 1000L; + } + + Matcher matcher = TOKEN.matcher(normalized.replace(" ", "")); + double total = 0.0; + int matches = 0; + while (matcher.find()) { + double value = Double.parseDouble(matcher.group(1)); + String unit = matcher.group(2).toLowerCase(Locale.ROOT); + total += switch (unit) { + case "ms" -> value; + case "s" -> value * 1000.0; + case "m" -> value * 60_000.0; + case "h" -> value * 3_600_000.0; + case "d" -> value * 86_400_000.0; + case "w" -> value * 604_800_000.0; + default -> 0.0; + }; + matches++; + } + return matches == 0 ? fallbackMillis : Math.max(0L, Math.round(total)); + } + + public static String formatDuration(long millis) { + if (millis <= 0L) { + return "never"; + } + Duration duration = Duration.ofMillis(millis); + long days = duration.toDays(); + duration = duration.minusDays(days); + long hours = duration.toHours(); + duration = duration.minusHours(hours); + long minutes = duration.toMinutes(); + duration = duration.minusMinutes(minutes); + long seconds = Math.max(0L, duration.toSeconds()); + + if (days > 0) { + return days + "d " + hours + "h"; + } + if (hours > 0) { + return hours + "h " + minutes + "m"; + } + if (minutes > 0) { + return minutes + "m " + seconds + "s"; + } + return seconds + "s"; + } + + public static String formatTimestamp(long epochMillis) { + if (epochMillis <= 0L) { + return "never"; + } + return java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") + .withZone(java.time.ZoneId.systemDefault()) + .format(java.time.Instant.ofEpochMilli(epochMillis)); + } +} diff --git a/src/main/java/com/dirtbagmc/dirtbounties/webhook/WebhookService.java b/src/main/java/com/dirtbagmc/dirtbounties/webhook/WebhookService.java new file mode 100644 index 0000000..65d305b --- /dev/null +++ b/src/main/java/com/dirtbagmc/dirtbounties/webhook/WebhookService.java @@ -0,0 +1,100 @@ +package com.dirtbagmc.dirtbounties.webhook; + +import com.dirtbagmc.dirtbounties.config.ConfigService; +import com.dirtbagmc.dirtbounties.service.MessageService; +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.IOException; +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.logging.Level; + +public final class WebhookService { + private final JavaPlugin plugin; + private final ConfigService configService; + private final MessageService messages; + private HttpClient client; + + public WebhookService(JavaPlugin plugin, ConfigService configService, MessageService messages) { + this.plugin = plugin; + this.configService = configService; + this.messages = messages; + } + + public void initialize() { + client = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(Math.max(1, configService.main().getInt("webhooks.timeout-seconds", 8)))) + .build(); + } + + public void placement(String placer, String target, double total, String reason, boolean increased) { + if (!configService.main().getBoolean("webhooks.notify-placements", true) + || total < configService.main().getDouble("webhooks.large-bounty-threshold", 25_000.0)) { + return; + } + String title = messages.raw("webhook.placement-title", "New bounty placed"); + String content = title + "\n" + placer + (increased ? " increased " : " placed ") + + "a bounty on " + target + " worth " + total + ". Reason: " + reason; + send(content); + } + + public void claim(String killer, String target, double payout) { + if (!configService.main().getBoolean("webhooks.notify-claims", true) + || payout < configService.main().getDouble("webhooks.large-claim-threshold", 25_000.0)) { + return; + } + String title = messages.raw("webhook.claim-title", "Bounty claimed"); + send(title + "\n" + killer + " claimed " + payout + " for killing " + target + "."); + } + + public void admin(String actor, String action) { + if (!configService.main().getBoolean("webhooks.notify-admin-actions", true)) { + return; + } + String title = messages.raw("webhook.admin-title", "Bounty admin action"); + send(title + "\n" + actor + ": " + action); + } + + private void send(String content) { + if (!configService.main().getBoolean("webhooks.enabled", false)) { + return; + } + String url = configService.main().getString("webhooks.url", ""); + if (url == null || url.isBlank()) { + return; + } + String username = configService.main().getString("webhooks.username", "DirtBounties"); + String body = "{\"username\":\"" + escape(username) + "\",\"content\":\"" + escape(content) + "\"}"; + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .timeout(Duration.ofSeconds(Math.max(1, configService.main().getInt("webhooks.timeout-seconds", 8)))) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(body)) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() >= 400) { + plugin.getLogger().warning("Webhook returned HTTP " + response.statusCode()); + } + } catch (IllegalArgumentException | IOException | InterruptedException ex) { + if (ex instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + plugin.getLogger().log(Level.WARNING, "Could not send DirtBounties webhook.", ex); + } + }); + } + + private String escape(String input) { + return (input == null ? "" : input) + .replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\n", "\\n") + .replace("\r", ""); + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..8c8acb0 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,154 @@ +# DirtBounties main configuration +# Paper 1.21.x, Java 21 +# +# Color formatting supports normal ampersand colors, hex colors like &#D4AF37, +# and the DirtbagMC gradient style shown below. + +server: + brand-name: "DirtbagMC" + brand-gradient: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛA4A2A&lʙB6B35&lᴀ&#A8873F&lɢ&#D4AF37&lᴍ&#B9C63F&lᴄ" + debug: false + +storage: + # Active bounty data is saved after important mutations and on this autosave interval. + autosave-interval: "5m" + cleanup-expired-interval: "10m" + save-player-ip-cache: true + write-history-to-disk-immediately: true + +economy: + enabled: true + # DirtBounties uses Bukkit ServicesManager economy registration. + # This supports normal Vault and CMI's Vault injector. + fail-if-missing: false + provider-log-on-enable: true + minimum-balance-after-withdraw: 0.0 + currency-format: "${amount}" + fees: + # Placement fee can be charged as extra money or deducted from the bounty value. + placement-percent: 5.0 + placement-mode: "extra" # extra, deduct + add-percent: 5.0 + add-mode: "extra" # extra, deduct + claim-tax-percent: 0.0 + claim-sink-percent: 0.0 + broadcasts: + enabled: true + placed-threshold: 5000.0 + claimed-threshold: 5000.0 + milestone-thresholds: + - 10000.0 + - 25000.0 + - 50000.0 + +bounties: + min-amount: 100.0 + max-amount: 1000000.0 + stack-existing: true + allow-self-target: false + allow-offline-targets: true + # If false, targets must be online or have joined before. + # If true, Bukkit may create an OfflinePlayer profile for unknown names. + allow-never-joined-targets: false + allow-banned-targets: false + allow-anonymous: true + anonymous-requires-permission: true + allow-reasons: true + max-reason-length: 80 + default-reason: "No reason given." + placement-cooldown: "30s" + require-confirmation-gui: true + +expiration: + enabled: true + default-duration: "14d" + max-duration: "30d" + # If true, a bounty without contributions gets removed during cleanup. + remove-empty-bounties: true + banned-player-action: "keep" # keep, expire + deleted-player-action: "keep" # keep, expire + +refunds: + # Refund percentage is based on the active contribution value, not placement fees. + on-admin-remove-percent: 100.0 + on-expire-percent: 50.0 + on-invalid-percent: 100.0 + refund-fees: false + refund-offline-players: true + minimum-refund: 0.01 + +claim-rules: + require-permission: true + require-pvp-kill: true + block-environmental-deaths: true + prevent-self-claims: true + prevent-same-ip-claims: true + prevent-shared-known-ip-claims: true + allow-bypass-permission: true + require-target-online-at-death: true + blocked-killer-game-modes: + - CREATIVE + - SPECTATOR + worlds: + mode: "blacklist" # disabled, whitelist, blacklist + list: + - spawn + - events + combat: + enabled: true + window: "30s" + min-damage: 4.0 + min-hits: 1 + min-combat-duration: "0s" + +anti-abuse: + enabled: true + log-failed-claims: true + log-same-ip-attempts: true + killer-claim-cooldown: "2m" + target-claim-cooldown: "2m" + killer-target-pair-cooldown: "12h" + same-victim-cooldown: "10m" + pair-window: "7d" + max-pair-claims-in-window: 2 + suspicious-history-limit: 500 + run-console-commands-on-suspicious: false + suspicious-commands: + - "staffmsg Suspicious bounty claim: {killer} -> {target}: {reason}" + +history: + enabled: true + max-records: 2000 + max-records-per-player-command: 10 + prune-older-than: "90d" + +gui: + enabled: true + open-sound: "BLOCK_BARREL_OPEN" + click-sound: "UI_BUTTON_CLICK" + success-sound: "ENTITY_PLAYER_LEVELUP" + error-sound: "ENTITY_VILLAGER_NO" + items-per-page: 28 + refresh-after-action: true + close-on-confirm: true + chat-input-timeout: "60s" + +webhooks: + enabled: false + url: "" + username: "DirtBounties" + large-bounty-threshold: 25000.0 + large-claim-threshold: 25000.0 + notify-placements: true + notify-claims: true + notify-admin-actions: true + timeout-seconds: 8 + +logging: + console: + economy-status: true + placements: true + claims: true + admin-actions: true + suspicious: true + file-history: true diff --git a/src/main/resources/gui.yml b/src/main/resources/gui.yml new file mode 100644 index 0000000..35dcff8 --- /dev/null +++ b/src/main/resources/gui.yml @@ -0,0 +1,177 @@ +titles: + main: "A2416&lDirtBounties &8| &6Active" + top: "A2416&lDirtBounties &8| &6Top" + detail: "A2416&lDirtBounties &8| &c{target}" + confirm: "A2416&lDirtBounties &8| &aConfirm" + admin-main: "A2416&lDirtBounties &8| &4Admin" + admin-history: "A2416&lDirtBounties &8| &4History" + admin-suspicious: "A2416&lDirtBounties &8| &4Suspicious" + +layout: + size: 54 + content-slots: + - 10 + - 11 + - 12 + - 13 + - 14 + - 15 + - 16 + - 19 + - 20 + - 21 + - 22 + - 23 + - 24 + - 25 + - 28 + - 29 + - 30 + - 31 + - 32 + - 33 + - 34 + - 37 + - 38 + - 39 + - 40 + - 41 + - 42 + - 43 + previous-slot: 45 + back-slot: 46 + refresh-slot: 49 + next-slot: 53 + place-slot: 48 + top-slot: 50 + admin-slot: 52 + +items: + filler: + material: BLACK_STAINED_GLASS_PANE + name: " " + lore: [] + border: + material: BROWN_STAINED_GLASS_PANE + name: " " + lore: [] + empty: + material: BARRIER + name: "&cNo bounties" + lore: + - "&7Nobody has a price on their head." + bounty: + material: PLAYER_HEAD + name: "&c&l{target}" + lore: + - "&7Bounty: &f{amount}" + - "&7Contributors: &f{contributors}" + - "&7Top reason: &f{reason}" + - "&7Expires: &f{expires}" + - "" + - "&eClick to view details." + top-bounty: + material: PLAYER_HEAD + name: "&6#{rank} &c&l{target}" + lore: + - "&7Bounty: &f{amount}" + - "&7Contributors: &f{contributors}" + - "" + - "&eClick to inspect." + detail-head: + material: PLAYER_HEAD + name: "&c&l{target}" + lore: + - "&7Total bounty: &f{amount}" + - "&7Contributors: &f{contributors}" + - "&7Expires: &f{expires}" + - "" + - "&6Top reasons:" + - "{reason_lines}" + claim-info: + material: BOOK + name: "&6Claim Conditions" + lore: + - "&7PvP required: &f{pvp}" + - "&7Same IP blocked: &f{same_ip}" + - "&7World mode: &f{world_mode}" + - "&7Combat: &f{combat}" + - "" + - "&8Claims are checked automatically on kill." + place: + material: GOLD_INGOT + name: "&6Place Bounty" + lore: + - "&7Start a guided bounty placement." + - "" + - "&eClick to begin." + add: + material: ANVIL + name: "&6Increase Bounty" + lore: + - "&7Add money to this target's bounty." + - "" + - "&eClick to continue." + top: + material: NETHER_STAR + name: "&6Top Bounties" + lore: + - "&7Sort by highest active value." + - "" + - "&eClick to view." + refresh: + material: SUNFLOWER + name: "&eRefresh" + lore: + - "&7Reload this view." + previous: + material: ARROW + name: "&ePrevious Page" + lore: + - "&7Go back one page." + next: + material: ARROW + name: "&eNext Page" + lore: + - "&7Go forward one page." + back: + material: OAK_DOOR + name: "&eBack" + lore: + - "&7Return to the previous view." + close: + material: BARRIER + name: "&cClose" + lore: + - "&7Close this menu." + confirm: + material: LIME_CONCRETE + name: "&aConfirm Bounty" + lore: + - "&7Target: &f{target}" + - "&7Amount: &f{amount}" + - "&7Fee: &f{fee}" + - "&7Total cost: &f{cost}" + - "&7Reason: &f{reason}" + - "" + - "&aClick to confirm." + cancel: + material: RED_CONCRETE + name: "&cCancel" + lore: + - "&7Return without placing this bounty." + admin-active: + material: CHEST + name: "&4Active Bounties" + lore: + - "&7Review and inspect all active bounties." + admin-history: + material: WRITABLE_BOOK + name: "&4Recent Claims and Changes" + lore: + - "&7Review bounty history records." + admin-suspicious: + material: REDSTONE_TORCH + name: "&4Suspicious Activity" + lore: + - "&7Review blocked and suspicious claims." diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml new file mode 100644 index 0000000..956abf4 --- /dev/null +++ b/src/main/resources/messages.yml @@ -0,0 +1,105 @@ +prefix: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛA4A2A&lʙB6B35&lᴀ&#A8873F&lɢ&#D4AF37&lᴍ&#B9C63F&lᴄ &8» " +no-permission: "{prefix}&cYou do not have permission to do that." +player-only: "{prefix}&cOnly players can use that command." +unknown-command: "{prefix}&cUnknown bounty command. Use &f/bounty help&c." +reload-complete: "{prefix}&aDirtBounties reloaded." +invalid-number: "{prefix}&cThat amount is not valid." +invalid-player: "{prefix}&cCould not find that player." +invalid-world: "{prefix}&cBounties cannot be claimed in this world." +economy-missing: "{prefix}&cEconomy is not available. Ask staff to check Vault or CMI's Vault injector." +not-enough-money: "{prefix}&cYou need &f{cost}&c, including fees, to place that bounty." +cooldown: "{prefix}&cSlow down. Try again in &f{time}&c." +reason-too-long: "{prefix}&cThat reason is too long. Maximum: &f{max}&c characters." +target-self: "{prefix}&cYou cannot place or claim a bounty on yourself." +target-banned: "{prefix}&cThat player cannot receive bounties while banned." +amount-too-low: "{prefix}&cMinimum bounty amount is &f{min}&c." +amount-too-high: "{prefix}&cMaximum bounty amount is &f{max}&c." +amount-would-exceed-max: "{prefix}&cThat would put the bounty above the maximum of &f{max}&c." +no-active-bounties: "{prefix}&7There are no active bounties right now." +bounty-not-found: "{prefix}&cThere is no active bounty on &f{target}&c." +claim-denied-format: "{prefix}&cBounty claim denied: &f{reason}" + +help: + - "{prefix}&6&lDirtBounties Commands" + - "&8- &e/bounty &7Open the bounty GUI." + - "&8- &e/bounty place [reason] &7Place a bounty." + - "&8- &e/bounty add [reason] &7Increase a bounty." + - "&8- &e/bounty list &7List active bounties." + - "&8- &e/bounty top &7View top bounties." + - "&8- &e/bounty view &7View bounty details." + - "&8- &e/bounty claiminfo &7View claim rules." + +admin-help: + - "{prefix}&6&lDirtBounties Admin" + - "&8- &e/bountyadmin reload &7Reload configs and storage." + - "&8- &e/bountyadmin remove &7Remove a bounty and refund by config." + - "&8- &e/bountyadmin clearall confirm &7Remove every active bounty." + - "&8- &e/bountyadmin set &7Set a bounty value." + - "&8- &e/bountyadmin expire &7Expire a bounty." + - "&8- &e/bountyadmin history &7Show history." + - "&8- &e/bountyadmin suspicious &7Show suspicious activity." + - "&8- &e/bountyadmin gui &7Open admin GUI." + +bounty: + placed: "{prefix}&aPlaced a bounty of &f{amount}&a on &f{target}&a. Fee: &f{fee}&a." + added: "{prefix}&aAdded &f{amount}&a to &f{target}&a's bounty. New total: &f{total}&a." + broadcast-placed: "{prefix}&6{placer}&e placed a bounty of &f{amount}&e on &c{target}&e." + broadcast-added: "{prefix}&6{placer}&e increased &c{target}&e's bounty to &f{total}&e." + milestone: "{prefix}&c{target}&6's bounty has reached &f{total}&6." + claimed: "{prefix}&aYou claimed &f{payout}&a from &c{target}&a's bounty." + claim-broadcast: "{prefix}&c{killer}&6 claimed &f{payout}&6 for killing &c{target}&6." + expired: "{prefix}&7The bounty on &f{target}&7 expired." + removed: "{prefix}&aRemoved the bounty on &f{target}&a." + set: "{prefix}&aSet &f{target}&a's bounty to &f{amount}&a." + clearall-warning: "{prefix}&cUse &f/bountyadmin clearall confirm &cto remove all active bounties." + clearall-done: "{prefix}&aRemoved &f{count}&a active bounties." + list-line: "&8- &c{target} &7» &f{amount} &8({contributors} contributors)" + top-line: "&6#{rank} &c{target} &7» &f{amount}" + view: + - "{prefix}&6&lBounty: &c{target}" + - "&7Amount: &f{amount}" + - "&7Contributors: &f{contributors}" + - "&7Expires: &f{expires}" + - "&7Reason: &f{reason}" + claiminfo: + - "{prefix}&6&lClaim Rules for &c{target}" + - "&8- &7PvP kill required: &f{pvp}" + - "&8- &7Same-IP claims blocked: &f{same_ip}" + - "&8- &7Allowed worlds: &f{worlds}" + - "&8- &7Combat requirement: &f{combat}" + +claim-denied: + no-bounty: "No active bounty exists." + no-permission: "You do not have permission to claim bounties." + self: "Self-claims are blocked." + same-ip: "Same-IP bounty claims are blocked." + shared-known-ip: "Shared known-IP bounty claims are blocked." + world: "Claims are disabled in this world." + gamemode: "Your game mode cannot claim bounties." + combat: "Combat requirements were not met." + cooldown: "A claim cooldown is active." + pair-limit: "This killer-target pair has too many recent claims." + target-offline: "The target must be online at death." + +gui: + unavailable: "{prefix}&cThe bounty GUI is disabled." + prompt-target: "{prefix}&eType the target player's name in chat, or type &fcancel&e." + prompt-amount: "{prefix}&eType the bounty amount for &f{target}&e, or type &fcancel&e." + prompt-reason: "{prefix}&eType a short reason for &f{target}&e, use &f-&e for none, or type &fcancel&e." + input-cancelled: "{prefix}&7Bounty input cancelled." + input-expired: "{prefix}&cYour bounty input expired." + confirm-opened: "{prefix}&7Review and confirm the bounty." + admin-opened: "{prefix}&7Opened the admin bounty view." + +admin: + history-empty: "{prefix}&7No bounty history found for &f{target}&7." + suspicious-empty: "{prefix}&7No suspicious bounty activity has been logged." + suspicious-line: "&8- &c{time} &7{type}: &f{details}" + history-line: "&8- &e{time} &7{type}: &f{amount} &8({note})" + refunded: "{prefix}&aRefunded &f{amount}&a to &f{player}&a." + action-logged: "{prefix}&7Admin action logged." + +webhook: + placement-title: "New bounty placed" + claim-title: "Bounty claimed" + admin-title: "Bounty admin action" diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..a99dc65 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,67 @@ +name: DirtBounties +version: 1.0.0 +main: com.dirtbagmc.dirtbounties.DirtBountiesPlugin +api-version: '1.21' +author: DirtbagMC +website: https://dirtbagmc.com +description: Premium bounty system for Paper SMP and anarchy servers. +softdepend: + - Vault + - CMI + - PlaceholderAPI + +commands: + bounty: + description: Open and manage player bounties. + usage: /bounty help + aliases: + - bounties + - dbounty + bountyadmin: + description: Admin controls for DirtBounties. + usage: /bountyadmin help + aliases: + - dbountyadmin + - ba + +permissions: + dirtbounties.use: + description: Allows opening the bounty GUI and using basic commands. + default: true + dirtbounties.place: + description: Allows placing new bounties. + default: true + dirtbounties.add: + description: Allows increasing existing bounties. + default: true + dirtbounties.view: + description: Allows viewing bounty details. + default: true + dirtbounties.top: + description: Allows viewing top bounties. + default: true + dirtbounties.claim: + description: Allows claiming bounties by killing targets. + default: true + dirtbounties.anonymous: + description: Allows placing anonymous bounties when enabled. + default: op + dirtbounties.bypass.cooldowns: + description: Bypasses placement and claim cooldown checks. + default: op + dirtbounties.bypass.claimrules: + description: Bypasses configured claim restrictions. + default: op + dirtbounties.admin: + description: Full DirtBounties administration access. + default: op + children: + dirtbounties.use: true + dirtbounties.place: true + dirtbounties.add: true + dirtbounties.view: true + dirtbounties.top: true + dirtbounties.claim: true + dirtbounties.anonymous: true + dirtbounties.bypass.cooldowns: true + dirtbounties.bypass.claimrules: true diff --git a/target/DirtBounties-1.0.0.jar b/target/DirtBounties-1.0.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..3abc3e4007b665ca5d4fcffc6e6f92fe32a433dd GIT binary patch literal 109916 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 z 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

%X=D0l5RB`y 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 literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/DirtBountiesPlugin.class b/target/classes/com/dirtbagmc/dirtbounties/DirtBountiesPlugin.class new file mode 100644 index 0000000000000000000000000000000000000000..6f07149b9e87187e0485e1500db2b66eef0dc64a GIT binary patch literal 11504 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 D)eV{E literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/command/BountyAdminCommand.class b/target/classes/com/dirtbagmc/dirtbounties/command/BountyAdminCommand.class new file mode 100644 index 0000000000000000000000000000000000000000..7f099036b3ad1d829b846e882794f8a07cdbbcc4 GIT binary patch literal 11567 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 QW*~^;1EGKsC`0vs0-y|ziU0rr literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/config/ConfigService.class b/target/classes/com/dirtbagmc/dirtbounties/config/ConfigService.class new file mode 100644 index 0000000000000000000000000000000000000000..0b3b7682860022f12dd558409665dc5401df466a GIT binary patch literal 2410 zcmb7GZBr9h6n<_9G znA@h|m~O*(P~EUhB*t+{<;$CmH4TQ%$M z+LG=2w%g$ObQq4NxDvE@Sj;UaaZVu7Xxkyo2+m7fP;O<*ay+vhdQ|aDOXQFjNHNdw zRNyLe?4hQn8E?1D!1i2YjY;dw!`snuQ9a~EiA$IfNH24zG{@- zF5CT*Rj#i}e2BRo{?ywDjO=|Yb@L;MkJU}Zj=Ge_4HOf&DKQU<(s*;?D`wN#lQ>`U zC7}@+0=M^teGs3oz-m4}m8_^c%wks*Z_8ph9)$77t7Pv6oE8^EQmmDv~OJ1F_ zJ6yJ1>wddgwOUWisuE(R?A1(X*=*TrA05Vnb(^hWw)~D=uSiEqbevSB2_$y~@;0^m zsyNu4C<%cee%Bmva(~7AqWAxm%>2!b#^} zK;#QMNc^hre$8Mc;Y43t)-!qmF^Kaq{e)ICbA<@C1 z4vszzmnZX?i4M->GgE5RU*FG%^9MU9Q-QX=eiXi5BTZtn@h*F`0`}<><(*U6+Qe91?u!aU9Vx%}5 zK|QC6l$x?VPh8+@E?w;4@lTNSkSH+5uhc6JXo8jSfa@4#%Ns|LVDsADWC+xCm&Lkv bH_Cq|HYFaz2JI;Ge=(V$Yy~#CkHPsLv&J+v literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/economy/EconomyService$EconomyResult.class b/target/classes/com/dirtbagmc/dirtbounties/economy/EconomyService$EconomyResult.class new file mode 100644 index 0000000000000000000000000000000000000000..efa2a942259333ba7d73ade6e6c555a9ae104676 GIT binary patch literal 2111 zcmb_c+fLL_6kTT+Xc>wF!$lDV6wsLgsVI0G1i1oTx4Nq$K~5)C9TkN-G-lm>9n)ZeXAw1V}jP0Nlu8JoN$#6<@;Z!DRK z+V+GKg-jL=*NqCoMe?qaBx?QClpmKxo3m~1_>`O)wEomaQ}CRj_ap~r#H4_lqT_5; zs!Wgo4AQoX(H4jyC3i1YE&nHd@)&N@cWsQsQbeCfBSQqzQa9rC2I{^;eH%|T9-!$f zU0KA+Cjw;A7XpR|Ah{C~P(}Xv`cbbw`(9~*mhS=3Nb9>lWEiAVGAP+Yv;xvx6dnW> zz?CqlMq0>s{s6xUeEBDM7Y0K32pN+lP_uo9==dAHHPAWEsR|u;eZT9dh(YRf{|;1V zzdfPDL4XGFM TlBGyb#W1GPh8fJ_1=@ZC2Q&MA literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/economy/EconomyService.class b/target/classes/com/dirtbagmc/dirtbounties/economy/EconomyService.class new file mode 100644 index 0000000000000000000000000000000000000000..ae2654124041bdab868d54c5a30ffbde44d8dc0d GIT binary patch literal 7927 zcmb_h33MCRb-kYeIRG@GBv2bAFOV$BB)Gt~7Tcs3iKIkHCMlJq-zJBTR0QQKX1R4<05Y^F$CV`dr8*@g+HuBS%LlgI#Sx=zp z4l8eYcL_w=+KaxnY3JQ!kErw)tytydzM+um{}+9 z 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 literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/gui/GuiHolder.class b/target/classes/com/dirtbagmc/dirtbounties/gui/GuiHolder.class new file mode 100644 index 0000000000000000000000000000000000000000..0e957066a408681952cac9a3da769994ececb7ce GIT binary patch literal 1176 zcma)5!EVz)5Pj<;4oTglN!tLWl#-T`7=gt(RV4%hEJbjj$=z`*v#n!S?z&R{i33PP zNF4Y8J_<3jNkbJyBp1)_=)IZucE-Q|{QL#r720JO$Q6;dP=G10apqllPUOX*^Koz% z_)5S$ma$YP0=ZVZU&0-fiYQxHgC)@NlaVu&sTz3U$e)~&ELJkObizzJ?=ty5iH1Qc zkXP3i0WrGEDW`<&aHooeDmI9F;f2H%E+^|2HnAm8R$dwgs+Y+j>F#RbnUayy>$z_< zy1pE`YbO6>=hnxqOT)h(F4t_2{c>nW%dOQ zTFWr3-KJ72PEv1+FINI4duY``Y z1r!aQF-4Pb1w5edF5M!^=P7R4KOpR9V-)S(G1l$pW7NKB)hyhdxSw)GeR#2ne zs$-9`_Jof|*ryv+(*1=#u>wP%SVN9_K1Ore*`dg&g8d!aU+G|gTd_QD$fd0bU|L#` zfdk60>=QzBVUs0q{}al8DX^mra)rhOH4|s*N8rY5IGonHEu=UvWN)F6DxNGLuYaMA Yw_P+Bk~eX*fLxpJe6*1KRJUmT1(d_**8l(j literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/gui/GuiManager$1.class b/target/classes/com/dirtbagmc/dirtbounties/gui/GuiManager$1.class new file mode 100644 index 0000000000000000000000000000000000000000..53ba859b0180547483593125b24635c4760da3c9 GIT binary patch literal 1584 zcmbVMZEw?76n@;Kb(6VkC><2WHdsSn=pdBQvO)(9Y1(>??W#_T5Fq3_R%&qV%CSMf zcO>{eY5M`w#z#P_stQ$n=I8JO(4On06f~hAEZgTk&$;)Ud(OG%zInH^3!sRlEIN>q zkyg-&E(sUxV53lT!)VQ_Z`jRI5c`qqGz#_DEi5^H&GqY71@}PpH{&R8MpoUC&~?x9 zVy7WtdSvuQgv_7iNl1;1sxl5q zIC{srXB9lluNU;djP3O~*YRrQFbqPmkpTrql8qEKV@0nD>RScJ5^A|zG!0!)-z)e* zP;$x8=hS5_ixc=!#-J!`UcylOJXRlUvWjD$Q9LGHOipSwq+nPSYW(w4-&&sG4o4N7 z!kC1#R#bHfBkfYW=UGJ8Ck&iXP!I-ERbxruaRuiD?wT!Ei)uws=S615#As5%1)K93fcX-8EbbOJCe{n zZF{coMwgTFt}?JvP-9kmE3WV8@y41HR;@LUOkX9iEpOEdU4dI<4_nE;%hTJ!Su==3 z+nIC4ie3iaI_qbIXEJ5q_JRi6QFEg8peExt38U>R-Lk^%>P{nC2pZhEqWZoQmOQJ` za2hgLjPW*x{@+0oI+ME~cbrXpnb%84Za|z4fRgP@hCVUFYlpo`5F^DAu(J(m_f_^K z~wZ9PNKA3$;;O6w_j4OkF9L6^oz#xVwhj|1iC?^@`ETxX?xJh{n z>$pql;XWQxKEh)>p?r$p@dxFf_zV9~KEuD*rc9y4+=$Bqc#AS#(;9Qiugq@>hpx$- urG5Nc{TS|Rb)iUpNMkNBG>-+!G*tc<6GlaavX4$Wpuw0q!gBG+b^iyt5{mNx literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/gui/GuiManager.class b/target/classes/com/dirtbagmc/dirtbounties/gui/GuiManager.class new file mode 100644 index 0000000000000000000000000000000000000000..1c2965b9c1e333577e7b7add2efa6fe46ab7bc96 GIT binary patch literal 26697 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 literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/gui/PendingBountyInput$Stage.class b/target/classes/com/dirtbagmc/dirtbounties/gui/PendingBountyInput$Stage.class new file mode 100644 index 0000000000000000000000000000000000000000..12f33d45b36e507639a7a9bc3de8af708fa8408c GIT binary patch literal 1482 zcmbVLTTc@~6#k~SZP!I=xe0-wOd&+I^(&pQiGpjmUO%H&eU>UPtY`W}i*Y!=f`EWa zX7hWw0ykY!pa|1$Dyimk+;myNAU8={`P_DTH_L5T6ofEDw%KAbvzJmCHV*nOMaBq5 zB@8JT!&L@vrEZxz!*Do~Ins``Xk9aF(R}$xuQ)Lo6SyW}Qo$6iQ*D17Qf8|`1s!Yk zmY#pZuoCWx-l}WOW~|4cIyMbC)|-I?2>fXUH!(v~jvJ=oY%+}YLUkk4k)j`7nPxHV zN5Buok3p_Q6wG6RPexSIWQd0&{m+J~_>JS)vZ^$e!Hi+*xmKgB+Xby$CrkD(W*fUkE7evw8uF#NCZ@)W@=8$rcie{ zQlvK*uhhF^ebIx(hac~|=FJ#`tXeI*qHh~~Bx4s@w8)Vck()nF1P~1JzW=Qf+ zZX@^!!{0ITfi#9i`uoJ$KKV}}3YYL-Cdnh@5Su^4_;)bw?>gsypDaqp3rl!F+Cy4i z3VY98>5;=Nz?Mk5=}4Mim^{UekF@<4l7wa6g+L0ps2$qVS`i_uL7BgPO37PVfrtLU zl=Ky>QmOO1pYRTKC>ni!$7*KiD=ezV{4Xa&pwaStxTpp$wcMe1afvrjuZlMh+Zc059pPUSSTeshNFB(W(9s*6G}Z4a9_uW0NE& LHIW!k663!BgN;_- literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/gui/PendingBountyInput.class b/target/classes/com/dirtbagmc/dirtbounties/gui/PendingBountyInput.class new file mode 100644 index 0000000000000000000000000000000000000000..9d2c2e94f41bcc64c7739f47d8e61202d31febdc GIT binary patch literal 2407 zcmbW3ZBrXn6vzKJKp^QNGyxIN0@9kN+HFO}8l(?IEl6o+5OjFAggbFN$%fh8bmTkf zs554q@r57259Rnjcef!UF>@2J+bO*C+K<#h5f;mhoMiox|pA}a2*AO{CV5C(Ed@+ zJtJKt+Y8;ceRQVZJWtw-qu6Mzs zfF~z%QngT%lPTA0N^I!K({j?^E4#K=IaV0o>7LQ-%)aaCgI?!U``GKyF1%U(cckPMt{vzge5xf8!6Y6lY+eyx z!P@ma?eDamAka+Ogehp;V_jv~=Q$Lln>foz4V#fgYerV78QIBZWOtm*RyN_aZt&fF2jP4zgL6+ZHjFTx(ni<-f zLy_G!2b(8Vo`}?4Qp?|xA?9PlnJ|bSOVXRXQ}OJRn8D^KQWMobG4neo8OU{6v$D$dMd~&K1&0+kpw9bq&xydia%tWNr z$66A^Cdl0pAX0?_GMyTcTrwgXsUXwo5y>S%HVN{@2oPxl0Vz=;O7n1PZvB#oNIey# zg!{4bphlMaxRnHXK#+$cK%@@^q?QU&NrLR8g4EJMDoK!U3G!qFh&%)WGLJ9$mBJs6 zNHLk3?@~eLQIC}e5nK#(6tfXE}nA0j>g(jE_(Lh$iY{C8@Hv%Hz*(RjG{KP)kE(EtDd literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/hook/DirtBountiesExpansion.class b/target/classes/com/dirtbagmc/dirtbounties/hook/DirtBountiesExpansion.class new file mode 100644 index 0000000000000000000000000000000000000000..12ca90f5529bfc51b6ca8fb49f77861180f68f28 GIT binary patch literal 5776 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 K;_vt;8vhH_??t)* literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/model/Bounty.class b/target/classes/com/dirtbagmc/dirtbounties/model/Bounty.class new file mode 100644 index 0000000000000000000000000000000000000000..9cf2cc58f80c6316f5e6734f7fc9652371c1fb19 GIT binary patch literal 3426 zcmb_fTT@e46#jM+I3XM`qC`Nv03yjjOuaz0D3yz1BH)FapshV52Rxb!O-@woMLW}< z;8UIHLtlJpr*@`MY-c)sYG?Wv`cFEY(zW(Egp(K@$9Bl%?6db?Yk%uo-&#BT@y}1c z02sy>0eIk5;8Rh9T7lp#{f-_@>6w-2#id(D!V;)GWoArkNWdG8%+)&djU~2GERs*qkX zaYM=^tA~yZ`Gq0hZOXwIE<)3XJ#{lcFvBg=*NISLn50Y-j;}pY$jix z5y#~ToK*0hib0$bXx@Ud<&*6S8OpD@Z0buX!v@*u1|WUCgqPzSQt>{{kec))DWcgi zaw%>wvKYV!Miq>4BF+jNdreN=DjRjhCgnO7z&Oq;h^dI+OyMFaBX8a(@zHdQr!T9xf@yjuwQ>Y8Cr)<^9Ko!LIn0xe zNH6(pMquY=2FvxkfJFt@R9wdg$an~+8>2pNbDAm5ewh1$1*!4EUI^p`wCiK*to|C$9 zCVkdTWPj}a|0aWNRF>s+bRztwiE-Oe-w{yV-A17QtsIfy^M*C- z^kVMPi5q z5M2$^*+MR1oHJRr_4X}xm{o%#)Z0CDdHU&oqDzoQO?-jT)5ABGeA4|P=jpM4@lFxPejryqEYY`y0DU*pTL^X7g+{tD zcc7JeY#TbUo9>c!x_|cI9QGnXJFn9&f`KFqdh&?F6>`J@GYR7|WUm}?&^|fhuxsUr z1NX}jhtB7Z^F>6~%QW8f){h%BUZI%_@N8g+e`5F*+?1aMug~XGsPEFx6u%!)`fL!2 zs#|EEvqixzhu;)!u1A`_iCskL$syEeKSFs*1CQjUR!(-vxNTH5PTbVSsv0*`8h2MU zZo+((lY^DUkyjXJ9OEIv$$ZzcAcCAz8#n)a1Nullhoy&*y{nY7TA3dXnQqb&5D$%P z3fjIexw0uZQWeO4O3WsITgH38t1#}bYJ8xoac8CR$*RUZRgJqVjZeSAIP1V3f|vf7 zzkO6ImHZ78eEItmq_-PU7mv_&q0#X9_jfm?m3?42^G`ZLh!rm9(|4A zJ{~=K48`{ZKJT}*=5-a?jLUON$#VwD=KVfCaSr*0*y8>&zGsFy;rI3zxa7gF7-=d>zh&kDf%H~jct9RJ Gc=#{hhPtHy literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/model/BountyContribution.class b/target/classes/com/dirtbagmc/dirtbounties/model/BountyContribution.class new file mode 100644 index 0000000000000000000000000000000000000000..b00fb993f5822f28319a99dd585d5aa4b86794a3 GIT binary patch literal 1409 zcmbVKO>fgc5Ph4Z`EcDfP182%XDBHo?uXkHB%~sw(g=wXl|yL`5f{fX!BuP{*Foxk z0s#^h5(j<&Cw>uPW>Z4t`ov{tc6Q&}_jcyz@9#eV9N|Dg0?7VxwsomHUe6ln zPkDQF?74w&cZY%PxdI8hM=!PHQ&bx0=jYFxhfF=KVupE@cLT$;{PUsR$z8iz9evmPwuLyVv|lxx*b@(Bc?qOcu|X*h~MAT*lga`-8reZ1yaZKV*)cR z+qF)IPS^5Z8Qp<&>Y2u%ZTL3tuZyYRtxWXnSt92u!8GEygGq<^QF zgP{4mN9%ps{X$Z*G_-FJ+E$2+wjE+h+X<1^N~sV`2IrJ+aRQ zPbq2RjD4kK#?I0*V{ciRvAYo;`%{Q#65H4Abc)P7p?yW>GbJOzX<1y6Vnz>7T#KQR zi3LwZ!IfBW0Xs1-=cC|iEO-ICV{l?DyZ9+0yA}&xiUpUV;O$uOYOL(#sO-I1a0UF! hJWDr!d+kF+_Jde(6?KJHy$ib?G`yDm zgs+#+{ReZEg%RW7QQQhaihfR!NR3IrY-+2PLUX9{&B16B^ zR8=_>+gov`W2)2`oO@Pvs1gRh>Cowmo%MH3~iHXN5o^oyrW%y4n1DVjg!% zw9{td0;0-aESgwSv^s%Qf3BEV#XUm1q31}iamb}d0w2n8Rks!VkBaK;Z;vWxG`FWT zb%BwZ-|>#S?YbAfcIquP$-s45XHMwz{pv9(-})BGLqz$V`J5`aks3NUR zMcNj{ECl(yOlO5|zc8Rsd)7AyYb`>?T8}VfZA2KeO34U$tCWf`X_e9urmSKn!mL#? zA}m;iY=q@6^hW{~otwlD7(~9YfY())uvU(dNV3x#fHjQGbem%uDMedNYz9RFP zk{Zd&5PjPmc7j9OG#uq9M=2yO5bi7GYM}>&Lr_&v__9e>t-6nCC_XY2>)kw_hrZ)2)s%11!jSUK z`ho}2GqHyOdD_^g(%;WeE-&^Xdo7iPBB?)M;UEq%n4#1$7^chh?JBBOdifRmh=n6K zO6ZebKBNVs^{;cmb%vjeI`o=+NYL^G^a78%kyxBj~0iN~FG zL-_N&;ZnB6x@>ZHf%}g7j*aPX*`e?kwqGXdtw$nUmaV7U=WP)RpLjA+Gca5#S9Zdf zZ3)j&t@NUt5EnO0uVqIuN|ahpGe*NO$FL_EH&Ik3|Loy-D}qtY^Ur0+Z;EM0)w%T7 zh@4f?=%_d5Z(vf&FbI9VqU#r!xWOr?kl5pW@pGXHUctyNeXnl_wuc%YR6gbwOkRLs7v{_vr_A=QNj`f=RdB9 zdm?E1&Ppi#B!o&31W7}DhOc@_lF|Q|z27yq<~&dM5v2$W%rJ~~HE*K90G;T_=B{)u zS}@MjwbMtVnL<(Bh4fZb7g@B@B}Hov)Zo}pUSst2n$|R}C3_9bKDY|QKD~;9eP$Jd zU&!8vlk}e@jN&{+|F-r=85Q~>WE4m3m!e22dP!Y~?LEs#-#AIE8;Q^Ch5 z5%nv4gZVODEEP*@*!!6bED<$BkTWg02-q%{74SmDkPwMU3Bd=1_wDvr4{lqs-IDO&Khi`K zHzq##1N~$M&3d z(|%LGlx`r9eyQ3jI1m`BR4=l~AZs9JVi=~tuG?wZ4dn-Qr`dA*UZ>X%le7Gf)IGh>j_Y_Aj<5LpHkb_FtFFM)lYcnoJ{_r2jdBqv z41#xu(-NxbmzI<}>-2nAzE(U~>Ap_3SRdG?H77`_S>#!Ag&}LM&OuY>1VwgiXMrcT3lqK$0fRl0ZjMpm&PIS0qW5u!?D{)7Lg;@Eo(` z1Mg@W>WvL-l4XxM3hgmVX1>Q_rzzQErb+EF*Oc~{ZCZQG*U%m_&S;N0XQ|>a&DUpa zljRO6>l=(O)?VOt3N{Ob}SMS7{u$k3m%Drmt(<`v7INP;I&wAF}8Cd3btaw z<=D=pD0nLtJQv$}CJNq(1uw>e=cC|fvEXGqj@kCor^u;Z#DdpIUxQO6^hx>7E1x6a LgIMs6&f5J8<;n2I literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/service/AntiAbuseService.class b/target/classes/com/dirtbagmc/dirtbounties/service/AntiAbuseService.class new file mode 100644 index 0000000000000000000000000000000000000000..da922b7fa43eae09fd40e4c14f716fde3580d4b2 GIT binary patch literal 4954 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 WaPBw!|7-k_y(n96;LrFgmi!OTl5D{M literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/service/BountyService$Fee.class b/target/classes/com/dirtbagmc/dirtbounties/service/BountyService$Fee.class new file mode 100644 index 0000000000000000000000000000000000000000..1012ec6a4cc242b20129d5b151e6779a534eb680 GIT binary patch literal 1850 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 AKL7v# literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/service/CombatTracker$CombatRecord.class b/target/classes/com/dirtbagmc/dirtbounties/service/CombatTracker$CombatRecord.class new file mode 100644 index 0000000000000000000000000000000000000000..10a1cb81441e64d6bdec1a3cd51404e7926df795 GIT binary patch literal 1141 zcmbtT+iuf95IviVodmZDp#*4waw%<+2i$ics7i!L6t&QnNIZbFaki;TY)iJ&uL2*{qxuN9{~1HFCc}qiHwCT41v;_JeT%J`a}Dm zf2N#Rz$z~Shxa9;F%M6_P`C}z8reabQg^M*j14osqox&l-&%x zzKoAU>3mS(*3>&xP7n?R%o8_^;yt=Wbh!1Zg(CMgMv}uEx@w_}YZMwtPYxAjwm5v< zLIpPjGN*1FQLs(hYNysC(+mcbR_waI+8=v;6&}m}h$7`~;K)%=hA#U_FcY7;RR6sD zUre58bvk-&F_IP|A;C1PHamr*U>rK?g{z_bCCXzKT_CrwqWEPH#U^$HcK;QSfYtVW z6*ft`ND(J%R%`5%XepX`hUBE#(JsYWYfA!aONx@^yew`);1+oYNYe)t>)#;in-iG4 ztbEZ0>$H}sC8uTZ3VFQ7Dh{zhk&3>qh}*bB9yQR{(x}MZO`6-3Bj_w!{|fUHZBpQt zh7P{bZLDcR(uU@`uy|#@`0bM7KeQSNEn|)i{|~@B65#y|3*&-?p+#dBWo**Q63wzG zeV$_>7Xm5cUQ&DsWM_iXrL0pRih!If0m4&bAQjxd0K)!M&b5zoK-`5uDtM3-H;6fN nH|iS`tjz=Q2#CJ~2tONo?Rcnf+`=Pmw^1cqppzW7QN#LgYznCn literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/service/CombatTracker.class b/target/classes/com/dirtbagmc/dirtbounties/service/CombatTracker.class new file mode 100644 index 0000000000000000000000000000000000000000..8265f07d4df55acca1b5b91af981f530a0cd8dba GIT binary patch literal 5250 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 literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/service/MessageService.class b/target/classes/com/dirtbagmc/dirtbounties/service/MessageService.class new file mode 100644 index 0000000000000000000000000000000000000000..918af76337341900713644ce0d97b860fc7e4cea GIT binary patch literal 7927 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 literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/service/PlayerCacheService$CacheEntry.class b/target/classes/com/dirtbagmc/dirtbounties/service/PlayerCacheService$CacheEntry.class new file mode 100644 index 0000000000000000000000000000000000000000..dfede2467c2609d14853684157fb1da020980766 GIT binary patch literal 1701 zcmbtUT~E_c7=F&ytz!%aV+;fpb)Z}MD1zt(WKoGmaH7c!H-2`d8HKG~+73eW|9EAh ziHnIB-kNyr?-Ac~+JS8429xD{ynR2O_j#YQUw^*;0B|37l87KGLrNirIK$wswy&vm z&8n%-s=J2nF~nC*%k)YN(L!;n4+-?iNT$$_6vI8;Zm8R)<5jg+CTcybOFLJ1Yj zWDCV!3K>R1if|%_I_@1j)Z%ovEwq}@E{$C(h`Py&-Ewr}k;$hp+N;lb-Zg1v({R1V zw(H4QVpu-cKp0Z%mPPU>{kaAWHhH3bERaAW^b93h={eCU!gWr2hKm~v8jT%eNuDALM1RcYrCV#-&@pXm@0y~Kr47r1?l zu>dy@R)XLXd2`fpK@n%dkLG`UK#ls{5gBs6SCE1HgU(5Lp+M5zK=wj`421(pcLT8r zNb?LJoOsSa7Bd90n~_X65H|#fFQr*ZGKRT;8I_WCXw7{(CdQ}rAS4M7o)3acG-NJ; pxH1_C>|a{nQ#T*Z=!Q#%o+M3`k75D0#J)(<1CsP5fkiB1{4XRTK$8Ff literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/service/PlayerCacheService.class b/target/classes/com/dirtbagmc/dirtbounties/service/PlayerCacheService.class new file mode 100644 index 0000000000000000000000000000000000000000..ce6766d5f207de3482689fc785b1072e1fbcaa9b GIT binary patch literal 6357 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 AJOBUy literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/storage/StorageManager$PlayerCacheData.class b/target/classes/com/dirtbagmc/dirtbounties/storage/StorageManager$PlayerCacheData.class new file mode 100644 index 0000000000000000000000000000000000000000..e7a469d1018c6c425ddd53a6a737c1defd243508 GIT binary patch literal 2578 zcmb_dZBrXn6n<_psNLqam&%fh5D= zBmS5hO>WhVd!j1s8bfl)v`lxIp(mT$yMz>aHT3DY1f8MZQ*XOw)2IlSAtt|JNN029 zj`M=Dx~yXWgXFF%TSwcZb~78%D#~V4R9#bAg%^!`k-?CLD>{ZT!mv`6hepk`-F;p^ ztol3Iwp>#i7C#gW89xE9+KOwNR=uEt zy`p1GMM^fg) zW-v=HTWoDwXR)AxRUZdr(+%qgpc^P&6@

BaW`BbtRm?hA(Aidna~+qS8M=}yF#A{IgSmJ1BGgRzQY@=wi9SA~t-9!-Uh z)Iiy^#8&%oU)Z~Rzv<11tn%g_w@vjN9LC*-NwpU*pozrI&A%OTqh-jULE zT${IiOq~?&GF(-JQSkYo5ORR`tZw-LaUQKvTH~}P2a@=-5BE^fu&d)U&}?0M&a1W} zt3zSRFD%r2(Hn?h}$mFJ6Pwmsd~w>h-8&Zq3H1i($zq>dG6^1e4po(lBvk@h{sh;7pJ^NOYDlVMHtpr@N7sV9 z)ASAbpo{qp;Ky^B&to|-U!q{D=v0301o@ZQ`@p15VN2mdd_*3IVNUBXyk{Vh{{`u@ zsz?NG7!5-RLezNle8v;hl9-2J)Oh?n#(o=CDB&sGp?}o|gPf^0sPrd)Is?Tp{RE1h z)+#>sJS%mfI6OBI*8 zw$-|{wY9clH$|&~KbA|J{4vn|Tug2L1If{@%QImvhfO z%XiN`_vV*7w%rS0rgdo$7V-kfcTj*rLFuaS>Tpdg9B->xw0u>hB`qkN9*sxSGX!~+ zRn0}PQ4~PXK`|V`AuWlHn$~DCy*%93(PG{bUGa1@lB!9i6UlH}q^8OIE)2){l_b5+ zSXWy#E*R60NVe51?`m(4rfW=LjUIkJd04C))dn#Lg99jaun$6lQJ>mFP|_Aj&km=< za}%*vHC}0DRf8rQP1MYd#v-IS1p5Xs)WI+e7dZY(ve(hh38nOOPqs0F4VJr8s_yQH z1u2029UOoI1;I!>)s>7WqJq(tW)v;ErgSnIZ>#Myuo7N{gHagmBWijN%4N~USV8g1 zsFGo~8dh~3@8BTS)hky$ZBz*crMgm`(UxeUE9JGF=wOm+>#H)&t0|DqShzcqoZJ#_ zSsC#<9PHo_)nTxytW$NWM*xQk_R%8hN=IWg^TMf>3&Wj3OhH}{hvSFw;<9@%L`SW z>{^Z%c~c}!+VfB!zm>VYTM*s2~HH`$5|j4;!*WMLu~*{a}dK=y%NI=8z<9H)8Qm1HMy%R z+Nw%Vb#NL^7ZkD3v=toLkKlLfNPgc6;!K1CSni+&tu*xPgibuMem0$RMKaOhjWV{M zQT!rSU1&Ee1Xi?FQC{gFidCeN498m&9foAc6M`14?xWhq9CUy-WTz9}c)8*-r4(1E zpe)Ph%w#g$-9R4?B7r;`)O-mIoY1kH10>=p8(kD49kPYq>Ird;65?9H_}xz_S4=k6 zIXGL%D5z*GN;Y=IVgYOr9J&Ye?ntyoVl}fA@^#bNGfdeyH_KBz+(867pO(WWsf2=~ z3y7fM4%JO*e4~SlaIw+&>_pdc1(f^t$M|d;mlASUL`dQ?l1N6vsYKky6#^pPRiqG( zC*s{CoKkeZ!i4Z^*{+2I(~;K6Va?Wc4z9-y#^o9k@wVOc zogc({d`+#4Ul%NJ+0N?Tm}%c{zwl?z-qn;(o?q2mjGOSy0KVnmW_&xxc9l6$4&hd+ zD`!4tHLk5XIe_m74jQ25Y}}f4L!+(RIPDYT?eWAKiue1`+^(j7hoJtmOn?6T`92}3xz(!4--){d__2e#aZe7Eab4luJG&@>99VGY?vte_ zja`cZ*diFAolI-u1Qm^RMCPn*iF7KbQUOJ;gKgMutg<1|rlXSmb9_6-QZC>%rna^S zKgGQP+~?qaJiuin>%*Cbk=2nHCA)Oy361rQ#|g@P6VGW>8)D$~po52$6A$YMw@2tZ zE#dUiXnLjcqI5W(P6hD@9u45<4t{}O3WoPHpv!k2iD#EDZH&3Y`9$R6kgsIP%4A|q zm~KhEsD(1G7>}uS<~KzA?3i_lSj;Sx$|9a{@T7)DgYG91Q-Wy5Zyh|1-%$js!?CW& zB0^~nLiM9Dmz@v<4~7_qKB%Za>tF>|DPEp;@JIZK29Z)L+(G@IhHoMs`Q{Mv6t#Rq z?L`MKDQFHFUK@#5A=-bv$Z>TxEnX{&4QERrlU}&>4#nfe? zA@;U|ckmCIlnXR7l^0i55kdZicLVshBK)4`>6(JUowCaLezK~NL<5HChYtRuiMlR2 zGaVurYUV$7@QJQ+y>+H`)}4-8L{tFbU);3cDaff;c=l`vMZ$3ZA)FpUP*Gbi0Y_}D zQnS`5m4+DL>$+k`94$gqr$ng8yb^+A8Zb7$6t9L*Vm`|pT!wgvZl=34%DxK2gECyo z12TeQko~-PQi~yj-a|7%kH4J3`g*+0pj!@fWTaFuAW4N+a}}9Yxz}-_F_%nS*=!5r z-9R$Zl1R3u0y0)Gb%3xs&jjWt=&rVmCs*n2&WJ6Q3S1i(G%i}&Xv+lJUAToonO?hX znUwj~NG#PVx=N%YK^Y?qzYF9L!G_(ytd>cR|L$qsF*Ds$i+&a+u1g^dR?br@Qyn=> z4(DVjkj`+D@*bXJQgymm1)K~lZ;~S&IZC(#$WJAbH2iY^Bz#SYF)~IO;67@U5DkPg zKgN;qh^vYx)Z@hv%1p&lRJXd(R8;hCvfR(j}rPk1un@J7!5X4LamW>I2udYauOrJ6qkV{J2OU+ zlN~ulPGu~^7^OZ=u?+18!s@H)gR)3YSL#3G|4;osSU8oQ9PJFqg&u#}ztn!1TQFNLqB-iQij7o^DwjBNseIAoBaDzu zaQA6@5e?Ttxj-&gM!^8Mk2`s5F_&yxRAj6uA#v2LIi9m$^?42B;1JVHEA#(lCM>J4dxImh0tfj(lBhA(aDg zV;f@9c^kXsx%Pg%%F^LeWyo0M4it5^{Nni4{%d4o5btwb54Hnv`5HMD9{@`7x7Du3WrjU{{%_NN9-M<4BL% zC>KW766`s`5ZUTTuPQ1rYkXs5jY^P&Fq*^S5&4NDKb3oFGb^I;-EDK419HD7Fmh18 zC=-`5#FU>o@}P<@3!CON&pBaE2xAoG!{rgR^gT+m>%+U@^cRl&Qb|eW50vMHFjjSI zmd70Vjp8>DP0ix^(au;zo>0L4q@bX3{`{(X9i#cu6uU{nRT1a%>$o9gdZ>YuYwYS+ z9!V}yaU)}mhD1v^)*Mbo)wf%ipJwigxZAMTZdl7jQsoyAKQ)UuUyYZtjN!N_}piclDg7pC*l0%`>~Ha50l6rn5}l_1vxb ziD(yQ2<<15%H)~~O(#Lsp2XwdfRjfnvR1IakB7b+8@36gymVGxyJERe$u2_P2Ky#x zWR~B}Y#j5qyO&k}IXjM$pG+jwYL=^ahCi~#sX-bD%+DXP`v#2TB77wSHzZEp}wW`lIl%qn_tj(7C&0ujjy;5-4Kv~a` z31#1@yASnABD*=G#AfzeF<=d7LCtniMlWw!>U%>(0YjvaC+2$Fx1Pec_Qu?8$bh7v}>!e?TN+pApN_-J0UA>FD za4yvPlt$3%o5S8>;lKi3t-lpH`uiUaG*~G&XTl-BR5>!^TS|es$uRRy+!TSh!<( zYj|ufIE-adqN8$8;x~axKbHNOI;o&EKNeZR2%{hwZClCTAwI@jI2(JgATa>0^dj7y zi-DicR)4(|U%{qCSF*)D?=5R$_Jb$d?zwigiVnHzHAYhDg%K4WGg>WWb}OAqC&Qf! zBk7fi)|73H6O3$Xk9Kx4y<8EEGg%sKtuO&$g*ML$v8{tRmM&w=Tg~q(%0#N8t+q9h z+YhPsovYc_WPg#~uUa*XOr_c!!`K!z$b3ll1jM!u)g`{fzjc@{$)^5o>j(zh?B$Ee zG%H8x(YV$2&ZKOMrbM3HBE!JZ)SgtU(;Vv<&5L`UW6h+9$eDMRV_S8Cie2Qf(&X1X zfs|Tv`XqpNe8#^@C1x^By1mNO*%r60V|HOTl}R3X9FvKqiiwjPYlO95skNVNouZ}W&nH=@`HRe0 z%mEciwU<+}Xi-6__OeR0)glkt zdsk1{1hJr?;ycfVdI&R)wYogK8+TN?t_IgWL%#XQmI zU8dBa!Ib=lS9lXD?(l#8`hP#Y9HI4{drqa1s9!WW)w-`g`_@+^^qKm2Jd&)V(WKNq zeemMkY#Q-E=Y;~+Mw;+0(&|sI0qbJHfqQOBqc%ps%PTgIkif$ob#meG%>pml=wIHN z(Z9SAqknnPg};NLKO;)nrE>L3&_Lrx@dy-yTms4nh?waVW3;C{7$RhRcb_v+^AI=A81bCFg?y z`TBNDS+)gJ8_37VtvGDqq)=@Sj&7X%C<-QSo}Aaqi(u%*ae`_2<@voJK;9rL)f-db z_oI1Q!Q}FSUYulMT48zNqbS_8^ZD|^&@xqcN*=c2jA;RWN4BD^2kn_Mdpi;w_N-|| ztW28z!SdiExV1d!AG5f;*o=uQ)SEk|QywgLOsA6a50Ye1(1Y%2gUXAx zWBoFgMN~_}N%>qx1(`2X5b#)b-EwN;$gP_IqL8XX5(dENqQH@ z;bY8`0@O<>=1Vyi$Y@?Bnt<7IFdF4hES8z%Wj>Zj6Z5mDGKCo74WF}km1jLpBZsHR z`8ZXs;}xD8afY_N>uF#2^IFUE@<*QkJ%eRp%b#d<_uy>#v;2j&*}`$2WLW@fq(ENa zn@w&%<~iky^rg%2PkD)bgB+z;UY5V|c|L~9EAlFPERY6yjkSfCDb4aa=@lqzb}ikO zYvgb82IW^Om&=>-7Cmy9Y?QyVN1nV*87dz&W4*(%O#6S(H~-1HZWQgn0Rh|@z~KSR z`WPhvG=74E0%+#vPD6YxC5I5xJBD=<4!D8*L!LAkD1C$O#tiJ1ePvt-br5e*p63=@?ZjEg z_mc#r;W@!ay{}^Lbu&0dK)whz4xr zea?$`Rr6AufiKe1Xhpaj7vl0ak+UYBhyb4~Se9(4n_GT#HI zf^D+EG->df7yy<=4~fQW7#f!YLo-xZhi_20HxY)uNf`PTVd!R@h;Jj!OH>!)JA8f@ zKgO-RNVbUpavT1J@8K;P!+ZFFR`paarc-!X&V>^mVD4o6OE|&%9-Q!Pw1E@6-4{+& zou$HEwU}P*gOi!BL-2sonof9*tQYK6Ddu3T32u*IZDyQuhV9umb=?HV@ zECNh87hoQzi+jPQ0hrq`;B z+cE(2k!yc8&#OIvc_RZbFYN*_Y-QyS5MVa+0T`=*CQ`Wf0JF#d%<}#LX1NanT!3lu z5Tn)jd&Gwr5nuC_raJ1ufs02mU-MYDxrav`9`(5Cp76fUGT+Iq(&bTf0cKq;z)a`| zV17fhc$@(91cBm70?bncnBNj$o+iNjjsWv}0?ad1`XBiAECJ>@0?hLSm_P0oVEBhd zdj&A|zyOAS*pL&N^Z^)My+?rAI|5mNDWdfRz5TX4$}E5an0tNu?HU(g&i>T+<}J!B$}Cb08P!t3j~Q52@)?6Bwi*+yh4z8l_2pNLE?3S#G5#UHV~!Dcj0Zu zDDQA-_y^;Qx42OKn-u>=t9h5HhW7{~?`tbLfmBaHomH&&h`0Elp(6tgYckMqZw4BA zEXVBqaC-(CZrueM*vcyD0}cJ}_&z{GUuYOaioUxn2e+1}0Oy9U3>FCT-$v z)3DYHK(5!B6uD7|jNJ2lLxmvJuiV6hifouxsCH;_Go@bGBj5I%^*iSKyWaP0P>4%? z_Q?0P$Pc~ZADL#idk(x&M+h}J4qVN2kqWeJ{FkYskC`g?gbuue4!jc)u88ZoS-VOs ze3{#^uSvdE#SF$)N5R`*%)xwXA4^?uu0)$vW)0D<>utU%2Uh_$uJOeNL%HQP?e{h9 zeF_)^>k|wy`-+eF_a|nJRz3LFbX1Z`!#&Dg3L@rh$B<_Ci`=r#+!kE(P8hA)^O6ZbPQC2 zypK^Gz>rx1xo-#yd0W|?)Rpd}MolF94>KX;>R2N^3;X8s+1Vo3VN1Z5Rd4KL>JoJBdRw?n7weBM46H zkze1yu{KX6)@){}5t+_^$^p#H9LSTN3O%?|8+&Dj3|Zw$C|VTxpgWfa)zODL%CVGy zYVUA_bzExJZI#D=$ZwNhWxkZ}zwps@to^yuRR1~F0en-`c$(7>wMJ5F?1$R*eW+<- zni{o_e5-;5mNm*6&0iHfY_P`g?RAW^(scW=`Z-=dEA?}NeooTQYW+M|Kd0#DRQ)_$ zKabSUY5F-`KWFIYvHCg7T8BJqwl&wP*WdH?vq3)_^>eX)o}iyg+}6$3GW~tBex9nI zr|ajL`ng;`TlI5=ey-HdRr(pz&$xbe>SxkApR*l7{c>+l+4q##|<3KHlZ3x6BX)=gG8Y5=p(a43k z?wX`)x-UuB)Lm$srcIm{gVQGNmbOWk|Ip9<0ZpDX^u049kSd(`gUuu9&Yg44J(}1C_A-Dz{Ie2eC|3|r5k!SR?K$m&7ENnrGJ4i+ZQ_{a6E46PIPA8 zvW=)VbwM|soUKP4ea?wyb=%O=#-q9&P3uW5F&~Y)KQU`IW0|BJStaNWVS@lFsZgzo zji?h)9a}TAY0c3G^f`g%miU@ABaUsD$?n$DttxCny@JguwqUEkv0I^>K-HAKYK%b7 zx~kmpq*3}})#Ppk+XS}!zbQv^MtVxOE3pHO3U;b^4|WyB&$F0*PwQsVNxAVGtV9Fe zCu6-^AkZ?{Iwtw|sA$IRq)enVyN@B4Z(@?PRYe;je4}MDx;Z7Vqa{}ALSL0`$!3!8N~R#U*NViNi?vjUm8(`LBv%INdXl}-8rGp z*D)*aQ*lzldCROeqsJ^Wp*iD*lj17tXr_~uY^PO>NVX8!#VdX`XqmH2%li$H1BSi z=<7JHbxhycarZ+PJ1@1$us^Qi34D@~W#7mcX`-;B^oV?sE(lW|I1q{z_mH=zno;`0i=pyG@8lE5t$E)Y%-16qQ)AaF|7(_60lS5$me zx+t8|=iCm=n(-vQuHqYbipX{yCoph}+}2Gf$E+y^vnFns`fzS`QnyF7Ng3X{xRub- zV;b8!pZ(2%lQOabk@!td4?H3)A>+;f0*BTG%?;hS)=Fu-VP_>|lfR%bq(yc{LypDuILRs9jJcd2Sy1 z1?)6E=(ao-m||kDiSWJZR`ir5=K=b)rkG3$xFA~u`$KUw<}>+-_$A-XxYhR5Q|hA9 zonDGRSbX43^Myx@q^Zg2PhjF*eRGpyOA8Y<6zntQOa_)TG+5Y`ZMqM=i_A5)7Fk-AP$f*$}St*o)#)O$B~m zi9g_v3jQRU`>O&?YpvQaFIY2r)I*Ec?~k)QST=jaQ-yBv2|eipnmLu$vrTc!n#pAf zd$!+t3Sw2nILffELq1VJ?+-48uJlF3v~+reQ{x#K*gW z*9G>JSh=3^3jQvz^PNdKA$gW^6b$js0)Dp1xr3{1@-o8T3hwb11A(Xcw4cAcARyRw z72*Xh%J5Bo%L@;9UkIQM-{MO3i12NEhfiUA7vJ**JV;6@VDl9yZGT3nu6hB1wk6ao zIzbPq5f-^}T%t`cT zBxI1p5#RFkn&m^0SD{9hai{c6u>E;1I|Y`}B{1Bvj3aWpmy3RZ>lo|`v^NBnaY8=q zxJLCMA5RJR0ju^OuVZu(V?zrouei<#xRKilh05Me__SavT4_WCU1&!V9XQ8(jKoJ9 zP4D9Kv-mkz;Be^0R<54IFKBudH2jiI@G!x6`}8mP^v~f6sia#@Bjmc}EmYGj^huS1 zz7_04(Dl+@_q$Kwd{30nZ97)ztde)UuFH1{M&BUVU!>eJ{EE2d9hm1xuXfO@GR)a4 zINP>}54cFWim4$&MSmKj5yIo#u!L8gc@>$iK*u7oUBQOn%bgYFol3n@UvU|Y4Z(Wl zKxe3dKqH#!LoTEgLh4@;`kbg*!Uq@e==j3Qvk`*q!{fzB+vn!wK4$BF)bf_M1qX?< zPP+OoUak(|6o1F*DxG(>6!B-`uD+Si9mZ86Oa>wA2O)$R zULuxgb}bSG_-jYtzo=C(P3Wy)4??cLAZG|KtyYaVohkqAs{8m7 z9(xgIm+-0D=4E`Yj1c^?#NpQjeXhuYB3uw{BQFSo5bdqzUmbI63u<^DSE==H_^u4U nh5YZ95IKIw&npC{tXky^^j`d>BFy1z6aLEI*YG#|0}cNJQe(4W literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/util/ItemBuilder.class b/target/classes/com/dirtbagmc/dirtbounties/util/ItemBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..782bb29e8942d3203173f986fc6a86906886da6d GIT binary patch literal 6389 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 ymF4+J69CK3qEh literal 0 HcmV?d00001 diff --git a/target/classes/com/dirtbagmc/dirtbounties/util/NumberUtil.class b/target/classes/com/dirtbagmc/dirtbounties/util/NumberUtil.class new file mode 100644 index 0000000000000000000000000000000000000000..9fb4e6ab9874da0bcfa5a99670a29a4eeb5eb6b5 GIT binary patch literal 922 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 literal 0 HcmV?d00001 diff --git a/target/classes/config.yml b/target/classes/config.yml new file mode 100644 index 0000000..8c8acb0 --- /dev/null +++ b/target/classes/config.yml @@ -0,0 +1,154 @@ +# DirtBounties main configuration +# Paper 1.21.x, Java 21 +# +# Color formatting supports normal ampersand colors, hex colors like &#D4AF37, +# and the DirtbagMC gradient style shown below. + +server: + brand-name: "DirtbagMC" + brand-gradient: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛA4A2A&lʙB6B35&lᴀ&#A8873F&lɢ&#D4AF37&lᴍ&#B9C63F&lᴄ" + debug: false + +storage: + # Active bounty data is saved after important mutations and on this autosave interval. + autosave-interval: "5m" + cleanup-expired-interval: "10m" + save-player-ip-cache: true + write-history-to-disk-immediately: true + +economy: + enabled: true + # DirtBounties uses Bukkit ServicesManager economy registration. + # This supports normal Vault and CMI's Vault injector. + fail-if-missing: false + provider-log-on-enable: true + minimum-balance-after-withdraw: 0.0 + currency-format: "${amount}" + fees: + # Placement fee can be charged as extra money or deducted from the bounty value. + placement-percent: 5.0 + placement-mode: "extra" # extra, deduct + add-percent: 5.0 + add-mode: "extra" # extra, deduct + claim-tax-percent: 0.0 + claim-sink-percent: 0.0 + broadcasts: + enabled: true + placed-threshold: 5000.0 + claimed-threshold: 5000.0 + milestone-thresholds: + - 10000.0 + - 25000.0 + - 50000.0 + +bounties: + min-amount: 100.0 + max-amount: 1000000.0 + stack-existing: true + allow-self-target: false + allow-offline-targets: true + # If false, targets must be online or have joined before. + # If true, Bukkit may create an OfflinePlayer profile for unknown names. + allow-never-joined-targets: false + allow-banned-targets: false + allow-anonymous: true + anonymous-requires-permission: true + allow-reasons: true + max-reason-length: 80 + default-reason: "No reason given." + placement-cooldown: "30s" + require-confirmation-gui: true + +expiration: + enabled: true + default-duration: "14d" + max-duration: "30d" + # If true, a bounty without contributions gets removed during cleanup. + remove-empty-bounties: true + banned-player-action: "keep" # keep, expire + deleted-player-action: "keep" # keep, expire + +refunds: + # Refund percentage is based on the active contribution value, not placement fees. + on-admin-remove-percent: 100.0 + on-expire-percent: 50.0 + on-invalid-percent: 100.0 + refund-fees: false + refund-offline-players: true + minimum-refund: 0.01 + +claim-rules: + require-permission: true + require-pvp-kill: true + block-environmental-deaths: true + prevent-self-claims: true + prevent-same-ip-claims: true + prevent-shared-known-ip-claims: true + allow-bypass-permission: true + require-target-online-at-death: true + blocked-killer-game-modes: + - CREATIVE + - SPECTATOR + worlds: + mode: "blacklist" # disabled, whitelist, blacklist + list: + - spawn + - events + combat: + enabled: true + window: "30s" + min-damage: 4.0 + min-hits: 1 + min-combat-duration: "0s" + +anti-abuse: + enabled: true + log-failed-claims: true + log-same-ip-attempts: true + killer-claim-cooldown: "2m" + target-claim-cooldown: "2m" + killer-target-pair-cooldown: "12h" + same-victim-cooldown: "10m" + pair-window: "7d" + max-pair-claims-in-window: 2 + suspicious-history-limit: 500 + run-console-commands-on-suspicious: false + suspicious-commands: + - "staffmsg Suspicious bounty claim: {killer} -> {target}: {reason}" + +history: + enabled: true + max-records: 2000 + max-records-per-player-command: 10 + prune-older-than: "90d" + +gui: + enabled: true + open-sound: "BLOCK_BARREL_OPEN" + click-sound: "UI_BUTTON_CLICK" + success-sound: "ENTITY_PLAYER_LEVELUP" + error-sound: "ENTITY_VILLAGER_NO" + items-per-page: 28 + refresh-after-action: true + close-on-confirm: true + chat-input-timeout: "60s" + +webhooks: + enabled: false + url: "" + username: "DirtBounties" + large-bounty-threshold: 25000.0 + large-claim-threshold: 25000.0 + notify-placements: true + notify-claims: true + notify-admin-actions: true + timeout-seconds: 8 + +logging: + console: + economy-status: true + placements: true + claims: true + admin-actions: true + suspicious: true + file-history: true diff --git a/target/classes/gui.yml b/target/classes/gui.yml new file mode 100644 index 0000000..35dcff8 --- /dev/null +++ b/target/classes/gui.yml @@ -0,0 +1,177 @@ +titles: + main: "A2416&lDirtBounties &8| &6Active" + top: "A2416&lDirtBounties &8| &6Top" + detail: "A2416&lDirtBounties &8| &c{target}" + confirm: "A2416&lDirtBounties &8| &aConfirm" + admin-main: "A2416&lDirtBounties &8| &4Admin" + admin-history: "A2416&lDirtBounties &8| &4History" + admin-suspicious: "A2416&lDirtBounties &8| &4Suspicious" + +layout: + size: 54 + content-slots: + - 10 + - 11 + - 12 + - 13 + - 14 + - 15 + - 16 + - 19 + - 20 + - 21 + - 22 + - 23 + - 24 + - 25 + - 28 + - 29 + - 30 + - 31 + - 32 + - 33 + - 34 + - 37 + - 38 + - 39 + - 40 + - 41 + - 42 + - 43 + previous-slot: 45 + back-slot: 46 + refresh-slot: 49 + next-slot: 53 + place-slot: 48 + top-slot: 50 + admin-slot: 52 + +items: + filler: + material: BLACK_STAINED_GLASS_PANE + name: " " + lore: [] + border: + material: BROWN_STAINED_GLASS_PANE + name: " " + lore: [] + empty: + material: BARRIER + name: "&cNo bounties" + lore: + - "&7Nobody has a price on their head." + bounty: + material: PLAYER_HEAD + name: "&c&l{target}" + lore: + - "&7Bounty: &f{amount}" + - "&7Contributors: &f{contributors}" + - "&7Top reason: &f{reason}" + - "&7Expires: &f{expires}" + - "" + - "&eClick to view details." + top-bounty: + material: PLAYER_HEAD + name: "&6#{rank} &c&l{target}" + lore: + - "&7Bounty: &f{amount}" + - "&7Contributors: &f{contributors}" + - "" + - "&eClick to inspect." + detail-head: + material: PLAYER_HEAD + name: "&c&l{target}" + lore: + - "&7Total bounty: &f{amount}" + - "&7Contributors: &f{contributors}" + - "&7Expires: &f{expires}" + - "" + - "&6Top reasons:" + - "{reason_lines}" + claim-info: + material: BOOK + name: "&6Claim Conditions" + lore: + - "&7PvP required: &f{pvp}" + - "&7Same IP blocked: &f{same_ip}" + - "&7World mode: &f{world_mode}" + - "&7Combat: &f{combat}" + - "" + - "&8Claims are checked automatically on kill." + place: + material: GOLD_INGOT + name: "&6Place Bounty" + lore: + - "&7Start a guided bounty placement." + - "" + - "&eClick to begin." + add: + material: ANVIL + name: "&6Increase Bounty" + lore: + - "&7Add money to this target's bounty." + - "" + - "&eClick to continue." + top: + material: NETHER_STAR + name: "&6Top Bounties" + lore: + - "&7Sort by highest active value." + - "" + - "&eClick to view." + refresh: + material: SUNFLOWER + name: "&eRefresh" + lore: + - "&7Reload this view." + previous: + material: ARROW + name: "&ePrevious Page" + lore: + - "&7Go back one page." + next: + material: ARROW + name: "&eNext Page" + lore: + - "&7Go forward one page." + back: + material: OAK_DOOR + name: "&eBack" + lore: + - "&7Return to the previous view." + close: + material: BARRIER + name: "&cClose" + lore: + - "&7Close this menu." + confirm: + material: LIME_CONCRETE + name: "&aConfirm Bounty" + lore: + - "&7Target: &f{target}" + - "&7Amount: &f{amount}" + - "&7Fee: &f{fee}" + - "&7Total cost: &f{cost}" + - "&7Reason: &f{reason}" + - "" + - "&aClick to confirm." + cancel: + material: RED_CONCRETE + name: "&cCancel" + lore: + - "&7Return without placing this bounty." + admin-active: + material: CHEST + name: "&4Active Bounties" + lore: + - "&7Review and inspect all active bounties." + admin-history: + material: WRITABLE_BOOK + name: "&4Recent Claims and Changes" + lore: + - "&7Review bounty history records." + admin-suspicious: + material: REDSTONE_TORCH + name: "&4Suspicious Activity" + lore: + - "&7Review blocked and suspicious claims." diff --git a/target/classes/messages.yml b/target/classes/messages.yml new file mode 100644 index 0000000..956abf4 --- /dev/null +++ b/target/classes/messages.yml @@ -0,0 +1,105 @@ +prefix: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛA4A2A&lʙB6B35&lᴀ&#A8873F&lɢ&#D4AF37&lᴍ&#B9C63F&lᴄ &8» " +no-permission: "{prefix}&cYou do not have permission to do that." +player-only: "{prefix}&cOnly players can use that command." +unknown-command: "{prefix}&cUnknown bounty command. Use &f/bounty help&c." +reload-complete: "{prefix}&aDirtBounties reloaded." +invalid-number: "{prefix}&cThat amount is not valid." +invalid-player: "{prefix}&cCould not find that player." +invalid-world: "{prefix}&cBounties cannot be claimed in this world." +economy-missing: "{prefix}&cEconomy is not available. Ask staff to check Vault or CMI's Vault injector." +not-enough-money: "{prefix}&cYou need &f{cost}&c, including fees, to place that bounty." +cooldown: "{prefix}&cSlow down. Try again in &f{time}&c." +reason-too-long: "{prefix}&cThat reason is too long. Maximum: &f{max}&c characters." +target-self: "{prefix}&cYou cannot place or claim a bounty on yourself." +target-banned: "{prefix}&cThat player cannot receive bounties while banned." +amount-too-low: "{prefix}&cMinimum bounty amount is &f{min}&c." +amount-too-high: "{prefix}&cMaximum bounty amount is &f{max}&c." +amount-would-exceed-max: "{prefix}&cThat would put the bounty above the maximum of &f{max}&c." +no-active-bounties: "{prefix}&7There are no active bounties right now." +bounty-not-found: "{prefix}&cThere is no active bounty on &f{target}&c." +claim-denied-format: "{prefix}&cBounty claim denied: &f{reason}" + +help: + - "{prefix}&6&lDirtBounties Commands" + - "&8- &e/bounty &7Open the bounty GUI." + - "&8- &e/bounty place [reason] &7Place a bounty." + - "&8- &e/bounty add [reason] &7Increase a bounty." + - "&8- &e/bounty list &7List active bounties." + - "&8- &e/bounty top &7View top bounties." + - "&8- &e/bounty view &7View bounty details." + - "&8- &e/bounty claiminfo &7View claim rules." + +admin-help: + - "{prefix}&6&lDirtBounties Admin" + - "&8- &e/bountyadmin reload &7Reload configs and storage." + - "&8- &e/bountyadmin remove &7Remove a bounty and refund by config." + - "&8- &e/bountyadmin clearall confirm &7Remove every active bounty." + - "&8- &e/bountyadmin set &7Set a bounty value." + - "&8- &e/bountyadmin expire &7Expire a bounty." + - "&8- &e/bountyadmin history &7Show history." + - "&8- &e/bountyadmin suspicious &7Show suspicious activity." + - "&8- &e/bountyadmin gui &7Open admin GUI." + +bounty: + placed: "{prefix}&aPlaced a bounty of &f{amount}&a on &f{target}&a. Fee: &f{fee}&a." + added: "{prefix}&aAdded &f{amount}&a to &f{target}&a's bounty. New total: &f{total}&a." + broadcast-placed: "{prefix}&6{placer}&e placed a bounty of &f{amount}&e on &c{target}&e." + broadcast-added: "{prefix}&6{placer}&e increased &c{target}&e's bounty to &f{total}&e." + milestone: "{prefix}&c{target}&6's bounty has reached &f{total}&6." + claimed: "{prefix}&aYou claimed &f{payout}&a from &c{target}&a's bounty." + claim-broadcast: "{prefix}&c{killer}&6 claimed &f{payout}&6 for killing &c{target}&6." + expired: "{prefix}&7The bounty on &f{target}&7 expired." + removed: "{prefix}&aRemoved the bounty on &f{target}&a." + set: "{prefix}&aSet &f{target}&a's bounty to &f{amount}&a." + clearall-warning: "{prefix}&cUse &f/bountyadmin clearall confirm &cto remove all active bounties." + clearall-done: "{prefix}&aRemoved &f{count}&a active bounties." + list-line: "&8- &c{target} &7» &f{amount} &8({contributors} contributors)" + top-line: "&6#{rank} &c{target} &7» &f{amount}" + view: + - "{prefix}&6&lBounty: &c{target}" + - "&7Amount: &f{amount}" + - "&7Contributors: &f{contributors}" + - "&7Expires: &f{expires}" + - "&7Reason: &f{reason}" + claiminfo: + - "{prefix}&6&lClaim Rules for &c{target}" + - "&8- &7PvP kill required: &f{pvp}" + - "&8- &7Same-IP claims blocked: &f{same_ip}" + - "&8- &7Allowed worlds: &f{worlds}" + - "&8- &7Combat requirement: &f{combat}" + +claim-denied: + no-bounty: "No active bounty exists." + no-permission: "You do not have permission to claim bounties." + self: "Self-claims are blocked." + same-ip: "Same-IP bounty claims are blocked." + shared-known-ip: "Shared known-IP bounty claims are blocked." + world: "Claims are disabled in this world." + gamemode: "Your game mode cannot claim bounties." + combat: "Combat requirements were not met." + cooldown: "A claim cooldown is active." + pair-limit: "This killer-target pair has too many recent claims." + target-offline: "The target must be online at death." + +gui: + unavailable: "{prefix}&cThe bounty GUI is disabled." + prompt-target: "{prefix}&eType the target player's name in chat, or type &fcancel&e." + prompt-amount: "{prefix}&eType the bounty amount for &f{target}&e, or type &fcancel&e." + prompt-reason: "{prefix}&eType a short reason for &f{target}&e, use &f-&e for none, or type &fcancel&e." + input-cancelled: "{prefix}&7Bounty input cancelled." + input-expired: "{prefix}&cYour bounty input expired." + confirm-opened: "{prefix}&7Review and confirm the bounty." + admin-opened: "{prefix}&7Opened the admin bounty view." + +admin: + history-empty: "{prefix}&7No bounty history found for &f{target}&7." + suspicious-empty: "{prefix}&7No suspicious bounty activity has been logged." + suspicious-line: "&8- &c{time} &7{type}: &f{details}" + history-line: "&8- &e{time} &7{type}: &f{amount} &8({note})" + refunded: "{prefix}&aRefunded &f{amount}&a to &f{player}&a." + action-logged: "{prefix}&7Admin action logged." + +webhook: + placement-title: "New bounty placed" + claim-title: "Bounty claimed" + admin-title: "Bounty admin action" diff --git a/target/classes/plugin.yml b/target/classes/plugin.yml new file mode 100644 index 0000000..a99dc65 --- /dev/null +++ b/target/classes/plugin.yml @@ -0,0 +1,67 @@ +name: DirtBounties +version: 1.0.0 +main: com.dirtbagmc.dirtbounties.DirtBountiesPlugin +api-version: '1.21' +author: DirtbagMC +website: https://dirtbagmc.com +description: Premium bounty system for Paper SMP and anarchy servers. +softdepend: + - Vault + - CMI + - PlaceholderAPI + +commands: + bounty: + description: Open and manage player bounties. + usage: /bounty help + aliases: + - bounties + - dbounty + bountyadmin: + description: Admin controls for DirtBounties. + usage: /bountyadmin help + aliases: + - dbountyadmin + - ba + +permissions: + dirtbounties.use: + description: Allows opening the bounty GUI and using basic commands. + default: true + dirtbounties.place: + description: Allows placing new bounties. + default: true + dirtbounties.add: + description: Allows increasing existing bounties. + default: true + dirtbounties.view: + description: Allows viewing bounty details. + default: true + dirtbounties.top: + description: Allows viewing top bounties. + default: true + dirtbounties.claim: + description: Allows claiming bounties by killing targets. + default: true + dirtbounties.anonymous: + description: Allows placing anonymous bounties when enabled. + default: op + dirtbounties.bypass.cooldowns: + description: Bypasses placement and claim cooldown checks. + default: op + dirtbounties.bypass.claimrules: + description: Bypasses configured claim restrictions. + default: op + dirtbounties.admin: + description: Full DirtBounties administration access. + default: op + children: + dirtbounties.use: true + dirtbounties.place: true + dirtbounties.add: true + dirtbounties.view: true + dirtbounties.top: true + dirtbounties.claim: true + dirtbounties.anonymous: true + dirtbounties.bypass.cooldowns: true + dirtbounties.bypass.claimrules: true diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties new file mode 100644 index 0000000..7a8f606 --- /dev/null +++ b/target/maven-archiver/pom.properties @@ -0,0 +1,3 @@ +artifactId=DirtBounties +groupId=com.dirtbagmc +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..fbd6f2b --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,36 @@ +com/dirtbagmc/dirtbounties/util/NumberUtil.class +com/dirtbagmc/dirtbounties/service/BountyService.class +com/dirtbagmc/dirtbounties/listener/BountyListener.class +com/dirtbagmc/dirtbounties/service/PlayerCacheService.class +com/dirtbagmc/dirtbounties/command/BountyAdminCommand.class +com/dirtbagmc/dirtbounties/command/BountyCommand.class +com/dirtbagmc/dirtbounties/service/HistoryService.class +com/dirtbagmc/dirtbounties/service/CombatTracker.class +com/dirtbagmc/dirtbounties/service/BountyService$Fee.class +com/dirtbagmc/dirtbounties/webhook/WebhookService.class +com/dirtbagmc/dirtbounties/gui/GuiManager$1.class +com/dirtbagmc/dirtbounties/service/AntiAbuseService.class +com/dirtbagmc/dirtbounties/model/BountyContribution.class +com/dirtbagmc/dirtbounties/gui/GuiHolder.class +com/dirtbagmc/dirtbounties/gui/GuiType.class +com/dirtbagmc/dirtbounties/util/TimeUtil.class +com/dirtbagmc/dirtbounties/DirtBountiesPlugin.class +com/dirtbagmc/dirtbounties/hook/DirtBountiesExpansion.class +com/dirtbagmc/dirtbounties/service/CombatTracker$CombatRecord.class +com/dirtbagmc/dirtbounties/economy/EconomyService$EconomyResult.class +com/dirtbagmc/dirtbounties/storage/StorageManager.class +com/dirtbagmc/dirtbounties/util/ColorUtil.class +com/dirtbagmc/dirtbounties/service/MessageService.class +com/dirtbagmc/dirtbounties/model/Bounty.class +com/dirtbagmc/dirtbounties/service/PlayerCacheService$CacheEntry.class +com/dirtbagmc/dirtbounties/model/ClaimValidation.class +com/dirtbagmc/dirtbounties/storage/StorageManager$PlayerCacheData.class +com/dirtbagmc/dirtbounties/util/ItemBuilder.class +com/dirtbagmc/dirtbounties/model/BountyHistoryRecord.class +com/dirtbagmc/dirtbounties/command/BountyCommand$ReasonInput.class +com/dirtbagmc/dirtbounties/gui/PendingBountyInput.class +com/dirtbagmc/dirtbounties/gui/PendingBountyInput$Stage.class +com/dirtbagmc/dirtbounties/config/ConfigService.class +com/dirtbagmc/dirtbounties/economy/EconomyService.class +com/dirtbagmc/dirtbounties/model/SuspiciousActivity.class +com/dirtbagmc/dirtbounties/gui/GuiManager.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..4a8ecb7 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,28 @@ +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/DirtBountiesPlugin.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/command/BountyAdminCommand.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/command/BountyCommand.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/config/ConfigService.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/economy/EconomyService.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/gui/GuiHolder.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/gui/GuiManager.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/gui/GuiType.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/gui/PendingBountyInput.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/hook/DirtBountiesExpansion.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/listener/BountyListener.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/model/Bounty.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/model/BountyContribution.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/model/BountyHistoryRecord.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/model/ClaimValidation.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/model/SuspiciousActivity.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/service/AntiAbuseService.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/service/BountyService.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/service/CombatTracker.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/service/HistoryService.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/service/MessageService.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/service/PlayerCacheService.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/storage/StorageManager.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/util/ColorUtil.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/util/ItemBuilder.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/util/NumberUtil.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/util/TimeUtil.java +/home/bitnix/Desktop/DirtBounties/src/main/java/com/dirtbagmc/dirtbounties/webhook/WebhookService.java