diff --git a/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/ISaneEconomy.java b/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/ISaneEconomy.java index 9fccb33..5cb943b 100644 --- a/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/ISaneEconomy.java +++ b/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/ISaneEconomy.java @@ -5,6 +5,7 @@ import org.appledash.saneeconomy.economy.logger.TransactionLogger; import org.appledash.saneeconomy.vault.VaultHook; import java.util.Optional; +import java.util.UUID; /** * Created by appledash on 9/18/16. @@ -27,4 +28,6 @@ public interface ISaneEconomy { Optional getTransactionLogger(); VaultHook getVaultHook(); + + String getLastName(UUID uuid); } diff --git a/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/SaneEconomy.java b/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/SaneEconomy.java index 8807c53..c99bb83 100644 --- a/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/SaneEconomy.java +++ b/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/SaneEconomy.java @@ -241,4 +241,9 @@ public class SaneEconomy extends SanePlugin implements ISaneEconomy { public VaultHook getVaultHook() { return this.vaultHook; } + + @Override + public String getLastName(UUID uuid) { + return this.economyManager.getBackend().getLastName("player:" + uuid.toString()); + } } diff --git a/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/command/PayCommand.java b/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/command/PayCommand.java index 38fdaf7..9ed38ad 100644 --- a/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/command/PayCommand.java +++ b/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/command/PayCommand.java @@ -68,6 +68,11 @@ public class PayCommand extends SaneCommand { return; } + if (!this.saneEconomy.getConfig().getConfigurationSection("economy").getBoolean("pay-offline-players", true) && !toPlayer.isOnline()) { + this.saneEconomy.getMessenger().sendMessage(sender, "You cannot pay an offline player."); + return; + } + String sAmount = args[1]; BigDecimal amount = NumberUtils.parseAndFilter(ecoMan.getCurrency(), sAmount); diff --git a/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/economy/Currency.java b/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/economy/Currency.java index c474ea6..4451fe8 100644 --- a/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/economy/Currency.java +++ b/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/economy/Currency.java @@ -77,7 +77,7 @@ public class Currency { */ public String formatAmount(BigDecimal amount) { return ChatColor.translateAlternateColorCodes('&', - MessageUtils.indexedFormat(this.balanceFormat, this.format.format(amount), amount.equals(BigDecimal.ONE) ? this.nameSingular : this.namePlural) + MessageUtils.indexedFormat(this.balanceFormat, this.format.format(amount), amount.compareTo(BigDecimal.ONE) == 0 ? this.nameSingular : this.namePlural) ); } diff --git a/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/economy/EconomyManager.java b/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/economy/EconomyManager.java index 19ea29f..4143dbb 100644 --- a/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/economy/EconomyManager.java +++ b/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/economy/EconomyManager.java @@ -11,6 +11,7 @@ import org.appledash.saneeconomy.event.SaneEconomyTransactionEvent; import org.appledash.saneeconomy.utils.MapUtil; import org.appledash.saneeconomy.utils.NumberUtils; import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; import java.math.BigDecimal; import java.util.LinkedHashMap; @@ -28,6 +29,8 @@ public class EconomyManager { private final Currency currency; private final EconomyStorageBackend backend; private final String serverAccountName; + + private static final BigDecimal REQUIRED_BALANCE_ACCURACY = new BigDecimal("0.0001"); public EconomyManager(ISaneEconomy saneEconomy, Currency currency, EconomyStorageBackend backend, String serverAccountName) { this.saneEconomy = saneEconomy; @@ -83,8 +86,25 @@ public class EconomyManager { * @return True if they have requiredBalance or more, false otherwise */ public boolean hasBalance(Economable targetPlayer, BigDecimal requiredBalance) { - return (EconomableConsole.isConsole(targetPlayer)) || (this.getBalance(targetPlayer).compareTo(requiredBalance) >= 0); - + return (EconomableConsole.isConsole(targetPlayer)) || (hasBalance(this.getBalance(targetPlayer), requiredBalance)); + } + + /** + * Compare account balance and required balance to a reasonable degree of accuracy.
+ * Visible for testing + * + * @param accountBalance account balance + * @param requiredBalance required balance + * @return true if the account has the required balance to some degree of accuracy, false otherwise + */ + public boolean hasBalance(BigDecimal accountBalance, BigDecimal requiredBalance) { + if (accountBalance.compareTo(requiredBalance) >= 0) { + return true; + } + // Must compare to degree of accuracy + // See https://github.com/AppleDash/SaneEconomy/issues/100 + BigDecimal difference = requiredBalance.subtract(accountBalance); + return difference.compareTo(REQUIRED_BALANCE_ACCURACY) < 0; // difference < PRECISION } /** @@ -192,20 +212,20 @@ public class EconomyManager { * @return Map of OfflinePlayer to Double */ public Map getTopBalances(int amount, int offset) { - LinkedHashMap uuidBalances = this.backend.getTopBalances(); + LinkedHashMap playerNamesToBalances = this.backend.getTopBalances(); - /* TODO - uuidBalances.forEach((uuid, balance) -> { - OfflinePlayer offlinePlayer = Bukkit.getServer().getOfflinePlayer(uuid); - if (offlinePlayer != null) { + /*uuidBalances.re((uuid, balance) -> { + String playerName = this.backend.getLastName(uuid); + + if (playerName != null) { if ((this.saneEconomy.getVaultHook() == null) || !this.saneEconomy.getVaultHook().hasPermission(offlinePlayer, "saneeconomy.balancetop.hide")) { playerBalances.put(Bukkit.getServer().getOfflinePlayer(uuid), balance); } } - }); - */ + });*/ - return MapUtil.skipAndTake(uuidBalances, offset, amount); + + return MapUtil.skipAndTake(playerNamesToBalances, offset, amount); } public EconomyStorageBackend getBackend() { diff --git a/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/utils/database/MySQLConnection.java b/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/utils/database/MySQLConnection.java index e0f0ba1..0958c42 100644 --- a/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/utils/database/MySQLConnection.java +++ b/SaneEconomyCore/src/main/java/org/appledash/saneeconomy/utils/database/MySQLConnection.java @@ -23,12 +23,6 @@ public class MySQLConnection { public MySQLConnection(DatabaseCredentials dbCredentials) { this.dbCredentials = dbCredentials; this.saneDatabase = new SaneDatabase(dbCredentials); - - try (Connection conn = this.saneDatabase.getConnection()){ - - } catch (SQLException e) { - this.canLockTables = false; - } } public Connection openConnection() { @@ -65,6 +59,10 @@ public class MySQLConnection { conn.prepareStatement("LOCK TABLE " + this.getTable(tableName) + " WRITE").execute(); this.canLockTables = true; } catch (SQLException e) { + if (this.canLockTables) { + LOGGER.warning("Your MySQL user does not have privileges to LOCK TABLES - this may cause issues if you are running this plugin with the same database on multiple servers."); + } + this.canLockTables = false; } } diff --git a/SaneEconomyCore/src/main/resources/config.yml b/SaneEconomyCore/src/main/resources/config.yml index 9225796..598ac0e 100644 --- a/SaneEconomyCore/src/main/resources/config.yml +++ b/SaneEconomyCore/src/main/resources/config.yml @@ -25,6 +25,7 @@ economy: notify-admin-give: false # Whether to notify players when /ecoadmin give is used on them. notify-admin-take: false # Whether to notify players when /ecoadmin take is used on them. notify-admin-set: false # Whether to notify players when /ecoadmin set is used on them. + pay-offline-players: true # Whether to allow paying offline players or not. multi-server-sync: false # Experimental balance syncing without player rejoins, across BungeeCord networks. update-check: true # Whether to check for updates to the plugin and notify admins about them. diff --git a/SaneEconomyCore/src/test/java/org/appledash/saneeconomy/test/EconomyManagerTest.java b/SaneEconomyCore/src/test/java/org/appledash/saneeconomy/test/EconomyManagerTest.java index feb2a75..ceffa34 100644 --- a/SaneEconomyCore/src/test/java/org/appledash/saneeconomy/test/EconomyManagerTest.java +++ b/SaneEconomyCore/src/test/java/org/appledash/saneeconomy/test/EconomyManagerTest.java @@ -18,6 +18,7 @@ import org.junit.Test; import java.math.BigDecimal; import java.text.DecimalFormat; import java.util.*; +import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; /** @@ -117,4 +118,25 @@ public class EconomyManagerTest { return true; } + + @Test + public void testHasRequiredBalance() { + for (int n = 0; n < 20; n++) { // in the absence of Junit 5's @RepeatedTest + + BigDecimal bigDecimal = randomBigDecimal(); + // We MUST modify the BigDecimal in some way otherwise the test will always succeed + // See https://github.com/AppleDash/SaneEconomy/issues/100 + for (int m = 0; m < 20; m++) { + bigDecimal = bigDecimal.add(randomBigDecimal()).subtract(randomBigDecimal()); + } + // + + Assert.assertTrue("Account must have required balance despite loss of precision (repeat " + n + ")", + economyManager.hasBalance(bigDecimal, new BigDecimal(bigDecimal.doubleValue()))); + } + } + + private static BigDecimal randomBigDecimal() { + return new BigDecimal(ThreadLocalRandom.current().nextDouble()); + } } diff --git a/SaneEconomyCore/src/test/java/org/appledash/saneeconomy/test/mock/MockSaneEconomy.java b/SaneEconomyCore/src/test/java/org/appledash/saneeconomy/test/mock/MockSaneEconomy.java index 70b1c94..07077b6 100644 --- a/SaneEconomyCore/src/test/java/org/appledash/saneeconomy/test/mock/MockSaneEconomy.java +++ b/SaneEconomyCore/src/test/java/org/appledash/saneeconomy/test/mock/MockSaneEconomy.java @@ -6,6 +6,7 @@ import org.appledash.saneeconomy.economy.logger.TransactionLogger; import org.appledash.saneeconomy.vault.VaultHook; import java.util.Optional; +import java.util.UUID; /** * Created by appledash on 9/18/16. @@ -26,4 +27,9 @@ public class MockSaneEconomy implements ISaneEconomy { public VaultHook getVaultHook() { return null; } + + @Override + public String getLastName(UUID uuid) { + return uuid.toString(); + } }