diff --git a/README.md b/README.md new file mode 100644 index 0000000..0bd8fc0 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Bank Accounts +A multi-account economy plugin. + +## Permissions +| Permission | Command | Description | Recommended group | +|---------------------|-----------|------------------------------------------|-------------------| +| `bank.command` | `bank` | Required to use any bank command | `default` | +| `bank.balance.self` | `balance` | List your accounts and check own balance | `default` | +| `bank.reload` | `reload` | Reload the plugin | `admin` | \ No newline at end of file diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java b/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java index 28a195a..f53a21d 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java @@ -4,14 +4,19 @@ import com.zaxxer.hikari.HikariDataSource; import org.bukkit.OfflinePlayer; import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; +import pro.cloudnode.smp.bankaccounts.commands.BankCommand; import pro.cloudnode.smp.bankaccounts.events.Join; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; +import java.math.RoundingMode; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.text.DecimalFormat; +import java.util.UUID; import java.util.logging.Level; public final class BankAccounts extends JavaPlugin { @@ -35,6 +40,10 @@ public void onEnable() { } createServerAccount(); + + // Register commands + getCommand("bank").setExecutor(new BankCommand()); + // Register events getServer().getPluginManager().registerEvents(new Join(), this); } diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java new file mode 100644 index 0000000..b643e70 --- /dev/null +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java @@ -0,0 +1,188 @@ +package pro.cloudnode.smp.bankaccounts.commands; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.jetbrains.annotations.NotNull; +import pro.cloudnode.smp.bankaccounts.Account; +import pro.cloudnode.smp.bankaccounts.BankAccounts; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +public class BankCommand implements CommandExecutor, TabCompleter { + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { + if (!sender.hasPermission("bank.command")) sender.sendMessage(MiniMessage.miniMessage().deserialize(Objects.requireNonNull(BankAccounts.getInstance().getConfig().getString("messages.errors.no-permission")))); + else run(sender, label, args); + return true; + } + + @Override + public ArrayList onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { + ArrayList suggestions = new ArrayList<>(); + if (!sender.hasPermission("bank.command")) return suggestions; + if (args.length == 1) { + suggestions.add("help"); + if (sender.hasPermission("bank.balance.self")) suggestions.addAll(Arrays.asList("balance", "bal", "account", "accounts")); + if (sender.hasPermission("bank.reload")) suggestions.add("reload"); + } + else { + switch (args[0]) { + case "balance", "bal", "account", "accounts" -> { + if (args.length == 2) { + if (sender.hasPermission("bank.balance.other")) suggestions.add("--player"); + if (sender instanceof OfflinePlayer player) { + Account[] accounts = Account.get(player); + for (Account account : accounts) suggestions.add(account.id); + } + else { + Account[] accounts = Account.get(); + for (Account account : accounts) suggestions.add(account.id); + } + } + else if (args.length == 3 && args[1].equals("--player") && sender.hasPermission("bank.balance.other")) { + Account[] accounts = Account.get(); + for (Account account : accounts) if (account.owner.getName() != null) suggestions.add(account.owner.getName()); + } + } + } + } + return suggestions; + } + + /** + * Decide which command to run + */ + public static void run(@NotNull CommandSender sender, @NotNull String label, String[] args) { + if (args.length == 0) overview(sender); + else switch (args[0]) { + case "help" -> help(sender, label); + case "bal", "balance", "account", "accounts" -> balance(sender, Arrays.copyOfRange(args, 1, args.length)); + case "reload" -> reload(sender); + default -> sender.sendMessage(MiniMessage.miniMessage().deserialize(BankAccounts.getInstance().getConfig().getString("messages.errors.unknown-command"))); + } + } + + /** + * Plugin overview + */ + public static void overview(@NotNull CommandSender sender) { + BankAccounts plugin = BankAccounts.getInstance(); + sender.sendMessage(MiniMessage.miniMessage().deserialize(" v by ", + Placeholder.unparsed("name", plugin.getPluginMeta().getName()), + Placeholder.unparsed("version", plugin.getPluginMeta().getVersion()), + Placeholder.unparsed("author", String.join(", ", plugin.getPluginMeta().getAuthors())))); + } + + /** + * Plugin help + */ + public static void help(@NotNull CommandSender sender, @NotNull String label) { + sender.sendMessage(MiniMessage.miniMessage().deserialize("---")); + sender.sendMessage(MiniMessage.miniMessage().deserialize("Available commands:")); + sender.sendMessage(MiniMessage.miniMessage().deserialize("")); + if (sender.hasPermission("bank.balance.self")) sender.sendMessage(MiniMessage.miniMessage().deserialize("/bank balance [account] - Check your accounts")); + if (sender.hasPermission("bank.balance.other")) sender.sendMessage(MiniMessage.miniMessage().deserialize("/bank balance --player [player] - List another player's accounts")); + if (sender.hasPermission("bank.reload")) sender.sendMessage(MiniMessage.miniMessage().deserialize("/bank reload - Reload plugin configuration")); + sender.sendMessage(MiniMessage.miniMessage().deserialize("---")); + } + + /** + * Get balance + *

Usage:

+ *
    + *
  • {@code balance} List all accounts. If only one account exists, its balance is displayed. Permission: {@code bank.balance.self}
  • + *
  • {@code balance } Display the balance of the specified account. + *

    Permissions:

    + *
      + *
    • {@code bank.balance.self} Allows seeing balances of own accounts
    • + *
    • {@code bank.balance.other} Allows seeing balances of any account
    • + *
    + *
  • + *
  • {@code balance --player } List all accounts owned by the specified player. Permission: `bank.balance.other`
  • + *
+ */ + public static void balance(@NotNull CommandSender sender, String[] args) throws NullPointerException { + if (args.length == 0) { + if (!sender.hasPermission("bank.balance.self")) { + sender.sendMessage(MiniMessage.miniMessage().deserialize(Objects.requireNonNull(BankAccounts.getInstance().getConfig().getString("messages.errors.no-permission")))); + return; + } + @NotNull OfflinePlayer player = sender instanceof OfflinePlayer ? (OfflinePlayer) sender : BankAccounts.getInstance().getServer().getOfflinePlayer(UUID.fromString("00000000-0000-0000-0000-000000000000")); + listAccounts(sender, player); + } + else switch (args[0]) { + case "--player" -> { + if (!sender.hasPermission("bank.balance.other")) { + sender.sendMessage(MiniMessage.miniMessage().deserialize(Objects.requireNonNull(BankAccounts.getInstance().getConfig().getString("messages.errors.no-permission")))); + return; + } + if (args.length == 1) { + sender.sendMessage(MiniMessage.miniMessage().deserialize("(!) Usage: / balance --player ")); + return; + } + @NotNull OfflinePlayer player = BankAccounts.getInstance().getServer().getOfflinePlayer(args[1]); + listAccounts(sender, player); + } + default -> { + if (!sender.hasPermission("bank.balance.self")) { + sender.sendMessage(MiniMessage.miniMessage().deserialize(Objects.requireNonNull(BankAccounts.getInstance().getConfig().getString("messages.errors.no-permission")))); + return; + } + Optional account = Account.get(args[0]); + if (account.isEmpty()) sender.sendMessage(MiniMessage.miniMessage().deserialize(Objects.requireNonNull(BankAccounts.getInstance().getConfig().getString("messages.errors.account-not-found")))); + else if (!sender.hasPermission("bank.balance.other") && !account.get().owner.getUniqueId().equals(((OfflinePlayer) sender).getUniqueId())) { + sender.sendMessage(MiniMessage.miniMessage().deserialize(Objects.requireNonNull(BankAccounts.getInstance().getConfig().getString("messages.errors.no-permission")))); + return; + } + else sender.sendMessage(accountPlaceholders(Objects.requireNonNull(BankAccounts.getInstance().getConfig().getString("messages.balance")), account.get())); + } + } + } + + private static void listAccounts(@NotNull CommandSender sender, @NotNull OfflinePlayer player) { + Account[] accounts = Account.get(player); + if (accounts.length == 0) sender.sendMessage(MiniMessage.miniMessage().deserialize(Objects.requireNonNull(BankAccounts.getInstance().getConfig().getString("messages.errors.no-accounts")))); + else if (accounts.length == 1) sender.sendMessage(accountPlaceholders(Objects.requireNonNull(BankAccounts.getInstance().getConfig().getString("messages.balance")), accounts[0])); + else { + sender.sendMessage(MiniMessage.miniMessage().deserialize(Objects.requireNonNull(BankAccounts.getInstance().getConfig().getString("messages.list-accounts.header")))); + for (Account account : accounts) sender.sendMessage(accountPlaceholders(Objects.requireNonNull(BankAccounts.getInstance().getConfig().getString("messages.list-accounts.entry")), account)); + } + } + + /** + * Reload plugin configuration + */ + public static void reload(@NotNull CommandSender sender) { + if (!sender.hasPermission("bank.reload")) { + sender.sendMessage(MiniMessage.miniMessage().deserialize(Objects.requireNonNull(BankAccounts.getInstance().getConfig().getString("messages.errors.no-permission")))); + return; + } + BankAccounts.getInstance().reloadConfig(); + sender.sendMessage(MiniMessage.miniMessage().deserialize(Objects.requireNonNull(BankAccounts.getInstance().getConfig().getString("messages.reload")))); + } + + /** + * Account placeholders + * @param string String to deserialize with MiniMessage and apply placeholders to + */ + public static Component accountPlaceholders(@NotNull String string, Account account) { + return MiniMessage.miniMessage().deserialize(string + .replace("", account.name == null ? (account.type == Account.Type.PERSONAL && account.owner.getName() != null ? account.owner.getName() : account.id) : account.name) + .replace("", account.id) + .replace("", account.type.name) + .replace("", account.balance == null ? "∞" : account.balance.toPlainString()) + .replace("", BankAccounts.formatCurrency(account.balance)) + .replace("", BankAccounts.formatCurrencyShort(account.balance)) + ); + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 07b8b22..23d90f6 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -21,6 +21,14 @@ db: elideSetAutoCommits: true maintainTimeStats: false +currency: + # Currency symbol + symbol: $ + + # Currency format + # See https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/text/DecimalFormat.html#special_pattern_character + format: "#,##0.00" + # Starting balance # Set to `null` to disable automatically creating a personal account on first join starting-balance: 0 @@ -39,3 +47,41 @@ server-account: # Starting balance # Use `Infinity` for infinite ∞ balance starting-balance: Infinity + +# Messages +messages: + # Currency symbol + currency: $ + + # Errors + errors: + # You have no accounts + no-accounts: "(!) You have no bank accounts." + # No permission + no-permission: "(!) You do not have permission to use this command." + # Account not found + account-not-found: "(!) Account not found." + # Command not recognised + unknown-command: "(!) Unknown command. Try /bank help." + + # Account balance + # Available placeholders: + # - Account name. Defaults to owner username or ID if not set. + # - Account ID + # - Account type (Personal or Business) + # - Account balance without formatting, example: 123456.78 + # - Account balance with formatting, example: 123,456.78 + # - Account balance with formatting, example: 123k + balance: | + ( account) + Balance: + > + + # List accounts + list-accounts: + header: "Bank accounts" + # Same placeholders as balance + entry: "- >: - :Click to view'> - " + + # Plugin reload + reload: "(!) Plugin configuration reloaded" \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 3add355..e9439ff 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -2,3 +2,7 @@ name: BankAccounts version: '${project.version}' main: pro.cloudnode.smp.bankaccounts.BankAccounts api-version: "1.20" +author: Cloudnode +commands: + bank: + usage: /