12 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
25 changed files with 996 additions and 184 deletions

View File

@ -13,7 +13,7 @@ As this plugin only listens to sign change events, there are some limitations:
## Commands
* /addpaidsign <name> <cost> \[permission] \[ignore case] \[ignore color]
* /addpaidsign <name> <cost> \[permission] \[ignore case] \[ignore color] \[match any condition]
* /addpaidsigncondition <name (of a paid sign)> <line number> <string to match> \[executeRegEx] \[ignoreCase]
\[ignoreColor]
* /listpaidsigns \[name (of a paid sign)] \[line number]
@ -35,6 +35,8 @@ This command adds a new paid sign that does nothing until a condition is added.
default uses the config file value).
* 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
@ -76,3 +78,16 @@ Removes a registered paid sign
* 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>0.2.0-ALPHA</version>
<version>0.4.0-ALPHA</version>
<packaging>jar</packaging>
<name>Paid Signs</name>

View File

@ -10,11 +10,16 @@ import net.knarcraft.paidsigns.command.ReloadTabCommand;
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;
@ -29,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
@ -52,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();
}
@ -71,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();
}
/**
@ -101,46 +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 listCommand = this.getCommand("listPaidSigns");
if (listCommand != null) {
listCommand.setExecutor(new ListCommand());
listCommand.setTabCompleter(new ListTabCompleter());
}
TabExecutor removeTabExecutor = new RemoveTabCommand();
registerCommand("removePaidSign", removeTabExecutor, removeTabExecutor);
TabExecutor reloadTabExecutor = new ReloadTabCommand();
registerCommand("reload", reloadTabExecutor, reloadTabExecutor);
}
PluginCommand addConditionCommand = this.getCommand("addPaidSignCondition");
if (addConditionCommand != null) {
addConditionCommand.setExecutor(new AddConditionCommand());
addConditionCommand.setTabCompleter(new AddConditionTabCompleter());
}
PluginCommand removeConditionCommand = this.getCommand("removePaidSignCondition");
if (removeConditionCommand != null) {
removeConditionCommand.setExecutor(new RemoveConditionCommand());
removeConditionCommand.setTabCompleter(new RemoveConditionTabCompleter());
}
PluginCommand removeCommand = this.getCommand("removePaidSign");
if (removeCommand != null) {
TabExecutor removeTabExecutor = new RemoveTabCommand();
removeCommand.setExecutor(removeTabExecutor);
removeCommand.setTabCompleter(removeTabExecutor);
}
PluginCommand reloadCommand = this.getCommand("reload");
if (reloadCommand != null) {
TabExecutor reloadTabExecutor = new ReloadTabCommand();
reloadCommand.setExecutor(reloadTabExecutor);
reloadCommand.setTabCompleter(reloadTabExecutor);
/**
* Registers a command if possible
*
* @param command <p>The command to register</p>
* @param commandExecutor <p>The command executor for executing the command</p>
* @param tabCompleter <p>The tab completer for tab-completing the command</p>
*/
private void registerCommand(String command, CommandExecutor commandExecutor, TabCompleter tabCompleter) {
PluginCommand pluginCommand = this.getCommand(command);
if (pluginCommand != null) {
pluginCommand.setExecutor(commandExecutor);
pluginCommand.setTabCompleter(tabCompleter);
}
}
@ -151,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");
}
/**
@ -164,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,6 +2,8 @@ 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;
@ -9,9 +11,6 @@ import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A representation of the command for adding a new paid sign
@ -31,7 +30,7 @@ public class AddCommand extends TokenizedCommand {
try {
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 = "";
@ -46,42 +45,44 @@ public class AddCommand extends TokenizedCommand {
if (argumentSize > 4) {
ignoreColor = OptionState.fromString(arguments.get(4));
}
boolean matchAnyCondition = false;
if (argumentSize > 5) {
matchAnyCondition = Boolean.parseBoolean(arguments.get(5));
}
return createPaidSign(sender, signName, cost, permission, ignoreCase, ignoreColor);
return createPaidSign(sender, signName, cost, permission, ignoreCase, ignoreColor, matchAnyCondition);
}
/**
* 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 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) {
OptionState ignoreCase, OptionState ignoreColor, boolean matchAnyCondition) {
PaidSignManager manager = PaidSigns.getInstance().getSignManager();
try {
PaidSign sign = new PaidSign(signName, cost, permission, ignoreCase, ignoreColor);
PaidSign sign = new PaidSign(signName, cost, permission, ignoreCase, ignoreColor, matchAnyCondition);
if (manager.getPaidSign(signName) != null) {
sender.sendMessage("A paid sign with the same name already exists");
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

@ -2,6 +2,8 @@ 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;
@ -9,9 +11,8 @@ import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/**
* A representation of the command for adding a new match condition for a sign
@ -19,7 +20,8 @@ import java.util.logging.Logger;
public class AddConditionCommand extends TokenizedCommand {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
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;
@ -29,14 +31,21 @@ public class AddConditionCommand extends TokenizedCommand {
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("Invalid line number given");
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) {
@ -46,23 +55,56 @@ public class AddConditionCommand extends TokenizedCommand {
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("No such paid sign exists");
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) {
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;
}
sender.sendMessage("Condition added");
sender.sendMessage(StringFormatter.getTranslatedInfoMessage(
TranslatableMessage.SUCCESS_ADDED_PAID_SIGN_CONDITION));
return true;
}

View File

@ -24,6 +24,7 @@ public class AddTabCompleter extends TokenizedTabCompleter {
private static List<String> plugins;
private static Map<String, List<String>> permissions;
private static List<String> options;
private static List<String> booleans;
@Nullable
@Override
@ -45,6 +46,8 @@ public class AddTabCompleter extends TokenizedTabCompleter {
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<>();
}
@ -131,6 +134,7 @@ public class AddTabCompleter extends TokenizedTabCompleter {
names = TabCompleteHelper.getExamplePaidSignNames();
costs = TabCompleteHelper.getCosts();
options = TabCompleteHelper.getOptionStates();
booleans = TabCompleteHelper.getBooleans();
}
}

View File

@ -3,12 +3,20 @@ 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
*/
@ -19,10 +27,7 @@ public class ListCommand extends TokenizedCommand {
@NotNull String[] args) {
super.onCommand(sender, command, label, args);
if (argumentSize < 1) {
sender.sendMessage("Paid signs:");
for (String signName : PaidSigns.getInstance().getSignManager().getAllPaidSigns().keySet()) {
sender.sendMessage(" | " + signName);
}
displaySigns(sender);
return true;
} else if (argumentSize < 3) {
return parsePaidSignSelection(sender);
@ -30,6 +35,21 @@ public class ListCommand extends TokenizedCommand {
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
*
@ -39,25 +59,30 @@ public class ListCommand extends TokenizedCommand {
private boolean parsePaidSignSelection(CommandSender sender) {
PaidSign paidSign = PaidSigns.getInstance().getSignManager().getPaidSign(arguments.get(0));
if (paidSign == null) {
sender.sendMessage("No such paid sign");
sender.sendMessage(getTranslatedErrorMessage(TranslatableMessage.ERROR_PAID_SIGN_NOT_FOUND));
return false;
}
if (argumentSize < 2) {
displayPaidSign(sender, paidSign);
} else {
short signLine;
short lineNumber;
try {
signLine = (short) (Short.parseShort(arguments.get(1)) - 1);
lineNumber = (short) (Short.parseShort(arguments.get(1)) - 1);
if (lineNumber < 0 || lineNumber > 3) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_INVALID_NUMBER));
return false;
}
} catch (NumberFormatException exception) {
sender.sendMessage("Invalid number given");
sender.sendMessage(getTranslatedErrorMessage(TranslatableMessage.ERROR_INVALID_NUMBER));
return false;
}
if (!paidSign.getConditions().containsKey(signLine)) {
sender.sendMessage("The paid sign you specified has no condition for line " + signLine);
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(signLine);
displayPaidSignCondition(sender, paidSign.getName(), signLine, condition);
PaidSignCondition condition = paidSign.getConditions().get(lineNumber);
displayPaidSignCondition(sender, paidSign.getName(), lineNumber, condition);
}
return true;
}
@ -72,13 +97,11 @@ public class ListCommand extends TokenizedCommand {
*/
private void displayPaidSignCondition(CommandSender sender, String signName, short signLine,
PaidSignCondition condition) {
sender.sendMessage("Paid sign condition info: ");
sender.sendMessage("Paid sign name: " + signName);
sender.sendMessage("Condition line: " + signLine);
sender.sendMessage("Condition match string: " + condition.getStringToMatch());
sender.sendMessage("Execute RegEx: " + condition.executeRegex());
sender.sendMessage("Ignore case: " + condition.ignoreCase());
sender.sendMessage("Ignore color: " + condition.ignoreColor());
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())}));
}
/**
@ -88,17 +111,20 @@ public class ListCommand extends TokenizedCommand {
* @param paidSign <p>The paid sign to display information about</p>
*/
private void displayPaidSign(CommandSender sender, PaidSign paidSign) {
sender.sendMessage("Paid sign info:");
sender.sendMessage("Name: " + paidSign.getName());
sender.sendMessage("Cost: " + paidSign.getCost());
sender.sendMessage("Permission: " + paidSign.getPermission());
sender.sendMessage("Ignore case: " + paidSign.getIgnoreCase());
sender.sendMessage("Ignore color: " + paidSign.getIgnoreColor());
sender.sendMessage("Sign conditions: ");
Map<Short, PaidSignCondition> conditions = paidSign.getConditions();
for (short lineIndex : conditions.keySet()) {
sender.sendMessage(" | " + (lineIndex + 1) + ". " + conditions.get(lineIndex).getStringToMatch());
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

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

@ -2,15 +2,14 @@ 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;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A representation of the command for removing a condition from a sign
@ -25,36 +24,38 @@ public class RemoveConditionCommand extends TokenizedCommand {
}
String name = arguments.get(0);
short line;
short lineNumber;
try {
line = (short) (Short.parseShort(arguments.get(1)) - 1);
lineNumber = (short) (Short.parseShort(arguments.get(1)) - 1);
if (lineNumber < 0 || lineNumber > 3) {
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_INVALID_NUMBER));
return false;
}
} catch (NumberFormatException exception) {
sender.sendMessage("Invalid line number given");
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("Invalid sign name given");
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_PAID_SIGN_NOT_FOUND));
return false;
}
if (!sign.getConditions().containsKey(line)) {
sender.sendMessage("No condition registered for line " + (line + 1));
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(line);
sign.removeCondition(lineNumber);
try {
signManager.saveSigns();
} catch (IOException e) {
Logger logger = PaidSigns.getInstance().getLogger();
logger.log(Level.SEVERE, "Exception encountered while trying to write to the data file");
logger.log(Level.SEVERE, Arrays.toString(e.getStackTrace()));
sender.sendMessage("An exception occurred. Please notify the server administrator or check the server log.");
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_EXCEPTION_OCCURRED));
return false;
}
sender.sendMessage("Condition removed");
sender.sendMessage(StringFormatter.getTranslatedInfoMessage(TranslatableMessage.SUCCESS_REMOVED_CONDITION));
return true;
}

View File

@ -2,6 +2,8 @@ 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;
@ -11,11 +13,8 @@ import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A representation of the command for removing a paid sign
@ -33,17 +32,15 @@ public class RemoveTabCommand implements TabExecutor {
try {
if (PaidSigns.getInstance().getSignManager().removePaidSign(name)) {
sender.sendMessage("Successfully removed paid sign");
sender.sendMessage(StringFormatter.getTranslatedInfoMessage(
TranslatableMessage.SUCCESS_REMOVED_PAID_SIGN));
} else {
sender.sendMessage("No matching paid sign was found");
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(
TranslatableMessage.ERROR_PAID_SIGN_NOT_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.");
sender.sendMessage(StringFormatter.getTranslatedErrorMessage(TranslatableMessage.ERROR_EXCEPTION_OCCURRED));
}
return false;
}

View File

@ -16,18 +16,21 @@ public class PaidSign {
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 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 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 name, double cost, String permission, OptionState ignoreCase, OptionState ignoreColor) {
public PaidSign(String name, double cost, String permission, OptionState ignoreCase, OptionState ignoreColor,
boolean matchAnyCondition) {
if (name == null || name.trim().isBlank()) {
throw new IllegalArgumentException("Name cannot be empty");
}
@ -42,6 +45,7 @@ public class PaidSign {
this.permission = permission;
this.ignoreCase = ignoreCase;
this.ignoreColor = ignoreColor;
this.matchAnyCondition = matchAnyCondition;
}
/**
@ -98,6 +102,15 @@ public class PaidSign {
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
*
@ -109,11 +122,18 @@ public class PaidSign {
if (this.conditions.isEmpty()) {
return false;
}
boolean success = true;
boolean matchAny = matchAnyCondition();
boolean success = !matchAny;
for (short i = 0; i < 4; i++) {
PaidSignCondition condition = this.conditions.get(i);
if (condition != null) {
success = success && condition.test(lines[i]);
boolean conditionMatches = condition.test(lines[i]);
if (matchAny) {
success |= conditionMatches;
} else {
success &= conditionMatches;
}
}
}
return success;

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,13 +2,17 @@ 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.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.io.IOException;
import java.util.Map;
/**
@ -50,19 +54,36 @@ public class SignListener implements Listener {
return true;
}
double cost = paidSign.getCost();
boolean canAfford = EconomyManager.canAfford(player, cost);
if (!canAfford) {
player.sendMessage("[PaidSigns] You cannot afford to create this sign");
event.setCancelled(true);
} else {
String unit = EconomyManager.getCurrency(cost != 1);
player.sendMessage(String.format("[PaidSigns] You paid %.2f %s to create the sign", cost, unit));
EconomyManager.withdraw(player, cost);
}
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

@ -9,9 +9,11 @@ import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
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
@ -96,7 +98,8 @@ public final class PaidSignManager {
String permission = signSection.getString(name + ".permission");
OptionState ignoreCase = OptionState.getFromBoolean(signSection.getBoolean(name + ".ignoreCase"));
OptionState ignoreColor = OptionState.getFromBoolean(signSection.getBoolean(name + ".ignoreColor"));
PaidSign sign = new PaidSign(name, cost, permission, ignoreCase, ignoreColor);
boolean matchAnyCondition = signSection.getBoolean(name + ".matchAnyCondition");
PaidSign sign = new PaidSign(name, cost, permission, ignoreCase, ignoreColor, matchAnyCondition);
loadConditions(signSection, sign);
paidSigns.put(name, sign);
}
@ -119,26 +122,45 @@ public final class PaidSignManager {
* @throws IOException <p>If unable to write to the signs file</p>
*/
public static void saveSigns(Map<String, PaidSign> signs) throws IOException {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(signsFile);
ConfigurationSection signSection = configuration.createSection("paidSigns");
try {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(signsFile);
ConfigurationSection signSection = configuration.createSection("paidSigns");
for (PaidSign sign : signs.values()) {
String name = sign.getName();
signSection.set(name + ".cost", sign.getCost());
signSection.set(name + ".permission", sign.getPermission());
signSection.set(name + ".ignoreCase", sign.getIgnoreCase());
signSection.set(name + ".ignoreColor", sign.getIgnoreColor());
ConfigurationSection conditionsSection = signSection.createSection(name + ".conditions");
Map<Short, PaidSignCondition> signConditions = sign.getConditions();
for (short lineIndex : signConditions.keySet()) {
PaidSignCondition condition = signConditions.get(lineIndex);
conditionsSection.set(lineIndex + ".stringToMatch", condition.getStringToMatch());
conditionsSection.set(lineIndex + ".executeRegEx", condition.executeRegex());
conditionsSection.set(lineIndex + ".ignoreCase", condition.ignoreCase());
conditionsSection.set(lineIndex + ".ignoreColor", condition.ignoreColor());
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;
}
}
/**
* Saves the given sign to the given configuration section
*
* @param signSection <p>The configuration section to save the sign to</p>
* @param sign <p>The sign to save</p>
*/
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());
}
configuration.save(signsFile);
}
/**

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

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

@ -15,23 +15,6 @@ public final class TabCompleteHelper {
}
/**
* Finds tab complete values that contain the typed text
*
* @param values <p>The values to filter</p>
* @param typedText <p>The text the player has started typing</p>
* @return <p>The given string values that contain the player's typed text</p>
*/
public static List<String> filterMatchingContains(List<String> values, String typedText) {
List<String> configValues = new ArrayList<>();
for (String value : values) {
if (value.toLowerCase().contains(typedText.toLowerCase())) {
configValues.add(value);
}
}
return configValues;
}
/**
* Finds tab complete values that match the start of the typed text
*

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

@ -10,7 +10,7 @@ website: https://git.knarcraft.net/EpicKnarvik97/PaidSigns
commands:
addpaidsign:
description: Used to add a new paid sign
usage: /<command> <name> <cost> [permission] [ignore case] [ignore color]
usage: /<command> <name> <cost> [permission] [ignore case] [ignore color] [match any condition]
permission: paidsigns.manage
addpaidsigncondition:
description: Used to add a new match condition for a paid sign

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"