From a1b1a5d1121433ba5bed5b91406ae296576344a1 Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Mon, 28 Feb 2022 14:41:14 +0100 Subject: [PATCH] Adds sign tracking and refunds #3 #8 --- .../net/knarcraft/paidsigns/PaidSigns.java | 87 +++++++----- .../paidsigns/container/TrackedSign.java | 42 ++++++ .../listener/BlockBreakListener.java | 35 +++++ .../paidsigns/listener/SignListener.java | 44 ++++-- .../paidsigns/manager/EconomyManager.java | 3 +- .../paidsigns/manager/TrackedSignManager.java | 128 ++++++++++++++++++ .../paidsigns/utility/TabCompleteHelper.java | 17 --- src/main/resources/config.yml | 12 +- 8 files changed, 303 insertions(+), 65 deletions(-) create mode 100644 src/main/java/net/knarcraft/paidsigns/container/TrackedSign.java create mode 100644 src/main/java/net/knarcraft/paidsigns/listener/BlockBreakListener.java create mode 100644 src/main/java/net/knarcraft/paidsigns/manager/TrackedSignManager.java diff --git a/src/main/java/net/knarcraft/paidsigns/PaidSigns.java b/src/main/java/net/knarcraft/paidsigns/PaidSigns.java index 367eee1..71171ad 100644 --- a/src/main/java/net/knarcraft/paidsigns/PaidSigns.java +++ b/src/main/java/net/knarcraft/paidsigns/PaidSigns.java @@ -13,8 +13,11 @@ import net.knarcraft.paidsigns.command.RemoveTabCommand; import net.knarcraft.paidsigns.listener.SignListener; import net.knarcraft.paidsigns.manager.EconomyManager; import net.knarcraft.paidsigns.manager.PaidSignManager; +import net.knarcraft.paidsigns.manager.TrackedSignManager; import net.milkbowl.vault.economy.Economy; +import org.bukkit.command.CommandExecutor; import org.bukkit.command.PluginCommand; +import org.bukkit.command.TabCompleter; import org.bukkit.command.TabExecutor; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.plugin.PluginManager; @@ -31,6 +34,8 @@ public final class PaidSigns extends JavaPlugin { private PaidSignManager signManager; private boolean ignoreCase; private boolean ignoreColor; + private boolean enableRefunds; + private int refundPercentage; /** * Instantiates a new paid signs object @@ -53,6 +58,7 @@ public final class PaidSigns extends JavaPlugin { public void onEnable() { setupVault(); signManager = new PaidSignManager(PaidSignManager.loadSigns()); + TrackedSignManager.loadTrackedSigns(); loadConfig(); PluginManager pluginManager = getServer().getPluginManager(); @@ -72,6 +78,7 @@ public final class PaidSigns extends JavaPlugin { this.reloadConfig(); loadConfig(); signManager = new PaidSignManager(PaidSignManager.loadSigns()); + TrackedSignManager.loadTrackedSigns(); } /** @@ -101,46 +108,56 @@ public final class PaidSigns extends JavaPlugin { return this.ignoreColor; } + /** + * Checks whether refunds are currently enabled + * + * @return

Whether refunds are currently enabled

+ */ + public boolean areRefundsEnabled() { + return this.enableRefunds; + } + + /** + * Gets the percentage of the initial cost to refund the sign creator + * + * @return

The percentage of the cost to refund

+ */ + public int getRefundPercentage() { + if (this.refundPercentage < 0) { + return 0; + } else if (refundPercentage > 100) { + return 100; + } + return this.refundPercentage; + } + /** * Registers the commands used by this plugin */ private void registerCommands() { - PluginCommand addCommand = this.getCommand("addPaidSign"); - if (addCommand != null) { - addCommand.setExecutor(new AddCommand()); - addCommand.setTabCompleter(new AddTabCompleter()); - } + registerCommand("addPaidSign", new AddCommand(), new AddTabCompleter()); + registerCommand("listPaidSigns", new ListCommand(), new ListTabCompleter()); + registerCommand("addPaidSignCondition", new AddConditionCommand(), new AddConditionTabCompleter()); + registerCommand("removePaidSignCondition", new RemoveConditionCommand(), new RemoveConditionTabCompleter()); - PluginCommand listCommand = this.getCommand("listPaidSigns"); - if (listCommand != null) { - listCommand.setExecutor(new ListCommand()); - listCommand.setTabCompleter(new ListTabCompleter()); - } + TabExecutor removeTabExecutor = new RemoveTabCommand(); + registerCommand("removePaidSign", removeTabExecutor, removeTabExecutor); + TabExecutor reloadTabExecutor = new ReloadTabCommand(); + registerCommand("reload", reloadTabExecutor, reloadTabExecutor); + } - PluginCommand addConditionCommand = this.getCommand("addPaidSignCondition"); - if (addConditionCommand != null) { - addConditionCommand.setExecutor(new AddConditionCommand()); - addConditionCommand.setTabCompleter(new AddConditionTabCompleter()); - } - - PluginCommand removeConditionCommand = this.getCommand("removePaidSignCondition"); - if (removeConditionCommand != null) { - removeConditionCommand.setExecutor(new RemoveConditionCommand()); - removeConditionCommand.setTabCompleter(new RemoveConditionTabCompleter()); - } - - PluginCommand removeCommand = this.getCommand("removePaidSign"); - if (removeCommand != null) { - TabExecutor removeTabExecutor = new RemoveTabCommand(); - removeCommand.setExecutor(removeTabExecutor); - removeCommand.setTabCompleter(removeTabExecutor); - } - - PluginCommand reloadCommand = this.getCommand("reload"); - if (reloadCommand != null) { - TabExecutor reloadTabExecutor = new ReloadTabCommand(); - reloadCommand.setExecutor(reloadTabExecutor); - reloadCommand.setTabCompleter(reloadTabExecutor); + /** + * Registers a command if possible + * + * @param command

The command to register

+ * @param commandExecutor

The command executor for executing the command

+ * @param tabCompleter

The tab completer for tab-completing the command

+ */ + private void registerCommand(String command, CommandExecutor commandExecutor, TabCompleter tabCompleter) { + PluginCommand pluginCommand = this.getCommand(command); + if (pluginCommand != null) { + pluginCommand.setExecutor(commandExecutor); + pluginCommand.setTabCompleter(tabCompleter); } } @@ -153,6 +170,8 @@ public final class PaidSigns extends JavaPlugin { this.saveDefaultConfig(); ignoreCase = config.getBoolean("ignoreCase", true); ignoreColor = config.getBoolean("ignoreColor", false); + enableRefunds = config.getBoolean("enableRefunds", true); + refundPercentage = config.getInt("refundPercentage", 100); } /** diff --git a/src/main/java/net/knarcraft/paidsigns/container/TrackedSign.java b/src/main/java/net/knarcraft/paidsigns/container/TrackedSign.java new file mode 100644 index 0000000..a050ea7 --- /dev/null +++ b/src/main/java/net/knarcraft/paidsigns/container/TrackedSign.java @@ -0,0 +1,42 @@ +package net.knarcraft.paidsigns.container; + +import java.util.UUID; + +/** + * A representation of a sign placed by a player that matched a paid sign + */ +public class TrackedSign { + + private final UUID playerId; + private final double cost; + + /** + * Instantiates a new tracked sign + * + * @param playerId

The unique id of the player that created the sign

+ * @param cost

The cost the player paid for creating the sign

+ */ + public TrackedSign(UUID playerId, double cost) { + this.playerId = playerId; + this.cost = cost; + } + + /** + * Gets the id of the player that created this tracked sign + * + * @return

The player that created this tracked sign

+ */ + public UUID getPlayerId() { + return this.playerId; + } + + /** + * Gets the cost the player paid for creating this paid sign + * + * @return

The cost paid for creating this sign

+ */ + public double getCost() { + return this.cost; + } + +} diff --git a/src/main/java/net/knarcraft/paidsigns/listener/BlockBreakListener.java b/src/main/java/net/knarcraft/paidsigns/listener/BlockBreakListener.java new file mode 100644 index 0000000..c7c5eed --- /dev/null +++ b/src/main/java/net/knarcraft/paidsigns/listener/BlockBreakListener.java @@ -0,0 +1,35 @@ +package net.knarcraft.paidsigns.listener; + +import net.knarcraft.paidsigns.PaidSigns; +import net.knarcraft.paidsigns.manager.TrackedSignManager; +import org.bukkit.block.Sign; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; + +import java.io.IOException; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A listener that listens for any tracked signs being broken + */ +public class BlockBreakListener implements Listener { + + @EventHandler(priority = EventPriority.MONITOR) + public void onBlockBreak(BlockBreakEvent event) { + if (event.getBlock().getState() instanceof Sign) { + try { + TrackedSignManager.removeTrackedSign(event.getBlock().getLocation()); + } catch (IOException e) { + e.printStackTrace(); + Logger logger = PaidSigns.getInstance().getLogger(); + logger.log(Level.SEVERE, "Exception encountered while trying to write to the data file"); + logger.log(Level.SEVERE, Arrays.toString(e.getStackTrace())); + } + } + } + +} diff --git a/src/main/java/net/knarcraft/paidsigns/listener/SignListener.java b/src/main/java/net/knarcraft/paidsigns/listener/SignListener.java index ceed610..0ae4c03 100644 --- a/src/main/java/net/knarcraft/paidsigns/listener/SignListener.java +++ b/src/main/java/net/knarcraft/paidsigns/listener/SignListener.java @@ -3,13 +3,18 @@ package net.knarcraft.paidsigns.listener; import net.knarcraft.paidsigns.PaidSigns; import net.knarcraft.paidsigns.container.PaidSign; import net.knarcraft.paidsigns.manager.EconomyManager; +import net.knarcraft.paidsigns.manager.TrackedSignManager; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.SignChangeEvent; +import java.io.IOException; +import java.util.Arrays; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; /** * A listener for listening to registered paid signs @@ -50,19 +55,38 @@ public class SignListener implements Listener { return true; } - double cost = paidSign.getCost(); - boolean canAfford = EconomyManager.canAfford(player, cost); - if (!canAfford) { - player.sendMessage("[PaidSigns] You cannot afford to create this sign"); - event.setCancelled(true); - } else { - String unit = EconomyManager.getCurrency(cost != 1); - player.sendMessage(String.format("[PaidSigns] You paid %.2f %s to create the sign", cost, unit)); - EconomyManager.withdraw(player, cost); - } + performPaidSignTransaction(paidSign, player, event); return true; } return false; } + /** + * Performs the transaction to pay for the paid sign + * + * @param paidSign

The paid sign a match has been found for

+ * @param player

The player that created the sign

+ * @param event

The sign change event that caused the sign to be created

+ */ + private void performPaidSignTransaction(PaidSign paidSign, Player player, SignChangeEvent event) { + double cost = paidSign.getCost(); + boolean canAfford = EconomyManager.canAfford(player, cost); + if (!canAfford) { + player.sendMessage("[PaidSigns] You cannot afford to create this sign"); + event.setCancelled(true); + } else { + String unit = EconomyManager.getCurrency(cost != 1); + player.sendMessage(String.format("[PaidSigns] You paid %.2f %s to create the sign", cost, unit)); + EconomyManager.withdraw(player, cost); + try { + TrackedSignManager.addTrackedSign(event.getBlock().getLocation(), player.getUniqueId(), cost); + } catch (IOException e) { + e.printStackTrace(); + Logger logger = PaidSigns.getInstance().getLogger(); + logger.log(Level.SEVERE, "Exception encountered while trying to write to the data file"); + logger.log(Level.SEVERE, Arrays.toString(e.getStackTrace())); + } + } + } + } diff --git a/src/main/java/net/knarcraft/paidsigns/manager/EconomyManager.java b/src/main/java/net/knarcraft/paidsigns/manager/EconomyManager.java index b92e865..183340e 100644 --- a/src/main/java/net/knarcraft/paidsigns/manager/EconomyManager.java +++ b/src/main/java/net/knarcraft/paidsigns/manager/EconomyManager.java @@ -2,7 +2,6 @@ package net.knarcraft.paidsigns.manager; import net.milkbowl.vault.economy.Economy; import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; /** * A manager that performs all Economy tasks @@ -55,7 +54,7 @@ public final class EconomyManager { * @param player

The player to withdraw money from

* @param cost

The amount of money to withdraw

*/ - public static void withdraw(Player player, double cost) { + public static void withdraw(OfflinePlayer player, double cost) { economy.withdrawPlayer(player, cost); } diff --git a/src/main/java/net/knarcraft/paidsigns/manager/TrackedSignManager.java b/src/main/java/net/knarcraft/paidsigns/manager/TrackedSignManager.java new file mode 100644 index 0000000..807b3aa --- /dev/null +++ b/src/main/java/net/knarcraft/paidsigns/manager/TrackedSignManager.java @@ -0,0 +1,128 @@ +package net.knarcraft.paidsigns.manager; + +import net.knarcraft.paidsigns.PaidSigns; +import net.knarcraft.paidsigns.container.TrackedSign; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.logging.Level; + +/** + * A manager for keeping track of plugin-signs created by players + */ +public class TrackedSignManager { + + private static Map trackedSigns = new HashMap<>(); + private static final File signsFile = new File(PaidSigns.getInstance().getDataFolder(), "data.yml"); + + /** + * Adds a tracked sign to the manager + * + * @param signLocation

The location the sign was created at

+ * @param playerId

The unique id of the player that created the sign

+ * @throws IOException

If unable to save the tracked signs

+ */ + public static void addTrackedSign(Location signLocation, UUID playerId, double cost) throws IOException { + trackedSigns.put(signLocation, new TrackedSign(playerId, cost)); + saveTrackedSigns(); + } + + /** + * Removes a tracked sign from the manager + * + * @param signLocation

The location the sign was removed from

+ * @throws IOException

If unable to save the tracked signs

+ */ + public static void removeTrackedSign(Location signLocation) throws IOException { + if (!trackedSigns.containsKey(signLocation)) { + return; + } + trackedSigns.remove(signLocation); + saveTrackedSigns(); + if (!PaidSigns.getInstance().areRefundsEnabled()) { + return; + } + TrackedSign trackedSign = trackedSigns.get(signLocation); + OfflinePlayer player = Bukkit.getOfflinePlayer(trackedSign.getPlayerId()); + double refundSum = trackedSign.getCost() / 100 * PaidSigns.getInstance().getRefundPercentage(); + EconomyManager.withdraw(player, refundSum); + } + + /** + * Loads all tracked signs from the data file + */ + public static void loadTrackedSigns() { + YamlConfiguration configuration = YamlConfiguration.loadConfiguration(signsFile); + ConfigurationSection signSection = configuration.getConfigurationSection("trackedSigns"); + trackedSigns = new HashMap<>(); + + if (signSection == null) { + PaidSigns.getInstance().getLogger().log(Level.WARNING, "Signs section not found in data.yml"); + return; + } + + for (String key : signSection.getKeys(false)) { + try { + loadSign(signSection, key); + } catch (InvalidConfigurationException e) { + PaidSigns.getInstance().getLogger().log(Level.SEVERE, "Unable to load sign " + key + ": " + + e.getMessage()); + } + } + } + + /** + * Loads a sign from the save file + * + * @param signSection

The configuration section containing signs

+ * @param key

The sign key which is also the sign's location

+ * @throws InvalidConfigurationException

If unable to load the sign

+ */ + private static void loadSign(ConfigurationSection signSection, String key) throws InvalidConfigurationException { + String[] locationParts = key.split(","); + Location signLocation; + try { + signLocation = new Location(Bukkit.getWorld(UUID.fromString(locationParts[0])), + Double.parseDouble(locationParts[1]), Double.parseDouble(locationParts[2]), + Double.parseDouble(locationParts[3])); + } catch (NumberFormatException exception) { + throw new InvalidConfigurationException("Invalid sign coordinates"); + } + + double cost = signSection.getDouble(key + ".cost"); + UUID playerId = UUID.fromString(Objects.requireNonNull(signSection.getString(key + ".playerId"))); + + TrackedSign trackedSign = new TrackedSign(playerId, cost); + trackedSigns.put(signLocation, trackedSign); + } + + /** + * Saves the managed tracked signs to the data file + * + * @throws IOException

If unable to write to the data file

+ */ + private static void saveTrackedSigns() throws IOException { + YamlConfiguration configuration = YamlConfiguration.loadConfiguration(signsFile); + ConfigurationSection signSection = configuration.createSection("trackedSigns"); + + for (Location signLocation : trackedSigns.keySet()) { + TrackedSign sign = trackedSigns.get(signLocation); + String locationString = Objects.requireNonNull(signLocation.getWorld()).getUID() + "," + + signLocation.getBlockX() + "," + signLocation.getBlockY() + "," + signLocation.getBlockZ(); + signSection.set(locationString + ".cost", sign.getCost()); + signSection.set(locationString + ".playerId", sign.getPlayerId()); + } + configuration.save(signsFile); + } + +} diff --git a/src/main/java/net/knarcraft/paidsigns/utility/TabCompleteHelper.java b/src/main/java/net/knarcraft/paidsigns/utility/TabCompleteHelper.java index d0a5031..d2277d8 100644 --- a/src/main/java/net/knarcraft/paidsigns/utility/TabCompleteHelper.java +++ b/src/main/java/net/knarcraft/paidsigns/utility/TabCompleteHelper.java @@ -15,23 +15,6 @@ public final class TabCompleteHelper { } - /** - * Finds tab complete values that contain the typed text - * - * @param values

The values to filter

- * @param typedText

The text the player has started typing

- * @return

The given string values that contain the player's typed text

- */ - public static List filterMatchingContains(List values, String typedText) { - List configValues = new ArrayList<>(); - for (String value : values) { - if (value.toLowerCase().contains(typedText.toLowerCase())) { - configValues.add(value); - } - } - return configValues; - } - /** * Finds tab complete values that match the start of the typed text * diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index c633c34..de76417 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,8 +1,16 @@ # Whether to ignore the case (lowercase/uppercase) of the paid sign text. The option can be set on a per-sign basis, but -# this value is used if not specified. The correct value depends on whether the plugin signs it should match are case-sensitive or not. +# this value is used if not specified. The correct value depends on whether the plugin signs it should match are +# case-sensitive or not. ignoreCase: true # Whether to ignore any color or formatting applied to the text when trying to match a paid sign's text. The option can # be set on a per-sign basis, but this value is used if not specified. The correct value depends on whether the plugin # signs it should match allow coloring or not. -ignoreColor: false \ No newline at end of file +ignoreColor: false + +# Whether to enable refunds to the sign creator when a sign detected as a paid sign is broken (payment will always go +# to the original creator) +enableRefunds: true + +# The percentage of the paid sign cost to refund (0-100) +refundPercentage: 100 \ No newline at end of file