From 28a8f58efc34bf519d139df92ef14bb5573daf89 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Sat, 30 Dec 2023 06:08:14 +0200 Subject: [PATCH 01/25] fix bad formatting examples --- src/main/resources/config.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index e595f313..0e9cf1a8 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -160,8 +160,8 @@ pos: # Placeholders: # - POS description # - Price of the POS contents without formatting, example: 123456.78 - # - Price of the POS contents with formatting, example: 123,456.78 - # - Price of the POS contents with formatting, example: 123k + # - Price of the POS contents with formatting, example: $123,456.78 + # - Price of the POS contents with formatting, example: $123k title: "Point of Sale " # POS info item @@ -307,8 +307,8 @@ messages: # - Account type (Personal or Business) # - Account owner username # - Account balance without formatting, example: 123456.78 - # - Account balance with formatting, example: 123,456.78 - # - Account balance with formatting, example: 123k + # - Account balance with formatting, example: $123,456.78 + # - Account balance with formatting, example: $123k balance: |- ( account) Balance: @@ -347,8 +347,8 @@ messages: # Example: , # Additional placeholders: # - Transfer amount without formatting, example: 123456.78 - # - Transfer amount with formatting, example: 123,456.78 - # - Transfer amount with formatting, example: 123k + # - Transfer amount with formatting, example: $123,456.78 + # - Transfer amount with formatting, example: $123k # - Transfer description # - Command to run to confirm transfer confirm-transfer: |- @@ -395,8 +395,8 @@ messages: # Same placeholders as balance (account is that of the seller). # Additional placeholders: # - Price of the POS contents without formatting, example: 123456.78 - # - Price of the POS contents with formatting, example: 123,456.78 - # - Price of the POS contents with formatting, example: 123k + # - Price of the POS contents with formatting, example: $123,456.78 + # - Price of the POS contents with formatting, example: $123k # - Bank statement description # - X coordinate of the chest # - Y coordinate of the chest From 6046378b6ae8e51008b14b41d783db2d5ac14faa Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Fri, 5 Jan 2024 06:00:48 +0200 Subject: [PATCH 02/25] db tables for owner change requests --- src/main/resources/db-init/mysql.sql | 8 ++++++++ src/main/resources/db-init/sql.sql | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/src/main/resources/db-init/mysql.sql b/src/main/resources/db-init/mysql.sql index de1b7ceb..6feb5c18 100644 --- a/src/main/resources/db-init/mysql.sql +++ b/src/main/resources/db-init/mysql.sql @@ -36,3 +36,11 @@ CREATE TABLE IF NOT EXISTS `pos` ALTER TABLE `bank_transactions` CHANGE COLUMN `instrument` `instrument` VARCHAR(24) CHARACTER SET latin1 COLLATE latin1_general_ci NULL DEFAULT NULL AFTER `description`; + +CREATE TABLE IF NOT EXISTS `change_owner_requests` +( + `account` CHAR(16) CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL, + `new_owner` CHAR(36) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL, + `created` DATETIME NOT NULL DEFAULT UTC_TIMESTAMP(), + KEY `id` (`account`, `new_owner`) +); diff --git a/src/main/resources/db-init/sql.sql b/src/main/resources/db-init/sql.sql index 31a23595..d29d9109 100644 --- a/src/main/resources/db-init/sql.sql +++ b/src/main/resources/db-init/sql.sql @@ -53,3 +53,11 @@ DROP TABLE `bank_transactions`; ALTER TABLE `new_bank_transactions` RENAME TO `bank_transactions`; -- END OF `bank_transactions` MODIFICATION + +CREATE TABLE IF NOT EXISTS `change_owner_requests` +( + `account` CHAR(16) NOT NULL COLLATE BINARY, + `new_owner` CHAR(36) NOT NULL COLLATE BINARY, + `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`account`, `new_owner`) +); From 53e04181d8fec479b21deff3e9350b1024b0d4a7 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Fri, 5 Jan 2024 06:01:20 +0200 Subject: [PATCH 03/25] change account owner config --- .../smp/bankaccounts/BankConfig.java | 30 +++++++++++++++++++ src/main/resources/config.yml | 19 ++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java b/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java index 7c8aace6..a54c4baf 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java @@ -196,6 +196,26 @@ public boolean instrumentsGlintEnabled() { return Objects.requireNonNull(Enchantment.getByKey(NamespacedKey.minecraft(Objects.requireNonNull(config.getString("instruments.glint.enchantment"))))); } + // change-owner.min-balance + public double changeOwnerMinBalance() { + return config.getDouble("change-owner.min-balance"); + } + + // change-owner.require-history + public boolean changeOwnerRequireHistory() { + return config.getBoolean("change-owner.require-history"); + } + + // change-owner.confirm + public boolean changeOwnerConfirm() { + return config.getBoolean("change-owner.confirm"); + } + + // change-owner.timeout + public int changeOwnerTimeout() { + return config.getInt("change-owner.timeout"); + } + // pos.allow-personal public boolean posAllowPersonal() { return config.getBoolean("pos.allow-personal"); @@ -486,6 +506,16 @@ public int interestInterval(final @NotNull Account.Type type) { return Objects.requireNonNull(config.getString("messages.errors.not-frozen")); } + // messages.errors.change-owner-balance + public @NotNull String messagesErrorsChangeOwnerBalance() { + return Objects.requireNonNull(config.getString("messages.errors.change-owner-balance")); + } + + // messages.errors.change-owner-no-history + public @NotNull String messagesErrorsChangeOwnerNoHistory() { + return Objects.requireNonNull(config.getString("messages.errors.change-owner-no-history")); + } + // messages.balance public @NotNull String messagesBalance() { return Objects.requireNonNull(config.getString("messages.balance")); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 93a92e97..50da0846 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -155,6 +155,17 @@ instruments: # Level 1 is always applied. enchantment: unbreaking +# Change account owner +change-owner: + # The minimum balance an account must have to be able to be transferred to another player + min-balance: 0 + # Require the account to have transaction history to be able to be transferred + require-history: true + # Require confirmation/approval by the new owner + confirm: true + # In minutes, when should an ownership change request expire + timeout: 15 + # POS # Note: you cannot use etc. in GUIs pos: @@ -332,6 +343,14 @@ messages: already-frozen: "(!) This account is already frozen." # Account is not frozen (placeholders same as balance) not-frozen: "(!) This account is not frozen." + # Account balance minimum to change owner not satisfied (placeholders same as balance) + # Additional placeholders: + # - required min balance, example: 123456.78 + # - required min balance with formatting, example: $123,456.78 + # - required min balance with formatting, example: $123k + change-owner-balance: "(!) The account balance must be at least " + # Account must have transaction history in order to be transferred + change-owner-no-history: "(!) You must have a transaction history in order to change the owner." # Account balance # Available placeholders: From b9907a4399cead46081a885a37a6f844072a033d Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Fri, 5 Jan 2024 06:01:44 +0200 Subject: [PATCH 04/25] account ownership change permissions --- src/main/java/pro/cloudnode/smp/bankaccounts/Permissions.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/Permissions.java b/src/main/java/pro/cloudnode/smp/bankaccounts/Permissions.java index 2ef1d5c6..891dbd51 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/Permissions.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/Permissions.java @@ -14,6 +14,9 @@ public final class Permissions { public static @NotNull String SET_NAME = "bank.set.name"; public static @NotNull String FREEZE = "bank.freeze"; public static @NotNull String DELETE = "bank.delete"; + public static @NotNull String CHANGE_OWNER = "bank.change.owner"; + public static @NotNull String CHANGE_OWNER_OTHER = "bank.change.owner.other"; + public static @NotNull String CHANGE_OWNER_SKIP_CONFIRMATION = "bank.change.owner.skip-confirmation"; public static @NotNull String BALTOP = "bank.baltop"; public static @NotNull String POS_CREATE = "bank.pos.create"; public static @NotNull String POS_USE = "bank.pos.use"; From 6f9db508ba41b759220b96616938612dda41397c Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Fri, 5 Jan 2024 06:02:13 +0200 Subject: [PATCH 05/25] ChangeOwnerRequest class --- .../cloudnode/smp/bankaccounts/Account.java | 188 +++++++++++++++++- 1 file changed, 187 insertions(+), 1 deletion(-) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/Account.java b/src/main/java/pro/cloudnode/smp/bankaccounts/Account.java index ac77a9ed..8f90cb8a 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/Account.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/Account.java @@ -13,6 +13,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataType; +import org.bukkit.scheduler.BukkitTask; import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; @@ -24,12 +25,14 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.UUID; +import java.util.function.Supplier; import java.util.logging.Level; /** @@ -43,7 +46,7 @@ public class Account { /** * Account owner */ - public final @NotNull OfflinePlayer owner; + public @NotNull OfflinePlayer owner; /** * Account type */ @@ -491,4 +494,187 @@ public void update() {} @Override public void delete() {} } + + /** + * A request to change the owner of a bank account (sent to the new owner) + */ + public final static class ChangeOwnerRequest { + /** + * Account id + */ + public final @NotNull String account; + + /** + * New owner UUID + */ + public final @NotNull UUID newOwner; + + /** + * Request creation timestamp + */ + public final @NotNull Date created; + + /** + * Create a new account ownership transfer request instance + * + * @param account Account to transfer ownership of + * @param newOwner The new account owner + */ + public ChangeOwnerRequest(final @NotNull Account account, final @NotNull UUID newOwner) { + this.account = account.id; + this.newOwner = newOwner; + this.created = new Date(); + } + + private ChangeOwnerRequest(final @NotNull ResultSet rs) throws SQLException { + this.account = rs.getString("id"); + this.newOwner = UUID.fromString(rs.getString("new_owner")); + this.created = new Date(rs.getDate("created").getTime()); + } + + /** + * Get account + */ + public @NotNull Optional<@NotNull Account> account() { + return Account.get(account); + } + + /** + * Get new owner + */ + public @NotNull OfflinePlayer newOwner() { + return BankAccounts.getInstance().getServer().getOfflinePlayer(newOwner); + } + + /** + * Check if request has expired + */ + public boolean expired() { + return System.currentTimeMillis() - created.getTime() > BankAccounts.getInstance().config().changeOwnerTimeout() * 6e4; + } + + /** + * Confirm/accept the request + * + * @return Whether the change was successful + */ + public boolean confirm() { + if (expired()) return false; + final @NotNull Optional<@NotNull Account> account = this.account(); + if (account.isEmpty()) return false; + if (account.get().frozen) return false; + account.get().owner = newOwner(); + account.get().update(); + this.delete(); + return true; + } + + /** + * Insert into database + */ + public void insert() { + try (final @NotNull Connection conn = BankAccounts.getInstance().getDb().getConnection(); + final @NotNull PreparedStatement stmt = conn.prepareStatement("INSERT INTO `change_owner_requests` (`account`, `new_owner`, `created`) VALUES (?, ?, ?)")) { + stmt.setString(1, account); + stmt.setString(2, newOwner.toString()); + stmt.setDate(3, new java.sql.Date(created.getTime())); + + stmt.executeUpdate(); + } + catch (final @NotNull Exception e) { + BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not save account ownership change request. account: " + account + ", newOwner: " + newOwner, e); + } + } + + /** + * Delete from database + */ + public void delete() { + try (final @NotNull Connection conn = BankAccounts.getInstance().getDb().getConnection(); + final @NotNull PreparedStatement stmt = conn.prepareStatement("DELETE FROM `change_owner_requests` WHERE `account` = ? AND `new_owner` = ?")) { + stmt.setString(1, account); + stmt.setString(2, newOwner.toString()); + stmt.executeUpdate(); + } + catch (final @NotNull Exception e) { + BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not delete account ownership change request. account: " + account + ", newOwner: " + newOwner, e); + } + } + + /** + * Delete all request to transfer a certain account + * + * @param account Account ID + */ + public static void delete(final @NotNull UUID account) { + try (final @NotNull Connection conn = BankAccounts.getInstance().getDb().getConnection(); + final @NotNull PreparedStatement stmt = conn.prepareStatement("DELETE FROM `change_owner_requests` WHERE `account` = ?")) { + stmt.setString(1, account.toString()); + stmt.executeUpdate(); + } + catch (final @NotNull Exception e) { + BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not delete account ownership change request. account: " + account, e); + } + } + + /** + * Delete expired requests + */ + private static void deleteExpired() { + try (final @NotNull Connection conn = BankAccounts.getInstance().getDb().getConnection(); + final @NotNull PreparedStatement stmt = conn.prepareStatement("DELETE FROM `change_owner_requests` WHERE `created` = ?")) { + stmt.setDate(1, new java.sql.Date(System.currentTimeMillis() - BankAccounts.getInstance().config().changeOwnerTimeout() * 60_000L)); + stmt.executeUpdate(); + } + catch (final @NotNull Exception e) { + BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not delete account ownership change request. account: ", e); + } + } + + /** + * Asynchronously delete expired requests + */ + public final static @NotNull Supplier<@NotNull BukkitTask> deleteExpiredLater = () -> BankAccounts.getInstance().getServer().getScheduler().runTaskAsynchronously(BankAccounts.getInstance(), ChangeOwnerRequest::deleteExpired); + + /** + * Get account ownership change request + * + * @param account Account + * @param newOwner New owner + */ + public static @NotNull Optional<@NotNull ChangeOwnerRequest> get(final @NotNull Account account, @NotNull OfflinePlayer newOwner) { + try (final @NotNull Connection conn = BankAccounts.getInstance().getDb().getConnection(); + final @NotNull PreparedStatement stmt = conn.prepareStatement("SELECT * FROM `change_owner_requests` WHERE `account` = ? AND `new_owner` = ? LIMIT 1")) { + stmt.setString(1, account.id); + stmt.setString(2, newOwner.getUniqueId().toString()); + final @NotNull ResultSet rs = stmt.executeQuery(); + return rs.next() ? Optional.of(new ChangeOwnerRequest(rs)) : Optional.empty(); + } + catch (final @NotNull Exception e) { + BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not get account ownership change request. account: " + account.id + ", newOwner: " + newOwner.getUniqueId(), e); + return Optional.empty(); + } + } + + /** + * Get account ownership change requests + * + * @param newOwner New owner + */ + public static @NotNull Account @NotNull [] get(final @NotNull OfflinePlayer newOwner) { + try (final @NotNull Connection conn = BankAccounts.getInstance().getDb().getConnection(); + final @NotNull PreparedStatement stmt = conn.prepareStatement("SELECT * FROM `change_owner_requests` WHERE `new_owner` = ?")) { + stmt.setString(1, newOwner.getUniqueId().toString()); + final @NotNull ResultSet rs = stmt.executeQuery(); + + final @NotNull List<@NotNull Account> accounts = new ArrayList<>(); + while (rs.next()) accounts.add(new Account(rs)); + return accounts.toArray(new Account[0]); + } + catch (final @NotNull Exception e) { + BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not get account ownership change requests. newOwner: " + newOwner.getUniqueId(), e); + return new Account[0]; + } + } + } } From 492475aaf7bc0af999b65f1cf5ebd2c54a845675 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Fri, 5 Jan 2024 06:02:50 +0200 Subject: [PATCH 06/25] tasks that run every minute --- .../smp/bankaccounts/BankAccounts.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java b/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java index 2ade5d0e..e840b0b1 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java @@ -41,10 +41,13 @@ import java.text.DecimalFormat; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.UUID; +import java.util.function.Supplier; import java.util.logging.Level; public final class BankAccounts extends JavaPlugin { @@ -157,6 +160,21 @@ public static void reload() { }); }); getInstance().startInterestTimer(); + getInstance().startMinuteLoop(); + getInstance().minuteLoopTasks.add(Account.ChangeOwnerRequest.deleteExpiredLater); + } + + private @Nullable BukkitTask minuteLoop = null; + public @NotNull HashSet<@NotNull Supplier<@NotNull BukkitTask>> minuteLoopTasks = new HashSet<>(); + + /** + * Start a task that runs every minute + */ + private void startMinuteLoop() { + if (minuteLoop != null) minuteLoop.cancel(); + minuteLoop = getServer().getScheduler().runTaskTimerAsynchronously(this, () -> { + minuteLoopTasks.forEach(Supplier::get); + }, 0L, 20L * 60); } /** From d5818f25f1ed3ba730d02e1931cef14bc210a93a Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Fri, 5 Jan 2024 06:08:01 +0200 Subject: [PATCH 07/25] remove unused import --- src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java b/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java index e840b0b1..8fdd8046 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java @@ -45,7 +45,6 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.UUID; import java.util.function.Supplier; import java.util.logging.Level; From 64f93ee8c6336d8ec26e2963a81c57119bd1331a Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Fri, 5 Jan 2024 06:09:08 +0200 Subject: [PATCH 08/25] use Runnable instead of Supplier for minute interval sched --- src/main/java/pro/cloudnode/smp/bankaccounts/Account.java | 2 +- .../java/pro/cloudnode/smp/bankaccounts/BankAccounts.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/Account.java b/src/main/java/pro/cloudnode/smp/bankaccounts/Account.java index 8f90cb8a..9d315401 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/Account.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/Account.java @@ -634,7 +634,7 @@ private static void deleteExpired() { /** * Asynchronously delete expired requests */ - public final static @NotNull Supplier<@NotNull BukkitTask> deleteExpiredLater = () -> BankAccounts.getInstance().getServer().getScheduler().runTaskAsynchronously(BankAccounts.getInstance(), ChangeOwnerRequest::deleteExpired); + public final static @NotNull Runnable deleteExpiredLater = () -> BankAccounts.getInstance().getServer().getScheduler().runTaskAsynchronously(BankAccounts.getInstance(), ChangeOwnerRequest::deleteExpired); /** * Get account ownership change request diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java b/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java index 8fdd8046..ba3efdc9 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java @@ -164,7 +164,7 @@ public static void reload() { } private @Nullable BukkitTask minuteLoop = null; - public @NotNull HashSet<@NotNull Supplier<@NotNull BukkitTask>> minuteLoopTasks = new HashSet<>(); + public @NotNull HashSet<@NotNull Runnable> minuteLoopTasks = new HashSet<>(); /** * Start a task that runs every minute @@ -172,7 +172,7 @@ public static void reload() { private void startMinuteLoop() { if (minuteLoop != null) minuteLoop.cancel(); minuteLoop = getServer().getScheduler().runTaskTimerAsynchronously(this, () -> { - minuteLoopTasks.forEach(Supplier::get); + minuteLoopTasks.forEach(Runnable::run); }, 0L, 20L * 60); } From 8207d3d7ddc249970efdb8ffb0ad788f9f7aee02 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Fri, 5 Jan 2024 06:13:41 +0200 Subject: [PATCH 09/25] move interest timer to minuteLoopTasks --- .../smp/bankaccounts/BankAccounts.java | 64 ++++++++----------- 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java b/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java index ba3efdc9..0ae05404 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java @@ -158,9 +158,9 @@ public static void reload() { getInstance().getLogger().warning("Update details: https://modrinth.com/plugin/bankaccounts/version/" + latestVersion); }); }); - getInstance().startInterestTimer(); getInstance().startMinuteLoop(); getInstance().minuteLoopTasks.add(Account.ChangeOwnerRequest.deleteExpiredLater); + getInstance().minuteLoopTasks.add(BankAccounts.getInstance().interestTask); } private @Nullable BukkitTask minuteLoop = null; @@ -221,43 +221,35 @@ private void initDbWrapper() { } } - private @Nullable BukkitTask interestTask = null; - - /** - * Start interest timer - */ - private void startInterestTimer() { - if (interestTask != null) interestTask.cancel(); - interestTask = getServer().getScheduler().runTaskTimerAsynchronously(this, () -> { - final int currentMinutes = (int) Math.floor(System.currentTimeMillis() / 60000.0); - final double personalRate = config().interestRate(Account.Type.PERSONAL); - final int personalInterval = config().interestInterval(Account.Type.PERSONAL); - final double businessRate = config().interestRate(Account.Type.BUSINESS); - final int businessInterval = config().interestInterval(Account.Type.BUSINESS); - if ((personalInterval <= 0 && businessInterval <= 0) || (personalRate == 0 && businessRate == 0)) return; - final @NotNull Optional<@NotNull Account> serverAccount = Account.getServerAccount(); - if (serverAccount.isEmpty() || serverAccount.get().frozen) return; - final @NotNull Account @NotNull [] accounts = Arrays.stream(Account.get()).filter(account -> !account.frozen && account.balance != null && account.balance.compareTo(BigDecimal.ZERO) > 0).toArray(Account[]::new); - if (personalInterval > 0 && personalRate != 0 && currentMinutes % personalInterval == 0) { - final @NotNull Account @NotNull [] personalAccounts = Arrays.stream(accounts).filter(account -> account.type == Account.Type.PERSONAL).toArray(Account[]::new); - for (final @NotNull Account account : personalAccounts) { - assert account.balance != null; - final @NotNull BigDecimal amount = account.balance.multiply(BigDecimal.valueOf(personalRate / 100.0)).abs().setScale(2, RoundingMode.DOWN); - if (amount.compareTo(BigDecimal.ZERO) <= 0) continue; - interestPayment(account, amount, personalRate, serverAccount.get()); - } + private final Runnable interestTask = () -> { + final int currentMinutes = (int) Math.floor(System.currentTimeMillis() / 60000.0); + final double personalRate = config().interestRate(Account.Type.PERSONAL); + final int personalInterval = config().interestInterval(Account.Type.PERSONAL); + final double businessRate = config().interestRate(Account.Type.BUSINESS); + final int businessInterval = config().interestInterval(Account.Type.BUSINESS); + if ((personalInterval <= 0 && businessInterval <= 0) || (personalRate == 0 && businessRate == 0)) return; + final @NotNull Optional<@NotNull Account> serverAccount = Account.getServerAccount(); + if (serverAccount.isEmpty() || serverAccount.get().frozen) return; + final @NotNull Account @NotNull [] accounts = Arrays.stream(Account.get()).filter(account -> !account.frozen && account.balance != null && account.balance.compareTo(BigDecimal.ZERO) > 0).toArray(Account[]::new); + if (personalInterval > 0 && personalRate != 0 && currentMinutes % personalInterval == 0) { + final @NotNull Account @NotNull [] personalAccounts = Arrays.stream(accounts).filter(account -> account.type == Account.Type.PERSONAL).toArray(Account[]::new); + for (final @NotNull Account account : personalAccounts) { + assert account.balance != null; + final @NotNull BigDecimal amount = account.balance.multiply(BigDecimal.valueOf(personalRate / 100.0)).abs().setScale(2, RoundingMode.DOWN); + if (amount.compareTo(BigDecimal.ZERO) <= 0) continue; + interestPayment(account, amount, personalRate, serverAccount.get()); } - if (businessInterval > 0 && businessRate != 0 && currentMinutes % businessInterval == 0) { - final @NotNull Account @NotNull [] businessAccounts = Arrays.stream(accounts).filter(account -> account.type == Account.Type.BUSINESS).toArray(Account[]::new); - for (final @NotNull Account account : businessAccounts) { - assert account.balance != null; - final @NotNull BigDecimal amount = account.balance.multiply(BigDecimal.valueOf(businessRate / 100.0)).abs().setScale(2, RoundingMode.DOWN); - if (amount.compareTo(BigDecimal.ZERO) <= 0) continue; - interestPayment(account, amount, businessRate, serverAccount.get()); - } + } + if (businessInterval > 0 && businessRate != 0 && currentMinutes % businessInterval == 0) { + final @NotNull Account @NotNull [] businessAccounts = Arrays.stream(accounts).filter(account -> account.type == Account.Type.BUSINESS).toArray(Account[]::new); + for (final @NotNull Account account : businessAccounts) { + assert account.balance != null; + final @NotNull BigDecimal amount = account.balance.multiply(BigDecimal.valueOf(businessRate / 100.0)).abs().setScale(2, RoundingMode.DOWN); + if (amount.compareTo(BigDecimal.ZERO) <= 0) continue; + interestPayment(account, amount, businessRate, serverAccount.get()); } - }, 0L, 20L*60); - } + } + }; private void interestPayment(final @NotNull Account account, final @NotNull BigDecimal amount, final double rate, final @NotNull Account serverAccount) { if (account.balance == null) return; From b2bcc620429610b29350625b27b6e857af470af3 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Fri, 5 Jan 2024 06:27:35 +0200 Subject: [PATCH 10/25] add err msg for when account is already owned by "new owner" player --- src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java | 5 +++++ src/main/resources/config.yml | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java b/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java index a54c4baf..7da87dab 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java @@ -516,6 +516,11 @@ public int interestInterval(final @NotNull Account.Type type) { return Objects.requireNonNull(config.getString("messages.errors.change-owner-no-history")); } + // messages.errors.already-owns-account + public @NotNull String messagesErrorsAlreadyOwnsAccount() { + return Objects.requireNonNull(config.getString("messages.errors.already-owns-account")); + } + // messages.balance public @NotNull String messagesBalance() { return Objects.requireNonNull(config.getString("messages.balance")); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 50da0846..04070e3c 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -351,6 +351,8 @@ messages: change-owner-balance: "(!) The account balance must be at least " # Account must have transaction history in order to be transferred change-owner-no-history: "(!) You must have a transaction history in order to change the owner." + # The account is already owned by (the new owner's username) + already-owns-account: "(!) This account is already owned by ." # Account balance # Available placeholders: From 977f552b518950deb0ecf5d9ca54fbc4640ad1fc Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Fri, 5 Jan 2024 06:27:42 +0200 Subject: [PATCH 11/25] change owner command --- .../bankaccounts/commands/BankCommand.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java index 5e52b7d0..70788d4a 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java @@ -446,6 +446,40 @@ public static boolean delete(final @NotNull CommandSender sender, final @NotNull return sendMessage(sender, Account.placeholders(BankAccounts.getInstance().config().messagesAccountDeleted(), account.get())); } + /** + * Change ownership of account + */ + public static boolean changeOwner(final @NotNull CommandSender sender, final @NotNull String @NotNull [] args, final @NotNull String label) { + if (!sender.hasPermission(Permissions.CHANGE_OWNER)) return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsNoPermission()); + if (args.length < 2) return sendUsage(sender, label, "changeowner " + (args.length > 0 ? args[0] : "") + " "); + final @NotNull Optional<@NotNull Account> account = Account.get(args[0]); + if (account.isEmpty()) return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsAccountNotFound()); + if (!sender.hasPermission(Permissions.CHANGE_OWNER_OTHER) && !account.get().owner.getUniqueId() + .equals(BankAccounts.getOfflinePlayer(sender).getUniqueId())) + return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsNotAccountOwner()); + final @NotNull Optional<@NotNull BigDecimal> balance = Optional.ofNullable(account.get().balance); + final double requiredBalance = BankAccounts.getInstance().config().changeOwnerMinBalance(); + if (balance.isPresent() && balance.get().compareTo(BigDecimal.valueOf(requiredBalance)) < 0) + return sendMessage(sender, Account.placeholders(BankAccounts.getInstance().config().messagesErrorsChangeOwnerBalance() + .replace("", String.valueOf(requiredBalance)) + .replace("", BankAccounts.formatCurrency(BigDecimal.valueOf(requiredBalance))) + .replace("", BankAccounts.formatCurrencyShort(BigDecimal.valueOf(requiredBalance))) + , account.get())); + if (BankAccounts.getInstance().config().changeOwnerRequireHistory() && Transaction.count(account.get()) == 0) + return sendMessage(sender, Account.placeholders(BankAccounts.getInstance().config().messagesErrorsChangeOwnerNoHistory(), account.get())); + final @NotNull OfflinePlayer newOwner = BankAccounts.getInstance().getServer().getOfflinePlayer(args[1]); + if (newOwner.getUniqueId().equals(account.get().owner.getUniqueId())) + return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsAlreadyOwnsAccount().replace("", Optional.ofNullable(newOwner.getName()).orElse("that player"))); + if (BankAccounts.getInstance().config().changeOwnerConfirm() && !sender.hasPermission(Permissions.CHANGE_OWNER_SKIP_CONFIRMATION)) { + final @NotNull Account.ChangeOwnerRequest request = new Account.ChangeOwnerRequest(account.get(), newOwner.getUniqueId()); + request.insert(); + // TODO: send confirmation message to new owner + } + new Account.ChangeOwnerRequest(account.get(), newOwner.getUniqueId()).confirm(); + + return true; + } + /** * Make a transfer to another account *

From 96be8070a7fea37dd5dfca88696f7ed2d1df6606 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Sun, 14 Jan 2024 12:27:11 +0200 Subject: [PATCH 12/25] register change owner sub-command --- .../smp/bankaccounts/commands/BankCommand.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java index 70788d4a..d6bd8f51 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java @@ -45,6 +45,7 @@ public boolean onCommand(final @NotNull CommandSender sender, final @NotNull Com if (sender.hasPermission(Permissions.SET_NAME)) suggestions.addAll(Arrays.asList("setname", "rename")); if (sender.hasPermission(Permissions.FREEZE)) suggestions.addAll(Arrays.asList("freeze", "disable", "block", "unfreeze", "enable", "unblock")); if (sender.hasPermission(Permissions.DELETE)) suggestions.add("delete"); + if (sender.hasPermission(Permissions.CHANGE_OWNER)) suggestions.addAll(Arrays.asList("changeowner", "newowner", "newholder", "changeholder")); if (sender.hasPermission(Permissions.TRANSFER_SELF) || sender.hasPermission(Permissions.TRANSFER_OTHER)) suggestions.addAll(Arrays.asList("transfer", "send", "pay")); if (sender.hasPermission(Permissions.HISTORY)) suggestions.addAll(Arrays.asList("transactions", "history")); @@ -110,6 +111,12 @@ else if (args.length == 4 && args[2].equals("--player") && sender.hasPermission( .filter(account -> sender.hasPermission(Permissions.DELETE_PERSONAL) || account.type != Account.Type.PERSONAL) .map(account -> account.id).collect(Collectors.toSet())); } + case "changeowner", "newowner", "newholder", "changeholder" -> { + if (!sender.hasPermission(Permissions.CHANGE_OWNER)) return suggestions; + if (args.length == 2) suggestions.addAll(Arrays + .stream(sender.hasPermission(Permissions.CHANGE_OWNER_OTHER) ? Account.get() : Account.get(BankAccounts.getOfflinePlayer(sender))) + .map(account -> account.id).collect(Collectors.toSet())); + } case "transfer", "send", "pay" -> { if (!sender.hasPermission(Permissions.TRANSFER_SELF) && !sender.hasPermission(Permissions.TRANSFER_OTHER)) return suggestions; @@ -172,6 +179,7 @@ public static boolean run(final @NotNull CommandSender sender, final @NotNull St case "freeze", "disable", "block" -> freeze(sender, argsSubset, label); case "unfreeze", "enable", "unblock" -> unfreeze(sender, argsSubset, label); case "delete" -> delete(sender, argsSubset, label); + case "changeowner", "newowner", "newholder", "changeholder" -> changeOwner(sender, argsSubset, label); case "transfer", "send", "pay" -> transfer(sender, argsSubset, label); case "transactions", "history" -> transactions(sender, argsSubset, label); case "instrument", "card" -> instrument(sender, argsSubset, label); @@ -216,6 +224,8 @@ public static boolean help(final @NotNull CommandSender sender) { } if (sender.hasPermission(Permissions.DELETE)) sendMessage(sender, "/bank delete - Delete an account"); + if (sender.hasPermission(Permissions.CHANGE_OWNER)) + sendMessage(sender, "/bank changeowner - Change account owner"); if (sender.hasPermission(Permissions.INSTRUMENT_CREATE)) sendMessage(sender, "/bank instrument " + (sender.hasPermission(Permissions.INSTRUMENT_CREATE_OTHER) ? " [player]" : "") + " - Create a new instrument"); if (sender.hasPermission(Permissions.WHOIS)) From 46e4ba146e9d705379232dfc3ed729fdacb2db59 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Mon, 15 Jan 2024 10:34:11 +0200 Subject: [PATCH 13/25] accept ownership change sub-command This command doesn't appear in the help or tab complete. It's intended to only be used with `` for better UX. --- .../cloudnode/smp/bankaccounts/Account.java | 10 +++---- .../smp/bankaccounts/BankConfig.java | 10 +++++++ .../smp/bankaccounts/Permissions.java | 1 + .../bankaccounts/commands/BankCommand.java | 28 +++++++++++++++++++ src/main/resources/config.yml | 4 +++ 5 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/Account.java b/src/main/java/pro/cloudnode/smp/bankaccounts/Account.java index 9d315401..742d45b3 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/Account.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/Account.java @@ -13,7 +13,6 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataType; -import org.bukkit.scheduler.BukkitTask; import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; @@ -32,7 +31,6 @@ import java.util.Objects; import java.util.Optional; import java.util.UUID; -import java.util.function.Supplier; import java.util.logging.Level; /** @@ -639,19 +637,19 @@ private static void deleteExpired() { /** * Get account ownership change request * - * @param account Account + * @param account Account ID * @param newOwner New owner */ - public static @NotNull Optional<@NotNull ChangeOwnerRequest> get(final @NotNull Account account, @NotNull OfflinePlayer newOwner) { + public static @NotNull Optional<@NotNull ChangeOwnerRequest> get(final @NotNull String account, @NotNull OfflinePlayer newOwner) { try (final @NotNull Connection conn = BankAccounts.getInstance().getDb().getConnection(); final @NotNull PreparedStatement stmt = conn.prepareStatement("SELECT * FROM `change_owner_requests` WHERE `account` = ? AND `new_owner` = ? LIMIT 1")) { - stmt.setString(1, account.id); + stmt.setString(1, account); stmt.setString(2, newOwner.getUniqueId().toString()); final @NotNull ResultSet rs = stmt.executeQuery(); return rs.next() ? Optional.of(new ChangeOwnerRequest(rs)) : Optional.empty(); } catch (final @NotNull Exception e) { - BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not get account ownership change request. account: " + account.id + ", newOwner: " + newOwner.getUniqueId(), e); + BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not get account ownership change request. account: " + account + ", newOwner: " + newOwner.getUniqueId(), e); return Optional.empty(); } } diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java b/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java index 7da87dab..e772c744 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java @@ -521,6 +521,16 @@ public int interestInterval(final @NotNull Account.Type type) { return Objects.requireNonNull(config.getString("messages.errors.already-owns-account")); } + // messages.errors.change-owner-not-found + public @NotNull String messagesErrorsChangeOwnerNotFound() { + return Objects.requireNonNull(config.getString("messages.errors.change-owner-not-found")); + } + + // messages.errors.change-owner-accept-failed + public @NotNull String messagesErrorsChangeOwnerAcceptFailed() { + return Objects.requireNonNull(config.getString("messages.errors.change-owner-accept-failed")); + } + // messages.balance public @NotNull String messagesBalance() { return Objects.requireNonNull(config.getString("messages.balance")); diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/Permissions.java b/src/main/java/pro/cloudnode/smp/bankaccounts/Permissions.java index 50acd060..9836e20f 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/Permissions.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/Permissions.java @@ -17,6 +17,7 @@ public final class Permissions { public static @NotNull String CHANGE_OWNER = "bank.change.owner"; public static @NotNull String CHANGE_OWNER_OTHER = "bank.change.owner.other"; public static @NotNull String CHANGE_OWNER_SKIP_CONFIRMATION = "bank.change.owner.skip-confirmation"; + public static @NotNull String CHANGE_OWNER_ACCEPT = "bank.change.owner.accept"; public static @NotNull String BALTOP = "bank.baltop"; public static @NotNull String POS_CREATE = "bank.pos.create"; public static @NotNull String POS_USE = "bank.pos.use"; diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java index d6bd8f51..5979f129 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java @@ -180,6 +180,7 @@ public static boolean run(final @NotNull CommandSender sender, final @NotNull St case "unfreeze", "enable", "unblock" -> unfreeze(sender, argsSubset, label); case "delete" -> delete(sender, argsSubset, label); case "changeowner", "newowner", "newholder", "changeholder" -> changeOwner(sender, argsSubset, label); + case "acceptchangeowner" -> acceptChangeOwner(sender, argsSubset, label); case "transfer", "send", "pay" -> transfer(sender, argsSubset, label); case "transactions", "history" -> transactions(sender, argsSubset, label); case "instrument", "card" -> instrument(sender, argsSubset, label); @@ -490,6 +491,33 @@ public static boolean changeOwner(final @NotNull CommandSender sender, final @No return true; } + /** + * Accept ownership change request + */ + public static boolean acceptChangeOwner(final @NotNull CommandSender sender, final @NotNull String @NotNull [] args, final @NotNull String label) { + if (!sender.hasPermission(Permissions.CHANGE_OWNER_ACCEPT)) return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsNoPermission()); + if (args.length < 1) return sendUsage(sender, label, "acceptchangeowner "); + final @NotNull Optional request = Account.ChangeOwnerRequest.get(args[0], BankAccounts.getOfflinePlayer(sender)); + if (request.isEmpty()) return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsChangeOwnerNotFound()); + final @NotNull Optional<@NotNull Account> account = request.get().account(); + if (account.isEmpty()) return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsAccountNotFound()); + + if (!sender.hasPermission(Permissions.ACCOUNT_CREATE_BYPASS)) { + final @NotNull Account @NotNull [] accounts = Account.get(BankAccounts.getOfflinePlayer(sender), account.get().type); + int limit = BankAccounts.getInstance().config().accountLimits(account.get().type); + if (limit != -1 && accounts.length >= limit) + return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsMaxAccounts(), + Placeholder.unparsed("type", account.get().type.getName()), + Placeholder.unparsed("limit", String.valueOf(limit)) + ); + } + + final boolean success = request.get().confirm(); + if (!success) return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsChangeOwnerAcceptFailed()); + // TODO: send success message to new owner + return true; + } + /** * Make a transfer to another account *

diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 04070e3c..39975c27 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -353,6 +353,10 @@ messages: change-owner-no-history: "(!) You must have a transaction history in order to change the owner." # The account is already owned by (the new owner's username) already-owns-account: "(!) This account is already owned by ." + # Change owner request not found + change-owner-not-found: "(!) This account ownership change request was not found." + # Cannot accept change owner request + change-owner-accept-failed: "(!) Failed to accept ownership change request." # Account balance # Available placeholders: From eb1fec794b16e698a04f3ad6a88bb3d368edf920 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Sun, 21 Jan 2024 10:59:24 +0200 Subject: [PATCH 14/25] use new config --- .../smp/bankaccounts/BankConfig.java | 20 +++++++++++++++---- .../bankaccounts/commands/BankCommand.java | 13 +++--------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java b/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java index ed4b36ee..71a23ff0 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java @@ -678,13 +678,25 @@ public int interestInterval(final @NotNull Account.Type type) { } // messages.errors.change-owner-balance - public @NotNull String messagesErrorsChangeOwnerBalance() { - return Objects.requireNonNull(config.getString("messages.errors.change-owner-balance")); + public @NotNull Component messagesErrorsChangeOwnerBalance(final @NotNull Account account, final @NotNull BigDecimal requiredBalance) { + return MiniMessage.miniMessage().deserialize( + Objects.requireNonNull(config.getString("messages.errors.change-owner-balance")) + .replace("", account.name()) + .replace("", account.id) + .replace("", account.type.getName()) + .replace("", account.ownerNameUnparsed()) + .replace("", account.balance == null ? "∞" : account.balance.toPlainString()) + .replace("", BankAccounts.formatCurrency(account.balance)) + .replace("", BankAccounts.formatCurrencyShort(account.balance)) + .replace("", requiredBalance.toPlainString()) + .replace("", BankAccounts.formatCurrency(requiredBalance)) + .replace("", BankAccounts.formatCurrencyShort(requiredBalance)) + ); } // messages.errors.change-owner-no-history - public @NotNull String messagesErrorsChangeOwnerNoHistory() { - return Objects.requireNonNull(config.getString("messages.errors.change-owner-no-history")); + public @NotNull Component messagesErrorsChangeOwnerNoHistory() { + return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("messages.errors.change-owner-no-history"))); } // messages.errors.already-owns-account diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java index 4eee9e1b..f03446d7 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java @@ -467,13 +467,9 @@ public static boolean changeOwner(final @NotNull CommandSender sender, final @No final @NotNull Optional<@NotNull BigDecimal> balance = Optional.ofNullable(account.get().balance); final double requiredBalance = BankAccounts.getInstance().config().changeOwnerMinBalance(); if (balance.isPresent() && balance.get().compareTo(BigDecimal.valueOf(requiredBalance)) < 0) - return sendMessage(sender, Account.placeholders(BankAccounts.getInstance().config().messagesErrorsChangeOwnerBalance() - .replace("", String.valueOf(requiredBalance)) - .replace("", BankAccounts.formatCurrency(BigDecimal.valueOf(requiredBalance))) - .replace("", BankAccounts.formatCurrencyShort(BigDecimal.valueOf(requiredBalance))) - , account.get())); + return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsChangeOwnerBalance(account.get(), BigDecimal.valueOf(requiredBalance))); if (BankAccounts.getInstance().config().changeOwnerRequireHistory() && Transaction.count(account.get()) == 0) - return sendMessage(sender, Account.placeholders(BankAccounts.getInstance().config().messagesErrorsChangeOwnerNoHistory(), account.get())); + return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsChangeOwnerNoHistory()); final @NotNull OfflinePlayer newOwner = BankAccounts.getInstance().getServer().getOfflinePlayer(args[1]); if (newOwner.getUniqueId().equals(account.get().owner.getUniqueId())) return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsAlreadyOwnsAccount().replace("", Optional.ofNullable(newOwner.getName()).orElse("that player"))); @@ -502,10 +498,7 @@ public static boolean acceptChangeOwner(final @NotNull CommandSender sender, fin final @NotNull Account @NotNull [] accounts = Account.get(BankAccounts.getOfflinePlayer(sender), account.get().type); int limit = BankAccounts.getInstance().config().accountLimits(account.get().type); if (limit != -1 && accounts.length >= limit) - return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsMaxAccounts(), - Placeholder.unparsed("type", account.get().type.getName()), - Placeholder.unparsed("limit", String.valueOf(limit)) - ); + return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsMaxAccounts(account.get().type, limit)); } final boolean success = request.get().confirm(); From c68b82fc1abdc8f1490e33be8953d1b8f12c5a90 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Tue, 6 Feb 2024 19:19:47 +0200 Subject: [PATCH 15/25] allow sending message to any audience --- .../cloudnode/smp/bankaccounts/Command.java | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/Command.java b/src/main/java/pro/cloudnode/smp/bankaccounts/Command.java index 3c723ae5..ad2239e6 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/Command.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/Command.java @@ -1,11 +1,14 @@ package pro.cloudnode.smp.bankaccounts; +import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.command.TabCompleter; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -14,28 +17,35 @@ public abstract class Command implements CommandExecutor, TabCompleter { /** - * Send message to sender. + * Send message to audience * - * @param sender Command sender. - * @param message Message to send. + * @param audience Message viewer/recipient. + * @param message Message to send. * @return Always true. */ - public static boolean sendMessage(final @NotNull CommandSender sender, final @NotNull Component message) { - sender.sendMessage(message); + @Contract("_, _ -> true") + public static boolean sendMessage(final @Nullable Audience audience, final @NotNull Component message) { + if (audience == null) return true; + if (audience instanceof final @NotNull OfflinePlayer player && player.getUniqueId() + .equals(BankAccounts.getConsoleOfflinePlayer().getUniqueId())) { + BankAccounts.getInstance().getServer().getConsoleSender().sendMessage(message); + return true; + } + audience.sendMessage(message); return true; } /** - * Send message to sender. + * Send message to audience * - * @param sender Command sender. + * @param audience Message viewer/recipient. * @param message Message to send. * @param placeholders Placeholders to replace. * @return Always true. */ - public static boolean sendMessage(final @NotNull CommandSender sender, final @NotNull String message, final @NotNull TagResolver @NotNull ... placeholders) { - sendMessage(sender, MiniMessage.miniMessage().deserialize(message, placeholders)); - return true; + @Contract("_, _, _ -> true") + public static boolean sendMessage(final @Nullable Audience audience, final @NotNull String message, final @NotNull TagResolver @NotNull ... placeholders) { + return sendMessage(audience, MiniMessage.miniMessage().deserialize(message, placeholders)); } /** From 87e52ddb029a938f3884539b5d785619630702d4 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Tue, 6 Feb 2024 19:30:34 +0200 Subject: [PATCH 16/25] send account owner change request message --- .../smp/bankaccounts/BankConfig.java | 21 +++++++++++++++++++ .../bankaccounts/commands/BankCommand.java | 8 +++---- src/main/resources/config.yml | 16 ++++++++++++++ 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java b/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java index cf64f090..cdaf2a66 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java @@ -7,6 +7,7 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import org.bukkit.Material; import org.bukkit.NamespacedKey; +import org.bukkit.OfflinePlayer; import org.bukkit.Registry; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.enchantments.Enchantment; @@ -1415,6 +1416,26 @@ public int invoicePerPage() { ); } + // messages.change-owner.request + public @NotNull Component messagesChangeOwnerRequest(final @NotNull Account.ChangeOwnerRequest request, final @NotNull String acceptCommand) { + final @NotNull Account account = request.account().orElse(new Account.ClosedAccount()); + final @NotNull OfflinePlayer newOwner = request.newOwner(); + return MiniMessage.miniMessage().deserialize( + Objects.requireNonNull(config.getString("messages.errors.frozen")) + .replace("", newOwner.getUniqueId().toString()) + .replace("", newOwner.getName() == null ? "unknown player" : newOwner.getName()) + .replace("", acceptCommand) + .replace("", account.name()) + .replace("", account.id) + .replace("", account.type.getName()) + .replace("", account.ownerNameUnparsed()) + .replace("", account.balance == null ? "∞" : account.balance.toPlainString()) + .replace("", BankAccounts.formatCurrency(account.balance)) + .replace("", BankAccounts.formatCurrencyShort(account.balance)), + Formatter.date("date", request.created.toInstant().atZone(ZoneOffset.UTC).toLocalDateTime()) + ); + } + // messages.update-available public @NotNull Component messagesUpdateAvailable(final @NotNull String version) { return MiniMessage.miniMessage().deserialize( diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java index 100ebc5d..c12331b1 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java @@ -479,7 +479,8 @@ public static boolean changeOwner(final @NotNull CommandSender sender, final @No if (BankAccounts.getInstance().config().changeOwnerConfirm() && !sender.hasPermission(Permissions.CHANGE_OWNER_SKIP_CONFIRMATION)) { final @NotNull Account.ChangeOwnerRequest request = new Account.ChangeOwnerRequest(account.get(), newOwner.getUniqueId()); request.insert(); - // TODO: send confirmation message to new owner + final @NotNull String acceptCommand = "/" + label + " acceptchangeowner " + account.get().id; + sendMessage(newOwner.getPlayer(), BankAccounts.getInstance().config().messagesChangeOwnerRequest(request, acceptCommand)); } new Account.ChangeOwnerRequest(account.get(), newOwner.getUniqueId()).confirm(); @@ -582,10 +583,7 @@ public static boolean transfer(final @NotNull CommandSender sender, final @NotNu final @NotNull Transaction transfer = from.get().transfer(to.get(), amount, description, null); sendMessage(sender, BankAccounts.getInstance().config().messagesTransferSent(transfer)); - final @NotNull Optional<@NotNull Player> player = Optional.ofNullable(to.get().owner.getPlayer()); - if (player.isPresent() && player.get().isOnline() && !player.get().getUniqueId() - .equals(BankAccounts.getOfflinePlayer(sender).getUniqueId())) - sendMessage(player.get(), BankAccounts.getInstance().config().messagesTransferReceived(transfer)); + sendMessage(to.get().owner.getPlayer(), BankAccounts.getInstance().config().messagesTransferReceived(transfer)); return true; } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index e63ee8a4..07560e5f 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -608,6 +608,22 @@ messages: # Same placeholders as details footer: ">← Previous Page >Next →" + # Account owner change requests + change-owner: + # You have received a request to become the new account owner + # Placeholders: + # - new owner UUID + # - new owner username. + # - command to accept the request + # - Account name. Defaults to owner username or ID if not set. + # - Account ID + # - Account type (Personal or Business) + # - Account owner username + # - Account balance without formatting, example: 123456.78 + # - Account balance with formatting, example: $123,456.78 + # - Account balance with formatting, example: $123k + request: (!) You have received a request to become the new owner of : Owned by '>> > ACCEPT + # New version available # Placeholders: # - New version From fca0b46dc90bd09fa7c2221fa8fa7d2be8dd901f Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Thu, 8 Feb 2024 05:14:38 +0200 Subject: [PATCH 17/25] de-register minute loop tasks --- src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java b/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java index f7a68a17..3a6baad6 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java @@ -155,6 +155,7 @@ public static void reload() { getInstance().getLogger().warning("Update details: https://modrinth.com/plugin/bankaccounts/version/" + latestVersion); }); }); + getInstance().minuteLoopTasks.clear(); getInstance().startMinuteLoop(); getInstance().minuteLoopTasks.add(Account.ChangeOwnerRequest.deleteExpiredLater); getInstance().minuteLoopTasks.add(BankAccounts.getInstance().interestTask); From 8679196c8ccf88319184362ace54b7adc8f5a9e5 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Thu, 8 Feb 2024 05:48:00 +0200 Subject: [PATCH 18/25] fix config path for change owner req msg --- src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java b/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java index cdaf2a66..6e464049 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java @@ -1421,7 +1421,7 @@ public int invoicePerPage() { final @NotNull Account account = request.account().orElse(new Account.ClosedAccount()); final @NotNull OfflinePlayer newOwner = request.newOwner(); return MiniMessage.miniMessage().deserialize( - Objects.requireNonNull(config.getString("messages.errors.frozen")) + Objects.requireNonNull(config.getString("messages.change-owner.request")) .replace("", newOwner.getUniqueId().toString()) .replace("", newOwner.getName() == null ? "unknown player" : newOwner.getName()) .replace("", acceptCommand) From 6485ede272428611db7f910396054cbaff5b27ac Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Thu, 8 Feb 2024 05:51:28 +0200 Subject: [PATCH 19/25] message for account ownership request sent --- .../smp/bankaccounts/BankConfig.java | 19 +++++++++++++++++++ .../bankaccounts/commands/BankCommand.java | 7 ++++--- src/main/resources/config.yml | 4 ++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java b/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java index 6e464049..70c806af 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java @@ -1436,6 +1436,25 @@ public int invoicePerPage() { ); } + // messages.change-owner.sent + public @NotNull Component messagesChangeOwnerSent(final @NotNull Account.ChangeOwnerRequest request) { + final @NotNull Account account = request.account().orElse(new Account.ClosedAccount()); + final @NotNull OfflinePlayer newOwner = request.newOwner(); + return MiniMessage.miniMessage().deserialize( + Objects.requireNonNull(config.getString("messages.change-owner.sent")) + .replace("", newOwner.getUniqueId().toString()) + .replace("", newOwner.getName() == null ? "unknown player" : newOwner.getName()) + .replace("", account.name()) + .replace("", account.id) + .replace("", account.type.getName()) + .replace("", account.ownerNameUnparsed()) + .replace("", account.balance == null ? "∞" : account.balance.toPlainString()) + .replace("", BankAccounts.formatCurrency(account.balance)) + .replace("", BankAccounts.formatCurrencyShort(account.balance)), + Formatter.date("date", request.created.toInstant().atZone(ZoneOffset.UTC).toLocalDateTime()) + ); + } + // messages.update-available public @NotNull Component messagesUpdateAvailable(final @NotNull String version) { return MiniMessage.miniMessage().deserialize( diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java index c12331b1..e895a982 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java @@ -481,10 +481,11 @@ public static boolean changeOwner(final @NotNull CommandSender sender, final @No request.insert(); final @NotNull String acceptCommand = "/" + label + " acceptchangeowner " + account.get().id; sendMessage(newOwner.getPlayer(), BankAccounts.getInstance().config().messagesChangeOwnerRequest(request, acceptCommand)); + return sendMessage(sender, BankAccounts.getInstance().config().messagesChangeOwnerSent(request)); } - new Account.ChangeOwnerRequest(account.get(), newOwner.getUniqueId()).confirm(); - - return true; + final @NotNull Account.ChangeOwnerRequest request = new Account.ChangeOwnerRequest(account.get(), newOwner.getUniqueId()); + request.confirm(); + return sendMessage(sender, BankAccounts.getInstance().config().messagesChangeOwnerSent(request)); } /** diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 07560e5f..58b611d1 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -624,6 +624,10 @@ messages: # - Account balance with formatting, example: $123k request: (!) You have received a request to become the new owner of : Owned by '>> > ACCEPT + # You have sent an account owner change request + # Same placeholders as details (except ) + sent: (!) You have sent a request to to become the new owner of account : Owned by '>. + # New version available # Placeholders: # - New version From 52a6a7ed81d502f51af71ecfd7ef5cc6590a2a2a Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Thu, 8 Feb 2024 05:54:09 +0200 Subject: [PATCH 20/25] successfully accepted account ownership message --- .../smp/bankaccounts/BankConfig.java | 19 +++++++++++++++++++ .../bankaccounts/commands/BankCommand.java | 3 +-- src/main/resources/config.yml | 4 ++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java b/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java index 70c806af..6bf6f1d4 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java @@ -1455,6 +1455,25 @@ public int invoicePerPage() { ); } + // messages.change-owner.accepted + public @NotNull Component messagesChangeOwnerAccepted(final @NotNull Account.ChangeOwnerRequest request) { + final @NotNull Account account = request.account().orElse(new Account.ClosedAccount()); + final @NotNull OfflinePlayer newOwner = request.newOwner(); + return MiniMessage.miniMessage().deserialize( + Objects.requireNonNull(config.getString("messages.change-owner.accepted")) + .replace("", newOwner.getUniqueId().toString()) + .replace("", newOwner.getName() == null ? "unknown player" : newOwner.getName()) + .replace("", account.name()) + .replace("", account.id) + .replace("", account.type.getName()) + .replace("", account.ownerNameUnparsed()) + .replace("", account.balance == null ? "∞" : account.balance.toPlainString()) + .replace("", BankAccounts.formatCurrency(account.balance)) + .replace("", BankAccounts.formatCurrencyShort(account.balance)), + Formatter.date("date", request.created.toInstant().atZone(ZoneOffset.UTC).toLocalDateTime()) + ); + } + // messages.update-available public @NotNull Component messagesUpdateAvailable(final @NotNull String version) { return MiniMessage.miniMessage().deserialize( diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java index e895a982..73a70b02 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java @@ -508,8 +508,7 @@ public static boolean acceptChangeOwner(final @NotNull CommandSender sender, fin final boolean success = request.get().confirm(); if (!success) return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsChangeOwnerAcceptFailed()); - // TODO: send success message to new owner - return true; + return sendMessage(sender, BankAccounts.getInstance().config().messagesChangeOwnerAccepted(request.get())); } /** diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 58b611d1..082989f8 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -628,6 +628,10 @@ messages: # Same placeholders as details (except ) sent: (!) You have sent a request to to become the new owner of account : Owned by '>. + # You have accepted an account owner change request + # Same placeholders as details (except ) + accepted: >(!) You are now the new owner of : Owned by '>. Click to view account. + # New version available # Placeholders: # - New version From 641eb689feca577e22f6b287d8f6d1efcc0a4c2e Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Mon, 16 Sep 2024 15:55:20 +0300 Subject: [PATCH 21/25] Revert "move interest timer to minuteLoopTasks" This reverts commit 8207d3d7ddc249970efdb8ffb0ad788f9f7aee02. --- .../smp/bankaccounts/BankAccounts.java | 62 +++++++++++-------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java b/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java index 55d83bae..fef468c8 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java @@ -218,35 +218,43 @@ private void initDbWrapper() { } } - private final Runnable interestTask = () -> { - final int currentMinutes = (int) Math.floor(System.currentTimeMillis() / 60000.0); - final double personalRate = config().interestRate(Account.Type.PERSONAL); - final int personalInterval = config().interestInterval(Account.Type.PERSONAL); - final double businessRate = config().interestRate(Account.Type.BUSINESS); - final int businessInterval = config().interestInterval(Account.Type.BUSINESS); - if ((personalInterval <= 0 && businessInterval <= 0) || (personalRate == 0 && businessRate == 0)) return; - final @NotNull Optional<@NotNull Account> serverAccount = Account.getServerAccount(); - if (serverAccount.isEmpty() || serverAccount.get().frozen) return; - final @NotNull Account @NotNull [] accounts = Arrays.stream(Account.get()).filter(account -> !account.frozen && account.balance != null && account.balance.compareTo(BigDecimal.ZERO) > 0).toArray(Account[]::new); - if (personalInterval > 0 && personalRate != 0 && currentMinutes % personalInterval == 0) { - final @NotNull Account @NotNull [] personalAccounts = Arrays.stream(accounts).filter(account -> account.type == Account.Type.PERSONAL).toArray(Account[]::new); - for (final @NotNull Account account : personalAccounts) { - assert account.balance != null; - final @NotNull BigDecimal amount = account.balance.multiply(BigDecimal.valueOf(personalRate / 100.0)).abs().setScale(2, RoundingMode.DOWN); - if (amount.compareTo(BigDecimal.ZERO) <= 0) continue; - interestPayment(account, amount, personalRate, serverAccount.get()); + private @Nullable BukkitTask interestTask = null; + + /** + * Start interest timer + */ + private void startInterestTimer() { + if (interestTask != null) interestTask.cancel(); + interestTask = getServer().getScheduler().runTaskTimerAsynchronously(this, () -> { + final int currentMinutes = (int) Math.floor(System.currentTimeMillis() / 60000.0); + final double personalRate = config().interestRate(Account.Type.PERSONAL); + final int personalInterval = config().interestInterval(Account.Type.PERSONAL); + final double businessRate = config().interestRate(Account.Type.BUSINESS); + final int businessInterval = config().interestInterval(Account.Type.BUSINESS); + if ((personalInterval <= 0 && businessInterval <= 0) || (personalRate == 0 && businessRate == 0)) return; + final @NotNull Optional<@NotNull Account> serverAccount = Account.getServerAccount(); + if (serverAccount.isEmpty() || serverAccount.get().frozen) return; + final @NotNull Account @NotNull [] accounts = Arrays.stream(Account.get()).filter(account -> !account.frozen && account.balance != null && account.balance.compareTo(BigDecimal.ZERO) > 0).toArray(Account[]::new); + if (personalInterval > 0 && personalRate != 0 && currentMinutes % personalInterval == 0) { + final @NotNull Account @NotNull [] personalAccounts = Arrays.stream(accounts).filter(account -> account.type == Account.Type.PERSONAL).toArray(Account[]::new); + for (final @NotNull Account account : personalAccounts) { + assert account.balance != null; + final @NotNull BigDecimal amount = account.balance.multiply(BigDecimal.valueOf(personalRate / 100.0)).abs().setScale(2, RoundingMode.DOWN); + if (amount.compareTo(BigDecimal.ZERO) <= 0) continue; + interestPayment(account, amount, personalRate, serverAccount.get()); + } } - } - if (businessInterval > 0 && businessRate != 0 && currentMinutes % businessInterval == 0) { - final @NotNull Account @NotNull [] businessAccounts = Arrays.stream(accounts).filter(account -> account.type == Account.Type.BUSINESS).toArray(Account[]::new); - for (final @NotNull Account account : businessAccounts) { - assert account.balance != null; - final @NotNull BigDecimal amount = account.balance.multiply(BigDecimal.valueOf(businessRate / 100.0)).abs().setScale(2, RoundingMode.DOWN); - if (amount.compareTo(BigDecimal.ZERO) <= 0) continue; - interestPayment(account, amount, businessRate, serverAccount.get()); + if (businessInterval > 0 && businessRate != 0 && currentMinutes % businessInterval == 0) { + final @NotNull Account @NotNull [] businessAccounts = Arrays.stream(accounts).filter(account -> account.type == Account.Type.BUSINESS).toArray(Account[]::new); + for (final @NotNull Account account : businessAccounts) { + assert account.balance != null; + final @NotNull BigDecimal amount = account.balance.multiply(BigDecimal.valueOf(businessRate / 100.0)).abs().setScale(2, RoundingMode.DOWN); + if (amount.compareTo(BigDecimal.ZERO) <= 0) continue; + interestPayment(account, amount, businessRate, serverAccount.get()); + } } - } - }; + }, 0L, 20L*60); + } private @Nullable BukkitTask invoiceNotificationTask = null; From 0031341b9a761b0cfffda53321b551be25e0964c Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Mon, 16 Sep 2024 15:57:16 +0300 Subject: [PATCH 22/25] remove unused import --- src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java b/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java index fef468c8..bf83a4a7 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java @@ -40,7 +40,6 @@ import java.sql.SQLException; import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Optional; From 5580d06cc4ef98820084d3fc9cecdccb1cb0af97 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Tue, 25 Feb 2025 09:49:21 +0200 Subject: [PATCH 23/25] allow Message command result to use null audience --- .../smp/bankaccounts/commands/result/Message.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/result/Message.java b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/result/Message.java index 5c6fde2e..0374968a 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/result/Message.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/result/Message.java @@ -5,22 +5,24 @@ import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public final class Message extends CommandResult { - public final @NotNull Audience audience; + public final @Nullable Audience audience; public final @NotNull Component message; - public Message(@NotNull Audience audience, @NotNull Component message) { + public Message(@Nullable Audience audience, @NotNull Component message) { super(); this.audience = audience; this.message = message; } - public Message(@NotNull Audience audience, @NotNull String message, final @NotNull TagResolver @NotNull ... placeholders) { + public Message(@Nullable Audience audience, @NotNull String message, final @NotNull TagResolver @NotNull ... placeholders) { this(audience, MiniMessage.miniMessage().deserialize(message, placeholders)); } public void send() { - audience.sendMessage(message); + if (audience != null) + audience.sendMessage(message); } } From 4bd866065fb00e83773061b29dd12b37c310399f Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Tue, 25 Feb 2025 10:03:33 +0200 Subject: [PATCH 24/25] fix transfer ownership send & accept --- .../cloudnode/smp/bankaccounts/Account.java | 52 ++++++++----- .../smp/bankaccounts/BankConfig.java | 73 +++++++------------ .../smp/bankaccounts/Permissions.java | 3 +- .../bankaccounts/commands/BankCommand.java | 66 +++++++++-------- src/main/resources/config.yml | 38 +++++----- 5 files changed, 121 insertions(+), 111 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/Account.java b/src/main/java/pro/cloudnode/smp/bankaccounts/Account.java index ef562013..1cff61e6 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/Account.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/Account.java @@ -29,6 +29,7 @@ import java.util.Optional; import java.util.UUID; import java.util.logging.Level; +import java.util.stream.Collectors; /** * Bank account @@ -534,12 +535,12 @@ public final static class ChangeOwnerRequest { /** * Account id */ - public final @NotNull String account; + private final @NotNull String account; /** * New owner UUID */ - public final @NotNull UUID newOwner; + public final @NotNull OfflinePlayer newOwner; /** * Request creation timestamp @@ -552,7 +553,7 @@ public final static class ChangeOwnerRequest { * @param account Account to transfer ownership of * @param newOwner The new account owner */ - public ChangeOwnerRequest(final @NotNull Account account, final @NotNull UUID newOwner) { + public ChangeOwnerRequest(final @NotNull Account account, final @NotNull OfflinePlayer newOwner) { this.account = account.id; this.newOwner = newOwner; this.created = new Date(); @@ -560,7 +561,7 @@ public ChangeOwnerRequest(final @NotNull Account account, final @NotNull UUID ne private ChangeOwnerRequest(final @NotNull ResultSet rs) throws SQLException { this.account = rs.getString("id"); - this.newOwner = UUID.fromString(rs.getString("new_owner")); + this.newOwner = BankAccounts.getInstance().getServer().getOfflinePlayer(UUID.fromString(rs.getString("new_owner"))); this.created = new Date(rs.getDate("created").getTime()); } @@ -571,13 +572,6 @@ private ChangeOwnerRequest(final @NotNull ResultSet rs) throws SQLException { return Account.get(account); } - /** - * Get new owner - */ - public @NotNull OfflinePlayer newOwner() { - return BankAccounts.getInstance().getServer().getOfflinePlayer(newOwner); - } - /** * Check if request has expired */ @@ -595,7 +589,7 @@ public boolean confirm() { final @NotNull Optional<@NotNull Account> account = this.account(); if (account.isEmpty()) return false; if (account.get().frozen) return false; - account.get().owner = newOwner(); + account.get().owner = newOwner; account.get().update(); this.delete(); return true; @@ -689,14 +683,38 @@ private static void deleteExpired() { } /** - * Get account ownership change requests + * List a player's incoming (received) account ownership change requests. * - * @param newOwner New owner + * @param player The player whose requests to list */ - public static @NotNull Account @NotNull [] get(final @NotNull OfflinePlayer newOwner) { + public static @NotNull Account @NotNull [] incoming(final @NotNull OfflinePlayer player) { try (final @NotNull Connection conn = BankAccounts.getInstance().getDb().getConnection(); final @NotNull PreparedStatement stmt = conn.prepareStatement("SELECT * FROM `change_owner_requests` WHERE `new_owner` = ?")) { - stmt.setString(1, newOwner.getUniqueId().toString()); + stmt.setString(1, player.getUniqueId().toString()); + final @NotNull ResultSet rs = stmt.executeQuery(); + + final @NotNull List<@NotNull Account> accounts = new ArrayList<>(); + while (rs.next()) accounts.add(new Account(rs)); + return accounts.toArray(new Account[0]); + } + catch (final @NotNull Exception e) { + BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not get incoming account ownership change requests. player: " + player.getUniqueId(), e); + return new Account[0]; + } + } + + /** + * List a player’s outgoing (sent) account ownership change requests. + * + * @param player The player whose requests to list + */ + public static @NotNull Account @NotNull [] outgoing(final @NotNull OfflinePlayer player) { + final @NotNull String @NotNull [] ids = Arrays.stream(Account.get(player)).map(a -> a.id).toArray(String[]::new); + final @NotNull String placeholders = Arrays.stream(ids).map(id -> "?").collect(Collectors.joining(", ")); + try (final @NotNull Connection conn = BankAccounts.getInstance().getDb().getConnection(); + final @NotNull PreparedStatement stmt = conn.prepareStatement("SELECT * FROM `change_owner_requests` WHERE `account` in (" + placeholders + ")")) { + for (int i = ids.length; i > 0;) + stmt.setString(i, ids[--i]); final @NotNull ResultSet rs = stmt.executeQuery(); final @NotNull List<@NotNull Account> accounts = new ArrayList<>(); @@ -704,7 +722,7 @@ private static void deleteExpired() { return accounts.toArray(new Account[0]); } catch (final @NotNull Exception e) { - BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not get account ownership change requests. newOwner: " + newOwner.getUniqueId(), e); + BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not get outgoing account ownership change requests. player: " + player.getUniqueId(), e); return new Account[0]; } } diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java b/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java index 842408bd..73c0d85a 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java @@ -7,7 +7,6 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import org.bukkit.Material; import org.bukkit.NamespacedKey; -import org.bukkit.OfflinePlayer; import org.bukkit.Registry; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.enchantments.Enchantment; @@ -239,16 +238,6 @@ public boolean instrumentsGlintEnabled() { return Objects.requireNonNull(Registry.ENCHANTMENT.get(NamespacedKey.minecraft(Objects.requireNonNull(config.getString("instruments.glint.enchantment"))))); } - // change-owner.min-balance - public double changeOwnerMinBalance() { - return config.getDouble("change-owner.min-balance"); - } - - // change-owner.require-history - public boolean changeOwnerRequireHistory() { - return config.getBoolean("change-owner.require-history"); - } - // change-owner.confirm public boolean changeOwnerConfirm() { return config.getBoolean("change-owner.confirm"); @@ -259,6 +248,11 @@ public int changeOwnerTimeout() { return config.getInt("change-owner.timeout"); } + // change-owner.limit-send + public int changeOwnerLimitSend() { + return config.getInt("change-owner.limit-send"); + } + // pos.allow-personal public boolean posAllowPersonal() { return config.getBoolean("pos.allow-personal"); @@ -786,42 +780,31 @@ public int invoiceNotifyInterval() { public @NotNull Component messagesErrorsPlayerNeverJoined() { return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("messages.errors.player-never-joined"))); } - - // messages.errors.change-owner-balance - public @NotNull Component messagesErrorsChangeOwnerBalance(final @NotNull Account account, final @NotNull BigDecimal requiredBalance) { + + // messages.errors.change-owner-same + public @NotNull Component messagesErrorsAlreadyOwnsAccount(final @NotNull Account account) { return MiniMessage.miniMessage().deserialize( - Objects.requireNonNull(config.getString("messages.errors.change-owner-balance")) - .replace("", account.name()) - .replace("", account.id) - .replace("", account.type.getName()) - .replace("", account.ownerNameUnparsed()) - .replace("", account.balance == null ? "∞" : account.balance.toPlainString()) - .replace("", BankAccounts.formatCurrency(account.balance)) - .replace("", BankAccounts.formatCurrencyShort(account.balance)) - .replace("", requiredBalance.toPlainString()) - .replace("", BankAccounts.formatCurrency(requiredBalance)) - .replace("", BankAccounts.formatCurrencyShort(requiredBalance)) + Objects.requireNonNull(config.getString("messages.errors.change-owner-same")), + Placeholder.parsed("player", account.ownerNameUnparsed()) ); } - // messages.errors.change-owner-no-history - public @NotNull Component messagesErrorsChangeOwnerNoHistory() { - return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("messages.errors.change-owner-no-history"))); - } - - // messages.errors.already-owns-account - public @NotNull String messagesErrorsAlreadyOwnsAccount() { - return Objects.requireNonNull(config.getString("messages.errors.already-owns-account")); + // messages.errors.change-owner-limit-send + public @NotNull Component messagesErrorsChangeOwnerLimitSend() { + return MiniMessage.miniMessage().deserialize( + Objects.requireNonNull(config.getString("messages.errors.change-owner-limit-send")), + Placeholder.unparsed("limit", String.valueOf(this.changeOwnerLimitSend())) + ); } // messages.errors.change-owner-not-found - public @NotNull String messagesErrorsChangeOwnerNotFound() { - return Objects.requireNonNull(config.getString("messages.errors.change-owner-not-found")); + public @NotNull Component messagesErrorsChangeOwnerNotFound() { + return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("messages.errors.change-owner-not-found"))); } // messages.errors.change-owner-accept-failed - public @NotNull String messagesErrorsChangeOwnerAcceptFailed() { - return Objects.requireNonNull(config.getString("messages.errors.change-owner-accept-failed")); + public @NotNull Component messagesErrorsChangeOwnerAcceptFailed() { + return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("messages.errors.change-owner-accept-failed"))); } // messages.errors.async-failed @@ -1511,11 +1494,10 @@ public int invoiceNotifyInterval() { // messages.change-owner.request public @NotNull Component messagesChangeOwnerRequest(final @NotNull Account.ChangeOwnerRequest request, final @NotNull String acceptCommand) { final @NotNull Account account = request.account().orElse(new Account.ClosedAccount()); - final @NotNull OfflinePlayer newOwner = request.newOwner(); return MiniMessage.miniMessage().deserialize( Objects.requireNonNull(config.getString("messages.change-owner.request")) - .replace("", newOwner.getUniqueId().toString()) - .replace("", newOwner.getName() == null ? "unknown player" : newOwner.getName()) + .replace("", request.newOwner.getUniqueId().toString()) + .replace("", request.newOwner.getName() == null ? "unknown player" : request.newOwner.getName()) .replace("", acceptCommand) .replace("", account.name()) .replace("", account.id) @@ -1531,11 +1513,10 @@ public int invoiceNotifyInterval() { // messages.change-owner.sent public @NotNull Component messagesChangeOwnerSent(final @NotNull Account.ChangeOwnerRequest request) { final @NotNull Account account = request.account().orElse(new Account.ClosedAccount()); - final @NotNull OfflinePlayer newOwner = request.newOwner(); return MiniMessage.miniMessage().deserialize( Objects.requireNonNull(config.getString("messages.change-owner.sent")) - .replace("", newOwner.getUniqueId().toString()) - .replace("", newOwner.getName() == null ? "unknown player" : newOwner.getName()) + .replace("", request.newOwner.getUniqueId().toString()) + .replace("", request.newOwner.getName() == null ? "unknown player" : request.newOwner.getName()) .replace("", account.name()) .replace("", account.id) .replace("", account.type.getName()) @@ -1550,11 +1531,10 @@ public int invoiceNotifyInterval() { // messages.change-owner.accepted public @NotNull Component messagesChangeOwnerAccepted(final @NotNull Account.ChangeOwnerRequest request) { final @NotNull Account account = request.account().orElse(new Account.ClosedAccount()); - final @NotNull OfflinePlayer newOwner = request.newOwner(); return MiniMessage.miniMessage().deserialize( Objects.requireNonNull(config.getString("messages.change-owner.accepted")) - .replace("", newOwner.getUniqueId().toString()) - .replace("", newOwner.getName() == null ? "unknown player" : newOwner.getName()) + .replace("", request.newOwner.getUniqueId().toString()) + .replace("", request.newOwner.getName() == null ? "unknown player" : request.newOwner.getName()) .replace("", account.name()) .replace("", account.id) .replace("", account.type.getName()) @@ -1587,6 +1567,7 @@ public enum HelpCommandsBank { FREEZE("freeze"), UNFREEZE("unfreeze"), DELETE("delete"), + CHANGE_OWNER("change-owner"), INSTRUMENT("instrument"), WHOIS("whois"), BALTOP("baltop"), diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/Permissions.java b/src/main/java/pro/cloudnode/smp/bankaccounts/Permissions.java index 486f6fde..021fdc58 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/Permissions.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/Permissions.java @@ -16,7 +16,8 @@ public final class Permissions { public static final @NotNull String DELETE = "bank.delete"; public static final @NotNull String CHANGE_OWNER = "bank.change.owner"; public static final @NotNull String CHANGE_OWNER_OTHER = "bank.change.owner.other"; - public static final @NotNull String CHANGE_OWNER_SKIP_CONFIRMATION = "bank.change.owner.skip-confirmation"; + public static final @NotNull String CHANGE_OWNER_BYPASS_CONFIRM = "bank.change.owner.bypass.confirm"; + public static final @NotNull String CHANGE_OWNER_BYPASS_LIMIT = "bank.change.owner.bypass.limit"; public static final @NotNull String CHANGE_OWNER_ACCEPT = "bank.change.owner.accept"; public static final @NotNull String BALTOP = "bank.baltop"; public static final @NotNull String POS_CREATE = "bank.pos.create"; diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java index 594379a4..65ec5916 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java @@ -272,7 +272,7 @@ else if (args.length == 3 && sender.hasPermission(Permissions.INSTRUMENT_CREATE_ if (sender.hasPermission(Permissions.DELETE)) BankAccounts.getInstance().config().messagesHelpBankCommands(BankConfig.HelpCommandsBank.DELETE, label + " delete", "").ifPresent(sender::sendMessage); if (sender.hasPermission(Permissions.CHANGE_OWNER)) - sendMessage(sender, "/bank changeowner - Change account owner"); + BankAccounts.getInstance().config().messagesHelpBankCommands(BankConfig.HelpCommandsBank.CHANGE_OWNER, label + " changeowner", " ").ifPresent(sender::sendMessage); if (sender.hasPermission(Permissions.INSTRUMENT_CREATE)) BankAccounts.getInstance().config().messagesHelpBankCommands(BankConfig.HelpCommandsBank.INSTRUMENT, label + " instrument", "" + (sender.hasPermission(Permissions.INSTRUMENT_CREATE_OTHER) ? " [player]" : "")).ifPresent(sender::sendMessage); if (sender.hasPermission(Permissions.WHOIS)) @@ -507,56 +507,64 @@ else if (accounts.length == 1) /** * Change ownership of account */ - public static boolean changeOwner(final @NotNull CommandSender sender, final @NotNull String @NotNull [] args, final @NotNull String label) { - if (!sender.hasPermission(Permissions.CHANGE_OWNER)) return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsNoPermission()); + public static @NotNull CommandResult changeOwner(final @NotNull CommandSender sender, final @NotNull String @NotNull [] args, final @NotNull String label) { + if (!sender.hasPermission(Permissions.CHANGE_OWNER)) return new Message(sender, BankAccounts.getInstance().config().messagesErrorsNoPermission()); if (args.length < 2) return sendUsage(sender, label, "changeowner " + (args.length > 0 ? args[0] : "") + " "); - final @NotNull Optional<@NotNull Account> account = Account.get(args[0]); - if (account.isEmpty()) return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsAccountNotFound()); + final @NotNull Optional<@NotNull Account> account = Account.get(Account.Tag.from(args[0])); + if (account.isEmpty()) return new Message(sender, BankAccounts.getInstance().config().messagesErrorsAccountNotFound()); if (!sender.hasPermission(Permissions.CHANGE_OWNER_OTHER) && !account.get().owner.getUniqueId() .equals(BankAccounts.getOfflinePlayer(sender).getUniqueId())) - return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsNotAccountOwner()); - final @NotNull Optional<@NotNull BigDecimal> balance = Optional.ofNullable(account.get().balance); - final double requiredBalance = BankAccounts.getInstance().config().changeOwnerMinBalance(); - if (balance.isPresent() && balance.get().compareTo(BigDecimal.valueOf(requiredBalance)) < 0) - return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsChangeOwnerBalance(account.get(), BigDecimal.valueOf(requiredBalance))); - if (BankAccounts.getInstance().config().changeOwnerRequireHistory() && Transaction.count(account.get()) == 0) - return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsChangeOwnerNoHistory()); - final @NotNull OfflinePlayer newOwner = BankAccounts.getInstance().getServer().getOfflinePlayer(args[1]); - if (newOwner.getUniqueId().equals(account.get().owner.getUniqueId())) - return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsAlreadyOwnsAccount().replace("", Optional.ofNullable(newOwner.getName()).orElse("that player"))); - if (BankAccounts.getInstance().config().changeOwnerConfirm() && !sender.hasPermission(Permissions.CHANGE_OWNER_SKIP_CONFIRMATION)) { - final @NotNull Account.ChangeOwnerRequest request = new Account.ChangeOwnerRequest(account.get(), newOwner.getUniqueId()); + return new Message(sender, BankAccounts.getInstance().config().messagesErrorsNotAccountOwner()); + final @NotNull OfflinePlayer recipient = BankAccounts.getInstance().getServer().getOfflinePlayer(args[1]); + if (!recipient.hasPlayedBefore()) + return new Message(sender, BankAccounts.getInstance().config().messagesErrorsPlayerNeverJoined()); + if (recipient.getUniqueId().equals(account.get().owner.getUniqueId())) + return new Message(sender, BankAccounts.getInstance().config().messagesErrorsAlreadyOwnsAccount(account.get())); + final int limit = BankAccounts.getInstance().config().changeOwnerLimitSend(); + if (limit >= 0 && !sender.hasPermission(Permissions.CHANGE_OWNER_BYPASS_LIMIT) && sender instanceof final @NotNull Player player) { + final @NotNull Account @NotNull [] accounts = Account.ChangeOwnerRequest.outgoing(player); + if (accounts.length >= limit) + return new Message(sender, BankAccounts.getInstance().config().messagesErrorsChangeOwnerLimitSend()); + } + final @NotNull Account.ChangeOwnerRequest request = new Account.ChangeOwnerRequest(account.get(), recipient); + if (BankAccounts.getInstance().config().changeOwnerConfirm() && !sender.hasPermission(Permissions.CHANGE_OWNER_BYPASS_CONFIRM)) { request.insert(); final @NotNull String acceptCommand = "/" + label + " acceptchangeowner " + account.get().id; - sendMessage(newOwner.getPlayer(), BankAccounts.getInstance().config().messagesChangeOwnerRequest(request, acceptCommand)); - return sendMessage(sender, BankAccounts.getInstance().config().messagesChangeOwnerSent(request)); + new Message( + recipient.getPlayer(), + BankAccounts.getInstance().config().messagesChangeOwnerRequest(request, acceptCommand) + ).send(); + return new Message(sender, BankAccounts.getInstance().config().messagesChangeOwnerSent(request)); } - final @NotNull Account.ChangeOwnerRequest request = new Account.ChangeOwnerRequest(account.get(), newOwner.getUniqueId()); request.confirm(); - return sendMessage(sender, BankAccounts.getInstance().config().messagesChangeOwnerSent(request)); + return new Message(sender, BankAccounts.getInstance().config().messagesChangeOwnerSent(request)); } /** * Accept ownership change request */ - public static boolean acceptChangeOwner(final @NotNull CommandSender sender, final @NotNull String @NotNull [] args, final @NotNull String label) { - if (!sender.hasPermission(Permissions.CHANGE_OWNER_ACCEPT)) return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsNoPermission()); + public static @NotNull CommandResult acceptChangeOwner(final @NotNull CommandSender sender, final @NotNull String @NotNull [] args, final @NotNull String label) { + if (!sender.hasPermission(Permissions.CHANGE_OWNER_ACCEPT)) + return new Message(sender, BankAccounts.getInstance().config().messagesErrorsNoPermission()); if (args.length < 1) return sendUsage(sender, label, "acceptchangeowner "); final @NotNull Optional request = Account.ChangeOwnerRequest.get(args[0], BankAccounts.getOfflinePlayer(sender)); - if (request.isEmpty()) return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsChangeOwnerNotFound()); + if (request.isEmpty()) + return new Message(sender, BankAccounts.getInstance().config().messagesErrorsChangeOwnerNotFound()); final @NotNull Optional<@NotNull Account> account = request.get().account(); - if (account.isEmpty()) return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsAccountNotFound()); + if (account.isEmpty()) { + request.get().delete(); + return new Message(sender, BankAccounts.getInstance().config().messagesErrorsAccountNotFound()); + } if (!sender.hasPermission(Permissions.ACCOUNT_CREATE_BYPASS)) { final @NotNull Account @NotNull [] accounts = Account.get(BankAccounts.getOfflinePlayer(sender), account.get().type); int limit = BankAccounts.getInstance().config().accountLimits(account.get().type); if (limit != -1 && accounts.length >= limit) - return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsMaxAccounts(account.get().type, limit)); + return new Message(sender, BankAccounts.getInstance().config().messagesErrorsMaxAccounts(account.get().type, limit)); } - final boolean success = request.get().confirm(); - if (!success) return sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsChangeOwnerAcceptFailed()); - return sendMessage(sender, BankAccounts.getInstance().config().messagesChangeOwnerAccepted(request.get())); + if (request.get().confirm()) return new Message(sender, BankAccounts.getInstance().config().messagesChangeOwnerAccepted(request.get())); + return new Message(sender, BankAccounts.getInstance().config().messagesErrorsChangeOwnerAcceptFailed()); } /** diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 8e434dd9..371c3e60 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -184,15 +184,18 @@ instruments: # Change account owner change-owner: - # The minimum balance an account must have to be able to be transferred to another player - min-balance: 0 - # Require the account to have transaction history to be able to be transferred - require-history: true - # Require confirmation/approval by the new owner + # Require confirmation/approval by the new owner. + # To bypass approval and forcefully transfer ownership, use the ‘bank.change.owner.bypass.confirm’ permission. confirm: true - # In minutes, when should an ownership change request expire + + # In minutes, when should an ownership change request expire. timeout: 15 + # The maximum number of pending sent ownership change requests that a player can have (to prevent spam). + # Set to ‘0’ to disable and allow unlimited ownership change requests. + # To bypass this limit, use the ‘bank.change.owner.bypass.limit’ permission. + limit-send: 5 + # Point of Sale. # This is a chest that can have any items. # Players can buy all the items in the POS for the specified price. @@ -372,6 +375,7 @@ messages: freeze: >/ - Freeze/disable an account. unfreeze: >/ - Unfreeze/unblock an account. delete: >/ - Close a bank account. + change-owner: >/ - Change account owner. instrument: >/ - Create a payment instrument (bank card). whois: >/ - Get information about an account. baltop: >/ - Top balances leaderboard. @@ -787,17 +791,15 @@ messages: delete-vault-account: (!) You cannot delete this account. transfer-to-server-vault: (!) You cannot transfer funds to this account. This account is for internal use only. - # Account balance minimum to change owner not satisfied (placeholders same as balance) - # Additional placeholders: - # - required min balance, example: 123456.78 - # - required min balance with formatting, example: $123,456.78 - # - required min balance with formatting, example: $123k - change-owner-balance: "(!) The account balance must be at least " - # Account must have transaction history in order to be transferred - change-owner-no-history: "(!) You must have a transaction history in order to change the owner." - # The account is already owned by (the new owner's username) - already-owns-account: "(!) This account is already owned by ." + # Placeholders: + # → The maximum number of pending sent ownership change requests. + change-owner-limit-send: (!) You’ve reached the limit for pending ownership change requests. You can send up to at a time. + + # Placeholders: + # → The name of the current account owner. + change-owner-same: (!) The account is already owned by . + # Change owner request not found - change-owner-not-found: "(!) This account ownership change request was not found." + change-owner-not-found: (!) The account ownership change request was not found. # Cannot accept change owner request - change-owner-accept-failed: "(!) Failed to accept ownership change request." + change-owner-accept-failed: (!) Failed to accept ownership of account. From 37cecdbaa569bf4cb2368923a90f59947a099fba Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Tue, 25 Feb 2025 16:30:35 +0200 Subject: [PATCH 25/25] fix: check for request expiration in accept command --- src/main/java/pro/cloudnode/smp/bankaccounts/Account.java | 1 - .../pro/cloudnode/smp/bankaccounts/commands/BankCommand.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/Account.java b/src/main/java/pro/cloudnode/smp/bankaccounts/Account.java index 1cff61e6..c0dd4ea0 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/Account.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/Account.java @@ -585,7 +585,6 @@ public boolean expired() { * @return Whether the change was successful */ public boolean confirm() { - if (expired()) return false; final @NotNull Optional<@NotNull Account> account = this.account(); if (account.isEmpty()) return false; if (account.get().frozen) return false; diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java index 65ec5916..23386635 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java @@ -548,7 +548,7 @@ else if (accounts.length == 1) return new Message(sender, BankAccounts.getInstance().config().messagesErrorsNoPermission()); if (args.length < 1) return sendUsage(sender, label, "acceptchangeowner "); final @NotNull Optional request = Account.ChangeOwnerRequest.get(args[0], BankAccounts.getOfflinePlayer(sender)); - if (request.isEmpty()) + if (request.isEmpty() || request.get().expired()) return new Message(sender, BankAccounts.getInstance().config().messagesErrorsChangeOwnerNotFound()); final @NotNull Optional<@NotNull Account> account = request.get().account(); if (account.isEmpty()) {