commit ed5690d197276c03d02d3c6a979a5b61ddfa506b Author: EpicKnarvik97 Date: Fri Feb 18 00:28:44 2022 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4788b4b --- /dev/null +++ b/.gitignore @@ -0,0 +1,113 @@ +# User-specific stuff +.idea/ + +*.iml +*.ipr +*.iws + +# IntelliJ +out/ + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +target/ + +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next + +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar +.flattened-pom.xml + +# Common working directory +run/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..9195693 --- /dev/null +++ b/pom.xml @@ -0,0 +1,92 @@ + + + 4.0.0 + + net.knarcraft + paidsigns + 1.0-SNAPSHOT + jar + + Paid Signs + + Add costs for creating plugin signs + + 17 + UTF-8 + + https://git.knarcraft.net + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 16 + 16 + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + false + + + + + + + + src/main/resources + true + + + + + + + spigotmc-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + sonatype + https://oss.sonatype.org/content/groups/public/ + + + vault-repo + http://nexus.hc.to/content/repositories/pub_releases + + + + + + org.spigotmc + spigot-api + 1.18.1-R0.1-SNAPSHOT + provided + + + net.milkbowl.vault + VaultAPI + 1.7 + provided + + + org.jetbrains + annotations + 22.0.0 + compile + + + diff --git a/src/main/java/net/knarcraft/paidsigns/PaidSigns.java b/src/main/java/net/knarcraft/paidsigns/PaidSigns.java new file mode 100644 index 0000000..092c127 --- /dev/null +++ b/src/main/java/net/knarcraft/paidsigns/PaidSigns.java @@ -0,0 +1,147 @@ +package net.knarcraft.paidsigns; + +import net.knarcraft.paidsigns.command.AddCommand; +import net.knarcraft.paidsigns.command.AddTabCompleter; +import net.knarcraft.paidsigns.command.ReloadTabCommand; +import net.knarcraft.paidsigns.command.RemoveCommand; +import net.knarcraft.paidsigns.command.RemoveTabCompleter; +import net.knarcraft.paidsigns.listener.SignListener; +import net.knarcraft.paidsigns.manager.EconomyManager; +import net.knarcraft.paidsigns.manager.PaidSignManager; +import net.milkbowl.vault.economy.Economy; +import org.bukkit.command.PluginCommand; +import org.bukkit.command.TabExecutor; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.bukkit.plugin.ServicesManager; +import org.bukkit.plugin.java.JavaPlugin; + +/** + * The PaidSigns plugin's main class + */ +public final class PaidSigns extends JavaPlugin { + + private static PaidSigns paidSigns; + private PaidSignManager signManager; + private boolean ignoreCase; + private boolean ignoreColor; + + /** + * Instantiates a new paid signs object + */ + @SuppressWarnings("unused") + public PaidSigns() { + paidSigns = this; + } + + /** + * Gets an instance of this plugin + * + * @return

An instance of this plugin

+ */ + public static PaidSigns getInstance() { + return paidSigns; + } + + @Override + public void onEnable() { + setupVault(); + signManager = new PaidSignManager(PaidSignManager.loadSigns()); + loadConfig(); + + PluginManager pluginManager = getServer().getPluginManager(); + pluginManager.registerEvents(new SignListener(), this); + + registerCommands(); + } + + @Override + public void onDisable() { + } + + /** + * Reloads this plugin + */ + public void reload() { + this.reloadConfig(); + loadConfig(); + signManager = new PaidSignManager(PaidSignManager.loadSigns()); + } + + /** + * Gets the paid sign manager used to manage paid signs + * + * @return

The paid sign manager

+ */ + public PaidSignManager getSignManager() { + return signManager; + } + + /** + * Gets the default setting for whether to ignore the case of paid sign ids + * + * @return

The default ignore case value

+ */ + public boolean ignoreCase() { + return this.ignoreCase; + } + + /** + * Gets the default setting for whether to ignore the color of paid sign ids + * + * @return

The default ignore color value

+ */ + public boolean ignoreColor() { + return this.ignoreColor; + } + + /** + * 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()); + } + + PluginCommand removeCommand = this.getCommand("removePaidSign"); + if (removeCommand != null) { + removeCommand.setExecutor(new RemoveCommand()); + removeCommand.setTabCompleter(new RemoveTabCompleter()); + } + + PluginCommand reloadCommand = this.getCommand("reload"); + if (reloadCommand != null) { + TabExecutor reloadTabExecutor = new ReloadTabCommand(); + reloadCommand.setExecutor(reloadTabExecutor); + reloadCommand.setTabCompleter(reloadTabExecutor); + } + } + + /** + * Loads the configuration file + */ + private void loadConfig() { + FileConfiguration config = this.getConfig(); + config.options().copyDefaults(true); + this.saveDefaultConfig(); + ignoreCase = config.getBoolean("ignoreCase", true); + ignoreColor = config.getBoolean("ignoreColor", false); + } + + /** + * Sets up Vault by getting plugins from their providers + */ + private void setupVault() { + ServicesManager servicesManager = this.getServer().getServicesManager(); + RegisteredServiceProvider economyProvider = servicesManager.getRegistration(Economy.class); + if (economyProvider != null) { + EconomyManager.initialize(economyProvider.getProvider()); + } else { + throw new IllegalStateException("[PaidSigns] Error: Vault could not be loaded"); + } + } + +} diff --git a/src/main/java/net/knarcraft/paidsigns/command/AddCommand.java b/src/main/java/net/knarcraft/paidsigns/command/AddCommand.java new file mode 100644 index 0000000..a5fe838 --- /dev/null +++ b/src/main/java/net/knarcraft/paidsigns/command/AddCommand.java @@ -0,0 +1,76 @@ +package net.knarcraft.paidsigns.command; + +import net.knarcraft.paidsigns.PaidSigns; +import net.knarcraft.paidsigns.container.PaidSign; +import net.knarcraft.paidsigns.manager.PaidSignManager; +import net.knarcraft.paidsigns.property.OptionState; +import net.knarcraft.paidsigns.utility.Tokenizer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A representation of the command for adding a new paid sign + */ +public class AddCommand implements CommandExecutor { + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, + @NotNull String[] args) { + if (args.length < 3) { + return false; + } + PaidSignManager manager = PaidSigns.getInstance().getSignManager(); + List arguments = Tokenizer.tokenize(String.join(" ", args)); + + String id = arguments.get(0); + short line; + double cost; + try { + line = (short) (Short.parseShort(arguments.get(1)) - 1); + cost = Double.parseDouble(arguments.get(2)); + } catch (NumberFormatException exception) { + sender.sendMessage("You provided an invalid number"); + return false; + } + OptionState ignoreCase = OptionState.DEFAULT; + OptionState ignoreColor = OptionState.DEFAULT; + if (arguments.size() > 3) { + ignoreCase = OptionState.fromString(arguments.get(3)); + } + if (arguments.size() > 4) { + ignoreColor = OptionState.fromString(arguments.get(4)); + } + + try { + PaidSign sign = new PaidSign(id, line, cost, ignoreCase, ignoreColor); + for (PaidSign similarSign : manager.getPaidSigns(sign.getCleanId(), sign.getLineIndex())) { + if (sign.matches(similarSign)) { + sender.sendMessage("A paid sign with the same id and line already exists"); + return false; + } + } + manager.addPaidSign(sign); + sender.sendMessage("Successfully added new paid sign"); + return true; + } catch (IOException e) { + 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())); + sender.sendMessage("An exception occurred. Please notify the server administrator or check the server log."); + return false; + } catch (IllegalArgumentException e) { + sender.sendMessage("Invalid input: " + e.getMessage()); + } + return false; + } + +} diff --git a/src/main/java/net/knarcraft/paidsigns/command/AddTabCompleter.java b/src/main/java/net/knarcraft/paidsigns/command/AddTabCompleter.java new file mode 100644 index 0000000..bde8835 --- /dev/null +++ b/src/main/java/net/knarcraft/paidsigns/command/AddTabCompleter.java @@ -0,0 +1,71 @@ +package net.knarcraft.paidsigns.command; + +import net.knarcraft.paidsigns.utility.Tokenizer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * The tab completer for the add paid sign command + */ +public class AddTabCompleter implements TabCompleter { + + private static List ids; + private static List lines; + private static List costs; + private static List options; + + @Nullable + @Override + public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, + @NotNull String[] args) { + if (ids == null) { + initializeValues(); + } + List arguments = Tokenizer.tokenize(String.join(" ", args)); + + if (arguments.size() < 1) { + return ids; + } else if (arguments.size() < 2) { + return lines; + } else if (arguments.size() < 3) { + return costs; + } else if (arguments.size() < 5) { + return options; + } + return new ArrayList<>(); + } + + /** + * Initializes the values available for tab completion + */ + private static void initializeValues() { + ids = new ArrayList<>(); + ids.add("[Gate]"); + ids.add("\"[Lift Up]\""); + ids.add("\"[Lift Down]\""); + + lines = new ArrayList<>(); + lines.add("1"); + lines.add("2"); + lines.add("3"); + lines.add("4"); + + costs = new ArrayList<>(); + costs.add("1"); + costs.add("5"); + costs.add("10"); + costs.add("15"); + + options = new ArrayList<>(); + options.add("default"); + options.add("true"); + options.add("false"); + } + +} diff --git a/src/main/java/net/knarcraft/paidsigns/command/ReloadTabCommand.java b/src/main/java/net/knarcraft/paidsigns/command/ReloadTabCommand.java new file mode 100644 index 0000000..7e24b59 --- /dev/null +++ b/src/main/java/net/knarcraft/paidsigns/command/ReloadTabCommand.java @@ -0,0 +1,30 @@ +package net.knarcraft.paidsigns.command; + +import net.knarcraft.paidsigns.PaidSigns; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * A representation of the command for reloading the plugin + */ +public class ReloadTabCommand implements TabExecutor { + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, + @NotNull String[] args) { + PaidSigns.getInstance().reload(); + return false; + } + + @Override + public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, + @NotNull String[] args) { + return new ArrayList<>(); + } + +} diff --git a/src/main/java/net/knarcraft/paidsigns/command/RemoveCommand.java b/src/main/java/net/knarcraft/paidsigns/command/RemoveCommand.java new file mode 100644 index 0000000..f77c007 --- /dev/null +++ b/src/main/java/net/knarcraft/paidsigns/command/RemoveCommand.java @@ -0,0 +1,55 @@ +package net.knarcraft.paidsigns.command; + +import net.knarcraft.paidsigns.PaidSigns; +import net.knarcraft.paidsigns.utility.Tokenizer; +import org.apache.commons.lang.ArrayUtils; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A representation of the command for removing a paid sign + */ +public class RemoveCommand implements CommandExecutor { + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, + @NotNull String[] args) { + if (args.length < 1) { + return false; + } + List arguments = Tokenizer.tokenize(String.join(" ", args)); + String[] input = arguments.get(0).split("\\|"); + short line; + try { + line = (short) (Short.parseShort(input[0]) - 1); + } catch (NumberFormatException exception) { + sender.sendMessage("Invalid line number given"); + return false; + } + String id = String.join("|", (String[]) ArrayUtils.remove(input, 0)); + try { + if (PaidSigns.getInstance().getSignManager().removePaidSign(id, line)) { + sender.sendMessage("Successfully removed paid sign"); + } else { + sender.sendMessage("No matching paid sign was found"); + } + return true; + } catch (IOException e) { + 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())); + sender.sendMessage("An exception occurred. Please notify the server administrator or check the server log."); + } + return false; + } + +} diff --git a/src/main/java/net/knarcraft/paidsigns/command/RemoveTabCompleter.java b/src/main/java/net/knarcraft/paidsigns/command/RemoveTabCompleter.java new file mode 100644 index 0000000..aaa48c0 --- /dev/null +++ b/src/main/java/net/knarcraft/paidsigns/command/RemoveTabCompleter.java @@ -0,0 +1,31 @@ +package net.knarcraft.paidsigns.command; + +import net.knarcraft.paidsigns.PaidSigns; +import net.knarcraft.paidsigns.container.PaidSign; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * The tab completer for the remove command + */ +public class RemoveTabCompleter implements TabCompleter { + + @Nullable + @Override + public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, + @NotNull String[] args) { + List allPaidSigns = PaidSigns.getInstance().getSignManager().getAllPaidSigns(); + List signIds = new ArrayList<>(); + for (PaidSign sign : allPaidSigns) { + signIds.add("\"" + (sign.getLineIndex() + 1) + "|" + sign.getId() + "\""); + } + return signIds; + } + +} diff --git a/src/main/java/net/knarcraft/paidsigns/container/PaidSign.java b/src/main/java/net/knarcraft/paidsigns/container/PaidSign.java new file mode 100644 index 0000000..3e9e6ff --- /dev/null +++ b/src/main/java/net/knarcraft/paidsigns/container/PaidSign.java @@ -0,0 +1,157 @@ +package net.knarcraft.paidsigns.container; + +import net.knarcraft.paidsigns.PaidSigns; +import net.knarcraft.paidsigns.property.OptionState; +import net.knarcraft.paidsigns.utility.ColorHelper; +import net.md_5.bungee.api.ChatColor; + +/** + * A representation of a paid sign + */ +public class PaidSign { + + private final String id; + private final String cleanId; + private final short lineIndex; + private final double cost; + private final OptionState ignoreCase; + private final OptionState ignoreColor; + + /** + * Instantiates a new paid sign + * + * @param id

The string that identifies this type of paid sign

+ * @param lineIndex

The line the id has to be on to trigger payment

+ * @param cost

The cost of creating this paid sign

+ * @param ignoreCase

Whether to ignore case when looking for this permission sign

+ * @param ignoreColor

Whether to ignore color when looking for this permission sign

+ */ + public PaidSign(String id, short lineIndex, double cost, OptionState ignoreCase, OptionState ignoreColor) { + if (id == null || id.trim().isBlank()) { + throw new IllegalArgumentException("Id cannot be empty"); + } + if (cost <= 0) { + throw new IllegalArgumentException("Cost must be larger than 0"); + } + if (lineIndex < 0 || lineIndex > 3) { + throw new IllegalArgumentException("Sign line must be between 0 and 3"); + } + if (ignoreCase == null || ignoreColor == null) { + throw new IllegalArgumentException("Ignore case and ignore color options cannot be null"); + } + this.id = id; + this.lineIndex = lineIndex; + this.cost = cost; + this.ignoreCase = ignoreCase; + this.ignoreColor = ignoreColor; + this.cleanId = getCleanString(id); + } + + /** + * Gets the id string of this paid sign + * + * @return

The id string of this paid sign

+ */ + public String getId() { + return id; + } + + /** + * Gets the cost of creating a sign matching this paid sign + * + * @return

The cost of creating a sign matching this paid sign

+ */ + public double getCost() { + return this.cost; + } + + /** + * Gets the line on the sign the id must be on to trigger payment + * + * @return

The sign line to search for the id

+ */ + public short getLineIndex() { + return lineIndex; + } + + /** + * Gets the clean id of this paid sign + * + * @return

The clean id of this paid sign

+ */ + public String getCleanId() { + return cleanId; + } + + /** + * Gets the "clean" version of the given string + * + *

The "cleaning" removes color codes and lower-cases the string.

+ * + * @param string

The string to clean

+ * @return

The cleaned string

+ */ + public static String getCleanString(String string) { + return ChatColor.stripColor(ColorHelper.translateAllColorCodes(string.toLowerCase())); + } + + /** + * Checks whether this paid sign matches the given line + * + * @param lineNumber

The line number of the given line

+ * @param line

The line to compare against this paid sign's id

+ * @return

True if the line matches this sign

+ */ + public boolean matches(short lineNumber, String line) { + if (lineNumber != this.lineIndex) { + return false; + } + String idCopy = id; + if (getIgnoreCase()) { + idCopy = idCopy.toLowerCase(); + line = line.toLowerCase(); + } + if (getIgnoreColor()) { + idCopy = ColorHelper.stripColorCodes(idCopy); + line = ColorHelper.stripColorCodes(line); + } + return idCopy.equals(line); + } + + /** + * Checks whether this paid sign matches another paid sign + * + * @param paidSign

The other paid sign to compare to

+ * @return

True if the signs match

+ */ + public boolean matches(PaidSign paidSign) { + return matches(paidSign.lineIndex, paidSign.id); + } + + /** + * Gets whether the text case should be ignored for this paid sign + * + * @return

Whether the text case should be ignored for this paid sign

+ */ + public boolean getIgnoreCase() { + if (this.ignoreCase == OptionState.DEFAULT) { + return PaidSigns.getInstance().ignoreCase(); + } else { + return OptionState.getBooleanValue(this.ignoreCase); + } + } + + /** + * Gets whether the text color should be ignored for this paid sign + * + * @return

Whether the text color should be ignored for this paid sign

+ */ + public boolean getIgnoreColor() { + if (this.ignoreColor == OptionState.DEFAULT) { + return PaidSigns.getInstance().ignoreColor(); + } else { + return OptionState.getBooleanValue(this.ignoreColor); + } + } + +} diff --git a/src/main/java/net/knarcraft/paidsigns/listener/SignListener.java b/src/main/java/net/knarcraft/paidsigns/listener/SignListener.java new file mode 100644 index 0000000..7509237 --- /dev/null +++ b/src/main/java/net/knarcraft/paidsigns/listener/SignListener.java @@ -0,0 +1,69 @@ +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.PaidSignManager; +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.util.List; + +/** + * A listener for listening to registered paid signs + */ +public class SignListener implements Listener { + + @EventHandler(priority = EventPriority.LOW) + public void onSignChange(SignChangeEvent event) { + if (event.isCancelled()) { + return; + } + + String[] lines = event.getLines(); + PaidSignManager signManager = PaidSigns.getInstance().getSignManager(); + for (short lineIndex = 0; lineIndex < lines.length; lineIndex++) { + //Get all "weak" matches (any paid sign with a clean id matching the clean line) + List matchingSigns = signManager.getPaidSigns(PaidSign.getCleanString(lines[lineIndex]), lineIndex); + if (matchingSigns.isEmpty()) { + continue; + } + if (testMatchingSigns(lineIndex, lines[lineIndex], matchingSigns, event)) { + return; + } + } + } + + /** + * Tests all weak matches of paid signs to check if a strong match is found + * + * @param lineIndex

The index of the currently managed line

+ * @param line

The text on the currently managed line

+ * @param matchingSigns

The signs that weakly match the

+ * @param event

The triggered sign change event

+ * @return

True if a match was found and thw work is finished

+ */ + private boolean testMatchingSigns(short lineIndex, String line, List matchingSigns, SignChangeEvent event) { + for (PaidSign paidSign : matchingSigns) { + if (paidSign.matches(lineIndex, line)) { + Player player = event.getPlayer(); + 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); + } + return true; + } + } + return false; + } + +} diff --git a/src/main/java/net/knarcraft/paidsigns/manager/EconomyManager.java b/src/main/java/net/knarcraft/paidsigns/manager/EconomyManager.java new file mode 100644 index 0000000..b92e865 --- /dev/null +++ b/src/main/java/net/knarcraft/paidsigns/manager/EconomyManager.java @@ -0,0 +1,62 @@ +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 + */ +public final class EconomyManager { + + private static Economy economy; + + private EconomyManager() { + + } + + /** + * Initializes the economy manager + * + * @param economy

The economy object to use for everything economy-related

+ */ + public static void initialize(Economy economy) { + EconomyManager.economy = economy; + } + + /** + * Checks whether the given player can afford the given cost + * + * @param player

The player to pay the cost

+ * @param cost

The cost the player needs to pay

+ * @return

True if the player is able to afford the cost

+ */ + public static boolean canAfford(OfflinePlayer player, double cost) { + return economy.has(player, cost); + } + + /** + * Gets the name of the used currency + * + * @param plural

Whether to get the plural name or the singular name

+ * @return

The name of the used currency

+ */ + public static String getCurrency(boolean plural) { + if (plural) { + return economy.currencyNamePlural(); + } else { + return economy.currencyNameSingular(); + } + } + + /** + * Withdraws the given cost from the given player's account + * + * @param player

The player to withdraw money from

+ * @param cost

The amount of money to withdraw

+ */ + public static void withdraw(Player player, double cost) { + economy.withdrawPlayer(player, cost); + } + +} diff --git a/src/main/java/net/knarcraft/paidsigns/manager/PaidSignManager.java b/src/main/java/net/knarcraft/paidsigns/manager/PaidSignManager.java new file mode 100644 index 0000000..4fbbe70 --- /dev/null +++ b/src/main/java/net/knarcraft/paidsigns/manager/PaidSignManager.java @@ -0,0 +1,160 @@ +package net.knarcraft.paidsigns.manager; + +import net.knarcraft.paidsigns.PaidSigns; +import net.knarcraft.paidsigns.container.PaidSign; +import net.knarcraft.paidsigns.property.OptionState; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; +import java.util.logging.Level; + +/** + * A manager that keeps track of all registered paid signs + */ +public final class PaidSignManager { + + private final List paidSigns; + private static final File signsFile = new File(PaidSigns.getInstance().getDataFolder(), "data.yml"); + private static final String signLineIdSeparator = "-,_,-"; + + /** + * Instantiate a new paid sign manager + * + * @param paidSigns

The paid signs this manager should manage

+ */ + public PaidSignManager(List paidSigns) { + this.paidSigns = paidSigns; + } + + /** + * Adds a new paid sign to this paid sign manager + * + * @param paidSign

The paid sign to add

+ * @throws IOException

If unable to write to the signs file

+ */ + public void addPaidSign(PaidSign paidSign) throws IOException { + this.paidSigns.add(paidSign); + saveSigns(this.paidSigns); + } + + /** + * Removes a paid sign from this paid sign manager + * + * @param id

The identifier for the paid sign to remove

+ * @param line

The line the identifier has to match to be valid

+ * @return

True if a sign was removed

+ * @throws IOException

If unable to write to the signs file

+ */ + public boolean removePaidSign(String id, short line) throws IOException { + boolean removed = this.paidSigns.removeIf((sign) -> sign.getId().equals(id) && sign.getLineIndex() == line); + if (!removed) { + return false; + } + saveSigns(this.paidSigns); + return true; + } + + /** + * Gets the paid signs that match the given properties + * + * @param cleanId

The clean id to search for

+ * @param line

The line number to search for

+ * @return

The paid signs that matched the given properties

+ */ + public List getPaidSigns(String cleanId, short line) { + return filterPaidSigns(filterPaidSigns(paidSigns, line), cleanId); + } + + /** + * Gets a copy of all registered paid signs + * + * @return

All registered paid signs

+ */ + public List getAllPaidSigns() { + return new ArrayList<>(paidSigns); + } + + /** + * Filters a list of paid signs to match the given line number + * + * @param paidSigns

The list of paid signs to start with

+ * @param line

The line number to filter by

+ * @return

The filtered list of paid signs

+ */ + private static List filterPaidSigns(List paidSigns, short line) { + return filterPaidSigns(paidSigns, (paidSign) -> paidSign.getLineIndex() == line); + } + + /** + * Loads paid signs from the signs file + * + * @return

The loaded paid signs

+ */ + public static List loadSigns() { + YamlConfiguration configuration = YamlConfiguration.loadConfiguration(signsFile); + ConfigurationSection signSection = configuration.getConfigurationSection("paidSigns"); + if (signSection == null) { + PaidSigns.getInstance().getLogger().log(Level.WARNING, "Signs section not found in data.yml"); + return new ArrayList<>(); + } + + List paidSigns = new ArrayList<>(); + for (String combinedId : signSection.getKeys(false)) { + String[] idParts = combinedId.split(signLineIdSeparator); + short lineNumber = Short.parseShort(idParts[0]); + String id = idParts[1]; + double cost = signSection.getDouble(combinedId + ".cost"); + OptionState ignoreCase = OptionState.getFromBoolean(signSection.getBoolean(combinedId + ".ignoreCase")); + OptionState ignoreColor = OptionState.getFromBoolean(signSection.getBoolean(combinedId + ".ignoreColor")); + paidSigns.add(new PaidSign(id, lineNumber, cost, ignoreCase, ignoreColor)); + } + return paidSigns; + } + + /** + * Saves the given paid signs to the signs file + * + * @param signs

The signs to save

+ * @throws IOException

If unable to write to the signs file

+ */ + public static void saveSigns(List signs) throws IOException { + YamlConfiguration configuration = YamlConfiguration.loadConfiguration(signsFile); + ConfigurationSection signSection = configuration.createSection("paidSigns"); + + for (PaidSign sign : signs) { + String signId = sign.getLineIndex() + signLineIdSeparator + sign.getId(); + signSection.set(signId + ".cost", sign.getCost()); + signSection.set(signId + ".ignoreCase", sign.getIgnoreCase()); + signSection.set(signId + ".ignoreColor", sign.getIgnoreColor()); + } + configuration.save(signsFile); + } + + /** + * Filters a list of paid signs to match the given clean id + * + * @param paidSigns

The list of paid signs to start with

+ * @param cleanId

The clean id to filter by

+ * @return

The filtered list of paid signs

+ */ + private static List filterPaidSigns(List paidSigns, String cleanId) { + return filterPaidSigns(paidSigns, (paidSign) -> paidSign.getCleanId().equals(cleanId)); + } + + /** + * Filters a list of paid signs using the given predicate + * + * @param paidSigns

The list of paid signs to start with

+ * @param predicate

The predicate used to filter paid signs

+ * @return

The filtered list of paid signs

+ */ + private static List filterPaidSigns(List paidSigns, Predicate predicate) { + return new ArrayList<>(paidSigns).stream().filter(predicate).toList(); + } + +} diff --git a/src/main/java/net/knarcraft/paidsigns/property/OptionState.java b/src/main/java/net/knarcraft/paidsigns/property/OptionState.java new file mode 100644 index 0000000..e39f5aa --- /dev/null +++ b/src/main/java/net/knarcraft/paidsigns/property/OptionState.java @@ -0,0 +1,65 @@ +package net.knarcraft.paidsigns.property; + +/** + * A class representing the different available states for a paid sign option + */ +public enum OptionState { + + /** + * The option is enabled + */ + TRUE, + + /** + * The option is disabled + */ + FALSE, + + /** + * The option is the same as the default value + */ + DEFAULT; + + /** + * Gets the boolean value of the given option state if it's boolean compatible + * + * @param optionState

The option state to get the boolean from

+ * @return

The boolean value, or an illegal argument exception if called on DEFAULT

+ */ + public static boolean getBooleanValue(OptionState optionState) { + return switch (optionState) { + case TRUE -> true; + case FALSE -> false; + case DEFAULT -> throw new IllegalArgumentException("No boolean value available for DEFAULT"); + }; + } + + /** + * Gets the corresponding option state from the given boolean + * + * @param value

The boolean to parse

+ * @return

The corresponding option state

+ */ + public static OptionState getFromBoolean(boolean value) { + if (value) { + return OptionState.TRUE; + } else { + return OptionState.FALSE; + } + } + + /** + * Gets the option state corresponding to the given string + * + * @param string

The string to parse to an option state

+ * @return

The option state corresponding to the given string

+ */ + public static OptionState fromString(String string) { + if (string.equalsIgnoreCase("default") || string.equalsIgnoreCase("def")) { + return OptionState.DEFAULT; + } else { + return getFromBoolean(Boolean.parseBoolean(string)); + } + } + +} diff --git a/src/main/java/net/knarcraft/paidsigns/utility/ColorHelper.java b/src/main/java/net/knarcraft/paidsigns/utility/ColorHelper.java new file mode 100644 index 0000000..24b2c76 --- /dev/null +++ b/src/main/java/net/knarcraft/paidsigns/utility/ColorHelper.java @@ -0,0 +1,43 @@ +package net.knarcraft.paidsigns.utility; + +import net.md_5.bungee.api.ChatColor; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A helper class for dealing with colors + */ +public final class ColorHelper { + + private ColorHelper() { + + } + + /** + * Strips all color codes from the given message + * + * @param message

The message to strip color codes from

+ * @return

The message without color codes

+ */ + public static String stripColorCodes(String message) { + return ChatColor.stripColor(translateAllColorCodes(message)); + } + + /** + * Translates all found color codes to formatting in a string + * + * @param message

The string to search for color codes

+ * @return

The message with color codes translated

+ */ + public static String translateAllColorCodes(String message) { + message = ChatColor.translateAlternateColorCodes('&', message); + Pattern pattern = Pattern.compile("(#[a-fA-F0-9]{6})"); + Matcher matcher = pattern.matcher(message); + while (matcher.find()) { + message = message.replace(matcher.group(), "" + ChatColor.of(matcher.group())); + } + return message; + } + +} diff --git a/src/main/java/net/knarcraft/paidsigns/utility/Tokenizer.java b/src/main/java/net/knarcraft/paidsigns/utility/Tokenizer.java new file mode 100644 index 0000000..30d3563 --- /dev/null +++ b/src/main/java/net/knarcraft/paidsigns/utility/Tokenizer.java @@ -0,0 +1,99 @@ +package net.knarcraft.paidsigns.utility; + +import java.util.ArrayList; +import java.util.List; + +/** + * A tokenizer for being able to support quotes in commands + */ +public class Tokenizer { + + /** + * Tokenizes a string + * + * @param input

A string.

+ * @return

A list of tokens.

+ */ + public static List tokenize(String input) { + List tokens = new ArrayList<>(); + boolean startedQuote = false; + StringBuilder currentToken = new StringBuilder(); + for (int index = 0; index < input.length(); index++) { + char character = input.charAt(index); + switch (character) { + case ' ': + if (tokenizeSpace(startedQuote, currentToken, tokens)) { + currentToken = new StringBuilder(); + } + break; + case '"': + if (startedQuote) { + //This quote signifies the end of the argument + if (isNotEmpty(currentToken)) { + tokens.add(currentToken.toString()); + currentToken = new StringBuilder(); + } + startedQuote = false; + } else { + //This quote signifies the start of the argument + startedQuote = true; + currentToken = new StringBuilder(); + } + break; + default: + tokenizeNormalCharacter(currentToken, character, input.length(), index, tokens); + break; + } + } + return tokens; + } + + /** + * Adds a normal character to the token. Adds the current token to tokens if at the end of the input + * + * @param currentToken

The string builder containing the current token.

+ * @param character

The character found in the input.

+ * @param inputLength

The length of the given input

+ * @param index

The index of the read character.

+ * @param tokens

The list of processed tokens.

+ */ + private static void tokenizeNormalCharacter(StringBuilder currentToken, char character, int inputLength, int index, + List tokens) { + currentToken.append(character); + if (index == inputLength - 1) { + tokens.add(currentToken.toString()); + } + } + + /** + * Tokenizes a space character + * + * @param startedQuote

Whether this space is inside a pair of quotes.

+ * @param currentToken

The string builder containing the current token.

+ * @param tokens

The list of processed tokens.

+ * @return

True if the token is finished.

+ */ + private static boolean tokenizeSpace(boolean startedQuote, StringBuilder currentToken, List tokens) { + if (!startedQuote) { + //If not inside "", a space marks the end of a parameter + if (isNotEmpty(currentToken)) { + tokens.add(currentToken.toString()); + } + return true; + } else { + currentToken.append(' '); + return false; + } + } + + /** + * Checks whether a string builder is empty + * + * @param builder

The string builder to check.

+ * @return

True if the string builder is non empty.

+ */ + private static boolean isNotEmpty(StringBuilder builder) { + return !builder.toString().trim().equals(""); + } + +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..c633c34 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,8 @@ +# 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. +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 diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..4341c77 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,39 @@ +name: PaidSigns +version: '${project.version}' +main: net.knarcraft.paidsigns.PaidSigns +api-version: 1.18 +prefix: PaidSigns +depend: [ Vault ] +authors: [ EpicKnarvik97 ] +description: Add costs for creating plugin signs +website: https://git.knarcraft.net +commands: + addpaidsign: + description: Used to add a new paid sign + usage: / [ignore case] [ignore color] + permission: paidsigns.add + removepaidsign: + description: Used to remove a paid sign + usage: / + permission: paidsigns.remove + reload: + description: Reloads paid signs from disk + usage: / + permision: paidsigns.reload +permissions: + paidsigns.*: + description: Grants all paid signs permissions + default: op + children: + paidsigns.create: true + paidsigns.paymentexempt: true + paidsigns.reload: true + paidsigns.add: + description: Grants the permission to add/remove a paid sign + default: false + paidsigns.reload: + description: Grants the permissions to reload the plugin + default: false + paidsigns.paymentexempt: + description: Makes this player exempt from the cost of paid signs + default: false