35 Commits

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

View File

@ -10,3 +10,84 @@ As this plugin only listens to sign change events, there are some limitations:
1. The plugin is not aware of whether the creation of a sign is successful
2. It is assumed that any protection plugins run before this plugin, but it's not guaranteed
3. Plugins changing the lines on signs when successful might create confusion and mismatches
## Commands
* /addpaidsign <name> <cost> \[permission] \[ignore case] \[ignore color] \[match any condition]
* /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).
* 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
This adds a condition to a paid sign which is used to decide if a sign created by a player matches the paid sign. Adding
a paid sign condition to a line that already has one will replace the previous condition.
* name - The name of the paid sign to add the condition to
* line number - The line on the sign (1-4) to search for any matches
* string to match - The string or regular expression to look for on a sign
* executeRegEx - Whether to use a regular expression match instead of looking for the exact string
* ignoreCase - Whether this condition should ignore case when trying to match the string (default uses the "parent"
sign's value)
* ignoreColor - Whether this condition should ignore color when trying to match the string (default uses the "parent"
sign's value)
### /listpaidsigns
This lists registered paid signs and paid sign conditions. No arguments will print a list of paid signs
* name - The name of the paid sign to see information about
* line number - The line number of the condition to see information about
### /removepaidsigncondition
Removes a paid sign condition from a sign
* name - The name of the paid sign to remove the condition from
* line number - The line the condition is associated with
### /removepaidsign
Removes a registered paid sign
* name - The name of the paid sign to remove
## Permissions
* paidsigns.* - Grants all paid signs permissions
* paidsigns.manage - Grants the permission to add/remove a paid sign
* paidsigns.reload - Grants the permissions to reload the plugin
* paidsigns.paymentexempt - Makes this player exempt from the cost of paid signs
## Configuration options
* 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
per-sign basis, but this value is used if not specified. The correct value depends on whether the plugin signs it
should match are case-sensitive or not.
* ignoreColor - Whether to ignore any color or formatting applied to the text when trying to match a paid sign's text.
The option can be set on a per-sign basis, but this value is used if not specified. The correct value depends on
whether the plugin signs it should match allow coloring or not.
* enableRefunds - Whether to enable refunds to the sign creator when a sign detected as a paid sign is broken (payment
will always go to the original creator)
* refundPercentage - The percentage of the paid sign cost to refund (0-100)

View File

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

View File

@ -1,15 +1,25 @@
package net.knarcraft.paidsigns;
import net.knarcraft.paidsigns.command.AddCommand;
import net.knarcraft.paidsigns.command.AddConditionCommand;
import net.knarcraft.paidsigns.command.AddConditionTabCompleter;
import net.knarcraft.paidsigns.command.AddTabCompleter;
import net.knarcraft.paidsigns.command.ListCommand;
import net.knarcraft.paidsigns.command.ListTabCompleter;
import net.knarcraft.paidsigns.command.ReloadTabCommand;
import net.knarcraft.paidsigns.command.RemoveCommand;
import net.knarcraft.paidsigns.command.RemoveTabCompleter;
import net.knarcraft.paidsigns.command.RemoveConditionCommand;
import net.knarcraft.paidsigns.command.RemoveConditionTabCompleter;
import net.knarcraft.paidsigns.command.RemoveTabCommand;
import net.knarcraft.paidsigns.formatting.Translator;
import net.knarcraft.paidsigns.listener.BlockBreakListener;
import net.knarcraft.paidsigns.listener.SignListener;
import net.knarcraft.paidsigns.manager.EconomyManager;
import net.knarcraft.paidsigns.manager.PaidSignManager;
import net.knarcraft.paidsigns.manager.TrackedSignManager;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabCompleter;
import org.bukkit.command.TabExecutor;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.PluginManager;
@ -24,8 +34,11 @@ public final class PaidSigns extends JavaPlugin {
private static PaidSigns paidSigns;
private PaidSignManager signManager;
private String language;
private boolean ignoreCase;
private boolean ignoreColor;
private boolean enableRefunds;
private int refundPercentage;
/**
* Instantiates a new paid signs object
@ -47,11 +60,14 @@ public final class PaidSigns extends JavaPlugin {
@Override
public void onEnable() {
setupVault();
signManager = new PaidSignManager(PaidSignManager.loadSigns());
loadConfig();
Translator.loadLanguages(language);
signManager = new PaidSignManager(PaidSignManager.loadSigns());
TrackedSignManager.loadTrackedSigns();
PluginManager pluginManager = getServer().getPluginManager();
pluginManager.registerEvents(new SignListener(), this);
pluginManager.registerEvents(new BlockBreakListener(), this);
registerCommands();
}
@ -66,7 +82,9 @@ public final class PaidSigns extends JavaPlugin {
public void reload() {
this.reloadConfig();
loadConfig();
Translator.loadLanguages(language);
signManager = new PaidSignManager(PaidSignManager.loadSigns());
TrackedSignManager.loadTrackedSigns();
}
/**
@ -96,27 +114,56 @@ public final class PaidSigns extends JavaPlugin {
return this.ignoreColor;
}
/**
* Checks whether refunds are currently enabled
*
* @return <p>Whether refunds are currently enabled</p>
*/
public boolean areRefundsEnabled() {
return this.enableRefunds;
}
/**
* Gets the percentage of the initial cost to refund the sign creator
*
* @return <p>The percentage of the cost to refund</p>
*/
public int getRefundPercentage() {
if (this.refundPercentage < 0) {
return 0;
} else if (refundPercentage > 100) {
return 100;
}
return this.refundPercentage;
}
/**
* Registers the commands used by this plugin
*/
private void registerCommands() {
PluginCommand addCommand = this.getCommand("addPaidSign");
if (addCommand != null) {
addCommand.setExecutor(new AddCommand());
addCommand.setTabCompleter(new AddTabCompleter());
}
registerCommand("addPaidSign", new AddCommand(), new AddTabCompleter());
registerCommand("listPaidSigns", new ListCommand(), new ListTabCompleter());
registerCommand("addPaidSignCondition", new AddConditionCommand(), new AddConditionTabCompleter());
registerCommand("removePaidSignCondition", new RemoveConditionCommand(), new RemoveConditionTabCompleter());
PluginCommand removeCommand = this.getCommand("removePaidSign");
if (removeCommand != null) {
removeCommand.setExecutor(new RemoveCommand());
removeCommand.setTabCompleter(new RemoveTabCompleter());
}
PluginCommand reloadCommand = this.getCommand("reload");
if (reloadCommand != null) {
TabExecutor removeTabExecutor = new RemoveTabCommand();
registerCommand("removePaidSign", removeTabExecutor, removeTabExecutor);
TabExecutor reloadTabExecutor = new ReloadTabCommand();
reloadCommand.setExecutor(reloadTabExecutor);
reloadCommand.setTabCompleter(reloadTabExecutor);
registerCommand("reload", reloadTabExecutor, reloadTabExecutor);
}
/**
* Registers a command if possible
*
* @param command <p>The command to register</p>
* @param commandExecutor <p>The command executor for executing the command</p>
* @param tabCompleter <p>The tab completer for tab-completing the command</p>
*/
private void registerCommand(String command, CommandExecutor commandExecutor, TabCompleter tabCompleter) {
PluginCommand pluginCommand = this.getCommand(command);
if (pluginCommand != null) {
pluginCommand.setExecutor(commandExecutor);
pluginCommand.setTabCompleter(tabCompleter);
}
}
@ -127,8 +174,12 @@ public final class PaidSigns extends JavaPlugin {
FileConfiguration config = this.getConfig();
config.options().copyDefaults(true);
this.saveDefaultConfig();
this.saveConfig();
ignoreCase = config.getBoolean("ignoreCase", true);
ignoreColor = config.getBoolean("ignoreColor", false);
enableRefunds = config.getBoolean("enableRefunds", true);
refundPercentage = config.getInt("refundPercentage", 100);
language = config.getString("language", "en");
}
/**
@ -140,7 +191,7 @@ public final class PaidSigns extends JavaPlugin {
if (economyProvider != null) {
EconomyManager.initialize(economyProvider.getProvider());
} else {
throw new IllegalStateException("[PaidSigns] Error: Vault could not be loaded");
throw new IllegalStateException("Error: Vault could not be loaded");
}
}

View File

@ -2,73 +2,87 @@ package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.container.PaidSign;
import net.knarcraft.paidsigns.formatting.StringFormatter;
import net.knarcraft.paidsigns.formatting.TranslatableMessage;
import net.knarcraft.paidsigns.manager.PaidSignManager;
import net.knarcraft.paidsigns.property.OptionState;
import net.knarcraft.paidsigns.utility.Tokenizer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A representation of the command for adding a new paid sign
*/
public class AddCommand implements CommandExecutor {
public class AddCommand extends TokenizedCommand {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) {
if (args.length < 3) {
super.onCommand(sender, command, label, args);
if (argumentSize < 2) {
return false;
}
PaidSignManager manager = PaidSigns.getInstance().getSignManager();
List<String> arguments = Tokenizer.tokenize(String.join(" ", args));
String id = arguments.get(0);
short line;
String signName = arguments.get(0).trim();
double cost;
try {
line = (short) (Short.parseShort(arguments.get(1)) - 1);
cost = Double.parseDouble(arguments.get(2));
cost = Double.parseDouble(arguments.get(1));
} catch (NumberFormatException exception) {
sender.sendMessage("You provided an invalid number");
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_INVALID_NUMBER));
return false;
}
String permission = "";
if (argumentSize > 2) {
permission = arguments.get(2);
}
OptionState ignoreCase = OptionState.DEFAULT;
OptionState ignoreColor = OptionState.DEFAULT;
if (arguments.size() > 3) {
if (argumentSize > 3) {
ignoreCase = OptionState.fromString(arguments.get(3));
}
if (arguments.size() > 4) {
if (argumentSize > 4) {
ignoreColor = OptionState.fromString(arguments.get(4));
}
try {
PaidSign sign = new PaidSign(id, line, cost, ignoreCase, ignoreColor);
for (PaidSign similarSign : manager.getPaidSigns(sign.getCleanId(), sign.getLineIndex())) {
if (sign.matches(similarSign)) {
sender.sendMessage("A paid sign with the same id and line already exists");
return false;
boolean matchAnyCondition = false;
if (argumentSize > 5) {
matchAnyCondition = Boolean.parseBoolean(arguments.get(5));
}
return createPaidSign(sender, signName, cost, permission, ignoreCase, ignoreColor, matchAnyCondition);
}
/**
* 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>
* @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>
*/
private boolean createPaidSign(CommandSender sender, String signName, double cost, String permission,
OptionState ignoreCase, OptionState ignoreColor, boolean matchAnyCondition) {
PaidSignManager manager = PaidSigns.getInstance().getSignManager();
try {
PaidSign sign = new PaidSign(signName, cost, permission, ignoreCase, ignoreColor, matchAnyCondition);
if (manager.getPaidSign(signName) != null) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_NAME_DUPLICATE));
return false;
}
manager.addPaidSign(sign);
sender.sendMessage("Successfully added new paid sign");
sender.sendMessage(StringFormatter.getTranslatedInfoMessage(TranslatableMessage.SUCCESS_ADDED_PAID_SIGN));
return true;
} catch (IOException e) {
Logger logger = PaidSigns.getInstance().getLogger();
logger.log(Level.SEVERE, "Exception encountered while trying to write " +
"to the data file");
logger.log(Level.SEVERE, Arrays.toString(e.getStackTrace()));
sender.sendMessage("An exception occurred. Please notify the server administrator or check the server log.");
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_EXCEPTION_OCCURRED));
return false;
} catch (IllegalArgumentException e) {
sender.sendMessage("Invalid input: " + e.getMessage());
sender.sendMessage(StringFormatter.replacePlaceholder(StringFormatter.getTranslatedErrorMessage(
TranslatableMessage.ERROR_INVALID_INPUT), "{input}", e.getMessage()));
}
return false;
}

View File

@ -0,0 +1,111 @@
package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.container.PaidSign;
import net.knarcraft.paidsigns.formatting.StringFormatter;
import net.knarcraft.paidsigns.formatting.TranslatableMessage;
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.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/**
* 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);
if (lineNumber < 0 || lineNumber > 3) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_INVALID_NUMBER));
return false;
}
} catch (NumberFormatException exception) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_INVALID_NUMBER));
return false;
}
String stringToMatch = arguments.get(2);
boolean executeRegEx = false;
if (argumentSize > 3) {
executeRegEx = Boolean.parseBoolean(arguments.get(3));
if (executeRegEx && !testRegEx(sender, stringToMatch)) {
return false;
}
}
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)));
}
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
*
* @param name <p>The name of the paid sign to add the condition to</p>
* @param lineNumber <p>The sign line the condition should check</p>
* @param stringToMatch <p>The string to look for on created signs</p>
* @param executeRegEx <p>Whether to treat the match string as a regular expression</p>
* @param ignoreCase <p>Whether to ignore case when matching</p>
* @param ignoreColor <p>Whether to ignore color when matching</p>
* @param sender <p>The command sender to notify when finished</p>
* @return <p>True if the condition was successfully added</p>
*/
private boolean addCondition(String name, short lineNumber, String stringToMatch, boolean executeRegEx,
OptionState ignoreCase, OptionState ignoreColor, CommandSender sender) {
PaidSignManager signManager = PaidSigns.getInstance().getSignManager();
PaidSign sign = signManager.getPaidSign(name);
if (sign == null) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_PAID_SIGN_NOT_FOUND));
return false;
}
sign.addCondition(lineNumber, stringToMatch, executeRegEx, ignoreCase, ignoreColor);
try {
signManager.saveSigns();
} catch (IOException e) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_EXCEPTION_OCCURRED));
return false;
}
sender.sendMessage(StringFormatter.getTranslatedInfoMessage(
TranslatableMessage.SUCCESS_ADDED_PAID_SIGN_CONDITION));
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,140 @@
package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.utility.Tokenizer;
import net.knarcraft.paidsigns.utility.TabCompleteHelper;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.permissions.Permission;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
/**
* The tab completer for the add paid sign command
*/
public class AddTabCompleter implements TabCompleter {
public class AddTabCompleter extends TokenizedTabCompleter {
private static List<String> ids;
private static List<String> lines;
private static List<String> names;
private static List<String> costs;
private static List<String> plugins;
private static Map<String, List<String>> permissions;
private static List<String> options;
private static List<String> booleans;
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] args) {
if (ids == null) {
super.onTabComplete(sender, command, alias, args);
if (names == null) {
initializeValues();
loadAvailablePermissions();
}
List<String> arguments = Tokenizer.tokenize(String.join(" ", args));
if (arguments.size() < 1) {
return ids;
} else if (arguments.size() < 2) {
return lines;
} else if (arguments.size() < 3) {
return costs;
} else if (arguments.size() < 5) {
return options;
if (argumentSize == 1) {
return TabCompleteHelper.filterMatchingStartsWith(names, arguments.get(0));
} else if (argumentSize == 2) {
return TabCompleteHelper.filterMatchingStartsWith(costs, arguments.get(1));
} else if (argumentSize == 3) {
return tabCompletePermission(arguments.get(arguments.size() - 1));
} else if (argumentSize == 4) {
return TabCompleteHelper.filterMatchingStartsWith(options, arguments.get(3));
} else if (argumentSize == 5) {
return TabCompleteHelper.filterMatchingStartsWith(options, arguments.get(4));
} else if (argumentSize == 6) {
return TabCompleteHelper.filterMatchingStartsWith(booleans, arguments.get(5));
}
return new ArrayList<>();
}
/**
* Gets the tab complete value for the permission typed
*
* @param typedNode <p>The full permission node typed by the player</p>
* @return <p>All known valid auto-complete options</p>
*/
private List<String> tabCompletePermission(String typedNode) {
List<String> output;
if (typedNode.contains(".")) {
List<String> matchingPermissions = permissions.get(typedNode.substring(0, typedNode.lastIndexOf(".")));
if (matchingPermissions == null) {
output = new ArrayList<>();
} else {
//Filter by the typed text
output = filterMatching(matchingPermissions, typedNode);
}
} else {
output = plugins;
}
//Add previous permissions in the comma-separated lists as a prefix
return output;
}
/**
* Find completable strings which match the text typed by the command's sender
*
* @param values <p>The values to filter</p>
* @param typedText <p>The text the player has started typing</p>
* @return <p>The given string values which start with the player's typed text</p>
*/
private List<String> filterMatching(List<String> values, String typedText) {
List<String> configValues = new ArrayList<>();
for (String value : values) {
if (value.toLowerCase().startsWith(typedText.toLowerCase())) {
configValues.add(value);
}
}
return configValues;
}
/**
* Loads all permissions available from bukkit plugins
*/
private static void loadAvailablePermissions() {
plugins = new ArrayList<>();
permissions = new HashMap<>();
for (Permission permission : Bukkit.getPluginManager().getPermissions()) {
loadPermission(permission.getName());
}
}
/**
* Loads a given permission into the proper lists and maps
*
* @param permissionName <p>The permission to load</p>
*/
private static void loadPermission(String permissionName) {
String[] permissionParts = permissionName.split("\\.");
if (permissionParts.length == 1 && !plugins.contains(permissionParts[0])) {
plugins.add(permissionParts[0]);
} else if (permissionParts.length > 1) {
StringJoiner pathJoiner = new StringJoiner(".");
for (int j = 0; j < permissionParts.length - 1; j++) {
pathJoiner.add(permissionParts[j]);
}
String path = pathJoiner.toString();
List<String> permissionList = permissions.computeIfAbsent(path, k -> new ArrayList<>());
permissionList.add(permissionName);
loadPermission(path);
}
}
/**
* Initializes the values available for tab completion
*/
private static void initializeValues() {
ids = new ArrayList<>();
ids.add("[Gate]");
ids.add("\"[Lift Up]\"");
ids.add("\"[Lift Down]\"");
lines = new ArrayList<>();
lines.add("1");
lines.add("2");
lines.add("3");
lines.add("4");
costs = new ArrayList<>();
costs.add("1");
costs.add("5");
costs.add("10");
costs.add("15");
options = new ArrayList<>();
options.add("default");
options.add("true");
options.add("false");
names = TabCompleteHelper.getExamplePaidSignNames();
costs = TabCompleteHelper.getCosts();
options = TabCompleteHelper.getOptionStates();
booleans = TabCompleteHelper.getBooleans();
}
}

View File

@ -0,0 +1,130 @@
package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.container.PaidSign;
import net.knarcraft.paidsigns.container.PaidSignCondition;
import net.knarcraft.paidsigns.formatting.StringFormatter;
import net.knarcraft.paidsigns.formatting.TranslatableMessage;
import net.knarcraft.paidsigns.formatting.Translator;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
import static net.knarcraft.paidsigns.formatting.StringFormatter.getTranslatedErrorMessage;
import static net.knarcraft.paidsigns.formatting.StringFormatter.replacePlaceholder;
import static net.knarcraft.paidsigns.formatting.StringFormatter.replacePlaceholders;
import static net.knarcraft.paidsigns.formatting.StringFormatter.translateBoolean;
/**
* 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) {
displaySigns(sender);
return true;
} else if (argumentSize < 3) {
return parsePaidSignSelection(sender);
}
return false;
}
/**
* Displays all available signs
*
* @param sender <p>The command sender to display the signs to</p>
*/
private void displaySigns(CommandSender sender) {
StringBuilder signs = new StringBuilder();
for (String signName : PaidSigns.getInstance().getSignManager().getAllPaidSigns().keySet()) {
signs.append(StringFormatter.replacePlaceholder(Translator.getTranslatedMessage(
TranslatableMessage.PAID_SIGNS_INFO_FORMAT), "{name}", signName));
}
sender.sendMessage(StringFormatter.replacePlaceholder(Translator.getTranslatedMessage(
TranslatableMessage.PAID_SIGNS_INFO), "{signs}", signs.toString()));
}
/**
* 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(getTranslatedErrorMessage(TranslatableMessage.ERROR_PAID_SIGN_NOT_FOUND));
return false;
}
if (argumentSize < 2) {
displayPaidSign(sender, paidSign);
} else {
short lineNumber;
try {
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) {
sender.sendMessage(getTranslatedErrorMessage(TranslatableMessage.ERROR_INVALID_NUMBER));
return false;
}
if (!paidSign.getConditions().containsKey(lineNumber)) {
sender.sendMessage(replacePlaceholder(getTranslatedErrorMessage(
TranslatableMessage.ERROR_NO_SUCH_CONDITION), "{line}", String.valueOf(lineNumber)));
return false;
}
PaidSignCondition condition = paidSign.getConditions().get(lineNumber);
displayPaidSignCondition(sender, paidSign.getName(), lineNumber, 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(StringFormatter.replacePlaceholders(Translator.getTranslatedMessage(
TranslatableMessage.PAID_SIGN_CONDITION_INFO), new String[]{"{name}", "{line}", "{match}", "{regex}",
"{case}", "{color}"}, new String[]{signName, String.valueOf(signLine + 1), condition.getStringToMatch(),
translateBoolean(condition.executeRegex()), translateBoolean(condition.ignoreCase()),
translateBoolean(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) {
Map<Short, PaidSignCondition> signConditions = paidSign.getConditions();
StringBuilder conditions = new StringBuilder();
for (short lineIndex : signConditions.keySet()) {
String format = Translator.getTranslatedMessage(TranslatableMessage.PAID_SIGN_INFO_CONDITION_FORMAT);
conditions.append(StringFormatter.replacePlaceholders(format, new String[]{"{line}", "{condition}"},
new String[]{String.valueOf((lineIndex + 1)), signConditions.get(lineIndex).getStringToMatch()}));
}
sender.sendMessage(replacePlaceholders(Translator.getTranslatedMessage(
TranslatableMessage.PAID_SIGN_INFO), new String[]{"{name}", "{cost}", "{permission}", "{case}",
"{color}", "{any}", "{conditions}"}, new String[]{paidSign.getName(), String.valueOf(paidSign.getCost()),
paidSign.getPermission(), translateBoolean(paidSign.getIgnoreCase()),
translateBoolean(paidSign.getIgnoreColor()), translateBoolean(paidSign.matchAnyCondition()),
conditions.toString()}));
}
}

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

@ -1,6 +1,8 @@
package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.formatting.StringFormatter;
import net.knarcraft.paidsigns.formatting.TranslatableMessage;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
@ -18,7 +20,8 @@ public class ReloadTabCommand implements TabExecutor {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) {
PaidSigns.getInstance().reload();
return false;
sender.sendMessage(StringFormatter.getTranslatedInfoMessage(TranslatableMessage.SUCCESS_RELOADED));
return true;
}
@Override

View File

@ -1,55 +0,0 @@
package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.utility.Tokenizer;
import org.apache.commons.lang.ArrayUtils;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A representation of the command for removing a paid sign
*/
public class RemoveCommand implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) {
if (args.length < 1) {
return false;
}
List<String> arguments = Tokenizer.tokenize(String.join(" ", args));
String[] input = arguments.get(0).split("\\|");
short line;
try {
line = (short) (Short.parseShort(input[0]) - 1);
} catch (NumberFormatException exception) {
sender.sendMessage("Invalid line number given");
return false;
}
String id = String.join("|", (String[]) ArrayUtils.remove(input, 0));
try {
if (PaidSigns.getInstance().getSignManager().removePaidSign(id, line)) {
sender.sendMessage("Successfully removed paid sign");
} else {
sender.sendMessage("No matching paid sign was found");
}
return true;
} catch (IOException e) {
Logger logger = PaidSigns.getInstance().getLogger();
logger.log(Level.SEVERE, "Exception encountered while trying to write " +
"to the data file");
logger.log(Level.SEVERE, Arrays.toString(e.getStackTrace()));
sender.sendMessage("An exception occurred. Please notify the server administrator or check the server log.");
}
return false;
}
}

View File

@ -0,0 +1,62 @@
package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.container.PaidSign;
import net.knarcraft.paidsigns.formatting.StringFormatter;
import net.knarcraft.paidsigns.formatting.TranslatableMessage;
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;
/**
* 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 lineNumber;
try {
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) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_INVALID_NUMBER));
return false;
}
PaidSignManager signManager = PaidSigns.getInstance().getSignManager();
PaidSign sign = signManager.getPaidSign(name);
if (sign == null) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_PAID_SIGN_NOT_FOUND));
return false;
}
if (!sign.getConditions().containsKey(lineNumber)) {
sender.sendMessage(StringFormatter.replacePlaceholder(StringFormatter.getTranslatedErrorMessage(
TranslatableMessage.ERROR_NO_SUCH_CONDITION), "{line}", String.valueOf((lineNumber + 1))));
return false;
}
sign.removeCondition(lineNumber);
try {
signManager.saveSigns();
} catch (IOException e) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_EXCEPTION_OCCURRED));
return false;
}
sender.sendMessage(StringFormatter.getTranslatedInfoMessage(TranslatableMessage.SUCCESS_REMOVED_CONDITION));
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

@ -0,0 +1,67 @@
package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.container.PaidSign;
import net.knarcraft.paidsigns.formatting.StringFormatter;
import net.knarcraft.paidsigns.formatting.TranslatableMessage;
import net.knarcraft.paidsigns.utility.Tokenizer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* A representation of the command for removing a paid sign
*/
public class RemoveTabCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) {
List<String> arguments = Tokenizer.tokenize(String.join(" ", args));
if (arguments.size() < 1) {
return false;
}
String name = arguments.get(0);
try {
if (PaidSigns.getInstance().getSignManager().removePaidSign(name)) {
sender.sendMessage(StringFormatter.getTranslatedInfoMessage(
TranslatableMessage.SUCCESS_REMOVED_PAID_SIGN));
} else {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(
TranslatableMessage.ERROR_PAID_SIGN_NOT_FOUND));
}
return true;
} catch (IOException e) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_EXCEPTION_OCCURRED));
}
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,59 @@ package net.knarcraft.paidsigns.container;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.property.OptionState;
import net.knarcraft.paidsigns.utility.ColorHelper;
import net.md_5.bungee.api.ChatColor;
import java.util.HashMap;
import java.util.Map;
/**
* A representation of a paid sign
*/
public class PaidSign {
private final String id;
private final String cleanId;
private final short lineIndex;
private final String name;
private final double cost;
private final String permission;
private final OptionState ignoreCase;
private final OptionState ignoreColor;
private final boolean matchAnyCondition;
private final Map<Short, PaidSignCondition> conditions = new HashMap<>();
/**
* Instantiates a new paid sign
*
* @param id <p>The string that identifies this type of paid sign</p>
* @param lineIndex <p>The line the id has to be on to trigger payment</p>
* @param name <p>A recognizable, unique, name used to identify this paid sign</p>
* @param cost <p>The cost of creating this paid sign</p>
* @param permission <p>The permission required to create the sign this paid sign matches</p>
* @param ignoreCase <p>Whether to ignore case when looking for this permission sign</p>
* @param ignoreColor <p>Whether to ignore color when looking for this permission sign</p>
* @param matchAnyCondition <p>Whether to treat a match for any condition as a sign match</p>
*/
public PaidSign(String id, short lineIndex, double cost, OptionState ignoreCase, OptionState ignoreColor) {
if (id == null || id.trim().isBlank()) {
throw new IllegalArgumentException("Id cannot be empty");
public PaidSign(String name, double cost, String permission, OptionState ignoreCase, OptionState ignoreColor,
boolean matchAnyCondition) {
if (name == null || name.trim().isBlank()) {
throw new IllegalArgumentException("Name cannot be empty");
}
if (cost <= 0) {
throw new IllegalArgumentException("Cost must be larger than 0");
}
if (lineIndex < 0 || lineIndex > 3) {
throw new IllegalArgumentException("Sign line must be between 0 and 3");
}
if (ignoreCase == null || ignoreColor == null) {
throw new IllegalArgumentException("Ignore case and ignore color options cannot be null");
}
this.id = id;
this.lineIndex = lineIndex;
this.name = name;
this.cost = cost;
this.permission = permission;
this.ignoreCase = ignoreCase;
this.ignoreColor = ignoreColor;
this.cleanId = getCleanString(id);
this.matchAnyCondition = matchAnyCondition;
}
/**
* Gets the id string of this paid sign
* Gets the name identifying this paid sign
*
* @return <p>The id string of this paid sign</p>
* @return <p>The name identifying this paid sign</p>
*/
public String getId() {
return id;
public String getName() {
return name;
}
/**
@ -66,66 +67,21 @@ public class PaidSign {
}
/**
* Gets the line on the sign the id must be on to trigger payment
* Gets the permission required by a player to create the sign this paid sign matches
*
* @return <p>The sign line to search for the id</p>
* @return <p>The permission required by a player to create the sign this paid sign matches</p>
*/
public short getLineIndex() {
return lineIndex;
public String getPermission() {
return this.permission;
}
/**
* Gets the clean id of this paid sign
* Gets all conditions registered for this paid sign
*
* @return <p>The clean id of this paid sign</p>
* @return <p>All conditions registered for this paid sign</p>
*/
public String getCleanId() {
return cleanId;
}
/**
* Gets the "clean" version of the given string
*
* <p>The "cleaning" removes color codes and lower-cases the string.</p>
*
* @param string <p>The string to clean</p>
* @return <p>The cleaned string</p>
*/
public static String getCleanString(String string) {
return ChatColor.stripColor(ColorHelper.translateAllColorCodes(string.toLowerCase()));
}
/**
* Checks whether this paid sign matches the given line
*
* @param lineNumber <p>The line number of the given line</p>
* @param line <p>The line to compare against this paid sign's id</p>
* @return <p>True if the line matches this sign</p>
*/
public boolean matches(short lineNumber, String line) {
if (lineNumber != this.lineIndex) {
return false;
}
String idCopy = id;
if (getIgnoreCase()) {
idCopy = idCopy.toLowerCase();
line = line.toLowerCase();
}
if (getIgnoreColor()) {
idCopy = ColorHelper.stripColorCodes(idCopy);
line = ColorHelper.stripColorCodes(line);
}
return idCopy.equals(line);
}
/**
* Checks whether this paid sign matches another paid sign
*
* @param paidSign <p>The other paid sign to compare to</p>
* @return <p>True if the signs match</p>
*/
public boolean matches(PaidSign paidSign) {
return matches(paidSign.lineIndex, paidSign.id);
public Map<Short, PaidSignCondition> getConditions() {
return new HashMap<>(this.conditions);
}
/**
@ -134,11 +90,7 @@ public class PaidSign {
* @return <p>Whether the text case should be ignored for this paid sign</p>
*/
public boolean getIgnoreCase() {
if (this.ignoreCase == OptionState.DEFAULT) {
return PaidSigns.getInstance().ignoreCase();
} else {
return OptionState.getBooleanValue(this.ignoreCase);
}
return OptionState.getBooleanValue(this.ignoreCase, PaidSigns.getInstance().ignoreCase());
}
/**
@ -147,11 +99,71 @@ public class PaidSign {
* @return <p>Whether the text color should be ignored for this paid sign</p>
*/
public boolean getIgnoreColor() {
if (this.ignoreColor == OptionState.DEFAULT) {
return PaidSigns.getInstance().ignoreColor();
} else {
return OptionState.getBooleanValue(this.ignoreColor);
return OptionState.getBooleanValue(this.ignoreColor, PaidSigns.getInstance().ignoreColor());
}
/**
* 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
*
* @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 matchAny = matchAnyCondition();
boolean success = !matchAny;
for (short i = 0; i < 4; i++) {
PaidSignCondition condition = this.conditions.get(i);
if (condition != null) {
boolean conditionMatches = condition.test(lines[i]);
if (matchAny) {
success |= conditionMatches;
} else {
success &= conditionMatches;
}
}
}
return success;
}
/**
* Adds a condition to this paid sign
*
* @param line <p>The line on the sign the matched text must be on</p>
* @param stringToMatch <p>The string that should be matched for the new condition to be true</p>
* @param executeRegex <p>Whether to execute the string as RegEx</p>
* @param ignoreCase <p>Whether to ignore case when matching against the condition</p>
* @param ignoreColor <p>Whether to ignore color when matching against the condition</p>
*/
public void addCondition(short line, String stringToMatch, boolean executeRegex, OptionState ignoreCase, OptionState ignoreColor) {
if (line < 0 || line > 3) {
throw new IllegalArgumentException("Invalid sign line given for new paid sign condition");
}
boolean ignoreCaseBoolean = OptionState.getBooleanValue(ignoreCase, this.getIgnoreCase());
boolean ignoreColorBoolean = OptionState.getBooleanValue(ignoreColor, this.getIgnoreColor());
this.conditions.put(line, new PaidSignCondition(stringToMatch, executeRegex, ignoreCaseBoolean, ignoreColorBoolean));
}
/**
* Removes a condition from this paid sign
*
* @param line <p>The sign line the condition belongs to</p>
*/
public void removeCondition(short line) {
this.conditions.remove(line);
}
}

View File

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

View File

@ -0,0 +1,42 @@
package net.knarcraft.paidsigns.container;
import java.util.UUID;
/**
* A representation of a sign placed by a player that matched a paid sign
*/
public class TrackedSign {
private final UUID playerId;
private final double cost;
/**
* Instantiates a new tracked sign
*
* @param playerId <p>The unique id of the player that created the sign</p>
* @param cost <p>The cost the player paid for creating the sign</p>
*/
public TrackedSign(UUID playerId, double cost) {
this.playerId = playerId;
this.cost = cost;
}
/**
* Gets the id of the player that created this tracked sign
*
* @return <p>The player that created this tracked sign</p>
*/
public UUID getPlayerId() {
return this.playerId;
}
/**
* Gets the cost the player paid for creating this paid sign
*
* @return <p>The cost paid for creating this sign</p>
*/
public double getCost() {
return this.cost;
}
}

View File

@ -0,0 +1,125 @@
package net.knarcraft.paidsigns.formatting;
import net.md_5.bungee.api.ChatColor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A formatter for formatting displayed messages
*/
public final class StringFormatter {
private StringFormatter() {
}
/**
* Translates the given boolean value
*
* @param booleanValue <p>The boolean value to translate</p>
* @return <p>The translation of the boolean value</p>
*/
public static String translateBoolean(boolean booleanValue) {
if (booleanValue) {
return Translator.getTranslatedMessage(TranslatableMessage.BOOLEAN_TRUE);
} else {
return Translator.getTranslatedMessage(TranslatableMessage.BOOLEAN_FALSE);
}
}
/**
* Replaces a placeholder in a string
*
* @param input <p>The input string to replace in</p>
* @param placeholder <p>The placeholder to replace</p>
* @param replacement <p>The replacement value</p>
* @return <p>The input string with the placeholder replaced</p>
*/
public static String replacePlaceholder(String input, String placeholder, String replacement) {
return input.replace(placeholder, replacement);
}
/**
* Replaces placeholders in a string
*
* @param input <p>The input string to replace in</p>
* @param placeholders <p>The placeholders to replace</p>
* @param replacements <p>The replacement values</p>
* @return <p>The input string with placeholders replaced</p>
*/
public static String replacePlaceholders(String input, String[] placeholders, String[] replacements) {
for (int i = 0; i < Math.min(placeholders.length, replacements.length); i++) {
input = replacePlaceholder(input, placeholders[i], replacements[i]);
}
return input;
}
/**
* Gets a translated and formatted info message
*
* @param translatableMessage <p>The translatable message to translate and format</p>
* @return <p>The translated and formatted message</p>
*/
public static String getTranslatedInfoMessage(TranslatableMessage translatableMessage) {
return formatInfoMessage(Translator.getTranslatedMessage(translatableMessage));
}
/**
* Gets a translated and formatted error message
*
* @param translatableMessage <p>The translatable message to translate and format</p>
* @return <p>The translated and formatted message</p>
*/
public static String getTranslatedErrorMessage(TranslatableMessage translatableMessage) {
return formatErrorMessage(Translator.getTranslatedMessage(translatableMessage));
}
/**
* Formats an information message by adding the prefix and text color
*
* @param message <p>The message to format</p>
* @return <p>The formatted message</p>
*/
public static String formatInfoMessage(String message) {
return ChatColor.DARK_GREEN + formatMessage(message);
}
/**
* Formats an error message by adding the prefix and text color
*
* @param message <p>The message to format</p>
* @return <p>The formatted message</p>
*/
public static String formatErrorMessage(String message) {
return ChatColor.DARK_RED + formatMessage(message);
}
/**
* Translates all found color codes to formatting in a string
*
* @param message <p>The string to search for color codes</p>
* @return <p>The message with color codes translated</p>
*/
public static String translateAllColorCodes(String message) {
message = ChatColor.translateAlternateColorCodes('&', message);
Pattern pattern = Pattern.compile("(#[a-fA-F0-9]{6})");
Matcher matcher = pattern.matcher(message);
while (matcher.find()) {
message = message.replace(matcher.group(), "" + ChatColor.of(matcher.group()));
}
return message;
}
/**
* Formats a message by adding the prefix and text color
*
* @param message <p>The message to format</p>
* @return <p>The formatted message</p>
*/
private static String formatMessage(String message) {
return Translator.getTranslatedMessage(TranslatableMessage.PREFIX) + " " +
ChatColor.RESET + message;
}
}

View File

@ -0,0 +1,123 @@
package net.knarcraft.paidsigns.formatting;
/**
* An enum representing all translatable messages
*/
public enum TranslatableMessage {
/**
* The prefix to display in messages
*/
PREFIX,
/**
* The message to display when a paid sign is successfully added
*/
SUCCESS_ADDED_PAID_SIGN,
/**
* The message to display when a paid sign condition is successfully added
*/
SUCCESS_ADDED_PAID_SIGN_CONDITION,
/**
* The message to display when a paid sign has been successfully removed
*/
SUCCESS_REMOVED_PAID_SIGN,
/**
* The message to display when a paid sign condition has been successfully removed
*/
SUCCESS_REMOVED_CONDITION,
/**
* The message to display after the plugin has been reloaded
*/
SUCCESS_RELOADED,
/**
* The message to display when a player has been charged for creating a paid sign
*/
SUCCESS_PAID_FOR_SIGN,
/**
* The message to display when a player has been refunded for breaking a sign
*/
SUCCESS_REFUNDED,
/**
* The info text used to display all paid signs
*/
PAID_SIGNS_INFO,
/**
* The format used for displaying a paid sign's name
*/
PAID_SIGNS_INFO_FORMAT,
/**
* The info text used to display information about a paid sign
*/
PAID_SIGN_INFO,
/**
* The info text used to display information about a paid sign condition
*/
PAID_SIGN_CONDITION_INFO,
/**
* The format used for displaying one of a paid sign's conditions
*/
PAID_SIGN_INFO_CONDITION_FORMAT,
/**
* The message to display for a true boolean value
*/
BOOLEAN_TRUE,
/**
* The message to display for a false boolean value
*/
BOOLEAN_FALSE,
/**
* The error to display when a command argument contains an invalid number
*/
ERROR_INVALID_NUMBER,
/**
* The error to display if a paid sign name duplicate is encountered
*/
ERROR_NAME_DUPLICATE,
/**
* The error to display if a severe exception occurs
*/
ERROR_EXCEPTION_OCCURRED,
/**
* The error to display if some input is invalid
*/
ERROR_INVALID_INPUT,
/**
* The error to display if a specified paid sign is not found
*/
ERROR_PAID_SIGN_NOT_FOUND,
/**
* The error to display if a paid sign condition is specified, but does not exist
*/
ERROR_NO_SUCH_CONDITION,
/**
* The error to display if a player cannot pay for a sign matching a paid sign
*/
ERROR_CANNOT_AFFORD,
/**
* The error to display if an invalid regular expression is provided
*/
ERROR_INVALID_REGULAR_EXPRESSION,
}

View File

@ -0,0 +1,118 @@
package net.knarcraft.paidsigns.formatting;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.utility.FileHelper;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
/**
* A tool to get strings translated to the correct language
*/
public final class Translator {
private static Map<TranslatableMessage, String> translatedMessages;
private static Map<TranslatableMessage, String> backupTranslatedMessages;
private Translator() {
}
/**
* Loads the languages used by this translator
*/
public static void loadLanguages(String selectedLanguage) {
backupTranslatedMessages = loadTranslatedMessages("en");
translatedMessages = loadCustomTranslatedMessages(selectedLanguage);
if (translatedMessages == null) {
translatedMessages = loadTranslatedMessages(selectedLanguage);
}
}
/**
* Gets a translated version of the given translatable message
*
* @param translatableMessage <p>The message to translate</p>
* @return <p>The translated message</p>
*/
public static String getTranslatedMessage(TranslatableMessage translatableMessage) {
if (translatedMessages == null) {
return "Translated strings not loaded";
}
String translatedMessage;
if (translatedMessages.containsKey(translatableMessage)) {
translatedMessage = translatedMessages.get(translatableMessage);
} else if (backupTranslatedMessages.containsKey(translatableMessage)) {
translatedMessage = backupTranslatedMessages.get(translatableMessage);
} else {
translatedMessage = translatableMessage.toString();
}
return StringFormatter.translateAllColorCodes(translatedMessage);
}
/**
* Loads all translated messages for the given language
*
* @param language <p>The language chosen by the user</p>
* @return <p>A mapping of all strings for the given language</p>
*/
public static Map<TranslatableMessage, String> loadTranslatedMessages(String language) {
try {
BufferedReader reader = FileHelper.getBufferedReaderForInternalFile("/strings.yml");
return loadTranslatableMessages(language, reader);
} catch (FileNotFoundException e) {
PaidSigns.getInstance().getLogger().log(Level.SEVERE, "Unable to load translated messages");
return null;
}
}
/**
* Tries to load translated messages from a custom strings.yml file
*
* @param language <p>The selected language</p>
* @return <p>The loaded translated strings, or null if no custom language file exists</p>
*/
public static Map<TranslatableMessage, String> loadCustomTranslatedMessages(String language) {
File strings = new File(PaidSigns.getInstance().getDataFolder(), "strings.yml");
if (!strings.exists()) {
PaidSigns.getInstance().getLogger().log(Level.FINEST, "Strings file not found");
return null;
}
try {
PaidSigns.getInstance().getLogger().log(Level.WARNING, "Loading custom strings...");
return loadTranslatableMessages(language, new BufferedReader(new InputStreamReader(new FileInputStream(strings))));
} catch (FileNotFoundException e) {
PaidSigns.getInstance().getLogger().log(Level.WARNING, "Unable to load custom messages");
return null;
}
}
/**
* Loads translatable messages from the given reader
*
* @param language <p>The selected language</p>
* @param reader <p>The buffered reader to read from</p>
* @return <p>The loaded translated strings</p>
*/
private static Map<TranslatableMessage, String> loadTranslatableMessages(String language, BufferedReader reader) {
Map<TranslatableMessage, String> translatedMessages = new HashMap<>();
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(reader);
for (TranslatableMessage message : TranslatableMessage.values()) {
String translated = configuration.getString(language + "." + message.toString());
if (translated != null) {
translatedMessages.put(message, translated);
}
}
return translatedMessages;
}
}

View File

@ -0,0 +1,27 @@
package net.knarcraft.paidsigns.listener;
import net.knarcraft.paidsigns.manager.TrackedSignManager;
import org.bukkit.block.Sign;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import java.io.IOException;
/**
* A listener that listens for any tracked signs being broken
*/
public class BlockBreakListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR)
public void onBlockBreak(BlockBreakEvent event) {
if (event.getBlock().getState() instanceof Sign) {
try {
TrackedSignManager.removeTrackedSign(event.getBlock().getLocation());
} catch (IOException ignored) {
}
}
}
}

View File

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

View File

@ -2,7 +2,6 @@ package net.knarcraft.paidsigns.manager;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
/**
* A manager that performs all Economy tasks
@ -55,7 +54,7 @@ public final class EconomyManager {
* @param player <p>The player to withdraw money from</p>
* @param cost <p>The amount of money to withdraw</p>
*/
public static void withdraw(Player player, double cost) {
public static void withdraw(OfflinePlayer player, double cost) {
economy.withdrawPlayer(player, cost);
}

View File

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

View File

@ -0,0 +1,149 @@
package net.knarcraft.paidsigns.manager;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.container.TrackedSign;
import net.knarcraft.paidsigns.formatting.StringFormatter;
import net.knarcraft.paidsigns.formatting.TranslatableMessage;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.block.Sign;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.logging.Level;
/**
* A manager for keeping track of plugin-signs created by players
*/
public final class TrackedSignManager {
private static Map<Location, TrackedSign> trackedSigns = new HashMap<>();
private static final File signsFile = new File(PaidSigns.getInstance().getDataFolder(), "data.yml");
private TrackedSignManager() {
}
/**
* Adds a tracked sign to the manager
*
* @param signLocation <p>The location the sign was created at</p>
* @param playerId <p>The unique id of the player that created the sign</p>
* @throws IOException <p>If unable to save the tracked signs</p>
*/
public static void addTrackedSign(Location signLocation, UUID playerId, double cost) throws IOException {
trackedSigns.put(signLocation, new TrackedSign(playerId, cost));
saveTrackedSigns();
}
/**
* Removes a tracked sign from the manager
*
* @param signLocation <p>The location the sign was removed from</p>
* @throws IOException <p>If unable to save the tracked signs</p>
*/
public static void removeTrackedSign(Location signLocation) throws IOException {
if (!trackedSigns.containsKey(signLocation)) {
return;
}
TrackedSign trackedSign = trackedSigns.get(signLocation);
trackedSigns.remove(signLocation);
saveTrackedSigns();
if (!PaidSigns.getInstance().areRefundsEnabled()) {
return;
}
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(trackedSign.getPlayerId());
double refundSum = trackedSign.getCost() / 100 * PaidSigns.getInstance().getRefundPercentage();
EconomyManager.withdraw(offlinePlayer, -refundSum);
if (offlinePlayer instanceof Player player) {
player.sendMessage(String.format(StringFormatter.replacePlaceholders(
StringFormatter.getTranslatedInfoMessage(TranslatableMessage.SUCCESS_REFUNDED),
new String[]{"{cost}", "{unit}"}, new String[]{"%.2f", "%s"}), refundSum,
EconomyManager.getCurrency(refundSum != 1)));
}
}
/**
* Loads all tracked signs from the data file
*/
public static void loadTrackedSigns() {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(signsFile);
ConfigurationSection signSection = configuration.getConfigurationSection("trackedSigns");
trackedSigns = new HashMap<>();
if (signSection == null) {
PaidSigns.getInstance().getLogger().log(Level.WARNING, "Tracked signs section not found in data.yml");
return;
}
for (String key : signSection.getKeys(false)) {
try {
loadSign(signSection, key);
} catch (InvalidConfigurationException e) {
PaidSigns.getInstance().getLogger().log(Level.SEVERE, "Unable to load sign " + key + ": " +
e.getMessage());
}
}
}
/**
* Loads a sign from the save file
*
* @param signSection <p>The configuration section containing signs</p>
* @param key <p>The sign key which is also the sign's location</p>
* @throws InvalidConfigurationException <p>If unable to load the sign</p>
*/
private static void loadSign(ConfigurationSection signSection, String key) throws InvalidConfigurationException {
String[] locationParts = key.split(",");
Location signLocation;
try {
signLocation = new Location(Bukkit.getWorld(UUID.fromString(locationParts[0])),
Double.parseDouble(locationParts[1]), Double.parseDouble(locationParts[2]),
Double.parseDouble(locationParts[3]));
} catch (NumberFormatException exception) {
throw new InvalidConfigurationException("Invalid sign coordinates");
}
//Prevent destroyed signs from being tracked indefinitely
if (!(signLocation.getBlock().getState() instanceof Sign)) {
PaidSigns.getInstance().getLogger().log(Level.WARNING, "The sign at " + signLocation + " no longer " +
"exists. Removing from sign tracker.");
return;
}
double cost = signSection.getDouble(key + ".cost");
UUID playerId = UUID.fromString(Objects.requireNonNull(signSection.getString(key + ".playerId")));
TrackedSign trackedSign = new TrackedSign(playerId, cost);
trackedSigns.put(signLocation, trackedSign);
}
/**
* Saves the managed tracked signs to the data file
*
* @throws IOException <p>If unable to write to the data file</p>
*/
private static void saveTrackedSigns() throws IOException {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(signsFile);
ConfigurationSection signSection = configuration.createSection("trackedSigns");
for (Location signLocation : trackedSigns.keySet()) {
TrackedSign sign = trackedSigns.get(signLocation);
String locationString = Objects.requireNonNull(signLocation.getWorld()).getUID() + "," +
signLocation.getBlockX() + "," + signLocation.getBlockY() + "," + signLocation.getBlockZ();
signSection.set(locationString + ".cost", sign.getCost());
signSection.set(locationString + ".playerId", sign.getPlayerId().toString());
}
configuration.save(signsFile);
}
}

View File

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

View File

@ -0,0 +1,32 @@
package net.knarcraft.paidsigns.utility;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
/**
* A helper class for dealing with files
*/
public final class FileHelper {
private FileHelper() {
}
/**
* Gets a buffered reader for
*
* @return <p>A buffered read for reading the file</p>
* @throws FileNotFoundException <p>If unable to get an input stream for the given file</p>
*/
public static BufferedReader getBufferedReaderForInternalFile(String file) throws FileNotFoundException {
InputStream inputStream = FileHelper.class.getResourceAsStream(file);
if (inputStream == null) {
throw new FileNotFoundException("Unable to read the given file");
}
return new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
}
}

View File

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

View File

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

View File

@ -1,8 +1,19 @@
# The currently enabled language. More languages can be added to the language file
language: en
# Whether to ignore the case (lowercase/uppercase) of the paid sign text. The option can be set on a per-sign basis, but
# this value is used if not specified. The correct value depends on whether the plugin signs it should match are case-sensitive or not.
# this value is used if not specified. The correct value depends on whether the plugin signs it should match are
# case-sensitive or not.
ignoreCase: true
# Whether to ignore any color or formatting applied to the text when trying to match a paid sign's text. The option can
# be set on a per-sign basis, but this value is used if not specified. The correct value depends on whether the plugin
# signs it should match allow coloring or not.
ignoreColor: false
# Whether to enable refunds to the sign creator when a sign detected as a paid sign is broken (payment will always go
# to the original creator)
enableRefunds: true
# The percentage of the paid sign cost to refund (0-100)
refundPercentage: 100

View File

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

View File

@ -0,0 +1,24 @@
en:
PREFIX: "[PaidSigns]"
SUCCESS_ADDED_PAID_SIGN: "&bSuccessfully added new paid sign"
SUCCESS_ADDED_PAID_SIGN_CONDITION: "&bSuccessfully added new paid sign condition"
SUCCESS_REMOVED_PAID_SIGN: "&bSuccessfully removed paid sign"
SUCCESS_REMOVED_CONDITION: "&bSuccessfully removed paid sign condition"
SUCCESS_RELOADED: "&bSuccessfully reloaded configuration"
SUCCESS_PAID_FOR_SIGN: "&bYou paid &3{cost} {unit} &bto create the 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_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| &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_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_FALSE: "&4false"
ERROR_INVALID_NUMBER: "&bYou provided an invalid number"
ERROR_NAME_DUPLICATE: "&bA paid sign with the same name already exists"
ERROR_EXCEPTION_OCCURRED: "&bAn exception occurred. Please notify the server administrator or check the server log."
ERROR_INVALID_INPUT: "&bInvalid input: {input}"
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_CANNOT_AFFORD: "&bYou cannot afford to create this sign"
ERROR_INVALID_REGULAR_EXPRESSION: "&bThe provided regular expression is invalid"