diff --git a/README.md b/README.md
index ffa50cd..4556ac0 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@ dependencies {
}
```
## API usage
-
+YOU PROBABLY DON'T NEED TO USE REDISECONOMY API: USE VAULT API https://github.com/MilkBowl/VaultAPI
```java
// Access Point
RedisEconomyAPI api = RedisEconomyAPI.getAPI();
@@ -53,7 +53,7 @@ api.getCurrencyBySymbol("€");//Gets the currency by symbol
//Currency is a Vault Economy https://github.com/MilkBowl/VaultAPI/blob/master/src/main/java/net/milkbowl/vault/economy/Economy.java,
//same methods and everything
currency.getBalance(offlinePlayer);
-currency.withdrawPlayer(offlinePlayer, 100);
+currency.withdrawPlayer(offlinePlayer, 100, "Reason of withdrawal");
//Modify a player balance (default currency)
api.getDefaultCurrency().setPlayerBalance(player.getUniqueId(), 1000);
diff --git a/jitpack.yml b/jitpack.yml
index f0cebce..f29a667 100644
--- a/jitpack.yml
+++ b/jitpack.yml
@@ -1,5 +1,5 @@
jdk:
- - openjdk16
+ - openjdk17
before_install:
- - sdk install java 16.0.1-open
- - sdk use java 16.0.1-open
\ No newline at end of file
+ - sdk install java 17.0.1-open
+ - sdk use java 17.0.1-open
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index b5eb37d..6f073d0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,13 +6,13 @@
dev.unnm3d
RedisEconomy
- 4.3.8-SNAPSHOT
+ 4.3.21
jar
RedisEconomy
- 16
+ 17
UTF-8
@@ -23,8 +23,8 @@
maven-compiler-plugin
3.8.1
-
- 16
+
+ 17
@@ -44,6 +44,10 @@
net.kyori
dev.unnm3d.shaded.kyori
+
+ de.exlll.configlib
+ dev.unnm3d.shaded.configlib
+
com.github.Anon8281.universalScheduler
dev.unnm3d.shaded.universalScheduler
@@ -114,8 +118,9 @@
https://repo.extendedclip.com/content/repositories/placeholderapi/
- devmart-other
- https://nexuslite.gcnt.net/repos/other/
+ maven_central
+ Maven Central
+ https://repo.maven.apache.org/maven2/
@@ -123,7 +128,7 @@
org.spigotmc
spigot-api
- 1.16.5-R0.1-SNAPSHOT
+ 1.17.1-R0.1-SNAPSHOT
provided
@@ -135,35 +140,35 @@
org.projectlombok
lombok
- 1.18.26
+ 1.18.34
provided
net.kyori
adventure-text-minimessage
- 4.14.0
+ 4.17.0
net.kyori
adventure-platform-bukkit
- 4.3.0
+ 4.3.3
me.clip
placeholderapi
- 2.11.3
+ 2.11.5
provided
io.lettuce
lettuce-core
- 6.2.4.RELEASE
+ 6.4.0.RELEASE
provided
- com.github.Emibergo02
- ConfigLib
- master-SNAPSHOT
+ de.exlll
+ configlib-yaml
+ 4.5.0
com.github.Anon8281
diff --git a/src/main/java/dev/unnm3d/rediseconomy/RedisEconomyPlugin.java b/src/main/java/dev/unnm3d/rediseconomy/RedisEconomyPlugin.java
index 740ce43..04a4a4b 100644
--- a/src/main/java/dev/unnm3d/rediseconomy/RedisEconomyPlugin.java
+++ b/src/main/java/dev/unnm3d/rediseconomy/RedisEconomyPlugin.java
@@ -30,12 +30,13 @@
import org.bukkit.plugin.java.JavaPlugin;
import java.time.Duration;
+import java.util.UUID;
import java.util.concurrent.TimeUnit;
public final class RedisEconomyPlugin extends JavaPlugin {
+ @Getter
private static RedisEconomyPlugin instance;
- //private EzRedisMessenger ezRedisMessenger;
@Getter
private ConfigManager configManager;
@Getter
@@ -43,6 +44,10 @@ public final class RedisEconomyPlugin extends JavaPlugin {
private RedisManager redisManager;
@Getter
private TaskScheduler scheduler;
+ @Getter
+ private Plugin vaultPlugin;
+ @Getter
+ private static UUID instanceUUID;
public Settings settings() {
@@ -53,13 +58,11 @@ public Langs langs() {
return configManager.getLangs();
}
- public static RedisEconomyPlugin getInstance() {
- return instance;
- }
-
@Override
public void onLoad() {
instance = this;
+ //Generate a unique instance id to not send redis updates to itself
+ instanceUUID = UUID.randomUUID();
this.configManager = new ConfigManager(this);
@@ -67,27 +70,32 @@ public void onLoad() {
this.getLogger().severe("Disabling: redis server unreachable!");
this.getLogger().severe("Please setup a redis server before running this plugin!");
this.getServer().getPluginManager().disablePlugin(this);
- return;
} else {
this.getLogger().info("Redis server connected!");
}
-
- if (!setupVault()) { //creates currenciesManager and exchange
- this.getLogger().severe("Disabled due to no Vault dependency found!");
- this.getServer().getPluginManager().disablePlugin(this);
- } else {
- this.getLogger().info("Hooked into Vault!");
- }
}
@Override
public void onEnable() {
+ if (redisManager == null) return;
+
this.scheduler = UniversalScheduler.getScheduler(this);
- this.configManager.postStartupLoad();
+ this.configManager.loadLangs();
+ this.vaultPlugin = getServer().getPluginManager().getPlugin("Vault");
+ if (this.vaultPlugin == null) { //creates currenciesManager and exchange
+ this.getLogger().severe("Disabled due to no Vault dependency found!");
+ this.getServer().getPluginManager().disablePlugin(this);
+ return;
+ }
+
+ this.currenciesManager = new CurrenciesManager(redisManager, this, configManager);
+ this.getLogger().info("Hooked into Vault!");
+
if (settings().migrationEnabled) {
scheduler.runTaskLater(() ->
- currenciesManager.getCompleteMigration().complete(null),
- 100L);//load: STARTUP doesn't consider dependencies on load so i have to wait a bit (bukkit bug?)
+ currenciesManager.migrateFromOfflinePlayers(getServer().getOfflinePlayers()), 100L);
+ } else {
+ currenciesManager.loadDefaultCurrency(this.vaultPlugin);
}
getServer().getPluginManager().registerEvents(currenciesManager, this);
@@ -124,11 +132,10 @@ public void onEnable() {
@Override
public void onDisable() {
- redisManager.close();
+ if (redisManager != null)
+ redisManager.close();
if (currenciesManager != null)
this.getServer().getServicesManager().unregister(Economy.class, currenciesManager.getDefaultCurrency());
- this.getServer().getMessenger().unregisterOutgoingPluginChannel(this);
- this.getServer().getMessenger().unregisterIncomingPluginChannel(this);
getLogger().info("RedisEconomy disabled successfully!");
}
@@ -143,16 +150,16 @@ private boolean setupRedis() {
if (configManager.getSettings().redis.user().equals("changecredentials"))
getLogger().warning("You are using default redis credentials. Please change them in the config.yml file!");
//Authentication params
- redisURIBuilder = configManager.getSettings().redis.password().equals("") ?
+ redisURIBuilder = configManager.getSettings().redis.password().isEmpty() ?
redisURIBuilder :
- configManager.getSettings().redis.user().equals("") ?
+ configManager.getSettings().redis.user().isEmpty() ?
redisURIBuilder.withPassword(configManager.getSettings().redis.password().toCharArray()) :
redisURIBuilder.withAuthentication(configManager.getSettings().redis.user(), configManager.getSettings().redis.password());
getLogger().info("Connecting to redis server " + redisURIBuilder.build().toString() + "...");
- this.redisManager = new RedisManager(RedisClient.create(redisURIBuilder.build()));
+ this.redisManager = new RedisManager(RedisClient.create(redisURIBuilder.build()), configManager.getSettings().redis.getPoolSize());
redisManager.isConnected().get(1, java.util.concurrent.TimeUnit.SECONDS);
- if (!configManager.getSettings().clusterId.equals(""))
+ if (!configManager.getSettings().clusterId.isEmpty())
RedisKeys.setClusterId(configManager.getSettings().clusterId);
return true;
} catch (Exception e) {
@@ -164,14 +171,6 @@ private boolean setupRedis() {
}
}
- private boolean setupVault() {
- Plugin vault = getServer().getPluginManager().getPlugin("Vault");
- if (vault == null)
- return false;
- this.currenciesManager = new CurrenciesManager(redisManager, this, configManager);
- currenciesManager.loadDefaultCurrency(vault);
- return true;
- }
private void loadCommand(String cmdName, CommandExecutor executor, TabCompleter tabCompleter) {
PluginCommand cmd = getServer().getPluginCommand(cmdName);
diff --git a/src/main/java/dev/unnm3d/rediseconomy/command/MainCommand.java b/src/main/java/dev/unnm3d/rediseconomy/command/MainCommand.java
index 2e143ba..e315d62 100644
--- a/src/main/java/dev/unnm3d/rediseconomy/command/MainCommand.java
+++ b/src/main/java/dev/unnm3d/rediseconomy/command/MainCommand.java
@@ -27,17 +27,29 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command
} else if (args.length == 1) {
if (!args[0].equalsIgnoreCase("reload")) return true;
if (!sender.hasPermission("rediseconomy.admin")) return true;
- String serverId = plugin.getConfigManager().getSettings().serverId; //Keep serverId
plugin.getConfigManager().loadSettingsConfig();//Reload configs
- plugin.getConfigManager().loadLangs();
- plugin.getConfigManager().getSettings().serverId = serverId; //Restore serverId
+ plugin.getConfigManager().loadLangs(); //Reload langs
plugin.getConfigManager().saveConfigs(); //Save configs
this.adventureWebuiEditorAPI = new AdventureWebuiEditorAPI(plugin.getConfigManager().getSettings().webEditorUrl);
- sender.sendMessage("§aReloaded successfully " + plugin.getConfigManager().getSettings().serverId + "!");
+ sender.sendMessage("§aReloaded successfully!");
return true;
}
String langField = args[1];
+ if (args[0].equalsIgnoreCase("expandpool")) {
+ if (!sender.hasPermission("rediseconomy.admin.expandpool")) {
+ plugin.getConfigManager().getLangs().send(sender, plugin.getConfigManager().getLangs().noPermission);
+ return true;
+ }
+ try {
+ plugin.getCurrenciesManager().getRedisManager().expandPool(Integer.parseInt(args[1]));
+ plugin.getConfigManager().getLangs().send(sender, "§aPool expanded successfully!");
+ } catch (Exception e) {
+ plugin.getConfigManager().getLangs().send(sender, "§cError expanding pool: " + e.getMessage());
+ }
+ return true;
+ }
+
if (!sender.hasPermission("rediseconomy.admin.editmessage")) {
plugin.getConfigManager().getLangs().send(sender, plugin.getConfigManager().getLangs().noPermission);
return true;
@@ -83,9 +95,15 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command
@Override
public @NotNull List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
if (args.length == 1) {
- return List.of("reload", "editmessage");
- } else if (args.length == 2 && sender.hasPermission("rediseconomy.admin.editmessage")) {
- return Arrays.stream(plugin.getConfigManager().getLangs().getClass().getFields()).filter(field -> field.getType().equals(String.class)).map(Field::getName).toList();
+ return List.of("reload", "editmessage", "expandpool");
+ } else if (args.length == 2 && args[0].equalsIgnoreCase("expandpool")) {
+ return List.of("1", "2", "3", "4", "5");
+ } else if (args.length == 2 && sender.hasPermission("rediseconomy.admin.editmessage") && args[0].equalsIgnoreCase("editmessage")) {
+ return Arrays.stream(plugin.getConfigManager().getLangs().getClass().getFields())
+ .filter(field -> field.getType().equals(String.class))
+ .map(Field::getName)
+ .filter(name -> name.startsWith(args[1]))
+ .toList();
}
return List.of();
}
diff --git a/src/main/java/dev/unnm3d/rediseconomy/command/PayCommand.java b/src/main/java/dev/unnm3d/rediseconomy/command/PayCommand.java
index 5bbbe48..f88a91a 100644
--- a/src/main/java/dev/unnm3d/rediseconomy/command/PayCommand.java
+++ b/src/main/java/dev/unnm3d/rediseconomy/command/PayCommand.java
@@ -45,8 +45,10 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command
if (args.length == 2) {
payCurrency(p, currenciesManager.getDefaultCurrency(), args);
} else {
- if (!sender.hasPermission("rediseconomy.pay." + args[2]))
+ if (!sender.hasPermission("rediseconomy.pay." + args[2])) {
plugin.langs().send(sender, plugin.langs().noPermission);
+ return true;
+ }
Currency currency = currenciesManager.getCurrencyByName(args[2]);
if (currency == null) {
plugin.langs().send(sender, plugin.langs().invalidCurrency);
@@ -66,7 +68,7 @@ private void payCurrency(Player sender, Currency currency, String[] args) {
}
long init = System.currentTimeMillis();
final String target = args[0];
- final double amount = plugin.langs().formatAmountString(args[1]);
+ final double amount = currenciesManager.formatAmountString(sender.getName(), currency, args[1]);
if (amount <= 0) {
plugin.langs().send(sender, plugin.langs().invalidAmount);
return;
@@ -78,6 +80,13 @@ private void payCurrency(Player sender, Currency currency, String[] args) {
if (target.equalsIgnoreCase(sender.getName())) {
plugin.langs().send(sender, plugin.langs().paySelf);
return;
+ } else if (target.equals("*")) {
+ if (sender.hasPermission("rediseconomy.payall")) {
+ payCurrencyAll(sender, currency, amount);
+ } else {
+ plugin.langs().send(sender, plugin.langs().noPermission);
+ }
+ return;
}
if (!currency.hasAccount(target)) {
plugin.langs().send(sender, plugin.langs().playerNotFound);
@@ -121,7 +130,48 @@ private void payCurrency(Player sender, Currency currency, String[] args) {
}
return currenciesManager.getExchange().savePaymentTransaction(sender.getUniqueId(), targetUUID, amount, currency, reason);
});
+ }
+ /**
+ * Pay to all online players
+ *
+ * @param sender Player who sends the payment
+ * @param currency Currency to pay
+ * @param amount Amount to pay
+ */
+ private void payCurrencyAll(Player sender, Currency currency, double amount) {
+ for (Player onlinePlayer : plugin.getServer().getOnlinePlayers()) {
+ if (onlinePlayer.getName().equals(sender.getName())) continue;
+
+ if (currenciesManager.isAccountLocked(onlinePlayer.getUniqueId(), sender.getUniqueId())) {
+ plugin.langs().send(sender, plugin.langs().blockedPayment.replace("%player%", onlinePlayer.getName()));
+ continue;
+ }
+
+ final EconomyResponse response = currency.payPlayer(sender.getName(), onlinePlayer.getName(), amount);
+ if (!response.transactionSuccess()) {
+ if (response.errorMessage.equals("Insufficient funds")) {
+ plugin.langs().send(sender, plugin.langs().insufficientFunds);
+ } else {
+ plugin.langs().send(sender, plugin.langs().payFail);
+ }
+ return;
+ }
+ //Send msg to sender
+ plugin.langs().send(sender,
+ plugin.langs().paySuccess
+ .replace("%amount%", currency.format(amount))
+ .replace("%player%", onlinePlayer.getName())
+ .replace("%tax_percentage%", (currency.getTransactionTax() * 100) + "%")
+ .replace("%tax_applied%", currency.format(currency.getTransactionTax() * amount))
+ );
+ //Send msg to target
+ currenciesManager.getRedisManager().getConnectionAsync(commands -> {
+ commands.publish(MSG_CHANNEL.toString(), sender.getName() + ";;" + onlinePlayer.getName() + ";;" + currency.format(amount));
+ //Register transaction
+ return currenciesManager.getExchange().savePaymentTransaction(sender.getUniqueId(), onlinePlayer.getUniqueId(), amount, currency, "Payment to all online players");
+ });
+ }
}
@Override
@@ -129,12 +179,14 @@ private void payCurrency(Player sender, Currency currency, String[] args) {
if (args.length == 1) {
if (args[0].length() < plugin.settings().tab_complete_chars)
return List.of();
- return currenciesManager.getNameUniqueIds().keySet().stream().filter(name -> name.toUpperCase().startsWith(args[0].toUpperCase())).toList();
- } else if (args.length == 2)
- return List.of("69");
- else if (args.length == 3)
+ return currenciesManager.getNameUniqueIds().keySet().stream()
+ .filter(name -> name.toUpperCase().startsWith(args[0].toUpperCase()))
+ .toList();
+ } else if (args.length == 2) {
+ return List.of("1");
+ } else if (args.length == 3) {
return currenciesManager.getCurrencies().stream().map(Currency::getCurrencyName).filter(name -> name.startsWith(args[2]) && sender.hasPermission("rediseconomy.pay." + args[2])).toList();
-
+ }
return List.of();
}
diff --git a/src/main/java/dev/unnm3d/rediseconomy/command/PurgeUserCommand.java b/src/main/java/dev/unnm3d/rediseconomy/command/PurgeUserCommand.java
index 71304f7..7f984c6 100644
--- a/src/main/java/dev/unnm3d/rediseconomy/command/PurgeUserCommand.java
+++ b/src/main/java/dev/unnm3d/rediseconomy/command/PurgeUserCommand.java
@@ -44,13 +44,11 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command
nameUUIDs = currenciesManager.removeNamePattern(target, !onlyNameUUID);
successMsg = plugin.langs().purgeUserSuccess.replace("%player%", target);
}
- if (nameUUIDs.size() == 0) {
+ if (nameUUIDs.isEmpty()) {
plugin.langs().send(sender, plugin.langs().playerNotFound);
return true;
}
plugin.langs().send(sender, successMsg);
-
-
return true;
}
diff --git a/src/main/java/dev/unnm3d/rediseconomy/command/balance/BalanceCommand.java b/src/main/java/dev/unnm3d/rediseconomy/command/balance/BalanceCommand.java
index 796438b..dc0207f 100644
--- a/src/main/java/dev/unnm3d/rediseconomy/command/balance/BalanceCommand.java
+++ b/src/main/java/dev/unnm3d/rediseconomy/command/balance/BalanceCommand.java
@@ -36,7 +36,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command
selfBalancePlayer(sender, defaultCurrency);
return true;
}
- String target = economy.getCaseSensitiveName(args[0]);
+ final String target = economy.getCaseSensitiveName(args[0]);
if (args.length == 1) {
balancePlayer(sender, defaultCurrency, target);
} else if (args.length == 2) {
@@ -62,7 +62,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command
plugin.langs().send(sender, plugin.langs().invalidCurrency);
return true;
}
- double amount = plugin.langs().formatAmountString(args[3]);
+ double amount = plugin.getCurrenciesManager().formatAmountString(target, currency, args[3]);
if (amount < 0) {
plugin.langs().send(sender, plugin.langs().invalidAmount);
return true;
@@ -84,6 +84,9 @@ else if (reasonOrCommand.startsWith("/")) {
} else if (args[2].equalsIgnoreCase("set")) {
setPlayer(sender, currency, amount, target);
+
+ } else if (args[2].equalsIgnoreCase("set-max")) {
+ setPlayerMaxBalance(sender, currency, amount, target);
}
}
if (plugin.settings().debug)
@@ -105,6 +108,8 @@ else if (reasonOrCommand.startsWith("/")) {
protected abstract void setPlayer(CommandSender sender, Currency currency, double amount, String target);
+ protected abstract void setPlayerMaxBalance(CommandSender sender, Currency currency, double amount, String target);
+
@Override
public @NotNull List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length == 1) {
@@ -118,7 +123,7 @@ else if (reasonOrCommand.startsWith("/")) {
} else if (args.length == 2)
return economy.getCurrencies().stream().map(Currency::getCurrencyName).filter(name -> name.startsWith(args[1]) && sender.hasPermission("rediseconomy.balance." + args[1])).toList();
else if (args.length == 3)
- return List.of("give", "take", "set");
+ return List.of("give", "take", "set", "set-max");
else if (args.length == 4)
return List.of("69");
return List.of();
diff --git a/src/main/java/dev/unnm3d/rediseconomy/command/balance/BalanceSubCommands.java b/src/main/java/dev/unnm3d/rediseconomy/command/balance/BalanceSubCommands.java
index c09f54d..b7e43c0 100644
--- a/src/main/java/dev/unnm3d/rediseconomy/command/balance/BalanceSubCommands.java
+++ b/src/main/java/dev/unnm3d/rediseconomy/command/balance/BalanceSubCommands.java
@@ -3,10 +3,13 @@
import dev.unnm3d.rediseconomy.RedisEconomyPlugin;
import dev.unnm3d.rediseconomy.currency.CurrenciesManager;
import dev.unnm3d.rediseconomy.currency.Currency;
+import dev.unnm3d.rediseconomy.utils.DecimalUtils;
import net.milkbowl.vault.economy.EconomyResponse;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
+import java.util.UUID;
+
public class BalanceSubCommands extends BalanceCommand {
public BalanceSubCommands(CurrenciesManager economy, RedisEconomyPlugin plugin) {
@@ -19,7 +22,12 @@ protected void balancePlayer(CommandSender sender, Currency currency, String tar
plugin.langs().send(sender, plugin.langs().playerNotFound);
return;
}
- plugin.langs().send(sender, plugin.langs().balanceOther.replace("%balance%", String.valueOf(currency.format(currency.getBalance(target)))).replace("%player%", target));
+ double currentBalance = currency.getBalance(target);
+ plugin.langs().send(sender, plugin.langs().balanceOther
+ .replace("%balance%", String.valueOf(currency.format(currentBalance)))
+ .replace("%balance_short%", DecimalUtils.shortAmount(currentBalance, currency.getDecimalFormat()) +
+ (currentBalance == 1 ? currency.getCurrencySingular() : currency.getCurrencyPlural()))
+ .replace("%player%", target));
}
@Override
@@ -28,7 +36,11 @@ protected void selfBalancePlayer(CommandSender sender, Currency currency) {
plugin.langs().send(sender, plugin.langs().noConsole);
return;
}
- plugin.langs().send(sender, plugin.langs().balance.replace("%balance%", String.valueOf(currency.format(currency.getBalance(p)))));
+
+ plugin.langs().send(sender, plugin.langs().balance.replace("%balance_short%",
+ DecimalUtils.shortAmount(currency.getBalance(p), currency.getDecimalFormat()) +
+ (currency.getBalance(p) == 1 ? currency.getCurrencySingular() : currency.getCurrencyPlural()))
+ .replace("%balance%", String.valueOf(currency.format(currency.getBalance(p)))));
}
@Override
@@ -72,4 +84,16 @@ protected void setPlayer(CommandSender sender, Currency currency, double amount,
plugin.langs().send(sender, plugin.langs().balanceSet.replace("%balance%", currency.format(er.balance)).replace("%player%", target));
else sender.sendMessage(er.errorMessage);
}
+
+ @Override
+ protected void setPlayerMaxBalance(CommandSender sender, Currency currency, double amount, String target) {
+ final UUID targetUUID = plugin.getCurrenciesManager().getUUIDFromUsernameCache(target);
+ if (targetUUID == null) {
+ plugin.langs().send(sender, plugin.langs().playerNotFound);
+ return;
+ }
+ currency.setPlayerMaxBalance(targetUUID, amount);
+ plugin.langs().send(sender, plugin.langs().maxBalanceSet.replace("%player%", target).replace("%amount%", currency.format(amount)));
+ }
+
}
diff --git a/src/main/java/dev/unnm3d/rediseconomy/command/balance/BalanceTopCommand.java b/src/main/java/dev/unnm3d/rediseconomy/command/balance/BalanceTopCommand.java
index 11330fb..f35b6d2 100644
--- a/src/main/java/dev/unnm3d/rediseconomy/command/balance/BalanceTopCommand.java
+++ b/src/main/java/dev/unnm3d/rediseconomy/command/balance/BalanceTopCommand.java
@@ -3,6 +3,7 @@
import dev.unnm3d.rediseconomy.RedisEconomyPlugin;
import dev.unnm3d.rediseconomy.currency.CurrenciesManager;
import dev.unnm3d.rediseconomy.currency.Currency;
+import dev.unnm3d.rediseconomy.utils.DecimalUtils;
import io.lettuce.core.ScoredValue;
import lombok.AllArgsConstructor;
import org.bukkit.command.Command;
@@ -65,6 +66,9 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command
plugin.langs().send(sender, plugin.langs().balanceTopFormat
.replace("%pos%", String.valueOf((pageData.pageNumber - 1) * 10 + i))
.replace("%player%", username == null ? tuple.getValue() + "-Unknown" : username)
+ .replace("%balance_short%",
+ DecimalUtils.shortAmount(tuple.getScore(), baltopCurrency.getDecimalFormat()) +
+ (tuple.getScore() == 1 ? baltopCurrency.getCurrencySingular() : baltopCurrency.getCurrencyPlural()))
.replace("%balance%", baltopCurrency.format(tuple.getScore())));
i++;
}
diff --git a/src/main/java/dev/unnm3d/rediseconomy/command/transaction/BrowseTransactionsCommand.java b/src/main/java/dev/unnm3d/rediseconomy/command/transaction/BrowseTransactionsCommand.java
index f4ed64d..6dd22f5 100644
--- a/src/main/java/dev/unnm3d/rediseconomy/command/transaction/BrowseTransactionsCommand.java
+++ b/src/main/java/dev/unnm3d/rediseconomy/command/transaction/BrowseTransactionsCommand.java
@@ -63,8 +63,6 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command
sendTransaction(sender, i, transactions.get(i), afterDateString + " " + beforeDateString);
- if (plugin.settings().debug)
- sender.sendMessage("Time: " + (System.currentTimeMillis() - init));
}
plugin.langs().send(sender, plugin.langs().transactionsEnd
diff --git a/src/main/java/dev/unnm3d/rediseconomy/config/ConfigManager.java b/src/main/java/dev/unnm3d/rediseconomy/config/ConfigManager.java
index c10ab23..ba966dc 100644
--- a/src/main/java/dev/unnm3d/rediseconomy/config/ConfigManager.java
+++ b/src/main/java/dev/unnm3d/rediseconomy/config/ConfigManager.java
@@ -1,19 +1,12 @@
package dev.unnm3d.rediseconomy.config;
-import com.google.common.io.ByteArrayDataInput;
-import com.google.common.io.ByteArrayDataOutput;
-import com.google.common.io.ByteStreams;
import de.exlll.configlib.YamlConfigurationProperties;
import de.exlll.configlib.YamlConfigurations;
import dev.unnm3d.rediseconomy.RedisEconomyPlugin;
import lombok.Getter;
-import org.bukkit.entity.Player;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.HandlerList;
-import org.bukkit.event.Listener;
import java.io.File;
-import java.util.concurrent.CompletableFuture;
+import java.nio.charset.StandardCharsets;
public class ConfigManager {
private final RedisEconomyPlugin plugin;
@@ -22,95 +15,51 @@ public class ConfigManager {
@Getter
private Langs langs;
+ private static final YamlConfigurationProperties PROPERTIES = YamlConfigurationProperties.newBuilder()
+ .header(
+ """
+ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+ ┃ RedisEconomy Config ┃
+ ┃ Developed by Unnm3d ┃
+ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+ """
+ )
+ .footer("Authors: Unnm3d")
+ .charset(StandardCharsets.UTF_8)
+ .build();
+
public ConfigManager(RedisEconomyPlugin plugin) {
this.plugin = plugin;
loadSettingsConfig();
}
- public void postStartupLoad() {
- loadLangs();
- getServerId().thenAccept(s -> {
- settings.serverId = s;
- saveConfigs();
- });
- }
-
public void loadSettingsConfig() {
- YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder()
- .header(
- """
- ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
- ┃ RedisEconomy Config ┃
- ┃ Developed by Unnm3d ┃
- ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
- """
- )
- .footer("Authors: Unnm3d")
- .build();
File settingsFile = new File(plugin.getDataFolder(), "config.yml");
settings = YamlConfigurations.update(
settingsFile.toPath(),
Settings.class,
- properties
+ PROPERTIES
);
+ if (settings.redis.tryAgainCount() < 2 || settings.redis.poolSize() < 2)
+ plugin.getLogger().severe("Please regenerate the redis configuration section. New settings have been added.");
}
public void saveConfigs() {
- YamlConfigurations.save(new File(plugin.getDataFolder(), "config.yml").toPath(), Settings.class, settings);
- YamlConfigurations.save(new File(plugin.getDataFolder(), settings.lang + ".yml").toPath(), Langs.class, langs);
+ YamlConfigurations.save(new File(plugin.getDataFolder(), "config.yml").toPath(), Settings.class, settings, PROPERTIES);
+ YamlConfigurations.save(new File(plugin.getDataFolder(), settings.lang + ".yml").toPath(), Langs.class, langs, PROPERTIES);
}
public void loadLangs() {
File settingsFile = new File(plugin.getDataFolder(), settings.lang + ".yml");
if (!settingsFile.exists()) {
- plugin.saveResource("it-IT.yml", false);//save default lang
+ plugin.saveResource("it-IT.yml", false);
+ plugin.saveResource("de-DE.yml", false);
}
langs = YamlConfigurations.update(
settingsFile.toPath(),
- Langs.class
+ Langs.class,
+ PROPERTIES
);
}
- @SuppressWarnings("UnstableApiUsage")
- public CompletableFuture getServerId() {
- CompletableFuture future = new CompletableFuture<>();
- plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, "BungeeCord");
- plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, "BungeeCord", (channel, player, message) -> {
- if (future.isDone()) return;
- ByteArrayDataInput in = ByteStreams.newDataInput(message);
- String subchannel = in.readUTF();
- if (subchannel.equals("GetServer")) {
- future.complete(in.readUTF());//Receive server name
- }
- });
- Listener listener = new Listener() {
- @EventHandler
- public void onJoin(org.bukkit.event.player.PlayerJoinEvent event) {
- if (future.isDone()) {
- return;
- }
- plugin.getScheduler().runTaskLaterAsynchronously(() -> sendServerIdRequest(event.getPlayer()), 20L);
- }
- };
- if (plugin.getServer().getOnlinePlayers().size() > 0) {
- sendServerIdRequest(plugin.getServer().getOnlinePlayers().iterator().next());
- } else {
- plugin.getServer().getPluginManager().registerEvents(listener, plugin);
- }
- return future.thenApply(s -> {
- //Remove listener and channel listeners
- HandlerList.unregisterAll(listener);
- plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin, "BungeeCord");
- plugin.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin, "BungeeCord");
- return s;
- });
- }
-
- @SuppressWarnings("UnstableApiUsage")
- private void sendServerIdRequest(Player p) {
- ByteArrayDataOutput out = ByteStreams.newDataOutput();
- out.writeUTF("GetServer");
- p.sendPluginMessage(plugin, "BungeeCord", out.toByteArray());//Request server name
- }
-
}
diff --git a/src/main/java/dev/unnm3d/rediseconomy/config/Langs.java b/src/main/java/dev/unnm3d/rediseconomy/config/Langs.java
index c314284..14698f8 100644
--- a/src/main/java/dev/unnm3d/rediseconomy/config/Langs.java
+++ b/src/main/java/dev/unnm3d/rediseconomy/config/Langs.java
@@ -9,6 +9,8 @@
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
+import java.util.NavigableMap;
+import java.util.TreeMap;
@Configuration
public final class Langs {
@@ -22,15 +24,16 @@ public final class Langs {
public String payCooldown = "Your previous payment is still in progress! Wait please";
public String invalidCurrency = "Invalid currency!";
public String insufficientFunds = "You do not have enough money!";
- public String balance = "You have %balance%!";
+ public String balance = "You have %balance_short%!";
public String balanceSet = "You set %player% account to %balance% !";
- public String balanceOther = "%player% has %balance% !";
+ public String maxBalanceSet = "You set %player% max balance to %amount% !";
+ public String balanceOther = "%player% has %balance_short% !";
public String balanceTop = "Top richest players:
%prevpage% %page% %nextpage%";
public String blockedAccounts = "Blocked accounts:
%list%";
public String blockedAccountSuccess = "Account %player% has been blocked!";
public String unblockedAccountSuccess = "Account %player% has been unblocked!";
public String blockedPayment = "Your payments to %player% have been blocked!";
- public String balanceTopFormat = "%pos% - %player% %balance%";
+ public String balanceTopFormat = "%pos% - %player% %balance_short%";
public String paySelf = "You cannot pay yourself!";
@Comment("Use %tax_percentage% for tax percentage and %tax_applied% for tax applied to the transaction.")
public String paySuccess = "You paid %player% %amount%";
@@ -75,27 +78,19 @@ public record UnitSymbols(
) {
}
- public void send(CommandSender sender, String text) {
- audiences.sender(sender).sendMessage(MiniMessage.miniMessage().deserialize(text));
+ public NavigableMap getSuffixes() {
+ return new TreeMap<>() {{
+ put(unitSymbols.thousand(), 1_000L);
+ put(unitSymbols.million(), 1_000_000L);
+ put(unitSymbols.billion(), 1_000_000_000L);
+ put(unitSymbols.trillion(), 1_000_000_000_000L);
+ put(unitSymbols.quadrillion(), 1_000_000_000_000_000L);
+ }};
+
}
- public double formatAmountString(String amount) {
- try {
- if (amount.endsWith(unitSymbols.quadrillion())) {
- return Double.parseDouble(amount.substring(0, amount.length() - unitSymbols.quadrillion().length())) * 1_000_000_000_000_000D;
- } else if (amount.endsWith(unitSymbols.trillion())) {
- return Double.parseDouble(amount.substring(0, amount.length() - unitSymbols.trillion().length())) * 1_000_000_000_000D;
- } else if (amount.endsWith(unitSymbols.billion())) {
- return Double.parseDouble(amount.substring(0, amount.length() - unitSymbols.billion().length())) * 1_000_000_000D;
- } else if (amount.endsWith(unitSymbols.million())) {
- return Double.parseDouble(amount.substring(0, amount.length() - unitSymbols.million().length())) * 1_000_000D;
- } else if (amount.endsWith(unitSymbols.thousand())) {
- return Double.parseDouble(amount.substring(0, amount.length() - unitSymbols.thousand().length())) * 1_000D;
- }
- return Double.parseDouble(amount);
- } catch (NumberFormatException e) {
- return -1;
- }
+ public void send(CommandSender sender, String text) {
+ audiences.sender(sender).sendMessage(MiniMessage.miniMessage().deserialize(text));
}
public @Nullable Field getStringField(String name) throws NoSuchFieldException {
diff --git a/src/main/java/dev/unnm3d/rediseconomy/config/Settings.java b/src/main/java/dev/unnm3d/rediseconomy/config/Settings.java
index 2856051..402b74a 100644
--- a/src/main/java/dev/unnm3d/rediseconomy/config/Settings.java
+++ b/src/main/java/dev/unnm3d/rediseconomy/config/Settings.java
@@ -4,30 +4,32 @@
import de.exlll.configlib.Configuration;
import java.util.List;
-import java.util.UUID;
@SuppressWarnings("unused")
@Configuration
public class Settings {
- @Comment({"This is automatically generated on server startup",
- "Change it only if you have disabled plugin messages on the proxy"})
- public String serverId = String.valueOf(UUID.randomUUID());
@Comment("Language file")
public String lang = "en-US";
@Comment("Webeditor URL")
public String webEditorUrl = "https://webui.advntr.dev/";
@Comment("Activate this before reporting an issue")
public boolean debug = false;
+ @Comment("A specific debug for cache update")
+ public boolean debugUpdateCache = false;
@Comment("If true, the plugin registers who's calling it's methods inside transactions")
public boolean registerCalls = false;
+ @Comment("List of regex to be excluded from the registerCalls")
+ public List callBlacklistRegex = List.of("^org\\.bukkit.*", "^dev\\.unnm3d\\.rediseconomy.*","^com\\.mojang.*");
@Comment({"if true, migrates the bukkit offline uuids accounts to the default RedisEconomy currency",
"During the migration, the plugin will be disabled. Restart all RedisEconomy instances after the migration."})
public boolean migrationEnabled = false;
+ @Comment("Allow paying with percentage (ex: /pay player 10% sends 'player' 10% of the sender balance)")
+ public boolean allowPercentagePayments = true;
@Comment({"Leave password or user empty if you don't have a password or user",
"Don't use the default credentials in production!! Generate new credentials on RedisLabs -> https://github.com/Emibergo02/RedisEconomy/wiki/Install-redis",
"Default credentials lead to a non-persistent redis server, only for testing!!",
})
- public RedisSettings redis = new RedisSettings("localhost", 6379, "", "", 0, 2000, "RedisEconomy");
+ public RedisSettings redis = new RedisSettings("localhost", 6379, "", "", 0, 300, "RedisEconomy", 5, 3);
@Comment({"All RedisEconomy instances with the same cluster id will share the same data"})
public String clusterId = "";
@Comment({"How many chars are needed for a command autocompletion", "Increase if you have a lot of players to list"})
@@ -39,14 +41,23 @@ public class Settings {
@Comment("Minimum amount of money that can be paid")
public double minPayAmount = 0.01;
@Comment({"Currencies", "payTax is the tax on payments, 0.1 = 10% tax"})
- public List currencies = List.of(new CurrencySettings("vault", "euro", "euros", "#.##", "en-US", 0, 0, true, false), new CurrencySettings("dollar", "$", "$", "#.##", "en-US", 0, 0, false, false));
+ public List currencies = List.of(new CurrencySettings("vault", "euro", "euros", "#.##", "en-US", 0, 100000000000000d, 0, true, true, false), new CurrencySettings("dollar", "$", "$", "#.##", "en-US", 0, 100000000000000d, 0, false, false, false));
public record CurrencySettings(String currencyName, String currencySingle, String currencyPlural,
String decimalFormat, String languageTag,
- double startingBalance, double payTax, boolean bankEnabled, boolean taxOnlyPay) {
+ double startingBalance, double maxBalance, double payTax,
+ boolean saveTransactions, boolean bankEnabled, boolean taxOnlyPay) {
}
public record RedisSettings(String host, int port, String user, String password, int database, int timeout,
- String clientName) {
+ String clientName, int poolSize, int tryAgainCount) {
+ //Those checks are for new config files, if the user doesn't have the new settings
+ public int getPoolSize() {
+ return poolSize == 0 ? 5 : poolSize;
+ }
+
+ public int getTryAgainCount() {
+ return tryAgainCount == 0 ? 3 : tryAgainCount;
+ }
}
}
diff --git a/src/main/java/dev/unnm3d/rediseconomy/currency/CurrenciesManager.java b/src/main/java/dev/unnm3d/rediseconomy/currency/CurrenciesManager.java
index e786bc3..80dbda8 100644
--- a/src/main/java/dev/unnm3d/rediseconomy/currency/CurrenciesManager.java
+++ b/src/main/java/dev/unnm3d/rediseconomy/currency/CurrenciesManager.java
@@ -17,6 +17,7 @@
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.server.PluginEnableEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.ServicePriority;
@@ -69,61 +70,63 @@ public CurrenciesManager(RedisManager redisManager, RedisEconomyPlugin plugin, C
currencies.put(currencySettings.currencyName(), currency);
});
if (currencies.get(configManager.getSettings().defaultCurrencyName) == null) {
- currencies.put(configManager.getSettings().defaultCurrencyName, new Currency(this, new Settings.CurrencySettings(configManager.getSettings().defaultCurrencyName, "€", "€", "#.##", "en-US", 0.0, 0.0, true, false)));
+ currencies.put(configManager.getSettings().defaultCurrencyName, new Currency(this, new Settings.CurrencySettings(configManager.getSettings().defaultCurrencyName, "€", "€", "#.##", "en-US", 0.0, Double.POSITIVE_INFINITY, 0.0, true, true, false)));
}
registerPayMsgChannel();
registerBlockAccountChannel();
}
-
+ /**
+ * Loads the default currency into the vault economy provider
+ * Unregisters the existent economy provider
+ *
+ * @param vaultPlugin the vault plugin
+ */
public void loadDefaultCurrency(Plugin vaultPlugin) {
- Currency defaultCurrency = getDefaultCurrency();
-
for (RegisteredServiceProvider registration : plugin.getServer().getServicesManager().getRegistrations(Economy.class)) {
plugin.getServer().getServicesManager().unregister(Economy.class, registration.getProvider());
}
+ plugin.getServer().getServicesManager().register(Economy.class, getDefaultCurrency(), vaultPlugin, ServicePriority.High);
+ }
- if (!configManager.getSettings().migrationEnabled) {
- plugin.getServer().getServicesManager().register(Economy.class, defaultCurrency, vaultPlugin, ServicePriority.High);
- return;
- }
-
+ /**
+ * Migrates the balances from the existent economy provider to the new one
+ * using the offline players
+ *
+ * @param offlinePlayers the offline players to migrate
+ */
+ public void migrateFromOfflinePlayers(OfflinePlayer[] offlinePlayers) {
+ final Currency defaultCurrency = getDefaultCurrency();
RegisteredServiceProvider existentProvider = plugin.getServer().getServicesManager().getRegistration(Economy.class);
if (existentProvider == null) {
plugin.getLogger().severe("Vault economy provider not found!");
return;
}
- completeMigration.thenApply(voids -> {
- plugin.getLogger().info("§aMigrating from " + existentProvider.getProvider().getName() + "...");
- if (existentProvider.getProvider() == defaultCurrency) {
- plugin.getLogger().info("There's no other provider apart RedisEconomy!");
- return defaultCurrency;
+ plugin.getLogger().info("§aMigrating from " + existentProvider.getProvider().getName() + "...");
+
+ final List> balances = new ArrayList<>();
+ final Map nameUniqueIds = new HashMap<>();
+ for (int i = 0; i < offlinePlayers.length; i++) {
+ final OfflinePlayer offlinePlayer = offlinePlayers[i];
+ try {
+ double bal = existentProvider.getProvider().getBalance(offlinePlayer);
+ balances.add(ScoredValue.just(bal, offlinePlayer.getUniqueId().toString()));
+ if (offlinePlayer.getName() != null)
+ nameUniqueIds.put(offlinePlayer.getName(), offlinePlayer.getUniqueId().toString());
+ defaultCurrency.updateAccountLocal(offlinePlayer.getUniqueId(), offlinePlayer.getName() == null ? offlinePlayer.getUniqueId().toString() : offlinePlayer.getName(), bal);
+ } catch (Exception e) {
+ e.printStackTrace();
}
-
- List> balances = new ArrayList<>();
- Map nameUniqueIds = new HashMap<>();
- for (OfflinePlayer offlinePlayer : Bukkit.getOfflinePlayers()) {
- try {
- double bal = existentProvider.getProvider().getBalance(offlinePlayer);
- balances.add(ScoredValue.just(bal, offlinePlayer.getUniqueId().toString()));
- if (offlinePlayer.getName() != null)
- nameUniqueIds.put(offlinePlayer.getName(), offlinePlayer.getUniqueId().toString());
- defaultCurrency.updateAccountLocal(offlinePlayer.getUniqueId(), offlinePlayer.getName() == null ? offlinePlayer.getUniqueId().toString() : offlinePlayer.getName(), bal);
- } catch (Exception e) {
- e.printStackTrace();
- }
+ if (i % 100 == 0) {
+ plugin.getLogger().info("Progress: " + i + "/" + offlinePlayers.length);
}
-
- defaultCurrency.updateBulkAccountsCloudCache(balances, nameUniqueIds);
- return defaultCurrency;
- }).thenAccept((vaultCurrency) -> {
- plugin.getServer().getServicesManager().register(Economy.class, vaultCurrency, vaultPlugin, ServicePriority.High);
- plugin.getLogger().info("§aMigration completed!");
- configManager.getSettings().migrationEnabled = false;
- configManager.saveConfigs();
- });
-
+ }
+ defaultCurrency.updateBulkAccountsCloudCache(balances, nameUniqueIds);
+ plugin.getLogger().info("§aMigration completed!");
+ plugin.getLogger().info("§aRestart the server to apply the changes.");
+ configManager.getSettings().migrationEnabled = false;
+ configManager.saveConfigs();
}
@Override
@@ -190,6 +193,15 @@ public HashMap removeNamePattern(String namePattern, boolean reset
*/
public HashMap resetBalanceNamePattern(String namePattern, Currency currencyReset) {
HashMap removed = new HashMap<>();
+ currencyReset.getOrderedAccounts(Integer.MAX_VALUE).thenAccept(accounts -> {
+ for (ScoredValue account : accounts) {
+ UUID uuid = UUID.fromString(account.getValue());
+ if (!nameUniqueIds.containsValue(uuid)) {
+ currencyReset.setPlayerBalance(uuid, null, 0.0);
+ }
+ }
+ });
+
for (Map.Entry entry : nameUniqueIds.entrySet()) {
if (entry.getKey().matches(namePattern)) {
removed.put(entry.getKey(), entry.getValue());
@@ -258,6 +270,15 @@ private void onJoin(PlayerJoinEvent e) {
}));
}
+ @EventHandler
+ private void onPluginEnable(PluginEnableEvent pluginEnableEvent) {
+ if (plugin.settings().migrationEnabled) return;
+ if (!plugin.getServer().getServicesManager().getRegistrations(net.milkbowl.vault.economy.Economy.class)
+ .stream().allMatch(registration -> registration.getPlugin().equals(plugin))) {
+ loadDefaultCurrency(plugin.getVaultPlugin());
+ }
+ }
+
private CompletionStage> loadRedisNameUniqueIds() {
return redisManager.getConnectionAsync(connection ->
connection.hgetall(NAME_UUID.toString())
@@ -403,6 +424,35 @@ public boolean isAccountLocked(UUID uuid, UUID target) {
getLockedAccounts(uuid).contains(RedisKeys.getAllAccountUUID());
}
+ /**
+ * Formats the amount string to a double
+ * Parses suffixes (10k, 10M for 10 thousand and 10 million)
+ * Parses percentages ("10%" for 10% of the accountOwner balance)
+ *
+ * @param targetName The account that has to be modified
+ * @param currency the currency to format the amount
+ * @param amount the amount to format
+ * @return the formatted amount
+ */
+ public double formatAmountString(String targetName, Currency currency, String amount) {
+ try {
+ //Check if last char of amount is number
+ if (Character.isDigit(amount.charAt(amount.length() - 1))) {
+ return Double.parseDouble(amount);
+ }
+ double parsedAmount = Double.parseDouble(amount.substring(0, amount.length() - 1));
+ return amount.endsWith("%") && plugin.settings().allowPercentagePayments ?
+ //Percentage 20%
+ parsedAmount / 100 * currency.getBalance(targetName) :
+ //Parse suffixes from map
+ parsedAmount * plugin.langs().getSuffixes()
+ .getOrDefault(amount.substring(amount.length() - 1), -1L);
+ } catch (NumberFormatException e) {
+ plugin.langs().send(Bukkit.getConsoleSender(), plugin.langs().invalidAmount);
+ }
+ return -1;
+ }
+
public List getLockedAccounts(UUID uuid) {
return lockedAccounts.getOrDefault(uuid, new ArrayList<>());
}
diff --git a/src/main/java/dev/unnm3d/rediseconomy/currency/Currency.java b/src/main/java/dev/unnm3d/rediseconomy/currency/Currency.java
index 6f57fcf..629fb31 100644
--- a/src/main/java/dev/unnm3d/rediseconomy/currency/Currency.java
+++ b/src/main/java/dev/unnm3d/rediseconomy/currency/Currency.java
@@ -4,7 +4,6 @@
import dev.unnm3d.rediseconomy.config.Settings;
import dev.unnm3d.rediseconomy.transaction.AccountID;
import dev.unnm3d.rediseconomy.transaction.Transaction;
-import io.lettuce.core.RedisFuture;
import io.lettuce.core.ScoredValue;
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
import lombok.AllArgsConstructor;
@@ -19,7 +18,10 @@
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.*;
-import java.util.concurrent.*;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import static dev.unnm3d.rediseconomy.redis.RedisKeys.*;
@@ -30,6 +32,8 @@ public class Currency implements Economy {
@Getter
protected final String currencyName;
private final ConcurrentHashMap accounts;
+ private final ConcurrentHashMap maxPlayerBalances;
+
private boolean enabled;
@Getter
private String currencySingular;
@@ -38,11 +42,15 @@ public class Currency implements Economy {
@Getter
private final DecimalFormat decimalFormat;
@Getter
- private double startingBalance;
+ private final double startingBalance;
+ @Getter
+ private final double maxBalance;
+ private boolean saveTransactions;
@Getter
private double transactionTax;
@Getter
private final boolean taxOnlyPay;
+ protected final ExecutorService updateExecutor;
/**
@@ -55,26 +63,39 @@ public class Currency implements Economy {
public Currency(CurrenciesManager currenciesManager, Settings.CurrencySettings currencySettings) {
this.currenciesManager = currenciesManager;
this.enabled = true;
+ this.updateExecutor = Executors.newSingleThreadExecutor();
this.currencyName = currencySettings.currencyName();
this.currencySingular = currencySettings.currencySingle();
this.currencyPlural = currencySettings.currencyPlural();
this.startingBalance = currencySettings.startingBalance();
+ this.maxBalance = currencySettings.maxBalance() == 0.0d ? Double.POSITIVE_INFINITY : currencySettings.maxBalance();
+ this.saveTransactions = currencySettings.saveTransactions();
this.transactionTax = currencySettings.payTax();
this.taxOnlyPay = currencySettings.taxOnlyPay();
this.accounts = new ConcurrentHashMap<>();
+ this.maxPlayerBalances = new ConcurrentHashMap<>();
this.decimalFormat = new DecimalFormat(
currencySettings.decimalFormat() != null ? currencySettings.decimalFormat() : "#.##",
new DecimalFormatSymbols(Locale.forLanguageTag(currencySettings.languageTag() != null ? currencySettings.languageTag() : "en-US"))
);
+
getOrderedAccounts(-1).thenApply(result -> {
- result.forEach(t ->
- accounts.put(UUID.fromString(t.getValue()), t.getScore()));
- if (RedisEconomyPlugin.getInstance().settings().debug && accounts.size() > 0) {
- Bukkit.getLogger().info("start1 Loaded " + accounts.size() + " accounts for currency " + currencyName);
- }
- return result;
- }
- ).toCompletableFuture().join(); //Wait to avoid API calls before accounts are loaded
+ result.forEach(t ->
+ accounts.put(UUID.fromString(t.getValue()), t.getScore()));
+ if (RedisEconomyPlugin.getInstance().settings().debug && !accounts.isEmpty()) {
+ Bukkit.getLogger().info("start1 Loaded " + accounts.size() + " accounts for currency " + currencyName);
+ }
+ return result;
+ }).toCompletableFuture().join(); //Wait to avoid API calls before accounts are loaded
+
+ getPlayerMaxBalances().thenApply(result -> {
+ maxPlayerBalances.putAll(result);
+ if (RedisEconomyPlugin.getInstance().settings().debug && !maxPlayerBalances.isEmpty()) {
+ Bukkit.getLogger().info("start1 Loaded " + maxPlayerBalances.size() + " max balances for currency " + currencyName);
+ }
+ return result;
+ }); //Not as critical as accounts, so we don't wait
+
registerUpdateListener();
}
@@ -85,21 +106,34 @@ private void registerUpdateListener() {
@Override
public void message(String channel, String message) {
String[] split = message.split(";;");
- if (split.length != 4) {
+ if (split.length < 3) {
Bukkit.getLogger().severe("Invalid message received from RedisEco channel, consider updating RedisEconomy");
- return;
}
- if (split[0].equals(RedisEconomyPlugin.getInstance().settings().serverId)) return;
- UUID uuid = UUID.fromString(split[1]);
- String playerName = split[2];
- double balance = Double.parseDouble(split[3]);
- updateAccountLocal(uuid, playerName, balance);
- if (RedisEconomyPlugin.getInstance().settings().debug) {
- Bukkit.getLogger().info("01b Received balance update " + playerName + " to " + balance);
+
+ if (split[0].equals(RedisEconomyPlugin.getInstanceUUID().toString())) return;
+ final UUID uuid = UUID.fromString(split[1]);
+
+ if (channel.equals(UPDATE_PLAYER_CHANNEL_PREFIX + currencyName)) {
+ String playerName = split[2];
+ double balance = Double.parseDouble(split[3]);
+ if (playerName == null) {
+ Bukkit.getLogger().severe("Player name not found for UUID " + uuid);
+ return;
+ }
+ updateAccountLocal(uuid, playerName, balance);
+ if (RedisEconomyPlugin.getInstance().settings().debug) {
+ Bukkit.getLogger().info("01b Received balance update " + playerName + " to " + balance);
+ }
+ } else if (channel.equals(UPDATE_MAX_BAL_PREFIX + currencyName)) {
+ double maxBal = Double.parseDouble(split[2]);
+ setPlayerMaxBalanceLocal(uuid, maxBal);
+ if (RedisEconomyPlugin.getInstance().settings().debug) {
+ Bukkit.getLogger().info("01b Received max balance update " + uuid + " to " + maxBal);
+ }
}
}
});
- connection.async().subscribe(UPDATE_PLAYER_CHANNEL_PREFIX + currencyName);
+ connection.async().subscribe(UPDATE_PLAYER_CHANNEL_PREFIX + currencyName, UPDATE_MAX_BAL_PREFIX + currencyName);
if (RedisEconomyPlugin.getInstance().settings().debug) {
Bukkit.getLogger().info("start1b Listening to RedisEco channel " + UPDATE_PLAYER_CHANNEL_PREFIX + currencyName);
}
@@ -110,6 +144,14 @@ public boolean isEnabled() {
return enabled;
}
+ public boolean shouldSaveTransactions() {
+ return saveTransactions;
+ }
+
+ public void setShouldSaveTransactions(boolean saveTransactions) {
+ this.saveTransactions = saveTransactions;
+ }
+
@Override
public String getName() {
return "RedisEconomy";
@@ -122,7 +164,7 @@ public boolean hasBankSupport() {
@Override
public int fractionalDigits() {
- return 0;
+ return decimalFormat.getMaximumFractionDigits();
}
@Override
@@ -333,7 +375,7 @@ public boolean createPlayerAccount(@NotNull UUID playerUUID, @Nullable String pl
if (hasAccount(playerUUID))
return false;
updateAccount(playerUUID, playerName, startingBalance);
- currenciesManager.getExchange().saveTransaction(new AccountID(playerUUID), new AccountID(), startingBalance, currencyName, "Account creation");
+ currenciesManager.getExchange().saveTransaction(new AccountID(playerUUID), new AccountID(), startingBalance, this, "Account creation");
return true;
}
@@ -366,7 +408,7 @@ public EconomyResponse withdrawPlayer(@NotNull UUID playerUUID, @Nullable String
return new EconomyResponse(amountToWithdraw, getBalance(playerUUID), EconomyResponse.ResponseType.FAILURE, "Insufficient funds");
updateAccount(playerUUID, playerName, getBalance(playerUUID) - amountToWithdraw);
- currenciesManager.getExchange().saveTransaction(new AccountID(playerUUID), new AccountID(), -amountToWithdraw, currencyName, reason == null ? "Withdraw" : reason);
+ currenciesManager.getExchange().saveTransaction(new AccountID(playerUUID), new AccountID(), -amountToWithdraw, this, reason == null ? "Withdraw" : reason);
return new EconomyResponse(amount, getBalance(playerUUID), EconomyResponse.ResponseType.SUCCESS, null);
}
@@ -387,10 +429,13 @@ public EconomyResponse payPlayer(@NotNull UUID sender, @NotNull UUID receiver, d
if (!has(sender, amountToWithdraw))
return new EconomyResponse(0, getBalance(sender), EconomyResponse.ResponseType.FAILURE, "Insufficient funds");
+ if (getBalance(receiver) + amount > getPlayerMaxBalance(receiver))
+ return new EconomyResponse(0, getBalance(receiver), EconomyResponse.ResponseType.FAILURE, "The receiver has reached the maximum balance");
+
updateAccount(sender, senderName, getBalance(sender) - amountToWithdraw);
- currenciesManager.getExchange().saveTransaction(new AccountID(sender), new AccountID(receiver), -amountToWithdraw, currencyName, reason == null ? "Payment" : reason);
+ currenciesManager.getExchange().saveTransaction(new AccountID(sender), new AccountID(receiver), -amountToWithdraw, this, reason == null ? "Payment" : reason);
updateAccount(sender, receiverName, getBalance(receiver) + amount);
- currenciesManager.getExchange().saveTransaction(new AccountID(receiver), new AccountID(sender), amount, currencyName, reason == null ? "Payment" : reason);
+ currenciesManager.getExchange().saveTransaction(new AccountID(receiver), new AccountID(sender), amount, this, reason == null ? "Payment" : reason);
return new EconomyResponse(amount, getBalance(sender), EconomyResponse.ResponseType.SUCCESS, null);
}
@@ -400,8 +445,11 @@ public EconomyResponse payPlayer(@NotNull String senderName, @NotNull String rec
return new EconomyResponse(amount, getBalance(senderName), EconomyResponse.ResponseType.FAILURE, "Account not found");
if (!hasAccount(receiverName))
return new EconomyResponse(amount, getBalance(receiverName), EconomyResponse.ResponseType.FAILURE, "Account not found");
- UUID sender = currenciesManager.getUUIDFromUsernameCache(senderName);
- UUID receiver = currenciesManager.getUUIDFromUsernameCache(receiverName);
+
+ final UUID sender = currenciesManager.getUUIDFromUsernameCache(senderName);
+ final UUID receiver = currenciesManager.getUUIDFromUsernameCache(receiverName);
+
+ //Calculate the amount to withdraw with the transaction tax
double amountToWithdraw = amount + (amount * transactionTax);
if (sender == null || receiver == null)
return new EconomyResponse(amount, getBalance(senderName), EconomyResponse.ResponseType.FAILURE, "Account not found");
@@ -410,7 +458,10 @@ public EconomyResponse payPlayer(@NotNull String senderName, @NotNull String rec
return new EconomyResponse(0, 0, EconomyResponse.ResponseType.FAILURE, "Invalid decimal amount format");
if (!has(senderName, amountToWithdraw))
- return new EconomyResponse(0, getBalance(senderName), EconomyResponse.ResponseType.FAILURE, "Insufficient funds");
+ return new EconomyResponse(0, getBalance(sender), EconomyResponse.ResponseType.FAILURE, "Insufficient funds");
+
+ if (getBalance(receiver) + amount > getPlayerMaxBalance(receiver))
+ return new EconomyResponse(0, getBalance(receiver), EconomyResponse.ResponseType.FAILURE, "The receiver has reached the maximum balance");
updateAccount(sender, senderName, getBalance(sender) - amountToWithdraw);
updateAccount(receiver, receiverName, getBalance(receiver) + amount);
@@ -440,9 +491,9 @@ public EconomyResponse setPlayerBalance(@NotNull OfflinePlayer player, double am
public EconomyResponse setPlayerBalance(@NotNull UUID playerUUID, @Nullable String playerName, double amount) {
if (amount == Double.POSITIVE_INFINITY || amount == Double.NEGATIVE_INFINITY || Double.isNaN(amount))
return new EconomyResponse(0, 0, EconomyResponse.ResponseType.FAILURE, "Invalid decimal amount format");
- currenciesManager.getExchange().saveTransaction(new AccountID(playerUUID), new AccountID(), -getBalance(playerUUID), currencyName, "Reset balance");
updateAccount(playerUUID, playerName, amount);
- currenciesManager.getExchange().saveTransaction(new AccountID(playerUUID), new AccountID(), amount, currencyName, "Set balance");
+ currenciesManager.getExchange().saveTransaction(new AccountID(playerUUID), new AccountID(), -getBalance(playerUUID), this, "Reset balance");
+ currenciesManager.getExchange().saveTransaction(new AccountID(playerUUID), new AccountID(), amount, this, "Set balance");
return new EconomyResponse(amount, getBalance(playerUUID), EconomyResponse.ResponseType.SUCCESS, null);
}
@@ -463,7 +514,7 @@ public CompletionStage revertTransaction(int transactionId, @NotNull Tr
if (RedisEconomyPlugin.getInstance().settings().debug) {
Bukkit.getLogger().info("revert01a reverted on account " + transaction.getAccountIdentifier() + " amount " + transaction.getAmount());
}
- return currenciesManager.getExchange().saveTransaction(transaction.getAccountIdentifier(), transaction.getActor(), -transaction.getAmount(), currencyName, "Revert #" + transactionId + ": " + transaction.getReason());
+ return currenciesManager.getExchange().saveTransaction(transaction.getAccountIdentifier(), transaction.getActor(), -transaction.getAmount(), this, "Revert #" + transactionId + ": " + transaction.getReason());
}
/**
@@ -494,8 +545,11 @@ public EconomyResponse depositPlayer(@NotNull UUID playerUUID, @Nullable String
if (amount == Double.POSITIVE_INFINITY || amount == Double.NEGATIVE_INFINITY || Double.isNaN(amount))
return new EconomyResponse(0, 0, EconomyResponse.ResponseType.FAILURE, "Invalid decimal amount format");
+ if (getBalance(playerUUID) + amount > getPlayerMaxBalance(playerUUID))
+ return new EconomyResponse(0, getBalance(playerUUID), EconomyResponse.ResponseType.FAILURE, "The player has reached the maximum balance");
+
updateAccount(playerUUID, playerName, getBalance(playerUUID) + amount);
- currenciesManager.getExchange().saveTransaction(new AccountID(playerUUID), new AccountID(), amount, currencyName, reason == null ? "Deposit" : reason);
+ currenciesManager.getExchange().saveTransaction(new AccountID(playerUUID), new AccountID(), amount, this, reason == null ? "Deposit" : reason);
return new EconomyResponse(amount, getBalance(playerUUID), EconomyResponse.ResponseType.SUCCESS, null);
}
@@ -510,33 +564,53 @@ protected void updateAccount(@NotNull UUID uuid, @Nullable String playerName, do
updateAccountLocal(uuid, playerName, balance);
}
- private void updateAccountCloudCache(@NotNull UUID uuid, @Nullable String playerName, double balance, int tries) {
- try {
- currenciesManager.getRedisManager().getConnectionPipeline(commands -> {
- commands.zadd(BALANCE_PREFIX + currencyName, balance, uuid.toString());
- if (playerName != null)
- commands.hset(NAME_UUID.toString(), playerName, uuid.toString());
- commands.publish(UPDATE_PLAYER_CHANNEL_PREFIX + currencyName, RedisEconomyPlugin.getInstance().settings().serverId + ";;" + uuid + ";;" + playerName + ";;" + balance).thenAccept((result) -> {
- if (RedisEconomyPlugin.getInstance().settings().debug) {
- Bukkit.getLogger().info("01 Sent update account " + playerName + " to " + balance);
+ private synchronized void updateAccountCloudCache(@NotNull UUID uuid, @Nullable String playerName, double balance, int tries) {
+ final RedisEconomyPlugin plugin = RedisEconomyPlugin.getInstance();
+ updateExecutor.submit(() -> {
+ try {
+ if (plugin.settings().debugUpdateCache) {
+ Bukkit.getLogger().info("01a Starting update account " + playerName + " to " + balance + " currency " + currencyName);
+ }
+ currenciesManager.getRedisManager().executeTransaction(reactiveCommands -> {
+ reactiveCommands.zadd(BALANCE_PREFIX + currencyName, balance, uuid.toString());
+ if (playerName != null)
+ reactiveCommands.hset(NAME_UUID.toString(), playerName, uuid.toString());
+ reactiveCommands.publish(UPDATE_PLAYER_CHANNEL_PREFIX + currencyName,
+ RedisEconomyPlugin.getInstanceUUID().toString() + ";;" + uuid + ";;" + playerName + ";;" + balance);
+ if (plugin.settings().debugUpdateCache) {
+ plugin.getLogger().info("01b Publishing update account " + playerName + " to " + balance + " currency " + currencyName);
}
- });
- return null;
- });
-
- } catch (Exception e) {
- if (tries < 3) {
- e.printStackTrace();
- Bukkit.getLogger().severe("Failed to update account " + playerName + " after 3 tries");
- Bukkit.getLogger().severe("Player accounts are desynchronized");
- updateAccountCloudCache(uuid, playerName, balance, tries + 1);
- } else {
- e.printStackTrace();
+ }).ifPresentOrElse(result -> {
+ if (RedisEconomyPlugin.getInstance().settings().debugUpdateCache) {
+ plugin.getLogger().info("01c Sent update account successfully " + playerName + " to " + balance + " currency " + currencyName);
+ }
+ }, () -> handleException(uuid, playerName, balance, tries, null));
+ } catch (Exception e) {
+ handleException(uuid, playerName, balance, tries, e);
}
- }
+ });
+ }
+ private void handleException(@NotNull UUID uuid, @Nullable String playerName, double balance, int tries, @Nullable Exception e) {
+ final RedisEconomyPlugin plugin = RedisEconomyPlugin.getInstance();
+ if (tries < plugin.settings().redis.getTryAgainCount()) {
+ if (plugin.settings().debugUpdateCache) {
+ plugin.getLogger().warning("Player accounts are desynchronized. try: " + tries);
+ if (e != null)
+ e.printStackTrace();
+ }
+ updateAccountCloudCache(uuid, playerName, balance, tries + 1);
+ return;
+ }
+ if (plugin.settings().debugUpdateCache) {
+ plugin.getLogger().severe("Failed to update account " + playerName + " after " + tries + " tries");
+ currenciesManager.getRedisManager().printPool();
+ if (e != null)
+ throw new RuntimeException(e);
+ }
}
+
/**
* Update the balances of all players and their nameuuids
* Do not use this method unless you know what you are doing
@@ -546,19 +620,16 @@ private void updateAccountCloudCache(@NotNull UUID uuid, @Nullable String player
*/
@SuppressWarnings("unchecked")
public void updateBulkAccountsCloudCache(@NotNull List> balances, @NotNull Map nameUUIDs) {
- currenciesManager.getRedisManager().getConnectionPipeline(commands -> {
+ currenciesManager.getRedisManager().executeTransaction(commands -> {
ScoredValue[] balancesArray = new ScoredValue[balances.size()];
balances.toArray(balancesArray);
- RedisFuture sortedAddFuture = commands.zadd(BALANCE_PREFIX + currencyName, balancesArray);
- RedisFuture hstFuture = commands.hset(NAME_UUID.toString(), nameUUIDs);
- try {
- Bukkit.getLogger().info("migration01 updated balances into " + BALANCE_PREFIX + currencyName + " accounts. result " + sortedAddFuture.get(20, TimeUnit.SECONDS));
- Bukkit.getLogger().info("migration02 updated nameuuids into " + NAME_UUID + " accounts. result " + hstFuture.get(20, TimeUnit.SECONDS));
- } catch (ExecutionException | InterruptedException | TimeoutException e) {
- e.printStackTrace();
- }
- return null;
+ commands.zadd(BALANCE_PREFIX + currencyName, balancesArray);
+ commands.hset(NAME_UUID.toString(), nameUUIDs);
+ }).ifPresent(result -> {
+ Bukkit.getLogger().info("migration01 updated balances into " + BALANCE_PREFIX + currencyName + " accounts. result " + result.get(0));
+ Bukkit.getLogger().info("migration02 updated nameuuids into " + NAME_UUID + " accounts. result " + result.get(1));
+
});
}
@@ -576,6 +647,40 @@ public CompletionStage>> getOrderedAccounts(int limit)
}
+ public double getPlayerMaxBalance(UUID uuid) {
+ return maxPlayerBalances.getOrDefault(uuid, maxBalance);
+ }
+
+ public void setPlayerMaxBalance(UUID uuid, double amount) {
+ setPlayerMaxBalanceCloud(uuid, amount);
+ setPlayerMaxBalanceLocal(uuid, amount);
+ }
+
+ public void setPlayerMaxBalanceLocal(UUID uuid, double amount) {
+ maxPlayerBalances.put(uuid, amount);
+ }
+
+ public void setPlayerMaxBalanceCloud(UUID uuid, double amount) {
+ currenciesManager.getRedisManager().getConnectionPipeline(asyncCommands -> {
+ if(amount == maxBalance) {
+ asyncCommands.hdel(MAX_PLAYER_BALANCES + currencyName, uuid.toString());
+ } else {
+ asyncCommands.hset(MAX_PLAYER_BALANCES + currencyName, uuid.toString(), String.valueOf(amount));
+ }
+ return asyncCommands.publish(UPDATE_MAX_BAL_PREFIX + currencyName, RedisEconomyPlugin.getInstanceUUID().toString() + ";;" + uuid + ";;" + amount);
+ });
+ }
+
+ public CompletionStage