23 Commits

Author SHA1 Message Date
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
23 changed files with 1158 additions and 300 deletions

View File

@ -9,4 +9,70 @@ 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 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 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 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

View File

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

View File

@ -1,10 +1,15 @@
package net.knarcraft.paidsigns; package net.knarcraft.paidsigns;
import net.knarcraft.paidsigns.command.AddCommand; 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.AddTabCompleter;
import net.knarcraft.paidsigns.command.ListCommand;
import net.knarcraft.paidsigns.command.ListTabCompleter;
import net.knarcraft.paidsigns.command.ReloadTabCommand; import net.knarcraft.paidsigns.command.ReloadTabCommand;
import net.knarcraft.paidsigns.command.RemoveCommand; import net.knarcraft.paidsigns.command.RemoveConditionCommand;
import net.knarcraft.paidsigns.command.RemoveTabCompleter; import net.knarcraft.paidsigns.command.RemoveConditionTabCompleter;
import net.knarcraft.paidsigns.command.RemoveTabCommand;
import net.knarcraft.paidsigns.listener.SignListener; import net.knarcraft.paidsigns.listener.SignListener;
import net.knarcraft.paidsigns.manager.EconomyManager; import net.knarcraft.paidsigns.manager.EconomyManager;
import net.knarcraft.paidsigns.manager.PaidSignManager; import net.knarcraft.paidsigns.manager.PaidSignManager;
@ -106,10 +111,29 @@ public final class PaidSigns extends JavaPlugin {
addCommand.setTabCompleter(new AddTabCompleter()); addCommand.setTabCompleter(new AddTabCompleter());
} }
PluginCommand listCommand = this.getCommand("listPaidSigns");
if (listCommand != null) {
listCommand.setExecutor(new ListCommand());
listCommand.setTabCompleter(new ListTabCompleter());
}
PluginCommand addConditionCommand = this.getCommand("addPaidSignCondition");
if (addConditionCommand != null) {
addConditionCommand.setExecutor(new AddConditionCommand());
addConditionCommand.setTabCompleter(new AddConditionTabCompleter());
}
PluginCommand removeConditionCommand = this.getCommand("removePaidSignCondition");
if (removeConditionCommand != null) {
removeConditionCommand.setExecutor(new RemoveConditionCommand());
removeConditionCommand.setTabCompleter(new RemoveConditionTabCompleter());
}
PluginCommand removeCommand = this.getCommand("removePaidSign"); PluginCommand removeCommand = this.getCommand("removePaidSign");
if (removeCommand != null) { if (removeCommand != null) {
removeCommand.setExecutor(new RemoveCommand()); TabExecutor removeTabExecutor = new RemoveTabCommand();
removeCommand.setTabCompleter(new RemoveTabCompleter()); removeCommand.setExecutor(removeTabExecutor);
removeCommand.setTabCompleter(removeTabExecutor);
} }
PluginCommand reloadCommand = this.getCommand("reload"); PluginCommand reloadCommand = this.getCommand("reload");

View File

@ -4,58 +4,71 @@ import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.container.PaidSign; import net.knarcraft.paidsigns.container.PaidSign;
import net.knarcraft.paidsigns.manager.PaidSignManager; import net.knarcraft.paidsigns.manager.PaidSignManager;
import net.knarcraft.paidsigns.property.OptionState; import net.knarcraft.paidsigns.property.OptionState;
import net.knarcraft.paidsigns.utility.Tokenizer;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
/** /**
* A representation of the command for adding a new paid sign * A representation of the command for adding a new paid sign
*/ */
public class AddCommand implements CommandExecutor { public class AddCommand extends TokenizedCommand {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) { @NotNull String[] args) {
if (args.length < 3) { super.onCommand(sender, command, label, args);
if (argumentSize < 2) {
return false; return false;
} }
PaidSignManager manager = PaidSigns.getInstance().getSignManager();
List<String> arguments = Tokenizer.tokenize(String.join(" ", args));
String id = arguments.get(0); String signName = arguments.get(0).trim();
short line;
double cost; double cost;
try { try {
line = (short) (Short.parseShort(arguments.get(1)) - 1); cost = Double.parseDouble(arguments.get(1));
cost = Double.parseDouble(arguments.get(2));
} catch (NumberFormatException exception) { } catch (NumberFormatException exception) {
sender.sendMessage("You provided an invalid number"); sender.sendMessage("You provided an invalid number");
return false; return false;
} }
String permission = "";
if (argumentSize > 2) {
permission = arguments.get(2);
}
OptionState ignoreCase = OptionState.DEFAULT; OptionState ignoreCase = OptionState.DEFAULT;
OptionState ignoreColor = OptionState.DEFAULT; OptionState ignoreColor = OptionState.DEFAULT;
if (arguments.size() > 3) { if (argumentSize > 3) {
ignoreCase = OptionState.fromString(arguments.get(3)); ignoreCase = OptionState.fromString(arguments.get(3));
} }
if (arguments.size() > 4) { if (argumentSize > 4) {
ignoreColor = OptionState.fromString(arguments.get(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 { try {
PaidSign sign = new PaidSign(id, line, cost, ignoreCase, ignoreColor); PaidSign sign = new PaidSign(signName, cost, permission, ignoreCase, ignoreColor);
for (PaidSign similarSign : manager.getPaidSigns(sign.getCleanId(), sign.getLineIndex())) { if (manager.getPaidSign(signName) != null) {
if (sign.matches(similarSign)) { sender.sendMessage("A paid sign with the same name already exists");
sender.sendMessage("A paid sign with the same id and line already exists"); return false;
return false;
}
} }
manager.addPaidSign(sign); manager.addPaidSign(sign);
sender.sendMessage("Successfully added new paid 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; 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.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter; import org.bukkit.permissions.Permission;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
/** /**
* The tab completer for the add paid sign command * 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> names;
private static List<String> lines;
private static List<String> costs; private static List<String> costs;
private static List<String> plugins;
private static Map<String, List<String>> permissions;
private static List<String> options; private static List<String> options;
@Nullable @Nullable
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] args) { @NotNull String[] args) {
if (ids == null) { super.onTabComplete(sender, command, alias, args);
if (names == null) {
initializeValues(); initializeValues();
loadAvailablePermissions();
} }
List<String> arguments = Tokenizer.tokenize(String.join(" ", args));
if (arguments.size() < 1) { if (argumentSize == 1) {
return ids; return TabCompleteHelper.filterMatchingStartsWith(names, arguments.get(0));
} else if (arguments.size() < 2) { } else if (argumentSize == 2) {
return lines; return TabCompleteHelper.filterMatchingStartsWith(costs, arguments.get(1));
} else if (arguments.size() < 3) { } else if (argumentSize == 3) {
return costs; return tabCompletePermission(arguments.get(arguments.size() - 1));
} else if (arguments.size() < 5) { } else if (argumentSize == 4) {
return options; return TabCompleteHelper.filterMatchingStartsWith(options, arguments.get(3));
} else if (argumentSize == 5) {
return TabCompleteHelper.filterMatchingStartsWith(options, arguments.get(4));
} }
return new ArrayList<>(); 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 * Initializes the values available for tab completion
*/ */
private static void initializeValues() { private static void initializeValues() {
ids = new ArrayList<>(); names = TabCompleteHelper.getExamplePaidSignNames();
ids.add("[Gate]"); costs = TabCompleteHelper.getCosts();
ids.add("\"[Lift Up]\""); options = TabCompleteHelper.getOptionStates();
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");
} }
} }

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

@ -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; package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.PaidSigns; import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.container.PaidSign;
import net.knarcraft.paidsigns.utility.Tokenizer; import net.knarcraft.paidsigns.utility.Tokenizer;
import org.apache.commons.lang.ArrayUtils;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
/** /**
* A representation of the command for removing a paid sign * A representation of the command for removing a paid sign
*/ */
public class RemoveCommand implements CommandExecutor { public class RemoveTabCommand implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) { @NotNull String[] args) {
if (args.length < 1) {
return false;
}
List<String> arguments = Tokenizer.tokenize(String.join(" ", args)); List<String> arguments = Tokenizer.tokenize(String.join(" ", args));
String[] input = arguments.get(0).split("\\|"); if (arguments.size() < 1) {
short line;
try {
line = (short) (Short.parseShort(input[0]) - 1);
} catch (NumberFormatException exception) {
sender.sendMessage("Invalid line number given");
return false; return false;
} }
String id = String.join("|", (String[]) ArrayUtils.remove(input, 0)); String name = arguments.get(0);
try { try {
if (PaidSigns.getInstance().getSignManager().removePaidSign(id, line)) { if (PaidSigns.getInstance().getSignManager().removePaidSign(name)) {
sender.sendMessage("Successfully removed paid sign"); sender.sendMessage("Successfully removed paid sign");
} else { } else {
sender.sendMessage("No matching paid sign was found"); sender.sendMessage("No matching paid sign was found");
@ -52,4 +48,23 @@ public class RemoveCommand implements CommandExecutor {
return false; 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.PaidSigns;
import net.knarcraft.paidsigns.property.OptionState; 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 * A representation of a paid sign
*/ */
public class PaidSign { public class PaidSign {
private final String id; private final String name;
private final String cleanId;
private final short lineIndex;
private final double cost; private final double cost;
private final String permission;
private final OptionState ignoreCase; private final OptionState ignoreCase;
private final OptionState ignoreColor; private final OptionState ignoreColor;
private final Map<Short, PaidSignCondition> conditions = new HashMap<>();
/** /**
* Instantiates a new paid sign * Instantiates a new paid sign
* *
* @param id <p>The string that identifies this type of paid sign</p> * @param name <p>A recognizable, unique, name used to identify this paid sign</p>
* @param lineIndex <p>The line the id has to be on to trigger payment</p>
* @param cost <p>The cost of creating 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 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> * @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) { public PaidSign(String name, double cost, String permission, OptionState ignoreCase, OptionState ignoreColor) {
if (id == null || id.trim().isBlank()) { if (name == null || name.trim().isBlank()) {
throw new IllegalArgumentException("Id cannot be empty"); throw new IllegalArgumentException("Name cannot be empty");
} }
if (cost <= 0) { if (cost <= 0) {
throw new IllegalArgumentException("Cost must be larger than 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) { if (ignoreCase == null || ignoreColor == null) {
throw new IllegalArgumentException("Ignore case and ignore color options cannot be null"); throw new IllegalArgumentException("Ignore case and ignore color options cannot be null");
} }
this.id = id; this.name = name;
this.lineIndex = lineIndex;
this.cost = cost; this.cost = cost;
this.permission = permission;
this.ignoreCase = ignoreCase; this.ignoreCase = ignoreCase;
this.ignoreColor = ignoreColor; 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() { public String getName() {
return id; 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() { public String getPermission() {
return lineIndex; 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() { public Map<Short, PaidSignCondition> getConditions() {
return cleanId; return new HashMap<>(this.conditions);
}
/**
* 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);
} }
/** /**
@ -134,11 +86,7 @@ public class PaidSign {
* @return <p>Whether the text case should be ignored for this paid sign</p> * @return <p>Whether the text case should be ignored for this paid sign</p>
*/ */
public boolean getIgnoreCase() { public boolean getIgnoreCase() {
if (this.ignoreCase == OptionState.DEFAULT) { return OptionState.getBooleanValue(this.ignoreCase, PaidSigns.getInstance().ignoreCase());
return PaidSigns.getInstance().ignoreCase();
} else {
return OptionState.getBooleanValue(this.ignoreCase);
}
} }
/** /**
@ -147,11 +95,55 @@ public class PaidSign {
* @return <p>Whether the text color should be ignored for this paid sign</p> * @return <p>Whether the text color should be ignored for this paid sign</p>
*/ */
public boolean getIgnoreColor() { public boolean getIgnoreColor() {
if (this.ignoreColor == OptionState.DEFAULT) { return OptionState.getBooleanValue(this.ignoreColor, PaidSigns.getInstance().ignoreColor());
return PaidSigns.getInstance().ignoreColor(); }
} else {
return OptionState.getBooleanValue(this.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

@ -3,65 +3,64 @@ package net.knarcraft.paidsigns.listener;
import net.knarcraft.paidsigns.PaidSigns; import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.container.PaidSign; import net.knarcraft.paidsigns.container.PaidSign;
import net.knarcraft.paidsigns.manager.EconomyManager; import net.knarcraft.paidsigns.manager.EconomyManager;
import net.knarcraft.paidsigns.manager.PaidSignManager;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.SignChangeEvent; import org.bukkit.event.block.SignChangeEvent;
import java.util.List; import java.util.Map;
/** /**
* A listener for listening to registered paid signs * A listener for listening to registered paid signs
*/ */
public class SignListener implements Listener { public class SignListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR) @EventHandler(priority = EventPriority.LOW)
@SuppressWarnings("unused")
public void onSignChange(SignChangeEvent event) { public void onSignChange(SignChangeEvent event) {
if (event.isCancelled() || event.getPlayer().hasPermission("paidsigns.paymentexempt")) { if (event.isCancelled() || event.getPlayer().hasPermission("paidsigns.paymentexempt")) {
return; return;
} }
String[] lines = event.getLines(); String[] lines = event.getLines();
PaidSignManager signManager = PaidSigns.getInstance().getSignManager(); Map<String, PaidSign> matchingSigns = PaidSigns.getInstance().getSignManager().getAllPaidSigns();
for (short lineIndex = 0; lineIndex < lines.length; lineIndex++) { for (PaidSign paidSign : matchingSigns.values()) {
//Get all "weak" matches (any paid sign with a clean id matching the clean line) //If a match is found, just return
List<PaidSign> matchingSigns = signManager.getPaidSigns(PaidSign.getCleanString(lines[lineIndex]), lineIndex); if (matchSign(paidSign, lines, event)) {
if (matchingSigns.isEmpty()) {
continue;
}
if (testMatchingSigns(lineIndex, lines[lineIndex], matchingSigns, event)) {
return; 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 paidSign <p>The paid sign to test against the sign lines</p>
* @param line <p>The text on the currently managed line</p> * @param lines <p>The lines of a sign</p>
* @param matchingSigns <p>The signs that weakly match the </p> * @param event <p>The triggered sign change event to cancel if necessary</p>
* @param event <p>The triggered sign change event</p> * @return <p>True if a match was found and actions have been taken</p>
* @return <p>True if a match was found and thw work is finished</p>
*/ */
private boolean testMatchingSigns(short lineIndex, String line, List<PaidSign> matchingSigns, SignChangeEvent event) { private boolean matchSign(PaidSign paidSign, String[] lines, SignChangeEvent event) {
for (PaidSign paidSign : matchingSigns) { if (paidSign.matches(lines)) {
if (paidSign.matches(lineIndex, line)) { Player player = event.getPlayer();
Player player = event.getPlayer(); String permission = paidSign.getPermission();
double cost = paidSign.getCost(); //If a match is found, but the player is missing the permission, assume no plugin sign was created
boolean canAfford = EconomyManager.canAfford(player, cost); if (permission != null && !permission.trim().isEmpty() && !player.hasPermission(permission)) {
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 true;
} }
double cost = paidSign.getCost();
boolean canAfford = EconomyManager.canAfford(player, cost);
if (!canAfford) {
player.sendMessage("[PaidSigns] You cannot afford to create this sign");
event.setCancelled(true);
} else {
String unit = EconomyManager.getCurrency(cost != 1);
player.sendMessage(String.format("[PaidSigns] You paid %.2f %s to create the sign", cost, unit));
EconomyManager.withdraw(player, cost);
}
return true;
} }
return false; return false;
} }

View File

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

View File

@ -23,14 +23,15 @@ public enum OptionState {
/** /**
* Gets the boolean value of the given option state if it's boolean compatible * 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> * @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 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) { return switch (optionState) {
case TRUE -> true; case TRUE -> true;
case FALSE -> false; case FALSE -> false;
case DEFAULT -> throw new IllegalArgumentException("No boolean value available for DEFAULT"); case DEFAULT -> defaultValue;
}; };
} }

View File

@ -0,0 +1,142 @@
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 contain 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 contain the player's typed text</p>
*/
public static List<String> filterMatchingContains(List<String> values, String typedText) {
List<String> configValues = new ArrayList<>();
for (String value : values) {
if (value.toLowerCase().contains(typedText.toLowerCase())) {
configValues.add(value);
}
}
return configValues;
}
/**
* Finds tab complete values that match the start of the typed text
*
* @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 * A tokenizer for being able to support quotes in commands
*/ */
public class Tokenizer { public final class Tokenizer {
private Tokenizer() {
}
/** /**
* Tokenizes a string * Tokenizes a string
* *
* @param input <p>A string.</p> * @param input <p>A string</p>
* @return <p>A list of tokens.</p> * @return <p>A list of tokens</p>
*/ */
public static List<String> tokenize(String input) { 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<>(); List<String> tokens = new ArrayList<>();
boolean startedQuote = false; boolean startedQuote = false;
StringBuilder currentToken = new StringBuilder(); StringBuilder currentToken = new StringBuilder();
@ -29,7 +44,7 @@ public class Tokenizer {
case '"': case '"':
if (startedQuote) { if (startedQuote) {
//This quote signifies the end of the argument //This quote signifies the end of the argument
if (isNotEmpty(currentToken)) { if (allowEmptyQuotes || isNotEmpty(currentToken)) {
tokens.add(currentToken.toString()); tokens.add(currentToken.toString());
currentToken = new StringBuilder(); currentToken = new StringBuilder();
} }

View File

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