Adds various improvements, fixes and a new feature

Adds and option to match any paid sign condition instead of all conditions
Adds checks for whether line indices are outside the allowed range
Disallows any invalid regular expressions in sign conditions
This commit is contained in:
Kristian Knarvik 2022-03-02 00:37:00 +01:00
parent 664115b2b4
commit 9bb234169d
11 changed files with 110 additions and 42 deletions

View File

@ -13,7 +13,7 @@ As this plugin only listens to sign change events, there are some limitations:
## Commands ## Commands
* /addpaidsign <name> <cost> \[permission] \[ignore case] \[ignore color] * /addpaidsign <name> <cost> \[permission] \[ignore case] \[ignore color] \[match any condition]
* /addpaidsigncondition <name (of a paid sign)> <line number> <string to match> \[executeRegEx] \[ignoreCase] * /addpaidsigncondition <name (of a paid sign)> <line number> <string to match> \[executeRegEx] \[ignoreCase]
\[ignoreColor] \[ignoreColor]
* /listpaidsigns \[name (of a paid sign)] \[line number] * /listpaidsigns \[name (of a paid sign)] \[line number]
@ -35,6 +35,8 @@ This command adds a new paid sign that does nothing until a condition is added.
default uses the config file value). default uses the config file value).
* ignore color - Whether any condition of the paid sign should ignore color by default, when matching against text ( * ignore color - Whether any condition of the paid sign should ignore color by default, when matching against text (
default uses the config file value). default uses the config file value).
* match any condition - Whether to trigger a paid sign match if a single one of the sign's conditions is true. This is
mainly useful if several lines may contain the match string, or if trying to match a word.
### /addpaidsigncondition ### /addpaidsigncondition
@ -79,6 +81,7 @@ Removes a registered paid sign
## Configuration options ## Configuration options
* language - The language to use for all messages displayed to players
* ignoreCase - Whether to ignore the case (lowercase/uppercase) of the paid sign text. The option can be set on a * 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 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. should match are case-sensitive or not.

View File

@ -45,8 +45,12 @@ public class AddCommand extends TokenizedCommand {
if (argumentSize > 4) { if (argumentSize > 4) {
ignoreColor = OptionState.fromString(arguments.get(4)); ignoreColor = OptionState.fromString(arguments.get(4));
} }
boolean matchAnyCondition = false;
if (argumentSize > 5) {
matchAnyCondition = Boolean.parseBoolean(arguments.get(5));
}
return createPaidSign(sender, signName, cost, permission, ignoreCase, ignoreColor); return createPaidSign(sender, signName, cost, permission, ignoreCase, ignoreColor, matchAnyCondition);
} }
/** /**
@ -58,13 +62,14 @@ public class AddCommand extends TokenizedCommand {
* @param permission <p>The permission required for creating the sign represented by the 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 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> * @param ignoreColor <p>Whether to ignore color for the paid sign's conditions</p>
* @param matchAnyCondition <p>Whether to treat any matching condition as a sign match</p>
* @return <p>True if the paid sign was successfully created and registered</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, private boolean createPaidSign(CommandSender sender, String signName, double cost, String permission,
OptionState ignoreCase, OptionState ignoreColor) { OptionState ignoreCase, OptionState ignoreColor, boolean matchAnyCondition) {
PaidSignManager manager = PaidSigns.getInstance().getSignManager(); PaidSignManager manager = PaidSigns.getInstance().getSignManager();
try { try {
PaidSign sign = new PaidSign(signName, cost, permission, ignoreCase, ignoreColor); PaidSign sign = new PaidSign(signName, cost, permission, ignoreCase, ignoreColor, matchAnyCondition);
if (manager.getPaidSign(signName) != null) { if (manager.getPaidSign(signName) != null) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_NAME_DUPLICATE)); sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_NAME_DUPLICATE));
return false; return false;

View File

@ -11,6 +11,8 @@ 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.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/** /**
* A representation of the command for adding a new match condition for a sign * A representation of the command for adding a new match condition for a sign
@ -29,6 +31,10 @@ public class AddConditionCommand extends TokenizedCommand {
short lineNumber; short lineNumber;
try { try {
lineNumber = (short) (Short.parseShort(arguments.get(1)) - 1); lineNumber = (short) (Short.parseShort(arguments.get(1)) - 1);
if (lineNumber < 0 || lineNumber > 3) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_INVALID_NUMBER));
return false;
}
} catch (NumberFormatException exception) { } catch (NumberFormatException exception) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_INVALID_NUMBER)); sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_INVALID_NUMBER));
return false; return false;
@ -37,6 +43,9 @@ public class AddConditionCommand extends TokenizedCommand {
boolean executeRegEx = false; boolean executeRegEx = false;
if (argumentSize > 3) { if (argumentSize > 3) {
executeRegEx = Boolean.parseBoolean(arguments.get(3)); executeRegEx = Boolean.parseBoolean(arguments.get(3));
if (executeRegEx && !testRegEx(sender, stringToMatch)) {
return false;
}
} }
OptionState ignoreCase = OptionState.DEFAULT; OptionState ignoreCase = OptionState.DEFAULT;
if (argumentSize > 4) { if (argumentSize > 4) {
@ -49,6 +58,24 @@ public class AddConditionCommand extends TokenizedCommand {
return addCondition(name, lineNumber, stringToMatch, executeRegEx, ignoreCase, ignoreColor, sender); return addCondition(name, lineNumber, stringToMatch, executeRegEx, ignoreCase, ignoreColor, sender);
} }
/**
* Tests whether the given regular expression is valid
*
* @param sender <p>The command sender to notify if the regular expression is invalid</p>
* @param regularExpression <p>The regular expression to test</p>
* @return <p>True if the regular expression is valid</p>
*/
private boolean testRegEx(CommandSender sender, String regularExpression) {
try {
Pattern.compile(regularExpression);
return true;
} catch (PatternSyntaxException exception) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(
TranslatableMessage.ERROR_INVALID_REGULAR_EXPRESSION));
return false;
}
}
/** /**
* Uses the given input to add a paid sign condition * Uses the given input to add a paid sign condition
* *

View File

@ -24,6 +24,7 @@ public class AddTabCompleter extends TokenizedTabCompleter {
private static List<String> plugins; private static List<String> plugins;
private static Map<String, List<String>> permissions; private static Map<String, List<String>> permissions;
private static List<String> options; private static List<String> options;
private static List<String> booleans;
@Nullable @Nullable
@Override @Override
@ -45,6 +46,8 @@ public class AddTabCompleter extends TokenizedTabCompleter {
return TabCompleteHelper.filterMatchingStartsWith(options, arguments.get(3)); return TabCompleteHelper.filterMatchingStartsWith(options, arguments.get(3));
} else if (argumentSize == 5) { } else if (argumentSize == 5) {
return TabCompleteHelper.filterMatchingStartsWith(options, arguments.get(4)); return TabCompleteHelper.filterMatchingStartsWith(options, arguments.get(4));
} else if (argumentSize == 6) {
return TabCompleteHelper.filterMatchingStartsWith(booleans, arguments.get(5));
} }
return new ArrayList<>(); return new ArrayList<>();
} }
@ -131,6 +134,7 @@ public class AddTabCompleter extends TokenizedTabCompleter {
names = TabCompleteHelper.getExamplePaidSignNames(); names = TabCompleteHelper.getExamplePaidSignNames();
costs = TabCompleteHelper.getCosts(); costs = TabCompleteHelper.getCosts();
options = TabCompleteHelper.getOptionStates(); options = TabCompleteHelper.getOptionStates();
booleans = TabCompleteHelper.getBooleans();
} }
} }

View File

@ -65,20 +65,24 @@ public class ListCommand extends TokenizedCommand {
if (argumentSize < 2) { if (argumentSize < 2) {
displayPaidSign(sender, paidSign); displayPaidSign(sender, paidSign);
} else { } else {
short signLine; short lineNumber;
try { try {
signLine = (short) (Short.parseShort(arguments.get(1)) - 1); lineNumber = (short) (Short.parseShort(arguments.get(1)) - 1);
if (lineNumber < 0 || lineNumber > 3) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_INVALID_NUMBER));
return false;
}
} catch (NumberFormatException exception) { } catch (NumberFormatException exception) {
sender.sendMessage(getTranslatedErrorMessage(TranslatableMessage.ERROR_INVALID_NUMBER)); sender.sendMessage(getTranslatedErrorMessage(TranslatableMessage.ERROR_INVALID_NUMBER));
return false; return false;
} }
if (!paidSign.getConditions().containsKey(signLine)) { if (!paidSign.getConditions().containsKey(lineNumber)) {
sender.sendMessage(replacePlaceholder(getTranslatedErrorMessage( sender.sendMessage(replacePlaceholder(getTranslatedErrorMessage(
TranslatableMessage.ERROR_NO_SUCH_CONDITION), "{line}", String.valueOf(signLine))); TranslatableMessage.ERROR_NO_SUCH_CONDITION), "{line}", String.valueOf(lineNumber)));
return false; return false;
} }
PaidSignCondition condition = paidSign.getConditions().get(signLine); PaidSignCondition condition = paidSign.getConditions().get(lineNumber);
displayPaidSignCondition(sender, paidSign.getName(), signLine, condition); displayPaidSignCondition(sender, paidSign.getName(), lineNumber, condition);
} }
return true; return true;
} }
@ -117,9 +121,10 @@ public class ListCommand extends TokenizedCommand {
sender.sendMessage(replacePlaceholders(Translator.getTranslatedMessage( sender.sendMessage(replacePlaceholders(Translator.getTranslatedMessage(
TranslatableMessage.PAID_SIGN_INFO), new String[]{"{name}", "{cost}", "{permission}", "{case}", TranslatableMessage.PAID_SIGN_INFO), new String[]{"{name}", "{cost}", "{permission}", "{case}",
"{color}", "{conditions}"}, new String[]{paidSign.getName(), String.valueOf(paidSign.getCost()), "{color}", "{any}", "{conditions}"}, new String[]{paidSign.getName(), String.valueOf(paidSign.getCost()),
paidSign.getPermission(), translateBoolean(paidSign.getIgnoreCase()), paidSign.getPermission(), translateBoolean(paidSign.getIgnoreCase()),
translateBoolean(paidSign.getIgnoreColor()), conditions.toString()})); translateBoolean(paidSign.getIgnoreColor()), translateBoolean(paidSign.matchAnyCondition()),
conditions.toString()}));
} }
} }

View File

@ -24,9 +24,13 @@ public class RemoveConditionCommand extends TokenizedCommand {
} }
String name = arguments.get(0); String name = arguments.get(0);
short line; short lineNumber;
try { try {
line = (short) (Short.parseShort(arguments.get(1)) - 1); lineNumber = (short) (Short.parseShort(arguments.get(1)) - 1);
if (lineNumber < 0 || lineNumber > 3) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_INVALID_NUMBER));
return false;
}
} catch (NumberFormatException exception) { } catch (NumberFormatException exception) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_INVALID_NUMBER)); sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_INVALID_NUMBER));
return false; return false;
@ -38,13 +42,13 @@ public class RemoveConditionCommand extends TokenizedCommand {
return false; return false;
} }
if (!sign.getConditions().containsKey(line)) { if (!sign.getConditions().containsKey(lineNumber)) {
sender.sendMessage(StringFormatter.replacePlaceholder(StringFormatter.getTranslatedErrorMessage( sender.sendMessage(StringFormatter.replacePlaceholder(StringFormatter.getTranslatedErrorMessage(
TranslatableMessage.ERROR_NO_SUCH_CONDITION), "{line}", String.valueOf((line + 1)))); TranslatableMessage.ERROR_NO_SUCH_CONDITION), "{line}", String.valueOf((lineNumber + 1))));
return false; return false;
} }
sign.removeCondition(line); sign.removeCondition(lineNumber);
try { try {
signManager.saveSigns(); signManager.saveSigns();
} catch (IOException e) { } catch (IOException e) {

View File

@ -5,8 +5,6 @@ import net.knarcraft.paidsigns.property.OptionState;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.logging.Level;
import java.util.regex.PatternSyntaxException;
/** /**
* A representation of a paid sign * A representation of a paid sign
@ -18,6 +16,7 @@ public class PaidSign {
private final String permission; private final String permission;
private final OptionState ignoreCase; private final OptionState ignoreCase;
private final OptionState ignoreColor; private final OptionState ignoreColor;
private final boolean matchAnyCondition;
private final Map<Short, PaidSignCondition> conditions = new HashMap<>(); private final Map<Short, PaidSignCondition> conditions = new HashMap<>();
/** /**
@ -28,8 +27,10 @@ public class PaidSign {
* @param permission <p>The permission required to create the sign this paid sign matches</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>
* @param matchAnyCondition <p>Whether to treat a match for any condition as a sign match</p>
*/ */
public PaidSign(String name, double cost, String permission, OptionState ignoreCase, OptionState ignoreColor) { public PaidSign(String name, double cost, String permission, OptionState ignoreCase, OptionState ignoreColor,
boolean matchAnyCondition) {
if (name == null || name.trim().isBlank()) { if (name == null || name.trim().isBlank()) {
throw new IllegalArgumentException("Name cannot be empty"); throw new IllegalArgumentException("Name cannot be empty");
} }
@ -44,6 +45,7 @@ public class PaidSign {
this.permission = permission; this.permission = permission;
this.ignoreCase = ignoreCase; this.ignoreCase = ignoreCase;
this.ignoreColor = ignoreColor; this.ignoreColor = ignoreColor;
this.matchAnyCondition = matchAnyCondition;
} }
/** /**
@ -100,6 +102,15 @@ public class PaidSign {
return OptionState.getBooleanValue(this.ignoreColor, PaidSigns.getInstance().ignoreColor()); return OptionState.getBooleanValue(this.ignoreColor, PaidSigns.getInstance().ignoreColor());
} }
/**
* Gets whether to treat a match for any condition as a sign match
*
* @return <p>Whether to treat a match for any condition as a sign match</p>
*/
public boolean matchAnyCondition() {
return this.matchAnyCondition;
}
/** /**
* Checks whether this paid sign matches the given set of sign lines * Checks whether this paid sign matches the given set of sign lines
* *
@ -111,17 +122,18 @@ public class PaidSign {
if (this.conditions.isEmpty()) { if (this.conditions.isEmpty()) {
return false; return false;
} }
boolean success = true; boolean matchAny = matchAnyCondition();
boolean success = !matchAny;
for (short i = 0; i < 4; i++) { for (short i = 0; i < 4; i++) {
PaidSignCondition condition = this.conditions.get(i); PaidSignCondition condition = this.conditions.get(i);
if (condition != null) { if (condition != null) {
try { boolean conditionMatches = condition.test(lines[i]);
success = success && condition.test(lines[i]); if (matchAny) {
} catch (PatternSyntaxException exception) { success |= conditionMatches;
PaidSigns.getInstance().getLogger().log(Level.SEVERE, "The paid sign condition match string:" + } else {
" " + condition.getStringToMatch() + " belonging to sign: " + this.getName() + success &= conditionMatches;
" is not a valid regular expression.");
} }
} }
} }
return success; return success;

View File

@ -115,4 +115,9 @@ public enum TranslatableMessage {
*/ */
ERROR_CANNOT_AFFORD, ERROR_CANNOT_AFFORD,
/**
* The error to display if an invalid regular expression is provided
*/
ERROR_INVALID_REGULAR_EXPRESSION,
} }

View File

@ -98,7 +98,8 @@ public final class PaidSignManager {
String permission = signSection.getString(name + ".permission"); String permission = signSection.getString(name + ".permission");
OptionState ignoreCase = OptionState.getFromBoolean(signSection.getBoolean(name + ".ignoreCase")); OptionState ignoreCase = OptionState.getFromBoolean(signSection.getBoolean(name + ".ignoreCase"));
OptionState ignoreColor = OptionState.getFromBoolean(signSection.getBoolean(name + ".ignoreColor")); OptionState ignoreColor = OptionState.getFromBoolean(signSection.getBoolean(name + ".ignoreColor"));
PaidSign sign = new PaidSign(name, cost, permission, ignoreCase, ignoreColor); boolean matchAnyCondition = signSection.getBoolean(name + ".matchAnyCondition");
PaidSign sign = new PaidSign(name, cost, permission, ignoreCase, ignoreColor, matchAnyCondition);
loadConditions(signSection, sign); loadConditions(signSection, sign);
paidSigns.put(name, sign); paidSigns.put(name, sign);
} }
@ -150,6 +151,7 @@ public final class PaidSignManager {
signSection.set(name + ".permission", sign.getPermission()); signSection.set(name + ".permission", sign.getPermission());
signSection.set(name + ".ignoreCase", sign.getIgnoreCase()); signSection.set(name + ".ignoreCase", sign.getIgnoreCase());
signSection.set(name + ".ignoreColor", sign.getIgnoreColor()); signSection.set(name + ".ignoreColor", sign.getIgnoreColor());
signSection.set(name + ".matchAnyCondition", sign.matchAnyCondition());
ConfigurationSection conditionsSection = signSection.createSection(name + ".conditions"); ConfigurationSection conditionsSection = signSection.createSection(name + ".conditions");
Map<Short, PaidSignCondition> signConditions = sign.getConditions(); Map<Short, PaidSignCondition> signConditions = sign.getConditions();
for (short lineIndex : signConditions.keySet()) { for (short lineIndex : signConditions.keySet()) {

View File

@ -10,7 +10,7 @@ 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> <name> <cost> [permission] [ignore case] [ignore color] usage: /<command> <name> <cost> [permission] [ignore case] [ignore color] [match any condition]
permission: paidsigns.manage permission: paidsigns.manage
addpaidsigncondition: addpaidsigncondition:
description: Used to add a new match condition for a paid sign description: Used to add a new match condition for a paid sign

View File

@ -9,7 +9,7 @@ en:
SUCCESS_REFUNDED: "&bYou were refunded &3{cost} {unit} &bfor your broken sign" SUCCESS_REFUNDED: "&bYou were refunded &3{cost} {unit} &bfor your broken sign"
PAID_SIGNS_INFO: "&f---&3Paid signs&f---{signs}\n&f-----------" PAID_SIGNS_INFO: "&f---&3Paid signs&f---{signs}\n&f-----------"
PAID_SIGNS_INFO_FORMAT: "\n&f| &3{name}" PAID_SIGNS_INFO_FORMAT: "\n&f| &3{name}"
PAID_SIGN_INFO: "&f---&3Paid sign&f---\n&f| &bName: &3{name}\n&f| &bCost: &3{cost}\n&f| &bPermission: &3{permission}\n&f| &bIgnore case: &3{case}\n&f| &bIgnore color: &3{color}\n&f| &bSign conditions: &3{conditions}\n&f---------------" PAID_SIGN_INFO: "&f---&3Paid sign&f---\n&f| &bName: &3{name}\n&f| &bCost: &3{cost}\n&f| &bPermission: &3{permission}\n&f| &bIgnore case: &3{case}\n&f| &bIgnore color: &3{color}\n&f| &bMatch any condition: &3{any}\n&f| &bSign conditions: &3{conditions}\n&f---------------"
PAID_SIGN_INFO_CONDITION_FORMAT: "\n&f| &b{line}: &3{condition}" PAID_SIGN_INFO_CONDITION_FORMAT: "\n&f| &b{line}: &3{condition}"
PAID_SIGN_CONDITION_INFO: "&f---&3Paid sign condition&f---\n&f| &bPaid sign name: &3{name}\n&f| &bCondition line: &3{line}\n&f| &bMatch string: &3{match}\n&f| &bExecute RegEx: &3{regex}\n&f| &bIgnore case: &3{case}\n&f| &bIgnore color: &3{color}\n&f---------------" PAID_SIGN_CONDITION_INFO: "&f---&3Paid sign condition&f---\n&f| &bPaid sign name: &3{name}\n&f| &bCondition line: &3{line}\n&f| &bMatch string: &3{match}\n&f| &bExecute RegEx: &3{regex}\n&f| &bIgnore case: &3{case}\n&f| &bIgnore color: &3{color}\n&f---------------"
BOOLEAN_TRUE: "&2true" BOOLEAN_TRUE: "&2true"
@ -21,3 +21,4 @@ en:
ERROR_PAID_SIGN_NOT_FOUND: "&bNo such paid sign exists" ERROR_PAID_SIGN_NOT_FOUND: "&bNo such paid sign exists"
ERROR_NO_SUCH_CONDITION: "&bThe paid sign you specified has no condition for line {line}" ERROR_NO_SUCH_CONDITION: "&bThe paid sign you specified has no condition for line {line}"
ERROR_CANNOT_AFFORD: "&bYou cannot afford to create this sign" ERROR_CANNOT_AFFORD: "&bYou cannot afford to create this sign"
ERROR_INVALID_REGULAR_EXPRESSION: "&bThe provided regular expression is invalid"