26 Commits

Author SHA1 Message Date
797020aa7f Updates version to 0.3.0-ALPHA 2022-02-28 15:14:59 +01:00
15426a46f3 Registers the block break listener and adds some small improvements 2022-02-28 15:06:58 +01:00
a1b1a5d112 Adds sign tracking and refunds #3 #8 2022-02-28 14:41:14 +01:00
996d062674 Adds additional information to the README 2022-02-27 15:49:25 +01:00
990339499a Fixes list paid signs tab completion 2022-02-27 14:51:37 +01:00
3b7268d2ff Fixes various tab completion problems 2022-02-27 14:28:46 +01:00
91d477b45a Fixes various bugs in commands 2022-02-27 12:47:05 +01:00
0f958f0908 Makes sure tab completed paid sign names are quoted 2022-02-26 23:15:48 +01:00
36678fd2d0 Fixes the index of the cost argument in the add command 2022-02-26 22:50:03 +01:00
4e44909f80 Fixes some errors caused when calling a command without an argument 2022-02-25 22:30:48 +01:00
0ad953cc14 Updates version to 0.2.0-ALPHA 2022-02-25 22:18:30 +01:00
db869c0c5b Adds a tab completer for the list command 2022-02-25 22:16:11 +01:00
8a030276c5 Removes some redundancy between tab completers 2022-02-25 20:38:10 +01:00
7e01d77723 Finishes the implementation of the list command 2022-02-25 19:41:43 +01:00
86cb1c0fed Removes some redundancy between commands 2022-02-25 18:18:52 +01:00
5bc62d5bc0 Adds some TODOs 2022-02-19 18:29:38 +01:00
2f559ce2af Implements the remove condition command and its tab completer 2022-02-19 18:22:39 +01:00
90b5ff7304 Implements the add condition command and its tab completion 2022-02-19 17:51:27 +01:00
d76d5cdf93 Fixes tab completion for the add and remove commands #7 2022-02-19 00:00:50 +01:00
6f35da03e7 Makes some changes in preparation for some commands that need to be added
Prepares the new command layout, changes the .add permission to .manage and adds 4 empty command classes
2022-02-18 23:16:47 +01:00
eaa4f929ca Fixes the check for testing if a paid sign already exists 2022-02-18 21:04:23 +01:00
419a79bc9f Updates the plugin description and the add tab completer 2022-02-18 19:59:50 +01:00
16faa1ddb2 Rewrites a bunch of code to improve the structure as specified in #5, #4, #2, #1 2022-02-18 19:39:20 +01:00
d2f152334f Updates README with commands 2022-02-18 01:37:47 +01:00
4189053ed8 Fixes the usage of /removepaidsign 2022-02-18 01:37:35 +01:00
5e52e3d4de Changes version to make sure this is treated as an alpha version 2022-02-18 00:58:45 +01:00
29 changed files with 1437 additions and 318 deletions

View File

@ -10,3 +10,81 @@ As this plugin only listens to sign change events, there are some limitations:
1. The plugin is not aware of whether the creation of a sign is successful
2. It is assumed that any protection plugins run before this plugin, but it's not guaranteed
3. Plugins changing the lines on signs when successful might create confusion and mismatches
## Commands
* /addpaidsign <name> <cost> \[permission] \[ignore case] \[ignore color]
* /addpaidsigncondition <name (of a paid sign)> <line number> <string to match> \[executeRegEx] \[ignoreCase]
\[ignoreColor]
* /listpaidsigns \[name (of a paid sign)] \[line number]
* /removepaidsigncondition <name (of a paid sign)> <line number>
* /removepaidsign <name (of a paid sign)>
* /reload
## Command explanation
### /addpaidsign
This command adds a new paid sign that does nothing until a condition is added.
* name - A recognizable name only used to differentiate between registered paid signs
* cost - The cost a player need to pay to create any sign matching the paid sign
* permission - If the paid sign is used to represent a plugin sign, the permission should be the permission necessary
for creating the plugin sign. This is used to decide if the plugin sign was created, or the player was denied.
* ignore case - Whether any conditions of the paid sign should ignore case by default, when matching against text (
default uses the config file value).
* ignore color - Whether any condition of the paid sign should ignore color by default, when matching against text (
default uses the config file value).
### /addpaidsigncondition
This adds a condition to a paid sign which is used to decide if a sign created by a player matches the paid sign. Adding
a paid sign condition to a line that already has one will replace the previous condition.
* name - The name of the paid sign to add the condition to
* line number - The line on the sign (1-4) to search for any matches
* string to match - The string or regular expression to look for on a sign
* executeRegEx - Whether to use a regular expression match instead of looking for the exact string
* ignoreCase - Whether this condition should ignore case when trying to match the string (default uses the "parent"
sign's value)
* ignoreColor - Whether this condition should ignore color when trying to match the string (default uses the "parent"
sign's value)
### /listpaidsigns
This lists registered paid signs and paid sign conditions. No arguments will print a list of paid signs
* name - The name of the paid sign to see information about
* line number - The line number of the condition to see information about
### /removepaidsigncondition
Removes a paid sign condition from a sign
* name - The name of the paid sign to remove the condition from
* line number - The line the condition is associated with
### /removepaidsign
Removes a registered paid sign
* name - The name of the paid sign to remove
## Permissions
* paidsigns.* - Grants all paid signs permissions
* paidsigns.manage - Grants the permission to add/remove a paid sign
* paidsigns.reload - Grants the permissions to reload the plugin
* paidsigns.paymentexempt - Makes this player exempt from the cost of paid signs
## Configuration options
* ignoreCase - 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.
* ignoreColor - 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.
* enableRefunds - 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)
* refundPercentage - The percentage of the paid sign cost to refund (0-100)

View File

@ -6,7 +6,7 @@
<groupId>net.knarcraft</groupId>
<artifactId>paidsigns</artifactId>
<version>1.0-SNAPSHOT</version>
<version>0.3.0-ALPHA</version>
<packaging>jar</packaging>
<name>Paid Signs</name>

View File

@ -1,15 +1,24 @@
package net.knarcraft.paidsigns;
import net.knarcraft.paidsigns.command.AddCommand;
import net.knarcraft.paidsigns.command.AddConditionCommand;
import net.knarcraft.paidsigns.command.AddConditionTabCompleter;
import net.knarcraft.paidsigns.command.AddTabCompleter;
import net.knarcraft.paidsigns.command.ListCommand;
import net.knarcraft.paidsigns.command.ListTabCompleter;
import net.knarcraft.paidsigns.command.ReloadTabCommand;
import net.knarcraft.paidsigns.command.RemoveCommand;
import net.knarcraft.paidsigns.command.RemoveTabCompleter;
import net.knarcraft.paidsigns.command.RemoveConditionCommand;
import net.knarcraft.paidsigns.command.RemoveConditionTabCompleter;
import net.knarcraft.paidsigns.command.RemoveTabCommand;
import net.knarcraft.paidsigns.listener.BlockBreakListener;
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;
@ -26,6 +35,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
@ -48,10 +59,12 @@ public final class PaidSigns extends JavaPlugin {
public void onEnable() {
setupVault();
signManager = new PaidSignManager(PaidSignManager.loadSigns());
TrackedSignManager.loadTrackedSigns();
loadConfig();
PluginManager pluginManager = getServer().getPluginManager();
pluginManager.registerEvents(new SignListener(), this);
pluginManager.registerEvents(new BlockBreakListener(), this);
registerCommands();
}
@ -67,6 +80,7 @@ public final class PaidSigns extends JavaPlugin {
this.reloadConfig();
loadConfig();
signManager = new PaidSignManager(PaidSignManager.loadSigns());
TrackedSignManager.loadTrackedSigns();
}
/**
@ -96,27 +110,56 @@ public final class PaidSigns extends JavaPlugin {
return this.ignoreColor;
}
/**
* Checks whether refunds are currently enabled
*
* @return <p>Whether refunds are currently enabled</p>
*/
public boolean areRefundsEnabled() {
return this.enableRefunds;
}
/**
* Gets the percentage of the initial cost to refund the sign creator
*
* @return <p>The percentage of the cost to refund</p>
*/
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 removeCommand = this.getCommand("removePaidSign");
if (removeCommand != null) {
removeCommand.setExecutor(new RemoveCommand());
removeCommand.setTabCompleter(new RemoveTabCompleter());
}
TabExecutor removeTabExecutor = new RemoveTabCommand();
registerCommand("removePaidSign", removeTabExecutor, removeTabExecutor);
TabExecutor reloadTabExecutor = new ReloadTabCommand();
registerCommand("reload", reloadTabExecutor, reloadTabExecutor);
}
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 <p>The command to register</p>
* @param commandExecutor <p>The command executor for executing the command</p>
* @param tabCompleter <p>The tab completer for tab-completing the command</p>
*/
private void registerCommand(String command, CommandExecutor commandExecutor, TabCompleter tabCompleter) {
PluginCommand pluginCommand = this.getCommand(command);
if (pluginCommand != null) {
pluginCommand.setExecutor(commandExecutor);
pluginCommand.setTabCompleter(tabCompleter);
}
}
@ -129,6 +172,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);
}
/**

View File

@ -4,58 +4,71 @@ 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 {
public class AddCommand extends TokenizedCommand {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) {
if (args.length < 3) {
super.onCommand(sender, command, label, args);
if (argumentSize < 2) {
return false;
}
PaidSignManager manager = PaidSigns.getInstance().getSignManager();
List<String> arguments = Tokenizer.tokenize(String.join(" ", args));
String id = arguments.get(0);
short line;
String signName = arguments.get(0).trim();
double cost;
try {
line = (short) (Short.parseShort(arguments.get(1)) - 1);
cost = Double.parseDouble(arguments.get(2));
cost = Double.parseDouble(arguments.get(1));
} catch (NumberFormatException exception) {
sender.sendMessage("You provided an invalid number");
return false;
}
String permission = "";
if (argumentSize > 2) {
permission = arguments.get(2);
}
OptionState ignoreCase = OptionState.DEFAULT;
OptionState ignoreColor = OptionState.DEFAULT;
if (arguments.size() > 3) {
if (argumentSize > 3) {
ignoreCase = OptionState.fromString(arguments.get(3));
}
if (arguments.size() > 4) {
if (argumentSize > 4) {
ignoreColor = OptionState.fromString(arguments.get(4));
}
return createPaidSign(sender, signName, cost, permission, ignoreCase, ignoreColor);
}
/**
* Creates a new paid sign with the given user input
*
* @param sender <p>The command sender that called the add command</p>
* @param signName <p>The name of the new paid sign</p>
* @param cost <p>The cost of the new paid sign</p>
* @param permission <p>The permission required for creating the sign represented by the paid sign</p>
* @param ignoreCase <p>Whether to ignore case for the paid sign's conditions</p>
* @param ignoreColor <p>Whether to ignore color for the paid sign's conditions</p>
* @return <p>True if the paid sign was successfully created and registered</p>
*/
private boolean createPaidSign(CommandSender sender, String signName, double cost, String permission,
OptionState ignoreCase, OptionState ignoreColor) {
PaidSignManager manager = PaidSigns.getInstance().getSignManager();
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;
}
PaidSign sign = new PaidSign(signName, cost, permission, ignoreCase, ignoreColor);
if (manager.getPaidSign(signName) != null) {
sender.sendMessage("A paid sign with the same name already exists");
return false;
}
manager.addPaidSign(sign);
sender.sendMessage("Successfully added new paid sign");

View File

@ -0,0 +1,69 @@
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 org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A representation of the command for adding a new match condition for a sign
*/
public class AddConditionCommand extends TokenizedCommand {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
super.onCommand(sender, command, label, args);
if (argumentSize < 3) {
return false;
}
String name = arguments.get(0);
short lineNumber;
try {
lineNumber = (short) (Short.parseShort(arguments.get(1)) - 1);
} catch (NumberFormatException exception) {
sender.sendMessage("Invalid line number given");
return false;
}
String stringToMatch = arguments.get(2);
boolean executeRegEx = false;
if (argumentSize > 3) {
executeRegEx = Boolean.parseBoolean(arguments.get(3));
}
OptionState ignoreCase = OptionState.DEFAULT;
if (argumentSize > 4) {
ignoreCase = OptionState.getFromBoolean(Boolean.parseBoolean(arguments.get(4)));
}
OptionState ignoreColor = OptionState.DEFAULT;
if (argumentSize > 5) {
ignoreColor = OptionState.getFromBoolean(Boolean.parseBoolean(arguments.get(5)));
}
PaidSignManager signManager = PaidSigns.getInstance().getSignManager();
PaidSign sign = signManager.getPaidSign(name);
if (sign == null) {
sender.sendMessage("No such paid sign exists");
return false;
}
sign.addCondition(lineNumber, stringToMatch, executeRegEx, ignoreCase, ignoreColor);
try {
signManager.saveSigns();
} 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;
}
sender.sendMessage("Condition added");
return true;
}
}

View File

@ -0,0 +1,56 @@
package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.utility.TabCompleteHelper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
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 condition command
*/
public class AddConditionTabCompleter extends TokenizedTabCompleter {
private List<String> lineIndices;
private List<String> stringsToMatch;
private List<String> booleans;
private List<String> optionStates;
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] args) {
super.onTabComplete(sender, command, alias, args);
if (lineIndices == null) {
initializeValues();
}
if (argumentSize == 1) {
return TabCompleteHelper.filterMatchingStartsWith(TabCompleteHelper.getPaidSignNames(), arguments.get(0));
} else if (argumentSize == 2) {
return TabCompleteHelper.filterMatchingStartsWith(this.lineIndices, arguments.get(1));
} else if (argumentSize == 3) {
return TabCompleteHelper.filterMatchingStartsWith(stringsToMatch, arguments.get(2));
} else if (argumentSize == 4) {
return TabCompleteHelper.filterMatchingStartsWith(booleans, arguments.get(3));
} else if (argumentSize == 5) {
return TabCompleteHelper.filterMatchingStartsWith(optionStates, arguments.get(4));
} else if (argumentSize == 6) {
return TabCompleteHelper.filterMatchingStartsWith(optionStates, arguments.get(5));
}
return new ArrayList<>();
}
/**
* Initializes the values available for tab completion
*/
private void initializeValues() {
lineIndices = TabCompleteHelper.getSignLines();
stringsToMatch = TabCompleteHelper.getExampleConditionStrings();
booleans = TabCompleteHelper.getBooleans();
optionStates = TabCompleteHelper.getOptionStates();
}
}

View File

@ -1,71 +1,136 @@
package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.utility.Tokenizer;
import net.knarcraft.paidsigns.utility.TabCompleteHelper;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.permissions.Permission;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
/**
* The tab completer for the add paid sign command
*/
public class AddTabCompleter implements TabCompleter {
public class AddTabCompleter extends TokenizedTabCompleter {
private static List<String> ids;
private static List<String> lines;
private static List<String> names;
private static List<String> costs;
private static List<String> plugins;
private static Map<String, List<String>> permissions;
private static List<String> options;
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] args) {
if (ids == null) {
super.onTabComplete(sender, command, alias, args);
if (names == null) {
initializeValues();
loadAvailablePermissions();
}
List<String> 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;
if (argumentSize == 1) {
return TabCompleteHelper.filterMatchingStartsWith(names, arguments.get(0));
} else if (argumentSize == 2) {
return TabCompleteHelper.filterMatchingStartsWith(costs, arguments.get(1));
} else if (argumentSize == 3) {
return tabCompletePermission(arguments.get(arguments.size() - 1));
} else if (argumentSize == 4) {
return TabCompleteHelper.filterMatchingStartsWith(options, arguments.get(3));
} else if (argumentSize == 5) {
return TabCompleteHelper.filterMatchingStartsWith(options, arguments.get(4));
}
return new ArrayList<>();
}
/**
* Gets the tab complete value for the permission typed
*
* @param typedNode <p>The full permission node typed by the player</p>
* @return <p>All known valid auto-complete options</p>
*/
private List<String> tabCompletePermission(String typedNode) {
List<String> output;
if (typedNode.contains(".")) {
List<String> matchingPermissions = permissions.get(typedNode.substring(0, typedNode.lastIndexOf(".")));
if (matchingPermissions == null) {
output = new ArrayList<>();
} else {
//Filter by the typed text
output = filterMatching(matchingPermissions, typedNode);
}
} else {
output = plugins;
}
//Add previous permissions in the comma-separated lists as a prefix
return output;
}
/**
* Find completable strings which match the text typed by the command's sender
*
* @param values <p>The values to filter</p>
* @param typedText <p>The text the player has started typing</p>
* @return <p>The given string values which start with the player's typed text</p>
*/
private List<String> filterMatching(List<String> values, String typedText) {
List<String> configValues = new ArrayList<>();
for (String value : values) {
if (value.toLowerCase().startsWith(typedText.toLowerCase())) {
configValues.add(value);
}
}
return configValues;
}
/**
* Loads all permissions available from bukkit plugins
*/
private static void loadAvailablePermissions() {
plugins = new ArrayList<>();
permissions = new HashMap<>();
for (Permission permission : Bukkit.getPluginManager().getPermissions()) {
loadPermission(permission.getName());
}
}
/**
* Loads a given permission into the proper lists and maps
*
* @param permissionName <p>The permission to load</p>
*/
private static void loadPermission(String permissionName) {
String[] permissionParts = permissionName.split("\\.");
if (permissionParts.length == 1 && !plugins.contains(permissionParts[0])) {
plugins.add(permissionParts[0]);
} else if (permissionParts.length > 1) {
StringJoiner pathJoiner = new StringJoiner(".");
for (int j = 0; j < permissionParts.length - 1; j++) {
pathJoiner.add(permissionParts[j]);
}
String path = pathJoiner.toString();
List<String> permissionList = permissions.computeIfAbsent(path, k -> new ArrayList<>());
permissionList.add(permissionName);
loadPermission(path);
}
}
/**
* 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");
names = TabCompleteHelper.getExamplePaidSignNames();
costs = TabCompleteHelper.getCosts();
options = TabCompleteHelper.getOptionStates();
}
}

View File

@ -0,0 +1,104 @@
package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.container.PaidSign;
import net.knarcraft.paidsigns.container.PaidSignCondition;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
/**
* A representation of the command for listing information about paid signs
*/
public class ListCommand extends TokenizedCommand {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) {
super.onCommand(sender, command, label, args);
if (argumentSize < 1) {
sender.sendMessage("Paid signs:");
for (String signName : PaidSigns.getInstance().getSignManager().getAllPaidSigns().keySet()) {
sender.sendMessage(" | " + signName);
}
return true;
} else if (argumentSize < 3) {
return parsePaidSignSelection(sender);
}
return false;
}
/**
* Parses the given input and displays the wanted paid-sign information
*
* @param sender <p>The command sender to display the information to</p>
* @return <p>True if successful. False if the input contained errors</p>
*/
private boolean parsePaidSignSelection(CommandSender sender) {
PaidSign paidSign = PaidSigns.getInstance().getSignManager().getPaidSign(arguments.get(0));
if (paidSign == null) {
sender.sendMessage("No such paid sign");
return false;
}
if (argumentSize < 2) {
displayPaidSign(sender, paidSign);
} else {
short signLine;
try {
signLine = (short) (Short.parseShort(arguments.get(1)) - 1);
} catch (NumberFormatException exception) {
sender.sendMessage("Invalid number given");
return false;
}
if (!paidSign.getConditions().containsKey(signLine)) {
sender.sendMessage("The paid sign you specified has no condition for line " + signLine);
return false;
}
PaidSignCondition condition = paidSign.getConditions().get(signLine);
displayPaidSignCondition(sender, paidSign.getName(), signLine, condition);
}
return true;
}
/**
* Displays information about a paid sign condition
*
* @param sender <p>The command sender to display the information to</p>
* @param signName <p>The name of the sign to display the condition for</p>
* @param signLine <p>The line the condition is for</p>
* @param condition <p>The condition to display information about</p>
*/
private void displayPaidSignCondition(CommandSender sender, String signName, short signLine,
PaidSignCondition condition) {
sender.sendMessage("Paid sign condition info: ");
sender.sendMessage("Paid sign name: " + signName);
sender.sendMessage("Condition line: " + signLine);
sender.sendMessage("Condition match string: " + condition.getStringToMatch());
sender.sendMessage("Execute RegEx: " + condition.executeRegex());
sender.sendMessage("Ignore case: " + condition.ignoreCase());
sender.sendMessage("Ignore color: " + condition.ignoreColor());
}
/**
* Displays information about a paid sign
*
* @param sender <p>The command sender to display the information to</p>
* @param paidSign <p>The paid sign to display information about</p>
*/
private void displayPaidSign(CommandSender sender, PaidSign paidSign) {
sender.sendMessage("Paid sign info:");
sender.sendMessage("Name: " + paidSign.getName());
sender.sendMessage("Cost: " + paidSign.getCost());
sender.sendMessage("Permission: " + paidSign.getPermission());
sender.sendMessage("Ignore case: " + paidSign.getIgnoreCase());
sender.sendMessage("Ignore color: " + paidSign.getIgnoreColor());
sender.sendMessage("Sign conditions: ");
Map<Short, PaidSignCondition> conditions = paidSign.getConditions();
for (short lineIndex : conditions.keySet()) {
sender.sendMessage(" | " + (lineIndex + 1) + ". " + conditions.get(lineIndex).getStringToMatch());
}
}
}

View File

@ -0,0 +1,39 @@
package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.container.PaidSign;
import net.knarcraft.paidsigns.utility.TabCompleteHelper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* The tab completer for the list paid signs command
*/
public class ListTabCompleter extends TokenizedTabCompleter {
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] args) {
super.onTabComplete(sender, command, alias, args);
if (argumentSize == 1) {
return TabCompleteHelper.filterMatchingStartsWith(TabCompleteHelper.getPaidSignNames(), arguments.get(0));
} else if (argumentSize == 2) {
PaidSign sign = PaidSigns.getInstance().getSignManager().getPaidSign(arguments.get(0));
if (sign != null) {
List<String> availableConditions = new ArrayList<>();
for (Short signLine : sign.getConditions().keySet()) {
availableConditions.add(String.valueOf(signLine + 1));
}
return TabCompleteHelper.filterMatchingStartsWith(availableConditions, arguments.get(1));
}
}
return new ArrayList<>();
}
}

View File

@ -18,7 +18,8 @@ public class ReloadTabCommand implements TabExecutor {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) {
PaidSigns.getInstance().reload();
return false;
sender.sendMessage("PaidSigns reloaded");
return true;
}
@Override

View File

@ -0,0 +1,61 @@
package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.container.PaidSign;
import net.knarcraft.paidsigns.manager.PaidSignManager;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A representation of the command for removing a condition from a sign
*/
public class RemoveConditionCommand extends TokenizedCommand {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
super.onCommand(sender, command, label, args);
if (argumentSize < 2) {
return false;
}
String name = arguments.get(0);
short line;
try {
line = (short) (Short.parseShort(arguments.get(1)) - 1);
} catch (NumberFormatException exception) {
sender.sendMessage("Invalid line number given");
return false;
}
PaidSignManager signManager = PaidSigns.getInstance().getSignManager();
PaidSign sign = signManager.getPaidSign(name);
if (sign == null) {
sender.sendMessage("Invalid sign name given");
return false;
}
if (!sign.getConditions().containsKey(line)) {
sender.sendMessage("No condition registered for line " + (line + 1));
return false;
}
sign.removeCondition(line);
try {
signManager.saveSigns();
} 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;
}
sender.sendMessage("Condition removed");
return true;
}
}

View File

@ -0,0 +1,52 @@
package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.container.PaidSign;
import net.knarcraft.paidsigns.utility.TabCompleteHelper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* The tab completer for the remove paid sign condition command
*/
public class RemoveConditionTabCompleter extends TokenizedTabCompleter {
private List<String> lineIndices;
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] args) {
super.onTabComplete(sender, command, alias, args);
if (lineIndices == null) {
initializeValues();
}
if (argumentSize == 1) {
return TabCompleteHelper.filterMatchingStartsWith(TabCompleteHelper.getPaidSignNames(), arguments.get(0));
} else if (argumentSize == 2) {
PaidSign sign = PaidSigns.getInstance().getSignManager().getPaidSign(arguments.get(0));
if (sign != null) {
List<String> availableConditions = new ArrayList<>();
for (Short signLine : sign.getConditions().keySet()) {
availableConditions.add(String.valueOf(signLine + 1));
}
return TabCompleteHelper.filterMatchingStartsWith(availableConditions, arguments.get(1));
}
}
return new ArrayList<>();
}
/**
* Initializes the values available for tab completion
*/
private void initializeValues() {
lineIndices = TabCompleteHelper.getSignLines();
}
}

View File

@ -1,42 +1,38 @@
package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.container.PaidSign;
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.bukkit.command.TabExecutor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
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 {
public class RemoveTabCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) {
if (args.length < 1) {
return false;
}
List<String> 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");
if (arguments.size() < 1) {
return false;
}
String id = String.join("|", (String[]) ArrayUtils.remove(input, 0));
String name = arguments.get(0);
try {
if (PaidSigns.getInstance().getSignManager().removePaidSign(id, line)) {
if (PaidSigns.getInstance().getSignManager().removePaidSign(name)) {
sender.sendMessage("Successfully removed paid sign");
} else {
sender.sendMessage("No matching paid sign was found");
@ -52,4 +48,23 @@ public class RemoveCommand implements CommandExecutor {
return false;
}
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] args) {
List<String> arguments = Tokenizer.tokenize(String.join(" ", args));
int argumentSize = args[args.length - 1].isEmpty() ? arguments.size() : arguments.size() - 1;
if (argumentSize < 1) {
Map<String, PaidSign> allPaidSigns = PaidSigns.getInstance().getSignManager().getAllPaidSigns();
List<String> signNames = new ArrayList<>();
for (String name : allPaidSigns.keySet()) {
signNames.add("\"" + name + "\"");
}
return signNames;
} else {
return new ArrayList<>();
}
}
}

View File

@ -1,31 +0,0 @@
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<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] args) {
List<PaidSign> allPaidSigns = PaidSigns.getInstance().getSignManager().getAllPaidSigns();
List<String> signIds = new ArrayList<>();
for (PaidSign sign : allPaidSigns) {
signIds.add("\"" + (sign.getLineIndex() + 1) + "|" + sign.getId() + "\"");
}
return signIds;
}
}

View File

@ -0,0 +1,26 @@
package net.knarcraft.paidsigns.command;
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.util.List;
/**
* A command executor with tokenized arguments
*/
public class TokenizedCommand implements CommandExecutor {
protected List<String> arguments;
protected int argumentSize;
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
arguments = Tokenizer.tokenize(String.join(" ", args));
argumentSize = arguments.size();
return true;
}
}

View File

@ -0,0 +1,36 @@
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.List;
/**
* A tab completer with tokenized arguments
*/
public class TokenizedTabCompleter implements TabCompleter {
protected List<String> arguments;
protected int argumentSize;
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] args) {
arguments = Tokenizer.tokenize(String.join(" ", args));
if (args.length == 0) {
argumentSize = 0;
} else {
if (args[args.length - 1].isEmpty()) {
arguments.add("");
}
argumentSize = arguments.size();
}
return null;
}
}

View File

@ -2,58 +2,55 @@ 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;
import java.util.HashMap;
import java.util.Map;
/**
* A representation of a paid sign
*/
public class PaidSign {
private final String id;
private final String cleanId;
private final short lineIndex;
private final String name;
private final double cost;
private final String permission;
private final OptionState ignoreCase;
private final OptionState ignoreColor;
private final Map<Short, PaidSignCondition> conditions = new HashMap<>();
/**
* Instantiates a new paid sign
*
* @param id <p>The string that identifies this type of paid sign</p>
* @param lineIndex <p>The line the id has to be on to trigger payment</p>
* @param name <p>A recognizable, unique, name used to identify this paid sign</p>
* @param cost <p>The cost of creating this paid sign</p>
* @param permission <p>The permission required to create the sign this paid sign matches</p>
* @param ignoreCase <p>Whether to ignore case when looking for this permission sign</p>
* @param ignoreColor <p>Whether to ignore color when looking for this permission sign</p>
*/
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");
public PaidSign(String name, double cost, String permission, OptionState ignoreCase, OptionState ignoreColor) {
if (name == null || name.trim().isBlank()) {
throw new IllegalArgumentException("Name 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.name = name;
this.cost = cost;
this.permission = permission;
this.ignoreCase = ignoreCase;
this.ignoreColor = ignoreColor;
this.cleanId = getCleanString(id);
}
/**
* Gets the id string of this paid sign
* Gets the name identifying this paid sign
*
* @return <p>The id string of this paid sign</p>
* @return <p>The name identifying this paid sign</p>
*/
public String getId() {
return id;
public String getName() {
return name;
}
/**
@ -66,66 +63,21 @@ public class PaidSign {
}
/**
* Gets the line on the sign the id must be on to trigger payment
* Gets the permission required by a player to create the sign this paid sign matches
*
* @return <p>The sign line to search for the id</p>
* @return <p>The permission required by a player to create the sign this paid sign matches</p>
*/
public short getLineIndex() {
return lineIndex;
public String getPermission() {
return this.permission;
}
/**
* Gets the clean id of this paid sign
* Gets all conditions registered for this paid sign
*
* @return <p>The clean id of this paid sign</p>
* @return <p>All conditions registered for this paid sign</p>
*/
public String getCleanId() {
return cleanId;
}
/**
* Gets the "clean" version of the given string
*
* <p>The "cleaning" removes color codes and lower-cases the string.</p>
*
* @param string <p>The string to clean</p>
* @return <p>The cleaned string</p>
*/
public static String getCleanString(String string) {
return ChatColor.stripColor(ColorHelper.translateAllColorCodes(string.toLowerCase()));
}
/**
* Checks whether this paid sign matches the given line
*
* @param lineNumber <p>The line number of the given line</p>
* @param line <p>The line to compare against this paid sign's id</p>
* @return <p>True if the line matches this sign</p>
*/
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 <p>The other paid sign to compare to</p>
* @return <p>True if the signs match</p>
*/
public boolean matches(PaidSign paidSign) {
return matches(paidSign.lineIndex, paidSign.id);
public Map<Short, PaidSignCondition> getConditions() {
return new HashMap<>(this.conditions);
}
/**
@ -134,11 +86,7 @@ public class PaidSign {
* @return <p>Whether the text case should be ignored for this paid sign</p>
*/
public boolean getIgnoreCase() {
if (this.ignoreCase == OptionState.DEFAULT) {
return PaidSigns.getInstance().ignoreCase();
} else {
return OptionState.getBooleanValue(this.ignoreCase);
}
return OptionState.getBooleanValue(this.ignoreCase, PaidSigns.getInstance().ignoreCase());
}
/**
@ -147,11 +95,55 @@ public class PaidSign {
* @return <p>Whether the text color should be ignored for this paid sign</p>
*/
public boolean getIgnoreColor() {
if (this.ignoreColor == OptionState.DEFAULT) {
return PaidSigns.getInstance().ignoreColor();
} else {
return OptionState.getBooleanValue(this.ignoreColor);
return OptionState.getBooleanValue(this.ignoreColor, PaidSigns.getInstance().ignoreColor());
}
/**
* Checks whether this paid sign matches the given set of sign lines
*
* @param lines <p>The sign lines to test against</p>
* @return <p>True if this paid sign matches the given lines</p>
*/
public boolean matches(String[] lines) {
//Make sure a paid sign without a condition never matches anything
if (this.conditions.isEmpty()) {
return false;
}
boolean success = true;
for (short i = 0; i < 4; i++) {
PaidSignCondition condition = this.conditions.get(i);
if (condition != null) {
success = success && condition.test(lines[i]);
}
}
return success;
}
/**
* Adds a condition to this paid sign
*
* @param line <p>The line on the sign the matched text must be on</p>
* @param stringToMatch <p>The string that should be matched for the new condition to be true</p>
* @param executeRegex <p>Whether to execute the string as RegEx</p>
* @param ignoreCase <p>Whether to ignore case when matching against the condition</p>
* @param ignoreColor <p>Whether to ignore color when matching against the condition</p>
*/
public void addCondition(short line, String stringToMatch, boolean executeRegex, OptionState ignoreCase, OptionState ignoreColor) {
if (line < 0 || line > 3) {
throw new IllegalArgumentException("Invalid sign line given for new paid sign condition");
}
boolean ignoreCaseBoolean = OptionState.getBooleanValue(ignoreCase, this.getIgnoreCase());
boolean ignoreColorBoolean = OptionState.getBooleanValue(ignoreColor, this.getIgnoreColor());
this.conditions.put(line, new PaidSignCondition(stringToMatch, executeRegex, ignoreCaseBoolean, ignoreColorBoolean));
}
/**
* Removes a condition from this paid sign
*
* @param line <p>The sign line the condition belongs to</p>
*/
public void removeCondition(short line) {
this.conditions.remove(line);
}
}

View File

@ -0,0 +1,96 @@
package net.knarcraft.paidsigns.container;
import net.knarcraft.paidsigns.utility.ColorHelper;
/**
* A condition for deciding if a paid sign matches a sign line
*/
public class PaidSignCondition {
final String stringToMatch;
final boolean executeRegex;
final boolean ignoreCase;
final boolean ignoreColor;
/**
* Instantiates a new paid sign condition
*
* @param stringToMatch <p>The string/regular expression the line has to match to fulfill this condition</p>
* @param executeRegex <p>Whether to execute the match string as a regular expression</p>
* @param ignoreCase <p>Whether to ignore uppercase/lowercase when comparing against this condition</p>
* @param ignoreColor <p>Whether to ignore color codes when comparing against this condition</p>
*/
public PaidSignCondition(String stringToMatch, boolean executeRegex, boolean ignoreCase, boolean ignoreColor) {
this.stringToMatch = stringToMatch;
this.executeRegex = executeRegex;
this.ignoreCase = ignoreCase;
this.ignoreColor = ignoreColor;
}
/**
* Gets the string this condition should match
*
* @return <p>The string this condition should match</p>
*/
public String getStringToMatch() {
return this.stringToMatch;
}
/**
* Gets whether to execute the match string as RegEx
*
* @return <p>Whether to execute the match string as RegEx</p>
*/
public boolean executeRegex() {
return this.executeRegex;
}
/**
* Gets whether to ignore case when trying to match strings
*
* @return <p>Whether to ignore case when trying to match strings</p>
*/
public boolean ignoreCase() {
return this.ignoreCase;
}
/**
* Gets whether to ignore color when trying to match strings
*
* @return <p>Whether to ignore color when trying to match strings</p>
*/
public boolean ignoreColor() {
return this.ignoreColor;
}
/**
* Tests whether the given line matches this condition
*
* @param line <p>The sign line to test</p>
* @return <p>True if this condition matches the given line</p>
*/
public boolean test(String line) {
String stringToMatch = this.stringToMatch;
//Strip color codes if they shouldn't matter
if (this.ignoreColor) {
stringToMatch = ColorHelper.stripColorCodes(stringToMatch);
line = ColorHelper.stripColorCodes(line);
}
if (this.executeRegex) {
//Match using RegEx
if (this.ignoreCase) {
return line.matches("(?i)" + stringToMatch);
} else {
return line.matches(stringToMatch);
}
} else {
//Match regularly
if (this.ignoreCase) {
return stringToMatch.equalsIgnoreCase(line);
} else {
return stringToMatch.equals(line);
}
}
}
}

View File

@ -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 <p>The unique id of the player that created the sign</p>
* @param cost <p>The cost the player paid for creating the sign</p>
*/
public TrackedSign(UUID playerId, double cost) {
this.playerId = playerId;
this.cost = cost;
}
/**
* Gets the id of the player that created this tracked sign
*
* @return <p>The player that created this tracked sign</p>
*/
public UUID getPlayerId() {
return this.playerId;
}
/**
* Gets the cost the player paid for creating this paid sign
*
* @return <p>The cost paid for creating this sign</p>
*/
public double getCost() {
return this.cost;
}
}

View File

@ -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()));
}
}
}
}

View File

@ -3,67 +3,90 @@ 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 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.util.List;
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
*/
public class SignListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR)
@EventHandler(priority = EventPriority.LOW)
@SuppressWarnings("unused")
public void onSignChange(SignChangeEvent event) {
if (event.isCancelled() || event.getPlayer().hasPermission("paidsigns.paymentexempt")) {
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<PaidSign> matchingSigns = signManager.getPaidSigns(PaidSign.getCleanString(lines[lineIndex]), lineIndex);
if (matchingSigns.isEmpty()) {
continue;
}
if (testMatchingSigns(lineIndex, lines[lineIndex], matchingSigns, event)) {
Map<String, PaidSign> matchingSigns = PaidSigns.getInstance().getSignManager().getAllPaidSigns();
for (PaidSign paidSign : matchingSigns.values()) {
//If a match is found, just return
if (matchSign(paidSign, lines, event)) {
return;
}
}
}
/**
* Tests all weak matches of paid signs to check if a strong match is found
* Checks if the given paid sign matches the given sign lines
*
* @param lineIndex <p>The index of the currently managed line</p>
* @param line <p>The text on the currently managed line</p>
* @param matchingSigns <p>The signs that weakly match the </p>
* @param event <p>The triggered sign change event</p>
* @return <p>True if a match was found and thw work is finished</p>
* @param paidSign <p>The paid sign to test against the sign lines</p>
* @param lines <p>The lines of a sign</p>
* @param event <p>The triggered sign change event to cancel if necessary</p>
* @return <p>True if a match was found and actions have been taken</p>
*/
private boolean testMatchingSigns(short lineIndex, String line, List<PaidSign> 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);
}
private boolean matchSign(PaidSign paidSign, String[] lines, SignChangeEvent event) {
if (paidSign.matches(lines)) {
Player player = event.getPlayer();
String permission = paidSign.getPermission();
//If a match is found, but the player is missing the permission, assume no plugin sign was created
if (permission != null && !permission.trim().isEmpty() && !player.hasPermission(permission)) {
return true;
}
performPaidSignTransaction(paidSign, player, event);
return true;
}
return false;
}
/**
* Performs the transaction to pay for the paid sign
*
* @param paidSign <p>The paid sign a match has been found for</p>
* @param player <p>The player that created the sign</p>
* @param event <p>The sign change event that caused the sign to be created</p>
*/
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()));
}
}
}
}

View File

@ -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 <p>The player to withdraw money from</p>
* @param cost <p>The amount of money to withdraw</p>
*/
public static void withdraw(Player player, double cost) {
public static void withdraw(OfflinePlayer player, double cost) {
economy.withdrawPlayer(player, cost);
}

View File

@ -2,15 +2,15 @@ package net.knarcraft.paidsigns.manager;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.container.PaidSign;
import net.knarcraft.paidsigns.container.PaidSignCondition;
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.HashMap;
import java.util.Map;
import java.util.logging.Level;
/**
@ -18,16 +18,15 @@ import java.util.logging.Level;
*/
public final class PaidSignManager {
private final List<PaidSign> paidSigns;
private final Map<String, PaidSign> 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 <p>The paid signs this manager should manage</p>
*/
public PaidSignManager(List<PaidSign> paidSigns) {
public PaidSignManager(Map<String, PaidSign> paidSigns) {
this.paidSigns = paidSigns;
}
@ -38,36 +37,35 @@ public final class PaidSignManager {
* @throws IOException <p>If unable to write to the signs file</p>
*/
public void addPaidSign(PaidSign paidSign) throws IOException {
this.paidSigns.add(paidSign);
saveSigns(this.paidSigns);
this.paidSigns.put(paidSign.getName(), paidSign);
saveSigns();
}
/**
* Removes a paid sign from this paid sign manager
*
* @param id <p>The identifier for the paid sign to remove</p>
* @param line <p>The line the identifier has to match to be valid</p>
* @param name <p>The name of the paid sign to remove</p>
* @return <p>True if a sign was removed</p>
* @throws IOException <p>If unable to write to the signs file</p>
*/
public boolean removePaidSign(String id, short line) throws IOException {
boolean removed = this.paidSigns.removeIf((sign) -> sign.getId().equals(id) && sign.getLineIndex() == line);
public boolean removePaidSign(String name) throws IOException {
boolean removed = this.paidSigns.remove(name) != null;
if (!removed) {
return false;
} else {
saveSigns();
return true;
}
saveSigns(this.paidSigns);
return true;
}
/**
* Gets the paid signs that match the given properties
* Gets the paid sign with the given name
*
* @param cleanId <p>The clean id to search for</p>
* @param line <p>The line number to search for</p>
* @return <p>The paid signs that matched the given properties</p>
* @param name <p>The paid sign with the given name</p>
* @return <p>The paid sign with the given name, or null if it does not exist</p>
*/
public List<PaidSign> getPaidSigns(String cleanId, short line) {
return filterPaidSigns(filterPaidSigns(paidSigns, line), cleanId);
public PaidSign getPaidSign(String name) {
return paidSigns.get(name);
}
/**
@ -75,19 +73,8 @@ public final class PaidSignManager {
*
* @return <p>All registered paid signs</p>
*/
public List<PaidSign> getAllPaidSigns() {
return new ArrayList<>(paidSigns);
}
/**
* Filters a list of paid signs to match the given line number
*
* @param paidSigns <p>The list of paid signs to start with</p>
* @param line <p>The line number to filter by</p>
* @return <p>The filtered list of paid signs</p>
*/
private static List<PaidSign> filterPaidSigns(List<PaidSign> paidSigns, short line) {
return filterPaidSigns(paidSigns, (paidSign) -> paidSign.getLineIndex() == line);
public Map<String, PaidSign> getAllPaidSigns() {
return new HashMap<>(paidSigns);
}
/**
@ -95,66 +82,85 @@ public final class PaidSignManager {
*
* @return <p>The loaded paid signs</p>
*/
public static List<PaidSign> loadSigns() {
public static Map<String, PaidSign> 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<>();
return new HashMap<>();
}
List<PaidSign> 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));
Map<String, PaidSign> paidSigns = new HashMap<>();
for (String name : signSection.getKeys(false)) {
double cost = signSection.getDouble(name + ".cost");
String permission = signSection.getString(name + ".permission");
OptionState ignoreCase = OptionState.getFromBoolean(signSection.getBoolean(name + ".ignoreCase"));
OptionState ignoreColor = OptionState.getFromBoolean(signSection.getBoolean(name + ".ignoreColor"));
PaidSign sign = new PaidSign(name, cost, permission, ignoreCase, ignoreColor);
loadConditions(signSection, sign);
paidSigns.put(name, sign);
}
return paidSigns;
}
/**
* Saves all signs registered to this paid sign manager
*
* @throws IOException <p>If unable to write to the signs file</p>
*/
public void saveSigns() throws IOException {
saveSigns(this.paidSigns);
}
/**
* Saves the given paid signs to the signs file
*
* @param signs <p>The signs to save</p>
* @throws IOException <p>If unable to write to the signs file</p>
*/
public static void saveSigns(List<PaidSign> signs) throws IOException {
public static void saveSigns(Map<String, PaidSign> 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());
for (PaidSign sign : signs.values()) {
String name = sign.getName();
signSection.set(name + ".cost", sign.getCost());
signSection.set(name + ".permission", sign.getPermission());
signSection.set(name + ".ignoreCase", sign.getIgnoreCase());
signSection.set(name + ".ignoreColor", sign.getIgnoreColor());
ConfigurationSection conditionsSection = signSection.createSection(name + ".conditions");
Map<Short, PaidSignCondition> signConditions = sign.getConditions();
for (short lineIndex : signConditions.keySet()) {
PaidSignCondition condition = signConditions.get(lineIndex);
conditionsSection.set(lineIndex + ".stringToMatch", condition.getStringToMatch());
conditionsSection.set(lineIndex + ".executeRegEx", condition.executeRegex());
conditionsSection.set(lineIndex + ".ignoreCase", condition.ignoreCase());
conditionsSection.set(lineIndex + ".ignoreColor", condition.ignoreColor());
}
}
configuration.save(signsFile);
}
/**
* Filters a list of paid signs to match the given clean id
* Loads any saved paid sign conditions and applies them to the given sign
*
* @param paidSigns <p>The list of paid signs to start with</p>
* @param cleanId <p>The clean id to filter by</p>
* @return <p>The filtered list of paid signs</p>
* @param signSection <p>The configuration section containing sign information</p>
* @param sign <p>The sign to load conditions for</p>
*/
private static List<PaidSign> filterPaidSigns(List<PaidSign> paidSigns, String cleanId) {
return filterPaidSigns(paidSigns, (paidSign) -> paidSign.getCleanId().equals(cleanId));
}
/**
* Filters a list of paid signs using the given predicate
*
* @param paidSigns <p>The list of paid signs to start with</p>
* @param predicate <p>The predicate used to filter paid signs</p>
* @return <p>The filtered list of paid signs</p>
*/
private static List<PaidSign> filterPaidSigns(List<PaidSign> paidSigns, Predicate<PaidSign> predicate) {
return new ArrayList<>(paidSigns).stream().filter(predicate).toList();
private static void loadConditions(ConfigurationSection signSection, PaidSign sign) {
ConfigurationSection conditionSection = signSection.getConfigurationSection(sign.getName() + ".conditions");
if (conditionSection != null) {
for (String lineIndex : conditionSection.getKeys(false)) {
short lineNumber = Short.parseShort(lineIndex);
String stringToMatch = conditionSection.getString(lineIndex + ".stringToMatch");
boolean executeRegEx = conditionSection.getBoolean(lineIndex + ".executeRegEx");
boolean ignoreConditionCase = conditionSection.getBoolean(lineIndex + ".ignoreCase");
boolean ignoreConditionColor = conditionSection.getBoolean(lineIndex + ".ignoreColor");
sign.addCondition(lineNumber, stringToMatch, executeRegEx,
OptionState.getFromBoolean(ignoreConditionCase),
OptionState.getFromBoolean(ignoreConditionColor));
}
}
}
}

View File

@ -0,0 +1,136 @@
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 org.bukkit.entity.Player;
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 final class TrackedSignManager {
private static Map<Location, TrackedSign> trackedSigns = new HashMap<>();
private static final File signsFile = new File(PaidSigns.getInstance().getDataFolder(), "data.yml");
private TrackedSignManager() {
}
/**
* Adds a tracked sign to the manager
*
* @param signLocation <p>The location the sign was created at</p>
* @param playerId <p>The unique id of the player that created the sign</p>
* @throws IOException <p>If unable to save the tracked signs</p>
*/
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 <p>The location the sign was removed from</p>
* @throws IOException <p>If unable to save the tracked signs</p>
*/
public static void removeTrackedSign(Location signLocation) throws IOException {
if (!trackedSigns.containsKey(signLocation)) {
return;
}
TrackedSign trackedSign = trackedSigns.get(signLocation);
trackedSigns.remove(signLocation);
saveTrackedSigns();
if (!PaidSigns.getInstance().areRefundsEnabled()) {
return;
}
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(trackedSign.getPlayerId());
double refundSum = trackedSign.getCost() / 100 * PaidSigns.getInstance().getRefundPercentage();
EconomyManager.withdraw(offlinePlayer, -refundSum);
if (offlinePlayer instanceof Player player) {
player.sendMessage("You were refunded " + refundSum + " for your broken sign");
}
}
/**
* 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, "Tracked 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 <p>The configuration section containing signs</p>
* @param key <p>The sign key which is also the sign's location</p>
* @throws InvalidConfigurationException <p>If unable to load the sign</p>
*/
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 <p>If unable to write to the data file</p>
*/
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().toString());
}
configuration.save(signsFile);
}
}

View File

@ -23,14 +23,15 @@ public enum OptionState {
/**
* Gets the boolean value of the given option state if it's boolean compatible
*
* @param optionState <p>The option state to get the boolean from</p>
* @return <p>The boolean value, or an illegal argument exception if called on DEFAULT</p>
* @param optionState <p>The option state to get the boolean from</p>
* @param defaultValue <p>The default value to use if the option state is DEFAULT</p>
* @return <p>The boolean value</p>
*/
public static boolean getBooleanValue(OptionState optionState) {
public static boolean getBooleanValue(OptionState optionState, boolean defaultValue) {
return switch (optionState) {
case TRUE -> true;
case FALSE -> false;
case DEFAULT -> throw new IllegalArgumentException("No boolean value available for DEFAULT");
case DEFAULT -> defaultValue;
};
}

View File

@ -0,0 +1,125 @@
package net.knarcraft.paidsigns.utility;
import net.knarcraft.paidsigns.PaidSigns;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* A helper class for providing common tab complete options
*/
public final class TabCompleteHelper {
private TabCompleteHelper() {
}
/**
* Finds tab complete values that match the start of the typed text
*
* @param values <p>The values to filter</p>
* @param typedText <p>The text the player has started typing</p>
* @return <p>The given string values that start with the player's typed text</p>
*/
public static List<String> filterMatchingStartsWith(List<String> values, String typedText) {
List<String> configValues = new ArrayList<>();
for (String value : values) {
if (value.toLowerCase().startsWith(typedText.toLowerCase())) {
configValues.add(value);
}
}
return configValues;
}
/**
* Gets the available boolean values for tab completion
*
* @return <p>The available boolean values</p>
*/
public static List<String> getBooleans() {
List<String> booleans = new ArrayList<>();
booleans.add("true");
booleans.add("false");
return booleans;
}
/**
* Gets the available option states for tab completion
*
* @return <p>The available option states</p>
*/
public static List<String> getOptionStates() {
List<String> optionStates = getBooleans();
optionStates.add("default");
return optionStates;
}
/**
* Gets the names of all registered paid signs
*
* @return <p>The names of all registered paid signs</p>
*/
public static List<String> getPaidSignNames() {
Set<String> paidSignNames = PaidSigns.getInstance().getSignManager().getAllPaidSigns().keySet();
List<String> quotedNames = new ArrayList<>(paidSignNames.size());
for (String paidSignName : paidSignNames) {
quotedNames.add("\"" + paidSignName + "\"");
}
return quotedNames;
}
/**
* Gets all the choices for lines on a sign
*
* @return <p>All the line indices of a sign</p>
*/
public static List<String> getSignLines() {
List<String> lines = new ArrayList<>();
for (int i = 1; i < 5; i++) {
lines.add(String.valueOf(i));
}
return lines;
}
/**
* Gets some valid costs as example values
*
* @return <p>Some valid costs</p>
*/
public static List<String> getCosts() {
List<String> costs = new ArrayList<>();
costs.add("1");
costs.add("5");
costs.add("10");
costs.add("15");
return costs;
}
/**
* Gets some example paid sign names for auto-completion
*
* @return <p>Some example paid sign names</p>
*/
public static List<String> getExamplePaidSignNames() {
List<String> names = new ArrayList<>();
names.add("sign1");
names.add("\"lift up sign\"");
names.add("\"lift down sign\"");
return names;
}
/**
* Gets some example condition matching strings for tab completion
*
* @return <p>Some example "stringToMatch" values</p>
*/
public static List<String> getExampleConditionStrings() {
List<String> conditionStrings = new ArrayList<>();
conditionStrings.add("[Gate]");
conditionStrings.add("\"[Lift Up]\"");
conditionStrings.add("\"[Lift Down]\"");
return conditionStrings;
}
}

View File

@ -6,15 +6,30 @@ import java.util.List;
/**
* A tokenizer for being able to support quotes in commands
*/
public class Tokenizer {
public final class Tokenizer {
private Tokenizer() {
}
/**
* Tokenizes a string
*
* @param input <p>A string.</p>
* @return <p>A list of tokens.</p>
* @param input <p>A string</p>
* @return <p>A list of tokens</p>
*/
public static List<String> tokenize(String input) {
return tokenize(input, true);
}
/**
* Tokenizes a string
*
* @param input <p>A string</p>
* @param allowEmptyQuotes <p>Whether to treat "" as a token</p>
* @return <p>A list of tokens</p>
*/
public static List<String> tokenize(String input, boolean allowEmptyQuotes) {
List<String> tokens = new ArrayList<>();
boolean startedQuote = false;
StringBuilder currentToken = new StringBuilder();
@ -29,7 +44,7 @@ public class Tokenizer {
case '"':
if (startedQuote) {
//This quote signifies the end of the argument
if (isNotEmpty(currentToken)) {
if (allowEmptyQuotes || isNotEmpty(currentToken)) {
tokens.add(currentToken.toString());
currentToken = new StringBuilder();
}

View File

@ -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
# 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

View File

@ -6,16 +6,28 @@ prefix: PaidSigns
depend: [ Vault ]
authors: [ EpicKnarvik97 ]
description: Add costs for creating plugin signs
website: https://git.knarcraft.net
website: https://git.knarcraft.net/EpicKnarvik97/PaidSigns
commands:
addpaidsign:
description: Used to add a new paid sign
usage: /<command> <id (the text to look for)> <line> <cost> [ignore case] [ignore color]
permission: paidsigns.add
usage: /<command> <name> <cost> [permission] [ignore case] [ignore color]
permission: paidsigns.manage
addpaidsigncondition:
description: Used to add a new match condition for a paid sign
usage: /<command> <name (of a paid sign)> <line number> <string to match> [executeRegEx] [ignoreCase] [ignoreColor]
permission: paidsigns.manage
listpaidsigns:
description: Lists all previously added paid signs
usage: /<command> [sign name] [line number]
permission: paidsigns.manage
removepaidsigncondition:
description: Used to remove a match condition from a paid sign
usage: /<command> <name (of a paid sign)> <line number>
permission: paidsigns.manage
removepaidsign:
description: Used to remove a paid sign
usage: /<command> <id (the text to look for)> <line>
permission: paidsigns.remove
usage: /<command> <name (of a paid sign)>
permission: paidsigns.manage
reload:
description: Reloads paid signs from disk
usage: /<command>
@ -28,7 +40,7 @@ permissions:
paidsigns.create: true
paidsigns.paymentexempt: true
paidsigns.reload: true
paidsigns.add:
paidsigns.manage:
description: Grants the permission to add/remove a paid sign
default: false
paidsigns.reload: