From 7dfdd5be6c22ada7eeec62b9852d4d9a68590a7a Mon Sep 17 00:00:00 2001 From: Mcayear Date: Fri, 21 Feb 2025 19:21:28 +0800 Subject: [PATCH 1/8] feat(provider): add MySQLProvider class to support MySQL database --- README.md | 4 + .../me/onebone/economyapi/EconomyAPI.java | 4 +- .../economyapi/config/EconomyAPIConfig.java | 19 ++ .../economyapi/provider/MySQLProvider.java | 184 ++++++++++++++++++ .../economyapi/provider/SQLiteProvider.java | 57 +++--- src/main/resources/config.yml | 10 +- 6 files changed, 248 insertions(+), 30 deletions(-) create mode 100644 src/main/java/me/onebone/economyapi/provider/MySQLProvider.java diff --git a/README.md b/README.md index e1fc51d..fe948e5 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,10 @@ Core of economy system for Nukkit ## For developers +> [!WARNING] +> MySQL provider: +> Due to precision issues, MySQL uses bigint to store monetary amounts. The retrieved values need to be divided by 100 to convert them to the actual amounts. + ### Single Currency Economies Developers can access to EconomyAPI's API by using: diff --git a/src/main/java/me/onebone/economyapi/EconomyAPI.java b/src/main/java/me/onebone/economyapi/EconomyAPI.java index 47f4033..ac6449f 100644 --- a/src/main/java/me/onebone/economyapi/EconomyAPI.java +++ b/src/main/java/me/onebone/economyapi/EconomyAPI.java @@ -35,6 +35,7 @@ import me.onebone.economyapi.event.money.AddMoneyEvent; import me.onebone.economyapi.event.money.ReduceMoneyEvent; import me.onebone.economyapi.event.money.SetMoneyEvent; +import me.onebone.economyapi.provider.MySQLProvider; import me.onebone.economyapi.provider.Provider; import me.onebone.economyapi.provider.SQLiteProvider; import me.onebone.economyapi.provider.YamlProvider; @@ -643,8 +644,9 @@ public void onLoad() { this.addProvider("yaml", YamlProvider.class); if (this.getServer().getPluginManager().getPlugin("EasySQLX") != null) { this.addProvider("sqlite", SQLiteProvider.class); + this.addProvider("mysql", MySQLProvider.class); } else { - this.getLogger().warning("EasySQLX is not found, SQLite provider will not be available."); + this.getLogger().warning("EasySQLX is not found, SQLite and MySQL provider will not be available."); } } diff --git a/src/main/java/me/onebone/economyapi/config/EconomyAPIConfig.java b/src/main/java/me/onebone/economyapi/config/EconomyAPIConfig.java index 9147c8a..e8cb107 100644 --- a/src/main/java/me/onebone/economyapi/config/EconomyAPIConfig.java +++ b/src/main/java/me/onebone/economyapi/config/EconomyAPIConfig.java @@ -2,7 +2,9 @@ import cn.nukkit.utils.Config; import cn.nukkit.utils.ConfigSection; +import com.smallaswater.easysqlx.mysql.utils.UserData; import me.onebone.economyapi.EconomyAPI; +import me.onebone.economyapi.provider.MySQLProvider; import java.util.HashMap; import java.util.List; @@ -25,6 +27,8 @@ public EconomyAPIConfig() { defaultCurrency = config.getString("data.default-currency", "USD"); autoSaveInterval = config.getInt("data.auto-save-interval", 10); provider = config.getString("data.provider", "yaml"); + + loadSqlConfig(); } // 读取货币配置 @@ -44,6 +48,21 @@ private void loadCurrencies() { } } + private void loadSqlConfig() { + if (provider == "mysql" && config.exists("data.mysql")) { + ConfigSection mysqlSection = config.getSection("data.mysql"); + String host = mysqlSection.getString("host", "localhost"); + int port = mysqlSection.getInt("port", 3306); + String database = mysqlSection.getString("database", "money"); + String username = mysqlSection.getString("username", "root"); + String password = mysqlSection.getString("password", "root123456"); + MySQLProvider.initTablePrefix(mysqlSection.getString("table-prefix", "v1_")); + MySQLProvider.initSql(new UserData( + username, password, host, port, database + )); + } + } + public Config getConfig() { return config; } diff --git a/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java b/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java new file mode 100644 index 0000000..dcdb4dc --- /dev/null +++ b/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java @@ -0,0 +1,184 @@ +package me.onebone.economyapi.provider; + +import com.smallaswater.easysqlx.common.data.SqlData; +import com.smallaswater.easysqlx.common.data.SqlDataList; +import com.smallaswater.easysqlx.exceptions.MySqlLoginException; +import com.smallaswater.easysqlx.mysql.manager.SqlManager; +import com.smallaswater.easysqlx.mysql.utils.DataType; +import com.smallaswater.easysqlx.mysql.utils.TableType; +import com.smallaswater.easysqlx.mysql.utils.UserData; +import me.onebone.economyapi.EconomyAPI; + +import java.io.File; +import java.util.LinkedHashMap; + +import static me.onebone.economyapi.EconomyAPI.MAIN_CONFIG; + +public class MySQLProvider implements Provider { + private static SqlManager manager; + private static String TABLE_NAME_PREFIX = ""; + + public static void initTablePrefix(String prefix) { + MySQLProvider.TABLE_NAME_PREFIX = prefix; + } + + public static void initSql(UserData userData) { + if (MySQLProvider.manager != null) { + EconomyAPI.getInstance().getLogger().warning("MySQL is already initialized."); + return; + } + try { + MySQLProvider.manager = new SqlManager(EconomyAPI.getInstance(), userData); + } catch (MySqlLoginException e) { + throw new RuntimeException(e); + } + MAIN_CONFIG.getCurrencyList().forEach(currencyName -> { // 初始化 sql 时创建表单 + MySQLProvider.manager.createTable( + TABLE_NAME_PREFIX + currencyName, + new TableType("player", DataType.getVARCHAR(), true), + new TableType("money", DataType.getBIGINT(), false) + ); + }); + } + + @Override + public void init(File path) { + // not required in MySQL, because it is initialized in config. + } + + @Override + public void open() { + if (MySQLProvider.manager == null) return; + MySQLProvider.manager.isEnable(); + // 重新连接 MySQLProvider.manager.connect(); + } + + @Override + public void save() { + // not required in MySQL. + } + + @Override + public void close() { + if (MySQLProvider.manager == null) return; + MySQLProvider.manager.disable(); + } + + @Override + public boolean accountExists(String currencyName, String id) { + if (MySQLProvider.manager.isExistTable(TABLE_NAME_PREFIX + currencyName)) { + return MySQLProvider.manager.isExistsData(TABLE_NAME_PREFIX + currencyName, "player", id); + } + return false; + } + + @Override + public boolean accountExists(String id) { + return accountExists(MAIN_CONFIG.getDefaultCurrency().getName(), id); + } + + @Override + public boolean removeAccount(String currencyName, String id) { + if (MySQLProvider.manager.isExistTable(TABLE_NAME_PREFIX + currencyName)) { + return MySQLProvider.manager.deleteData(TABLE_NAME_PREFIX + currencyName, new SqlData("player", id)); + } + return false; + } + + @Override + public boolean removeAccount(String id) { + return removeAccount(MAIN_CONFIG.getDefaultCurrency().getName(), id); + } + + @Override + public boolean createAccount(String currencyName, String id, double defaultMoney) { + // convert money to bigint + int money = (int) defaultMoney * 100; + if (!accountExists(currencyName, id)) { + SqlData sqlData = new SqlData("player", id).put("money", money); + return MySQLProvider.manager.insertData(TABLE_NAME_PREFIX + currencyName, sqlData); + } + return false; + } + + @Override + public boolean createAccount(String id, double defaultMoney) { + return createAccount(MAIN_CONFIG.getDefaultCurrency().getName(), id, defaultMoney); + } + + @Override + public boolean setMoney(String currencyName, String id, double amount) { + int money = (int) amount * 100; + return MySQLProvider.manager.setData(TABLE_NAME_PREFIX + currencyName, new SqlData("money", money), new SqlData("player", id)); + } + + @Override + public boolean setMoney(String id, double amount) { + return setMoney(MAIN_CONFIG.getDefaultCurrency().getName(), id, amount); + } + + @Override + public boolean addMoney(String currencyName, String id, double amount) { + int money = (int) (getMoney(currencyName, id) + amount) * 100; + return MySQLProvider.manager.setData(TABLE_NAME_PREFIX + currencyName, new SqlData("money", money), new SqlData("player", id)); + } + + @Override + public boolean addMoney(String id, double amount) { + return addMoney(MAIN_CONFIG.getDefaultCurrency().getName(), id, amount); + } + + @Override + public boolean reduceMoney(String currencyName, String id, double amount) { + int money = (int) (getMoney(currencyName, id) - amount) * 100; + return MySQLProvider.manager.setData(TABLE_NAME_PREFIX + currencyName, new SqlData("money", money), new SqlData("player", id)); + } + + @Override + public boolean reduceMoney(String id, double amount) { + return reduceMoney(MAIN_CONFIG.getDefaultCurrency().getName(), id, amount); + } + + @Override + public double getMoney(String currencyName, String id) { + SqlDataList sqlDataList = MySQLProvider.manager.getData(TABLE_NAME_PREFIX + currencyName, "money", new SqlData("player", id)); + if (sqlDataList.isEmpty()) return 0; + return sqlDataList.get(0).getInt("money") / 100.0; + } + + @Override + public double getMoney(String id) { + return getMoney(MAIN_CONFIG.getDefaultCurrency().getName(), id); + } + + @Override + public LinkedHashMap getAll(String currencyName) { + LinkedHashMap map = new LinkedHashMap<>(); + SqlData emptyData = new SqlData(); + SqlDataList sqlDataList = MySQLProvider.manager.getData(TABLE_NAME_PREFIX + currencyName, "*", emptyData); + if (sqlDataList == null) { + return null; + } + for (SqlData sqlData : sqlDataList) { + LinkedHashMap data = sqlData.getData(); + try { + String playerId = (String) data.get("player"); + int moneyObj = (int) data.get("money"); + map.put(playerId, moneyObj / 100.0); + } catch (Exception e) { + EconomyAPI.getInstance().getLogger().error("Error processing SqlData: " + sqlData, e); + } + } + return map; + } + + @Override + public LinkedHashMap getAll() { + return getAll(MAIN_CONFIG.getDefaultCurrency().getName()); + } + + @Override + public String getName() { + return "MySQL"; + } +} diff --git a/src/main/java/me/onebone/economyapi/provider/SQLiteProvider.java b/src/main/java/me/onebone/economyapi/provider/SQLiteProvider.java index 1bfffe1..43d1fd8 100644 --- a/src/main/java/me/onebone/economyapi/provider/SQLiteProvider.java +++ b/src/main/java/me/onebone/economyapi/provider/SQLiteProvider.java @@ -2,6 +2,7 @@ import com.smallaswater.easysqlx.sqlite.SQLiteHelper; import com.smallaswater.easysqlx.sqlite.SQLiteHelper.DBTable; + import java.io.File; import java.util.HashMap; import java.util.LinkedHashMap; @@ -24,7 +25,7 @@ public class SQLiteProvider implements Provider { @Override public void init(File path) { try { - this.sqLiteHelper = new SQLiteHelper(path.getAbsolutePath() + File.separator +"MoneyV3.db"); + this.sqLiteHelper = new SQLiteHelper(path.getAbsolutePath() + File.separator + "MoneyV3.db"); if (!this.sqLiteHelper.exists(TABLE_NAME)) { this.sqLiteHelper.addTable(TABLE_NAME, DBTable.asDbTable(MoneyData.class)); } @@ -70,12 +71,12 @@ public boolean accountExists(String id) { @Override public boolean removeAccount(String currencyName, String id) { - if (accountExists(currencyName, id)) { - this.sqLiteHelper.remove(TABLE_NAME, COLUMN_PLAYER, id); // 移除账户时使用 player 作为 key,currency 通过 MoneyData 对象内部 currency 字段区分 - this.cache.remove(getCacheKey(currencyName, id)); // 从缓存中移除 - return true; + if (!accountExists(currencyName, id)) { + return false; } - return false; + this.sqLiteHelper.remove(TABLE_NAME, COLUMN_PLAYER, id); // 移除账户时使用 player 作为 key,currency 通过 MoneyData 对象内部 currency 字段区分 + this.cache.remove(getCacheKey(currencyName, id)); // 从缓存中移除 + return true; } @Override @@ -85,14 +86,14 @@ public boolean removeAccount(String id) { @Override public boolean createAccount(String currencyName, String id, double defaultMoney) { - if (!accountExists(currencyName, id)) { - MoneyData values = new MoneyData(id, defaultMoney); - values.setCurrency(currencyName); // 设置 currencyName - this.sqLiteHelper.add(TABLE_NAME, values); - this.cache.put(getCacheKey(currencyName, id), values); // 添加到缓存 - return true; + if (accountExists(currencyName, id)) { + return false; } - return false; + MoneyData values = new MoneyData(id, defaultMoney); + values.setCurrency(currencyName); // 设置 currencyName + this.sqLiteHelper.add(TABLE_NAME, values); + this.cache.put(getCacheKey(currencyName, id), values); // 添加到缓存 + return true; } @Override @@ -102,14 +103,14 @@ public boolean createAccount(String id, double defaultMoney) { @Override public boolean setMoney(String currencyName, String id, double amount) { - if (accountExists(currencyName, id)) { - MoneyData moneyData = this.getMoneyData(currencyName, id); - moneyData.setMoney(amount); - this.sqLiteHelper.set(TABLE_NAME, COLUMN_PLAYER, id, moneyData); // 使用 player 作为 key 更新,MoneyData 对象内部包含 currency 信息 - this.cache.put(getCacheKey(currencyName, id), moneyData); // 更新缓存 - return true; + if (!accountExists(currencyName, id)) { + return false; } - return false; + MoneyData moneyData = this.getMoneyData(currencyName, id); + moneyData.setMoney(amount); + this.sqLiteHelper.set(TABLE_NAME, COLUMN_PLAYER, id, moneyData); // 使用 player 作为 key 更新,MoneyData 对象内部包含 currency 信息 + this.cache.put(getCacheKey(currencyName, id), moneyData); // 更新缓存 + return true; } @Override @@ -119,11 +120,11 @@ public boolean setMoney(String id, double amount) { @Override public boolean addMoney(String currencyName, String id, double amount) { - if (accountExists(currencyName, id)) { - this.setMoney(currencyName, id, this.getMoney(currencyName, id) + amount); - return true; + if (!accountExists(currencyName, id)) { + return false; } - return false; + this.setMoney(currencyName, id, this.getMoney(currencyName, id) + amount); + return true; } @Override @@ -133,11 +134,11 @@ public boolean addMoney(String id, double amount) { @Override public boolean reduceMoney(String currencyName, String id, double amount) { - if (accountExists(currencyName, id)) { - this.setMoney(currencyName, id, this.getMoney(currencyName, id) - amount); - return true; + if (!accountExists(currencyName, id)) { + return false; } - return false; + this.setMoney(currencyName, id, this.getMoney(currencyName, id) - amount); + return true; } @Override diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index e9fa06f..aa8b965 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -18,4 +18,12 @@ currencies: data: default-currency: USD auto-save-interval: 10 # unit: minutes - provider: yaml # available values: yaml | sqlite + provider: yaml # available values: yaml | sqlite | mysql +sql: + mysql: + host: localhost + port: 3306 + database: money + username: root + password: root123456 + table-prefix: "v1_" From 9967e41a669ee7287a14dff46e30834a668c5cfe Mon Sep 17 00:00:00 2001 From: Mcayear Date: Fri, 21 Feb 2025 20:49:39 +0800 Subject: [PATCH 2/8] refactor(mysql): improve MySQL configuration and initialization --- .../economyapi/config/EconomyAPIConfig.java | 19 ----------- .../economyapi/provider/MySQLProvider.java | 32 ++++++++++++++----- src/main/resources/config.yml | 2 +- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/main/java/me/onebone/economyapi/config/EconomyAPIConfig.java b/src/main/java/me/onebone/economyapi/config/EconomyAPIConfig.java index e8cb107..9147c8a 100644 --- a/src/main/java/me/onebone/economyapi/config/EconomyAPIConfig.java +++ b/src/main/java/me/onebone/economyapi/config/EconomyAPIConfig.java @@ -2,9 +2,7 @@ import cn.nukkit.utils.Config; import cn.nukkit.utils.ConfigSection; -import com.smallaswater.easysqlx.mysql.utils.UserData; import me.onebone.economyapi.EconomyAPI; -import me.onebone.economyapi.provider.MySQLProvider; import java.util.HashMap; import java.util.List; @@ -27,8 +25,6 @@ public EconomyAPIConfig() { defaultCurrency = config.getString("data.default-currency", "USD"); autoSaveInterval = config.getInt("data.auto-save-interval", 10); provider = config.getString("data.provider", "yaml"); - - loadSqlConfig(); } // 读取货币配置 @@ -48,21 +44,6 @@ private void loadCurrencies() { } } - private void loadSqlConfig() { - if (provider == "mysql" && config.exists("data.mysql")) { - ConfigSection mysqlSection = config.getSection("data.mysql"); - String host = mysqlSection.getString("host", "localhost"); - int port = mysqlSection.getInt("port", 3306); - String database = mysqlSection.getString("database", "money"); - String username = mysqlSection.getString("username", "root"); - String password = mysqlSection.getString("password", "root123456"); - MySQLProvider.initTablePrefix(mysqlSection.getString("table-prefix", "v1_")); - MySQLProvider.initSql(new UserData( - username, password, host, port, database - )); - } - } - public Config getConfig() { return config; } diff --git a/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java b/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java index dcdb4dc..dbc6c30 100644 --- a/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java +++ b/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java @@ -1,5 +1,7 @@ package me.onebone.economyapi.provider; +import cn.nukkit.Server; +import cn.nukkit.utils.ConfigSection; import com.smallaswater.easysqlx.common.data.SqlData; import com.smallaswater.easysqlx.common.data.SqlDataList; import com.smallaswater.easysqlx.exceptions.MySqlLoginException; @@ -22,16 +24,34 @@ public static void initTablePrefix(String prefix) { MySQLProvider.TABLE_NAME_PREFIX = prefix; } - public static void initSql(UserData userData) { + @Override + public void init(File path) { if (MySQLProvider.manager != null) { EconomyAPI.getInstance().getLogger().warning("MySQL is already initialized."); return; } + + if (!MAIN_CONFIG.getConfig().exists("sql.mysql")) { + EconomyAPI.getInstance().getLogger().error("MySQL is not configured."); + return; + } + ConfigSection mysqlSection = MAIN_CONFIG.getConfig().getSection("sql.mysql"); + String host = mysqlSection.getString("host", "localhost"); + int port = mysqlSection.getInt("port", 3306); + String database = mysqlSection.getString("database", "economy"); + String username = mysqlSection.getString("username", "root"); + String password = mysqlSection.getString("password", "root123456"); + MySQLProvider.initTablePrefix(mysqlSection.getString("table-prefix", "v1_")); try { - MySQLProvider.manager = new SqlManager(EconomyAPI.getInstance(), userData); + MySQLProvider.manager = new SqlManager(EconomyAPI.getInstance(), new UserData( + username, password, host, port, database + )); } catch (MySqlLoginException e) { - throw new RuntimeException(e); + EconomyAPI.getInstance().getLogger().error("MySQL connection failed.", e); + Server.getInstance().getPluginManager().disablePlugin(EconomyAPI.getInstance()); + return; } + MAIN_CONFIG.getCurrencyList().forEach(currencyName -> { // 初始化 sql 时创建表单 MySQLProvider.manager.createTable( TABLE_NAME_PREFIX + currencyName, @@ -39,11 +59,7 @@ public static void initSql(UserData userData) { new TableType("money", DataType.getBIGINT(), false) ); }); - } - - @Override - public void init(File path) { - // not required in MySQL, because it is initialized in config. + EconomyAPI.getInstance().getLogger().info("MySQL initialized!"); } @Override diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index aa8b965..7efd63f 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -23,7 +23,7 @@ sql: mysql: host: localhost port: 3306 - database: money + database: economy username: root password: root123456 table-prefix: "v1_" From 1f72a4cfaf85631718739bc65cc2c408e395b420 Mon Sep 17 00:00:00 2001 From: Mcayear Date: Sat, 22 Feb 2025 07:52:50 +0800 Subject: [PATCH 3/8] perf(MySQLProvider): use UUID data type for player column --- src/main/java/me/onebone/economyapi/provider/MySQLProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java b/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java index dbc6c30..84c702f 100644 --- a/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java +++ b/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java @@ -55,7 +55,7 @@ public void init(File path) { MAIN_CONFIG.getCurrencyList().forEach(currencyName -> { // 初始化 sql 时创建表单 MySQLProvider.manager.createTable( TABLE_NAME_PREFIX + currencyName, - new TableType("player", DataType.getVARCHAR(), true), + new TableType("player", DataType.getUUID(), true), new TableType("money", DataType.getBIGINT(), false) ); }); From 88ce3a0614f7b9b28ee847cb7cdc22a04d16376e Mon Sep 17 00:00:00 2001 From: Mcayear Date: Sat, 22 Feb 2025 08:20:36 +0800 Subject: [PATCH 4/8] fix(provider): Fix MySQL provider, close connection on disable, improve reconnection logic, and change money handling from int to long for precision --- .../me/onebone/economyapi/EconomyAPI.java | 7 +++++-- .../economyapi/provider/MySQLProvider.java | 19 ++++++++++--------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/java/me/onebone/economyapi/EconomyAPI.java b/src/main/java/me/onebone/economyapi/EconomyAPI.java index ac6449f..23300c2 100644 --- a/src/main/java/me/onebone/economyapi/EconomyAPI.java +++ b/src/main/java/me/onebone/economyapi/EconomyAPI.java @@ -392,7 +392,7 @@ public double myMoney(String id, String currencyName) { } private double myMoneyInternal(String id, String currencyName) { - return this.provider.getMoney(id.toLowerCase(), currencyName); + return this.provider.getMoney(currencyName, id.toLowerCase()); } public int setMoney(Player player, double amount, String currencyName) { @@ -438,7 +438,7 @@ private int setMoneyInternal(String id, double amount, String currencyName, bool SetMoneyEvent event = new SetMoneyEvent(id, amount, currencyName); this.getServer().getPluginManager().callEvent(event); if (!event.isCancelled() || force) { - if (this.provider.accountExists(id, currencyName)) { + if (this.provider.accountExists(currencyName, id)) { amount = event.getAmount(); if (amount <= getMaxMoney(currencyName)) { this.provider.setMoney(currencyName, id, amount); @@ -684,6 +684,9 @@ public void onJoin(PlayerJoinEvent event) { @Override public void onDisable() { this.saveAll(); + if (MAIN_CONFIG.getProvider().equals("mysql")) { + provider.close(); + } } private boolean initialize() { diff --git a/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java b/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java index 84c702f..0e934ba 100644 --- a/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java +++ b/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java @@ -26,7 +26,7 @@ public static void initTablePrefix(String prefix) { @Override public void init(File path) { - if (MySQLProvider.manager != null) { + if (MySQLProvider.manager != null && MySQLProvider.manager.isEnable()) { EconomyAPI.getInstance().getLogger().warning("MySQL is already initialized."); return; } @@ -65,8 +65,9 @@ public void init(File path) { @Override public void open() { if (MySQLProvider.manager == null) return; - MySQLProvider.manager.isEnable(); - // 重新连接 MySQLProvider.manager.connect(); + if (!MySQLProvider.manager.isEnable()) { + this.init(null); + } } @Override @@ -109,7 +110,7 @@ public boolean removeAccount(String id) { @Override public boolean createAccount(String currencyName, String id, double defaultMoney) { // convert money to bigint - int money = (int) defaultMoney * 100; + long money = (long) defaultMoney * 100; if (!accountExists(currencyName, id)) { SqlData sqlData = new SqlData("player", id).put("money", money); return MySQLProvider.manager.insertData(TABLE_NAME_PREFIX + currencyName, sqlData); @@ -124,7 +125,7 @@ public boolean createAccount(String id, double defaultMoney) { @Override public boolean setMoney(String currencyName, String id, double amount) { - int money = (int) amount * 100; + long money = (long) amount * 100; return MySQLProvider.manager.setData(TABLE_NAME_PREFIX + currencyName, new SqlData("money", money), new SqlData("player", id)); } @@ -135,7 +136,7 @@ public boolean setMoney(String id, double amount) { @Override public boolean addMoney(String currencyName, String id, double amount) { - int money = (int) (getMoney(currencyName, id) + amount) * 100; + long money = (long) (getMoney(currencyName, id) + amount) * 100; return MySQLProvider.manager.setData(TABLE_NAME_PREFIX + currencyName, new SqlData("money", money), new SqlData("player", id)); } @@ -146,7 +147,7 @@ public boolean addMoney(String id, double amount) { @Override public boolean reduceMoney(String currencyName, String id, double amount) { - int money = (int) (getMoney(currencyName, id) - amount) * 100; + long money = (long) (getMoney(currencyName, id) - amount) * 100; return MySQLProvider.manager.setData(TABLE_NAME_PREFIX + currencyName, new SqlData("money", money), new SqlData("player", id)); } @@ -159,7 +160,7 @@ public boolean reduceMoney(String id, double amount) { public double getMoney(String currencyName, String id) { SqlDataList sqlDataList = MySQLProvider.manager.getData(TABLE_NAME_PREFIX + currencyName, "money", new SqlData("player", id)); if (sqlDataList.isEmpty()) return 0; - return sqlDataList.get(0).getInt("money") / 100.0; + return sqlDataList.get(0).getLong("money") / 100.0; } @Override @@ -179,7 +180,7 @@ public LinkedHashMap getAll(String currencyName) { LinkedHashMap data = sqlData.getData(); try { String playerId = (String) data.get("player"); - int moneyObj = (int) data.get("money"); + long moneyObj = (long) data.get("money"); map.put(playerId, moneyObj / 100.0); } catch (Exception e) { EconomyAPI.getInstance().getLogger().error("Error processing SqlData: " + sqlData, e); From 3bd2a297416217339708cc3cf9bb3227dbf1ae02 Mon Sep 17 00:00:00 2001 From: Mcayear Date: Sat, 22 Feb 2025 08:23:54 +0800 Subject: [PATCH 5/8] refactor(config): set default currency name to USD for upgrade process. --- .../me/onebone/economyapi/config/UpgradeConfig.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/main/java/me/onebone/economyapi/config/UpgradeConfig.java b/src/main/java/me/onebone/economyapi/config/UpgradeConfig.java index b1a355a..a0cbaef 100644 --- a/src/main/java/me/onebone/economyapi/config/UpgradeConfig.java +++ b/src/main/java/me/onebone/economyapi/config/UpgradeConfig.java @@ -39,12 +39,12 @@ public static boolean tryUpgradeConfigVersion(int oldVersion) { return false; } - protected static String TEMP_CURRENCY_NAME = ""; + protected static String TEMP_CURRENCY_NAME = "USD"; public static boolean updateDoubleConfirmation() { System.out.println("An older version of the Money.yml configuration file has been detected. Would you like to upgrade?"); System.out.println("The upgrade process will perform the following steps:"); - System.out.println("1. Copy the Money.yml file to a new file under the money directory (money/.yml)"); + System.out.println("1. Copy the Money.yml file to a new file under the money directory (money/" + TEMP_CURRENCY_NAME + ".yml)"); System.out.println("2. Modify the version of the new file to 3"); System.out.println("3. Rename the old Money.yml file to Money.old.yml"); System.out.print("Please confirm whether to upgrade (yes/no): "); @@ -54,13 +54,6 @@ public static boolean updateDoubleConfirmation() { System.out.println("The plugin version is too high and does not support this configuration file. Please use version 2.0.6 of EconomyAPI!"); System.exit(1); } - // Input default currency - System.out.print("Please enter the default currency name (default: USD): "); - TEMP_CURRENCY_NAME = new Scanner(System.in).nextLine().trim(); - if (TEMP_CURRENCY_NAME.isEmpty()) { - TEMP_CURRENCY_NAME = "USD"; - } - System.out.println("Default currency name used: " + TEMP_CURRENCY_NAME); EconomyAPI.getInstance().saveDefaultConfig(); EconomyAPI.getInstance().getConfig().getSection("currencies").set( From 99ee34518f6c4f71cbe35157f484c89f6ba17f88 Mon Sep 17 00:00:00 2001 From: Mcayear Date: Sat, 22 Feb 2025 09:23:10 +0800 Subject: [PATCH 6/8] perf(provider): move MySQL connection and table creation to async task. --- .../economyapi/command/TopMoneyCommand.java | 7 +--- .../economyapi/provider/MySQLProvider.java | 41 +++++++++++-------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/main/java/me/onebone/economyapi/command/TopMoneyCommand.java b/src/main/java/me/onebone/economyapi/command/TopMoneyCommand.java index 6b1f170..96f99c7 100644 --- a/src/main/java/me/onebone/economyapi/command/TopMoneyCommand.java +++ b/src/main/java/me/onebone/economyapi/command/TopMoneyCommand.java @@ -81,12 +81,9 @@ public boolean execute(final CommandSender sender, String label, final String[] try { int arg = args.length > 0 ? Integer.parseInt(args[0]) : 1; - String currencyName = MAIN_CONFIG.getDefaultCurrency().getName(); - if (args.length >= 2) { - currencyName = args[1]; - } - final LinkedHashMap money = new LinkedHashMap<>(plugin.getAllMoney(currencyName)); + final String currencyName = args.length >= 2 ? args[1] : MAIN_CONFIG.getDefaultCurrency().getName(); sender.getServer().getScheduler().scheduleTask(EconomyAPI.getInstance(), () -> { + final LinkedHashMap money = new LinkedHashMap<>(plugin.getAllMoney(currencyName)); int page = args.length > 0 ? Math.max(1, Math.min(arg, money.size())) : 1; List list = new LinkedList<>(money.keySet()); list.sort((s1, s2) -> Double.compare(money.get(s2), money.get(s1))); diff --git a/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java b/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java index 0e934ba..03fdbcc 100644 --- a/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java +++ b/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java @@ -1,6 +1,7 @@ package me.onebone.economyapi.provider; import cn.nukkit.Server; +import cn.nukkit.scheduler.AsyncTask; import cn.nukkit.utils.ConfigSection; import com.smallaswater.easysqlx.common.data.SqlData; import com.smallaswater.easysqlx.common.data.SqlDataList; @@ -41,25 +42,29 @@ public void init(File path) { String database = mysqlSection.getString("database", "economy"); String username = mysqlSection.getString("username", "root"); String password = mysqlSection.getString("password", "root123456"); - MySQLProvider.initTablePrefix(mysqlSection.getString("table-prefix", "v1_")); - try { - MySQLProvider.manager = new SqlManager(EconomyAPI.getInstance(), new UserData( - username, password, host, port, database - )); - } catch (MySqlLoginException e) { - EconomyAPI.getInstance().getLogger().error("MySQL connection failed.", e); - Server.getInstance().getPluginManager().disablePlugin(EconomyAPI.getInstance()); - return; - } + String tablePrefix = mysqlSection.getString("table-prefix", "v1_"); + MySQLProvider.initTablePrefix(tablePrefix); - MAIN_CONFIG.getCurrencyList().forEach(currencyName -> { // 初始化 sql 时创建表单 - MySQLProvider.manager.createTable( - TABLE_NAME_PREFIX + currencyName, - new TableType("player", DataType.getUUID(), true), - new TableType("money", DataType.getBIGINT(), false) - ); - }); - EconomyAPI.getInstance().getLogger().info("MySQL initialized!"); + Server.getInstance().getScheduler().scheduleTask(EconomyAPI.getInstance(), () -> { + try { + SqlManager manager = new SqlManager(EconomyAPI.getInstance(), new UserData( + username, password, host, port, database + )); + MySQLProvider.manager = manager; + + MAIN_CONFIG.getCurrencyList().forEach(currencyName -> { + manager.createTable( + TABLE_NAME_PREFIX + currencyName, + new TableType("player", DataType.getUUID(), true), + new TableType("money", DataType.getBIGINT(), false) + ); + }); + EconomyAPI.getInstance().getLogger().info("MySQL initialized!"); + } catch (MySqlLoginException e) { + EconomyAPI.getInstance().getLogger().error("MySQL connection failed.", e); + Server.getInstance().getPluginManager().disablePlugin(EconomyAPI.getInstance()); + } + }, true); } @Override From e5497997ed51a30afbd2ea7133fa534a4b2ea172 Mon Sep 17 00:00:00 2001 From: Mcayear Date: Sat, 22 Feb 2025 13:05:37 +0800 Subject: [PATCH 7/8] feat(async): Add asynchronous economy operations support with AsyncOperator class,update commands for async handling, and enhance error validation --- .../me/onebone/economyapi/AsyncOperator.java | 537 ++++++++++++++++++ .../me/onebone/economyapi/EconomyAPI.java | 18 +- .../economyapi/command/GiveMoneyCommand.java | 42 +- .../economyapi/command/MyMoneyCommand.java | 23 +- .../economyapi/command/PayCommand.java | 21 +- .../economyapi/command/SetMoneyCommand.java | 59 +- .../economyapi/command/TakeMoneyCommand.java | 47 +- .../economyapi/command/TopMoneyCommand.java | 67 ++- 8 files changed, 687 insertions(+), 127 deletions(-) create mode 100644 src/main/java/me/onebone/economyapi/AsyncOperator.java diff --git a/src/main/java/me/onebone/economyapi/AsyncOperator.java b/src/main/java/me/onebone/economyapi/AsyncOperator.java new file mode 100644 index 0000000..0d10148 --- /dev/null +++ b/src/main/java/me/onebone/economyapi/AsyncOperator.java @@ -0,0 +1,537 @@ +package me.onebone.economyapi; + +import cn.nukkit.IPlayer; +import cn.nukkit.Player; +import cn.nukkit.Server; +import cn.nukkit.scheduler.AsyncTask; +import me.onebone.economyapi.event.money.AddMoneyEvent; +import me.onebone.economyapi.event.money.ReduceMoneyEvent; +import me.onebone.economyapi.event.money.SetMoneyEvent; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public class AsyncOperator { + private static class MoneyLookupTask extends AsyncTask { + private final String id; + private final String currencyName; + private final CompletableFuture future; + + public MoneyLookupTask(String id, String currencyName, CompletableFuture future) { + this.id = id; + this.currencyName = currencyName; + this.future = future; + } + + @Override + public void onRun() { + double money = EconomyAPI.getInstance().provider.getMoney(currencyName, id.toLowerCase()); + this.setResult(money); + } + + @Override + public void onCompletion(Server server) { + future.complete((Double) this.getResult()); + } + } + + /** + * Returns money of player asynchronously + * + * @param player + * @return CompletableFuture containing money of player. -1 if player does not exist. + */ + public CompletableFuture myMoney(Player player) { + return this.myMoney(player.getUniqueId()); + } + + public CompletableFuture myMoney(IPlayer player) { + return this.myMoney(player.getUniqueId()); + } + + public CompletableFuture myMoney(UUID id) { + checkAndConvertLegacy(id); + return myMoneyInternal(id.toString()); + } + + public CompletableFuture myMoney(String id) { + Optional uuid = checkAndConvertLegacy(id); + return uuid.map(this::myMoney) + .orElseGet(() -> myMoneyInternal(id)); + } + + private CompletableFuture myMoneyInternal(String id) { + return myMoneyInternal(id, EconomyAPI.MAIN_CONFIG.getDefaultCurrency().getName()); + } + + private static class MoneySetTask extends AsyncTask { + private final String id; + private final double amount; + private final String currencyName; + private final boolean force; + private final CompletableFuture future; + + public MoneySetTask(String id, double amount, String currencyName, boolean force, CompletableFuture future) { + this.id = id; + this.amount = amount; + this.currencyName = currencyName; + this.force = force; + this.future = future; + } + + @Override + public void onRun() { + String lowerId = id.toLowerCase(); + if (amount < 0) { + this.setResult(EconomyAPI.RET_INVALID); + return; + } + + SetMoneyEvent event = new SetMoneyEvent(lowerId, amount, currencyName); + Server.getInstance().getPluginManager().callEvent(event); + + if (!event.isCancelled() || force) { + if (EconomyAPI.getInstance().provider.accountExists(currencyName, lowerId)) { + double finalAmount = event.getAmount(); + if (finalAmount <= EconomyAPI.getInstance().getMaxMoney(currencyName)) { + EconomyAPI.getInstance().provider.setMoney(currencyName, lowerId, finalAmount); + this.setResult(EconomyAPI.RET_SUCCESS); + } else { + this.setResult(EconomyAPI.RET_INVALID); + } + } else { + this.setResult(EconomyAPI.RET_NO_ACCOUNT); + } + } else { + this.setResult(EconomyAPI.RET_CANCELLED); + } + } + + @Override + public void onCompletion(Server server) { + future.complete((Integer) this.getResult()); + } + } + + public CompletableFuture setMoney(Player player, double amount) { + return this.setMoney(player, amount, false); + } + + public CompletableFuture setMoney(Player player, double amount, boolean force) { + return this.setMoney(player.getUniqueId(), amount, force); + } + + public CompletableFuture setMoney(IPlayer player, double amount) { + return this.setMoney(player, amount, false); + } + + public CompletableFuture setMoney(IPlayer player, double amount, boolean force) { + return this.setMoney(player.getUniqueId(), amount, force); + } + + public CompletableFuture setMoney(UUID id, double amount) { + return setMoney(id, amount, false); + } + + public CompletableFuture setMoney(UUID id, double amount, boolean force) { + checkAndConvertLegacy(id); + return setMoneyInternal(id.toString(), amount, force); + } + + public CompletableFuture setMoney(String id, double amount) { + return this.setMoney(id, amount, false); + } + + public CompletableFuture setMoney(String id, double amount, boolean force) { + Optional uuid = checkAndConvertLegacy(id); + return uuid.map(uuid1 -> setMoney(uuid1, amount, force)) + .orElseGet(() -> setMoneyInternal(id, amount, force)); + } + + private CompletableFuture setMoneyInternal(String id, double amount, boolean force) { + return setMoneyInternal(id, amount, EconomyAPI.MAIN_CONFIG.getDefaultCurrency().getName(), force); + } + + + private static class MoneyAddTask extends AsyncTask { + private final String id; + private final double amount; + private final String currencyName; + private final boolean force; + private final CompletableFuture future; + + public MoneyAddTask(String id, double amount, String currencyName, boolean force, CompletableFuture future) { + this.id = id; + this.amount = amount; + this.currencyName = currencyName; + this.force = force; + this.future = future; + } + + @Override + public void onRun() { + String lowerId = id.toLowerCase(); + if (amount < 0) { + this.setResult(EconomyAPI.RET_INVALID); + return; + } + + AddMoneyEvent event = new AddMoneyEvent(lowerId, amount, currencyName); + Server.getInstance().getPluginManager().callEvent(event); + + if (!event.isCancelled() || force) { + double money = EconomyAPI.getInstance().provider.getMoney(currencyName, lowerId); + if (money != -1) { + if (money + amount > EconomyAPI.getInstance().getMaxMoney(currencyName)) { + this.setResult(EconomyAPI.RET_INVALID); + } else { + EconomyAPI.getInstance().provider.addMoney(currencyName, lowerId, amount); + this.setResult(EconomyAPI.RET_SUCCESS); + } + } else { + this.setResult(EconomyAPI.RET_NO_ACCOUNT); + } + } else { + this.setResult(EconomyAPI.RET_CANCELLED); + } + } + + @Override + public void onCompletion(Server server) { + future.complete((Integer) this.getResult()); + } + } + + public CompletableFuture addMoney(Player player, double amount) { + return this.addMoney(player, amount, false); + } + + public CompletableFuture addMoney(Player player, double amount, boolean force) { + return this.addMoney(player.getUniqueId(), amount, force); + } + + public CompletableFuture addMoney(IPlayer player, double amount) { + return this.addMoney(player, amount, false); + } + + public CompletableFuture addMoney(IPlayer player, double amount, boolean force) { + return this.addMoney(player.getUniqueId(), amount, force); + } + + public CompletableFuture addMoney(UUID id, double amount) { + return addMoney(id, amount, false); + } + + public CompletableFuture addMoney(UUID id, double amount, boolean force) { + checkAndConvertLegacy(id); + return addMoneyInternal(id.toString(), amount, force); + } + + public CompletableFuture addMoney(String id, double amount) { + return this.addMoney(id, amount, false); + } + + public CompletableFuture addMoney(String id, double amount, boolean force) { + Optional uuid = checkAndConvertLegacy(id); + return uuid.map(uuid1 -> addMoney(uuid1, amount, force)) + .orElseGet(() -> addMoneyInternal(id, amount, force)); + } + + private CompletableFuture addMoneyInternal(String id, double amount, boolean force) { + CompletableFuture future = new CompletableFuture<>(); + Server.getInstance().getScheduler().scheduleAsyncTask( + EconomyAPI.getInstance(), + new MoneyAddTask(id, amount, EconomyAPI.MAIN_CONFIG.getDefaultCurrency().getName(), force, future) + ); + return future; + } + + private static class MoneyReduceTask extends AsyncTask { + private final String id; + private final double amount; + private final String currencyName; + private final boolean force; + private final CompletableFuture future; + + public MoneyReduceTask(String id, double amount, String currencyName, boolean force, CompletableFuture future) { + this.id = id; + this.amount = amount; + this.currencyName = currencyName; + this.force = force; + this.future = future; + } + + @Override + public void onRun() { + String lowerId = id.toLowerCase(); + if (amount < 0) { + this.setResult(EconomyAPI.RET_INVALID); + return; + } + + ReduceMoneyEvent event = new ReduceMoneyEvent(lowerId, amount, currencyName); + Server.getInstance().getPluginManager().callEvent(event); + + if (!event.isCancelled() || force) { + double finalAmount = event.getAmount(); + double money = EconomyAPI.getInstance().provider.getMoney(currencyName, lowerId); + if (money != -1) { + if (money - finalAmount < 0) { + this.setResult(EconomyAPI.RET_INVALID); + } else { + EconomyAPI.getInstance().provider.reduceMoney(currencyName, lowerId, finalAmount); + this.setResult(EconomyAPI.RET_SUCCESS); + } + } else { + this.setResult(EconomyAPI.RET_NO_ACCOUNT); + } + } else { + this.setResult(EconomyAPI.RET_CANCELLED); + } + } + + @Override + public void onCompletion(Server server) { + future.complete((Integer) this.getResult()); + } + } + + public CompletableFuture reduceMoney(Player player, double amount) { + return this.reduceMoney(player, amount, false); + } + + public CompletableFuture reduceMoney(Player player, double amount, boolean force) { + return this.reduceMoney(player.getUniqueId(), amount, force); + } + + public CompletableFuture reduceMoney(IPlayer player, double amount) { + return this.reduceMoney(player, amount, false); + } + + public CompletableFuture reduceMoney(IPlayer player, double amount, boolean force) { + return this.reduceMoney(player.getUniqueId(), amount, force); + } + + public CompletableFuture reduceMoney(UUID id, double amount) { + return reduceMoney(id, amount, false); + } + + public CompletableFuture reduceMoney(UUID id, double amount, boolean force) { + checkAndConvertLegacy(id); + return reduceMoneyInternal(id.toString(), amount, force); + } + + public CompletableFuture reduceMoney(String id, double amount) { + return this.reduceMoney(id, amount, false); + } + + public CompletableFuture reduceMoney(String id, double amount, boolean force) { + Optional uuid = checkAndConvertLegacy(id); + return uuid.map(uuid1 -> reduceMoney(uuid1, amount, force)) + .orElseGet(() -> reduceMoneyInternal(id, amount, force)); + } + + private CompletableFuture reduceMoneyInternal(String id, double amount, boolean force) { + CompletableFuture future = new CompletableFuture<>(); + Server.getInstance().getScheduler().scheduleAsyncTask( + EconomyAPI.getInstance(), + new MoneyReduceTask(id, amount, EconomyAPI.MAIN_CONFIG.getDefaultCurrency().getName(), force, future) + ); + return future; + } + + + // === mutiple currency + + public CompletableFuture myMoney(Player player, String currencyName) { + return this.myMoney(player.getUniqueId(), currencyName); + } + + public CompletableFuture myMoney(IPlayer player, String currencyName) { + return this.myMoney(player.getUniqueId(), currencyName); + } + + public CompletableFuture myMoney(UUID id, String currencyName) { + checkAndConvertLegacy(id); + return myMoneyInternal(id.toString(), currencyName); + } + + public CompletableFuture myMoney(String id, String currencyName) { + Optional uuid = checkAndConvertLegacy(id); + return uuid.map(uuid1 -> myMoney(uuid1, currencyName)) + .orElseGet(() -> myMoneyInternal(id, currencyName)); + } + + private CompletableFuture myMoneyInternal(String id, String currencyName) { + CompletableFuture future = new CompletableFuture<>(); + Server.getInstance().getScheduler().scheduleAsyncTask( + EconomyAPI.getInstance(), + new MoneyLookupTask(id, currencyName, future) + ); + return future; + } + + public CompletableFuture setMoney(Player player, double amount, String currencyName) { + return this.setMoney(player.getUniqueId(), amount, currencyName, false); + } + + public CompletableFuture setMoney(Player player, double amount, String currencyName, boolean force) { + return this.setMoney(player.getUniqueId(), amount, currencyName, force); + } + + public CompletableFuture setMoney(IPlayer player, double amount, String currencyName) { + return this.setMoney(player.getUniqueId(), amount, currencyName, false); + } + + public CompletableFuture setMoney(IPlayer player, double amount, String currencyName, boolean force) { + return this.setMoney(player.getUniqueId(), amount, currencyName, force); + } + + public CompletableFuture setMoney(UUID id, double amount, String currencyName) { + return setMoney(id, amount, currencyName, false); + } + + public CompletableFuture setMoney(UUID id, double amount, String currencyName, boolean force) { + checkAndConvertLegacy(id); + return setMoneyInternal(id.toString(), amount, currencyName, force); + } + + public CompletableFuture setMoney(String id, double amount, String currencyName) { + return this.setMoney(id, amount, currencyName, false); + } + + public CompletableFuture setMoney(String id, double amount, String currencyName, boolean force) { + Optional uuid = checkAndConvertLegacy(id); + return uuid.map(uuid1 -> setMoney(uuid1, amount, currencyName, force)) + .orElseGet(() -> setMoneyInternal(id, amount, currencyName, force)); + } + + private CompletableFuture setMoneyInternal(String id, double amount, String currencyName, boolean force) { + CompletableFuture future = new CompletableFuture<>(); + Server.getInstance().getScheduler().scheduleAsyncTask( + EconomyAPI.getInstance(), + new MoneySetTask(id, amount, currencyName, force, future) + ); + return future; + } + + // Add Money methods + public CompletableFuture addMoney(Player player, double amount, String currencyName) { + return this.addMoney(player.getUniqueId(), amount, currencyName, false); + } + + public CompletableFuture addMoney(Player player, double amount, String currencyName, boolean force) { + return this.addMoney(player.getUniqueId(), amount, currencyName, force); + } + + public CompletableFuture addMoney(IPlayer player, double amount, String currencyName) { + return this.addMoney(player.getUniqueId(), amount, currencyName, false); + } + + public CompletableFuture addMoney(IPlayer player, double amount, String currencyName, boolean force) { + return this.addMoney(player.getUniqueId(), amount, currencyName, force); + } + + public CompletableFuture addMoney(UUID id, double amount, String currencyName) { + return addMoney(id, amount, currencyName, false); + } + + public CompletableFuture addMoney(UUID id, double amount, String currencyName, boolean force) { + checkAndConvertLegacy(id); + return addMoneyInternal(id.toString(), amount, currencyName, force); + } + + public CompletableFuture addMoney(String id, double amount, String currencyName) { + return this.addMoney(id, amount, currencyName, false); + } + + public CompletableFuture addMoney(String id, double amount, String currencyName, boolean force) { + Optional uuid = checkAndConvertLegacy(id); + return uuid.map(uuid1 -> addMoney(uuid1, amount, currencyName, force)) + .orElseGet(() -> addMoneyInternal(id, amount, currencyName, force)); + } + + private CompletableFuture addMoneyInternal(String id, double amount, String currencyName, boolean force) { + CompletableFuture future = new CompletableFuture<>(); + Server.getInstance().getScheduler().scheduleAsyncTask( + EconomyAPI.getInstance(), + new MoneyAddTask(id, amount, currencyName, force, future) + ); + return future; + } + + // Reduce Money methods + public CompletableFuture reduceMoney(Player player, double amount, String currencyName) { + return this.reduceMoney(player.getUniqueId(), amount, currencyName, false); + } + + public CompletableFuture reduceMoney(Player player, double amount, String currencyName, boolean force) { + return this.reduceMoney(player.getUniqueId(), amount, currencyName, force); + } + + public CompletableFuture reduceMoney(IPlayer player, double amount, String currencyName) { + return this.reduceMoney(player.getUniqueId(), amount, currencyName, false); + } + + public CompletableFuture reduceMoney(IPlayer player, double amount, String currencyName, boolean force) { + return this.reduceMoney(player.getUniqueId(), amount, currencyName, force); + } + + public CompletableFuture reduceMoney(UUID id, double amount, String currencyName) { + return reduceMoney(id, amount, currencyName, false); + } + + public CompletableFuture reduceMoney(UUID id, double amount, String currencyName, boolean force) { + checkAndConvertLegacy(id); + return reduceMoneyInternal(id.toString(), amount, currencyName, force); + } + + public CompletableFuture reduceMoney(String id, double amount, String currencyName) { + return this.reduceMoney(id, amount, currencyName, false); + } + + public CompletableFuture reduceMoney(String id, double amount, String currencyName, boolean force) { + Optional uuid = checkAndConvertLegacy(id); + return uuid.map(uuid1 -> reduceMoney(uuid1, amount, currencyName, force)) + .orElseGet(() -> reduceMoneyInternal(id, amount, currencyName, force)); + } + + private CompletableFuture reduceMoneyInternal(String id, double amount, String currencyName, boolean force) { + CompletableFuture future = new CompletableFuture<>(); + Server.getInstance().getScheduler().scheduleAsyncTask( + EconomyAPI.getInstance(), + new MoneyReduceTask(id, amount, currencyName, force, future) + ); + return future; + } + + // === other + + private void checkAndConvertLegacy(UUID uuid) { + IPlayer player = Server.getInstance().getOfflinePlayer(uuid); + if (player != null && player.getName() != null) { + checkAndConvertLegacy(uuid, player.getName()); + } + } + + private Optional checkAndConvertLegacy(String id) { + Optional uuid = Server.getInstance().lookupName(id); + uuid.ifPresent(uuid1 -> checkAndConvertLegacy(uuid1, id)); + return uuid; + } + + private void checkAndConvertLegacy(UUID uuid, String name) { + name = name.toLowerCase(); + if (!EconomyAPI.getInstance().provider.accountExists(name)) { + return; + } + if (EconomyAPI.getInstance().provider.accountExists(uuid.toString())) { + EconomyAPI.getInstance().provider.removeAccount(name); + return; + } + double money = EconomyAPI.getInstance().provider.getMoney(name); + EconomyAPI.getInstance().provider.createAccount(uuid.toString(), money); + EconomyAPI.getInstance().provider.removeAccount(name); + } +} \ No newline at end of file diff --git a/src/main/java/me/onebone/economyapi/EconomyAPI.java b/src/main/java/me/onebone/economyapi/EconomyAPI.java index 23300c2..7651048 100644 --- a/src/main/java/me/onebone/economyapi/EconomyAPI.java +++ b/src/main/java/me/onebone/economyapi/EconomyAPI.java @@ -61,8 +61,9 @@ public class EconomyAPI extends PluginBase implements Listener { private static EconomyAPI instance; private static PluginI18n i18n; public static LangCode serverLangCode; - private Provider provider; - private final HashMap> providerClass = new HashMap<>(); + protected Provider provider; + protected final HashMap> providerClass = new HashMap<>(); + protected static AsyncOperator asyncOperator = new AsyncOperator(); static { MONEY_FORMAT.setMaximumFractionDigits(2); @@ -732,6 +733,19 @@ public boolean addProvider(String name, Class providerClass) return true; } + /** + * Gets the async operator for economy operations. + * + * @return AsyncOperator instance for performing asynchronous economy operations + * @example
+     * EconomyAPI.getAsyncOperator().myMoney(player).thenAccept(money -> {
+     *     player.sendMessage("Your balance: " + money);
+     * });
+     * 
+ */ + public static AsyncOperator getAsyncOperator() { + return asyncOperator; + } private void checkAndConvertLegacy(UUID uuid) { IPlayer player = getServer().getOfflinePlayer(uuid); diff --git a/src/main/java/me/onebone/economyapi/command/GiveMoneyCommand.java b/src/main/java/me/onebone/economyapi/command/GiveMoneyCommand.java index f52f117..0a0b607 100644 --- a/src/main/java/me/onebone/economyapi/command/GiveMoneyCommand.java +++ b/src/main/java/me/onebone/economyapi/command/GiveMoneyCommand.java @@ -56,13 +56,13 @@ public boolean execute(CommandSender sender, String label, String[] args) { if (!this.plugin.isEnabled()) return false; LangCode langCode = sender instanceof Player ? ((Player) sender).getLanguageCode() : serverLangCode; if (!sender.hasPermission("economyapi.command.givemoney")) { - sender.sendMessage(new TranslationContainer(TextFormat.RED + "%commands.generic.permission")); + sender.sendMessage(new TranslationContainer(TextFormat.RED + "%cofalsemmands.generic.permission")); return false; } if (args.length < 2) { sender.sendMessage(new TranslationContainer("commands.generic.usage", this.getUsage())); - return true; + return false; } String player = args[0]; @@ -70,35 +70,39 @@ public boolean execute(CommandSender sender, String label, String[] args) { if (p != null) { player = p.getName(); } - String currencyName = MAIN_CONFIG.getDefaultCurrency().getName(); - if (args.length >= 3) { - currencyName = args[2]; - } + + double amount; try { - double amount = Double.parseDouble(args[1]); + amount = Double.parseDouble(args[1]); if (amount <= 0 || !Double.isFinite(amount)) { sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "givemoney-invalid-number")); - return true; + return false; } + } catch (NumberFormatException e) { + sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "givemoney-must-be-number")); + return false; + } + + final String finalPlayer = player; + final double finalAmount = amount; + final String currencyName = args.length >= 3 ? args[2] : MAIN_CONFIG.getDefaultCurrency().getName(); - int result = this.plugin.addMoney(player, amount, currencyName); + EconomyAPI.getAsyncOperator().addMoney(player, amount, currencyName).thenAccept(result -> { switch (result) { case EconomyAPI.RET_INVALID: - sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "reached-max", EconomyAPI.MONEY_FORMAT.format(amount), plugin.getMonetaryUnit())); - return true; + sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "reached-max", EconomyAPI.MONEY_FORMAT.format(finalAmount), plugin.getMonetaryUnit())); + break; case EconomyAPI.RET_NO_ACCOUNT: - sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "player-never-connected", player)); - return true; + sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "player-never-connected", finalPlayer)); + break; case EconomyAPI.RET_SUCCESS: - sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "givemoney-gave-money", player, EconomyAPI.MONEY_FORMAT.format(amount), plugin.getMonetaryUnit())); + sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "givemoney-gave-money", finalPlayer, EconomyAPI.MONEY_FORMAT.format(finalAmount), plugin.getMonetaryUnit())); if (p != null) { - p.sendMessage(EconomyAPI.getI18n().tr(p.getLanguageCode(), "givemoney-money-given", EconomyAPI.MONEY_FORMAT.format(amount), plugin.getMonetaryUnit())); + p.sendMessage(EconomyAPI.getI18n().tr(p.getLanguageCode(), "givemoney-money-given", EconomyAPI.MONEY_FORMAT.format(finalAmount), plugin.getMonetaryUnit())); } - return true; + break; } - } catch (NumberFormatException e) { - sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "givemoney-must-be-number")); - } + }); return true; } diff --git a/src/main/java/me/onebone/economyapi/command/MyMoneyCommand.java b/src/main/java/me/onebone/economyapi/command/MyMoneyCommand.java index f862d57..8594916 100644 --- a/src/main/java/me/onebone/economyapi/command/MyMoneyCommand.java +++ b/src/main/java/me/onebone/economyapi/command/MyMoneyCommand.java @@ -68,18 +68,19 @@ public boolean execute(CommandSender sender, String label, String[] args) { target = args[0]; } - double money = this.plugin.myMoney(target); - if (money == -1) { - sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "player-never-connected", target)); - return true; - } + EconomyAPI.getAsyncOperator().myMoney(target).thenAccept(money -> { + if (money == -1) { + sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "player-never-connected", target)); + return; + } - String moneyString = EconomyAPI.MONEY_FORMAT.format(money); - if (sender.getName().equals(target)) { - sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "mymoney-mymoney", moneyString, plugin.getMonetaryUnit())); - } else { - sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "seemoney-seemoney", target, moneyString, plugin.getMonetaryUnit())); - } + String moneyString = EconomyAPI.MONEY_FORMAT.format(money); + if (sender.getName().equals(target)) { + sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "mymoney-mymoney", moneyString, plugin.getMonetaryUnit())); + } else { + sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "seemoney-seemoney", target, moneyString, plugin.getMonetaryUnit())); + } + }); return true; } } diff --git a/src/main/java/me/onebone/economyapi/command/PayCommand.java b/src/main/java/me/onebone/economyapi/command/PayCommand.java index d3ee979..98cbb53 100644 --- a/src/main/java/me/onebone/economyapi/command/PayCommand.java +++ b/src/main/java/me/onebone/economyapi/command/PayCommand.java @@ -63,19 +63,19 @@ public boolean execute(CommandSender sender, String label, String[] args) { if (!(sender instanceof Player)) { sender.sendMessage(new TranslationContainer("%commands.generic.ingame")); - return true; + return false; } if (args.length < 2) { sender.sendMessage(new TranslationContainer("commands.generic.usage", this.getUsage())); - return true; + return false; } String player = args[0]; Player p = this.plugin.getServer().getPlayer(player); if (p != null) { if (sender == p) { sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "pay-failed-self")); - return true; + return false; } player = p.getName(); @@ -84,27 +84,26 @@ public boolean execute(CommandSender sender, String label, String[] args) { try { amount = Double.parseDouble(args[1]); if (!Double.isFinite(amount)) { - throw new NumberFormatException(); + sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "takemoney-invalid-number")); + return false; } } catch (NumberFormatException e) { sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "takemoney-must-be-number")); - return true; + return false; } if (amount < 0.01) { sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "pay-too-low")); - return true; + return false; } if (!this.plugin.hasAccount(player)) { sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "player-never-connected", player)); - return true; + return false; } - String currencyName = MAIN_CONFIG.getDefaultCurrency().getName(); - if (args.length >= 3) { - currencyName = args[2]; - } + final String currencyName = args.length >= 3 ? args[2] : MAIN_CONFIG.getDefaultCurrency().getName(); + int result = this.plugin.reduceMoney((Player) sender, amount, currencyName); switch (result) { case EconomyAPI.RET_NO_ACCOUNT: diff --git a/src/main/java/me/onebone/economyapi/command/SetMoneyCommand.java b/src/main/java/me/onebone/economyapi/command/SetMoneyCommand.java index 107a795..8994860 100644 --- a/src/main/java/me/onebone/economyapi/command/SetMoneyCommand.java +++ b/src/main/java/me/onebone/economyapi/command/SetMoneyCommand.java @@ -63,7 +63,7 @@ public boolean execute(CommandSender sender, String label, String[] args) { if (args.length < 2) { sender.sendMessage(new TranslationContainer("commands.generic.usage", this.getUsage())); - return true; + return false; } String player = args[0]; @@ -71,39 +71,42 @@ public boolean execute(CommandSender sender, String label, String[] args) { if (p != null) { player = p.getName(); } - double amount = 0; + + double amount; try { amount = Double.parseDouble(args[1]); - if (!Double.isFinite(amount)) { - throw new NumberFormatException(); + if (amount <= 0 || !Double.isFinite(amount)) { + sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "setmoney-invalid-number")); + return false; } } catch (NumberFormatException e) { - sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "setmoney-invalid-number", amount, plugin.getMonetaryUnit())); - return true; + sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "givemoney-must-be-number")); + return false; } - String currencyName = MAIN_CONFIG.getDefaultCurrency().getName(); - if (args.length >= 3) { - currencyName = args[2]; - } - int result = this.plugin.setMoney(player, amount, currencyName); - switch (result) { - case EconomyAPI.RET_NO_ACCOUNT: - sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "player-never-connected", player)); - return true; - case EconomyAPI.RET_CANCELLED: - sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "setmoney-failed")); - return true; - case EconomyAPI.RET_INVALID: - sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "reached-max", EconomyAPI.MONEY_FORMAT.format(amount), plugin.getMonetaryUnit(currencyName))); - return true; - case EconomyAPI.RET_SUCCESS: - sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "setmoney-setmoney", player, EconomyAPI.MONEY_FORMAT.format(amount), plugin.getMonetaryUnit(currencyName))); - if (p != null) { - p.sendMessage(EconomyAPI.getI18n().tr(p.getLanguageCode(), "setmoney-set", EconomyAPI.MONEY_FORMAT.format(amount), plugin.getMonetaryUnit(currencyName))); - } - return true; - } + final String finalPlayer = player; + final double finalAmount = amount; + final String currencyName = args.length >= 3 ? args[2] : MAIN_CONFIG.getDefaultCurrency().getName(); + + EconomyAPI.getAsyncOperator().setMoney(player, amount, currencyName).thenAccept(result -> { + switch (result) { + case EconomyAPI.RET_NO_ACCOUNT: + sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "player-never-connected", finalPlayer)); + break; + case EconomyAPI.RET_CANCELLED: + sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "setmoney-failed")); + break; + case EconomyAPI.RET_INVALID: + sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "reached-max", EconomyAPI.MONEY_FORMAT.format(finalAmount), plugin.getMonetaryUnit(currencyName))); + break; + case EconomyAPI.RET_SUCCESS: + sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "setmoney-setmoney", finalPlayer, EconomyAPI.MONEY_FORMAT.format(finalAmount), plugin.getMonetaryUnit(currencyName))); + if (p != null) { + p.sendMessage(EconomyAPI.getI18n().tr(p.getLanguageCode(), "setmoney-set", EconomyAPI.MONEY_FORMAT.format(finalAmount), plugin.getMonetaryUnit(currencyName))); + } + break; + } + }); return true; } diff --git a/src/main/java/me/onebone/economyapi/command/TakeMoneyCommand.java b/src/main/java/me/onebone/economyapi/command/TakeMoneyCommand.java index 6191446..23e8ea6 100644 --- a/src/main/java/me/onebone/economyapi/command/TakeMoneyCommand.java +++ b/src/main/java/me/onebone/economyapi/command/TakeMoneyCommand.java @@ -63,7 +63,7 @@ public boolean execute(CommandSender sender, String label, String[] args) { if (args.length < 2) { sender.sendMessage(new TranslationContainer("commands.generic.usage", this.getUsage())); - return true; + return false; } String player = args[0]; @@ -71,38 +71,43 @@ public boolean execute(CommandSender sender, String label, String[] args) { if (p != null) { player = p.getName(); } + + double amount = 0; try { - double amount = Double.parseDouble(args[1]); - if (amount < 0 || !Double.isFinite(amount)) { + amount = Double.parseDouble(args[1]); + if (amount <= 0 || !Double.isFinite(amount)) { sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "takemoney-invalid-number")); - return true; + return false; } + } catch (NumberFormatException e) { + sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "takemoney-must-be-number")); + return false; + } - String currencyName = MAIN_CONFIG.getDefaultCurrency().getName(); - if (args.length >= 3) { - currencyName = args[2]; - } - int result = this.plugin.reduceMoney(player, amount, currencyName); + + final String finalPlayer = player; + final double finalAmount = amount; + final String currencyName = args.length >= 3 ? args[2] : MAIN_CONFIG.getDefaultCurrency().getName(); + + EconomyAPI.getAsyncOperator().reduceMoney(player, amount, currencyName).thenAccept(result -> { switch (result) { case EconomyAPI.RET_INVALID: - sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "takemoney-player-lack-of-money", player, EconomyAPI.MONEY_FORMAT.format(amount), EconomyAPI.MONEY_FORMAT.format(this.plugin.myMoney(player, currencyName)), plugin.getMonetaryUnit(currencyName))); - return true; + sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "takemoney-player-lack-of-money", finalPlayer, EconomyAPI.MONEY_FORMAT.format(finalAmount), EconomyAPI.MONEY_FORMAT.format(this.plugin.myMoney(finalPlayer, currencyName)), plugin.getMonetaryUnit(currencyName))); + break; case EconomyAPI.RET_NO_ACCOUNT: - sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "player-never-connected", player)); - return true; + sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "player-never-connected", finalPlayer)); + break; case EconomyAPI.RET_CANCELLED: - sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "takemoney-failed", player)); - return true; + sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "takemoney-failed", finalPlayer)); + break; case EconomyAPI.RET_SUCCESS: - sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "takemoney-took-money", player, EconomyAPI.MONEY_FORMAT.format(amount), plugin.getMonetaryUnit(currencyName))); + sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "takemoney-took-money", finalPlayer, EconomyAPI.MONEY_FORMAT.format(finalAmount), plugin.getMonetaryUnit(currencyName))); if (p != null) { - p.sendMessage(EconomyAPI.getI18n().tr(p.getLanguageCode(), "takemoney-money-taken", EconomyAPI.MONEY_FORMAT.format(amount), plugin.getMonetaryUnit(currencyName))); + p.sendMessage(EconomyAPI.getI18n().tr(p.getLanguageCode(), "takemoney-money-taken", EconomyAPI.MONEY_FORMAT.format(finalAmount), plugin.getMonetaryUnit(currencyName))); } - return true; + break; } - } catch (NumberFormatException e) { - sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "takemoney-must-be-number")); - } + }); return true; } diff --git a/src/main/java/me/onebone/economyapi/command/TopMoneyCommand.java b/src/main/java/me/onebone/economyapi/command/TopMoneyCommand.java index 96f99c7..69d51dd 100644 --- a/src/main/java/me/onebone/economyapi/command/TopMoneyCommand.java +++ b/src/main/java/me/onebone/economyapi/command/TopMoneyCommand.java @@ -70,7 +70,7 @@ private static String getName(String possibleUuid) { return possibleUuid; } - @Override + @Override public boolean execute(final CommandSender sender, String label, final String[] args) { if (!this.plugin.isEnabled()) return false; LangCode langCode = sender instanceof Player ? ((Player) sender).getLanguageCode() : serverLangCode; @@ -79,44 +79,41 @@ public boolean execute(final CommandSender sender, String label, final String[] return false; } - try { - int arg = args.length > 0 ? Integer.parseInt(args[0]) : 1; - final String currencyName = args.length >= 2 ? args[1] : MAIN_CONFIG.getDefaultCurrency().getName(); - sender.getServer().getScheduler().scheduleTask(EconomyAPI.getInstance(), () -> { - final LinkedHashMap money = new LinkedHashMap<>(plugin.getAllMoney(currencyName)); - int page = args.length > 0 ? Math.max(1, Math.min(arg, money.size())) : 1; - List list = new LinkedList<>(money.keySet()); - list.sort((s1, s2) -> Double.compare(money.get(s2), money.get(s1))); - StringBuilder output = new StringBuilder(); - output.append(EconomyAPI.getI18n().tr(langCode, "topmoney-tag", Integer.toString(page), Integer.toString(((money.size() + 6) / 5)))).append("\n"); - if (page == 1) { - double total = 0; - for (double val : money.values()) { - total += val; - } - output.append(EconomyAPI.getI18n().tr(langCode, "topmoney-total", EconomyAPI.MONEY_FORMAT.format(total))).append("\n\n"); + int arg = args.length > 0 ? Integer.parseInt(args[0]) : 1; + final String currencyName = args.length >= 2 ? args[1] : MAIN_CONFIG.getDefaultCurrency().getName(); + + sender.getServer().getScheduler().scheduleTask(EconomyAPI.getInstance(), () -> { + final LinkedHashMap money = new LinkedHashMap<>(plugin.getAllMoney(currencyName)); + int page = args.length > 0 ? Math.max(1, Math.min(arg, money.size())) : 1; + List list = new LinkedList<>(money.keySet()); + list.sort((s1, s2) -> Double.compare(money.get(s2), money.get(s1))); + StringBuilder output = new StringBuilder(); + output.append(EconomyAPI.getI18n().tr(langCode, "topmoney-tag", Integer.toString(page), Integer.toString(((money.size() + 6) / 5)))).append("\n"); + if (page == 1) { + double total = 0; + for (double val : money.values()) { + total += val; } - int duplicate = 0; - double prev = -1D; - for (int n = 0; n < list.size(); n++) { - int current = (int) Math.ceil((double) (n + 1) / 5); - if (page == current) { - double m = money.get(list.get(n)); - if (m == prev) duplicate++; - else duplicate = 0; - prev = m; - output.append(EconomyAPI.getI18n().tr(langCode, "topmoney-format", Integer.toString(n + 1 - duplicate), getName(list.get(n)), EconomyAPI.MONEY_FORMAT.format(m))).append("\n"); - } else if (page < current) { - break; - } + output.append(EconomyAPI.getI18n().tr(langCode, "topmoney-total", EconomyAPI.MONEY_FORMAT.format(total))).append("\n\n"); + } + int duplicate = 0; + double prev = -1D; + for (int n = 0; n < list.size(); n++) { + int current = (int) Math.ceil((double) (n + 1) / 5); + if (page == current) { + double m = money.get(list.get(n)); + if (m == prev) duplicate++; + else duplicate = 0; + prev = m; + output.append(EconomyAPI.getI18n().tr(langCode, "topmoney-format", Integer.toString(n + 1 - duplicate), getName(list.get(n)), EconomyAPI.MONEY_FORMAT.format(m))).append("\n"); + } else if (page < current) { + break; } + } - sender.sendMessage(output.substring(0, output.length() - 1)); - }, true); - } catch (NumberFormatException e) { + sender.sendMessage(output.substring(0, output.length() - 1)); + }, true); - sender.sendMessage(EconomyAPI.getI18n().tr(langCode, "topmoney-invalid-page-number")); - } return true; } } From 088a88b6336f00f3a9e2bb4145d016652c2bfaff Mon Sep 17 00:00:00 2001 From: Mcayear Date: Sat, 22 Feb 2025 13:13:44 +0800 Subject: [PATCH 8/8] fix(provider): add currency existence check to MySQLProvider methods. --- .../me/onebone/economyapi/provider/MySQLProvider.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java b/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java index 03fdbcc..12aec4f 100644 --- a/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java +++ b/src/main/java/me/onebone/economyapi/provider/MySQLProvider.java @@ -1,7 +1,6 @@ package me.onebone.economyapi.provider; import cn.nukkit.Server; -import cn.nukkit.scheduler.AsyncTask; import cn.nukkit.utils.ConfigSection; import com.smallaswater.easysqlx.common.data.SqlData; import com.smallaswater.easysqlx.common.data.SqlDataList; @@ -88,6 +87,7 @@ public void close() { @Override public boolean accountExists(String currencyName, String id) { + if (!MAIN_CONFIG.getCurrencyList().contains(currencyName)) return false; if (MySQLProvider.manager.isExistTable(TABLE_NAME_PREFIX + currencyName)) { return MySQLProvider.manager.isExistsData(TABLE_NAME_PREFIX + currencyName, "player", id); } @@ -101,6 +101,7 @@ public boolean accountExists(String id) { @Override public boolean removeAccount(String currencyName, String id) { + if (!MAIN_CONFIG.getCurrencyList().contains(currencyName)) return false; if (MySQLProvider.manager.isExistTable(TABLE_NAME_PREFIX + currencyName)) { return MySQLProvider.manager.deleteData(TABLE_NAME_PREFIX + currencyName, new SqlData("player", id)); } @@ -114,6 +115,7 @@ public boolean removeAccount(String id) { @Override public boolean createAccount(String currencyName, String id, double defaultMoney) { + if (!MAIN_CONFIG.getCurrencyList().contains(currencyName)) return false; // convert money to bigint long money = (long) defaultMoney * 100; if (!accountExists(currencyName, id)) { @@ -130,6 +132,7 @@ public boolean createAccount(String id, double defaultMoney) { @Override public boolean setMoney(String currencyName, String id, double amount) { + if (!MAIN_CONFIG.getCurrencyList().contains(currencyName)) return false; long money = (long) amount * 100; return MySQLProvider.manager.setData(TABLE_NAME_PREFIX + currencyName, new SqlData("money", money), new SqlData("player", id)); } @@ -141,6 +144,7 @@ public boolean setMoney(String id, double amount) { @Override public boolean addMoney(String currencyName, String id, double amount) { + if (!MAIN_CONFIG.getCurrencyList().contains(currencyName)) return false; long money = (long) (getMoney(currencyName, id) + amount) * 100; return MySQLProvider.manager.setData(TABLE_NAME_PREFIX + currencyName, new SqlData("money", money), new SqlData("player", id)); } @@ -152,6 +156,7 @@ public boolean addMoney(String id, double amount) { @Override public boolean reduceMoney(String currencyName, String id, double amount) { + if (!MAIN_CONFIG.getCurrencyList().contains(currencyName)) return false; long money = (long) (getMoney(currencyName, id) - amount) * 100; return MySQLProvider.manager.setData(TABLE_NAME_PREFIX + currencyName, new SqlData("money", money), new SqlData("player", id)); } @@ -163,6 +168,7 @@ public boolean reduceMoney(String id, double amount) { @Override public double getMoney(String currencyName, String id) { + if (!MAIN_CONFIG.getCurrencyList().contains(currencyName)) return 0; SqlDataList sqlDataList = MySQLProvider.manager.getData(TABLE_NAME_PREFIX + currencyName, "money", new SqlData("player", id)); if (sqlDataList.isEmpty()) return 0; return sqlDataList.get(0).getLong("money") / 100.0; @@ -176,6 +182,7 @@ public double getMoney(String id) { @Override public LinkedHashMap getAll(String currencyName) { LinkedHashMap map = new LinkedHashMap<>(); + if (!MAIN_CONFIG.getCurrencyList().contains(currencyName)) return map; SqlData emptyData = new SqlData(); SqlDataList sqlDataList = MySQLProvider.manager.getData(TABLE_NAME_PREFIX + currencyName, "*", emptyData); if (sqlDataList == null) {