Add Purpur and XConomy placeholder compatibility
This commit is contained in:
@@ -1,12 +1,13 @@
|
|||||||
# 🏦 CraftBank(工艺银行)
|
# 🏦 CraftBank(工艺银行)
|
||||||
|
|
||||||
> 适用于 **Folia 1.21.8** 的全能经济与银行核心插件 —— 旨在完全替代 XConomy 等传统经济插件。
|
> 适用于 **Folia / Purpur 1.21.8** 的全能经济与银行核心插件 —— 旨在完全替代 XConomy 等传统经济插件。
|
||||||
|
|
||||||
[](https://papermc.io/software/folia)
|
[](https://papermc.io/software/folia)
|
||||||
|
[](https://purpurmc.org/)
|
||||||
[](https://adoptium.net/)
|
[](https://adoptium.net/)
|
||||||
[](https://www.spigotmc.org/resources/vault.34315/)
|
[](https://www.spigotmc.org/resources/vault.34315/)
|
||||||
|
|
||||||
CraftBank 内置完善的「个人钱包 / 现金」系统,并引入深度模拟的银行系统:实体银行卡、可交易支票、活期储蓄与严格的定期存款(带利息)。全程遵循 **Folia 多核服务端**的线程模型,使用 `AsyncScheduler` / `GlobalRegionScheduler` / `RegionScheduler` 调度,绝不使用已弃用的 `Bukkit.getScheduler()`。
|
CraftBank 内置完善的「个人钱包 / 现金」系统,并引入深度模拟的银行系统:实体银行卡、可交易支票、活期储蓄与严格的定期存款(带利息)。在 Folia 上遵循多核服务端线程模型,使用 `AsyncScheduler` / `GlobalRegionScheduler` / `RegionScheduler` 调度;在 Purpur / Paper 上自动回退到 Bukkit 同步 / 异步任务调度。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ CraftBank 内置完善的「个人钱包 / 现金」系统,并引入深度模
|
|||||||
| 模块 | 说明 |
|
| 模块 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| 💵 **核心经济** | 每位玩家独立现金账户;实现 Vault `Economy` 接口并以 `Highest` 优先级注册,接管全服扣款 |
|
| 💵 **核心经济** | 每位玩家独立现金账户;实现 Vault `Economy` 接口并以 `Highest` 优先级注册,接管全服扣款 |
|
||||||
|
| 🧩 **平台兼容** | 同一个 jar 支持 Folia 与 Purpur/Paper;运行时自动选择正确调度器 |
|
||||||
| 💳 **银行卡** | 实体物品,PDC 绑定 UUID + 序列号;右键指定方块开柜;丢失可挂失补办(旧卡作废)|
|
| 💳 **银行卡** | 实体物品,PDC 绑定 UUID + 序列号;右键指定方块开柜;丢失可挂失补办(旧卡作废)|
|
||||||
| 📜 **支票** | `/cheque <金额>` 具现化为可流通实体物品;右键兑现入钱包;PDC 防伪 + 防刷 |
|
| 📜 **支票** | `/cheque <金额>` 具现化为可流通实体物品;右键兑现入钱包;PDC 防伪 + 防刷 |
|
||||||
| 💰 **活期储蓄** | 存取自由,按真实时间戳逐日复利结算(含离线补偿,带封顶)|
|
| 💰 **活期储蓄** | 存取自由,按真实时间戳逐日复利结算(含离线补偿,带封顶)|
|
||||||
@@ -65,6 +67,8 @@ CraftBank 内置完善的「个人钱包 / 现金」系统,并引入深度模
|
|||||||
|
|
||||||
## 🔌 PlaceholderAPI 变量
|
## 🔌 PlaceholderAPI 变量
|
||||||
|
|
||||||
|
### CraftBank 原生变量
|
||||||
|
|
||||||
| 变量 | 含义 |
|
| 变量 | 含义 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `%craftbank_cash%` | 现金(纯数字)|
|
| `%craftbank_cash%` | 现金(纯数字)|
|
||||||
@@ -76,6 +80,28 @@ CraftBank 内置完善的「个人钱包 / 现金」系统,并引入深度模
|
|||||||
| `%craftbank_has_card%` | 是否持有有效银行卡 |
|
| `%craftbank_has_card%` | 是否持有有效银行卡 |
|
||||||
| `%craftbank_savings_rate%` | 当前活期日利率百分比 |
|
| `%craftbank_savings_rate%` | 当前活期日利率百分比 |
|
||||||
|
|
||||||
|
### XConomy 兼容变量
|
||||||
|
|
||||||
|
CraftBank 会额外注册 `xconomy` PlaceholderAPI 扩展,原先使用 XConomy 的变量无需替换即可继续解析。
|
||||||
|
|
||||||
|
| 变量 | 含义 |
|
||||||
|
|------|------|
|
||||||
|
| `%xconomy_balance%` | 现金余额,按 XConomy 默认显示格式输出 |
|
||||||
|
| `%xconomy_balance_value%` | 现金余额原始数字 |
|
||||||
|
| `%xconomy_balance_formatted%` | 现金余额缩写格式(k / m)|
|
||||||
|
| `%xconomy_top_player_<名次>%` | 指定名次的玩家名,例如 `%xconomy_top_player_1%` |
|
||||||
|
| `%xconomy_top_balance_<名次>%` | 指定名次的余额 |
|
||||||
|
| `%xconomy_top_balance_value_<名次>%` | 指定名次的余额原始数字 |
|
||||||
|
| `%xconomy_top_balance_formatted_<名次>%` | 指定名次的余额缩写格式 |
|
||||||
|
| `%xconomy_top_rank%` | 当前玩家现金排行榜名次 |
|
||||||
|
| `%xconomy_top_rank_<玩家名>%` | 指定玩家现金排行榜名次 |
|
||||||
|
| `%xconomy_top_hidden%` | 兼容返回 `0`(CraftBank 不隐藏排行榜)|
|
||||||
|
| `%xconomy_paypermission%` | 兼容返回 `DEFAULT` |
|
||||||
|
| `%xconomy_paypermission_global%` | 兼容返回 `1` |
|
||||||
|
| `%xconomy_paytoggle%` | 兼容返回 `1` |
|
||||||
|
| `%xconomy_sum_balance%` | 全服现金总额 |
|
||||||
|
| `%xconomy_sum_balance_value%` | 全服现金总额原始数字 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚙️ 配置文件
|
## ⚙️ 配置文件
|
||||||
@@ -97,8 +123,8 @@ CraftBank 内置完善的「个人钱包 / 现金」系统,并引入深度模
|
|||||||
|
|
||||||
## 🤖 Folia 线程安全设计
|
## 🤖 Folia 线程安全设计
|
||||||
|
|
||||||
- **后台任务**(数据库、`/baltop` 排序、利息扫描)走 `AsyncScheduler`。
|
- **Folia 后台任务**(数据库、`/baltop` 排序、利息扫描)走 `AsyncScheduler`;Purpur/Paper 走 Bukkit 异步任务。
|
||||||
- **玩家相关操作**(扣物品、开 GUI、发消息)从异步切回时调度到 `player.getScheduler()`(实体区域线程)。
|
- **Folia 玩家相关操作**(扣物品、开 GUI、发消息)从异步切回时调度到 `player.getScheduler()`(实体区域线程);Purpur/Paper 走 Bukkit 主线程任务。
|
||||||
- 经济数据缓存使用 `ConcurrentHashMap`,单账户余额变更用 `synchronized` 保证原子,抵御来自其它插件异步线程(经 Vault)的并发刷钱。
|
- 经济数据缓存使用 `ConcurrentHashMap`,单账户余额变更用 `synchronized` 保证原子,抵御来自其它插件异步线程(经 Vault)的并发刷钱。
|
||||||
- 支票兑现在玩家区域线程独占操作物品栏;定期领取用数据库 `UPDATE ... WHERE claimed = 0` 原子置位防重复发放。
|
- 支票兑现在玩家区域线程独占操作物品栏;定期领取用数据库 `UPDATE ... WHERE claimed = 0` 原子置位防重复发放。
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "com.craftbank"
|
group = "com.craftbank"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
|
|
||||||
// 用当前 JDK(25)编译,通过 --release 21 产出兼容 Java 21 的字节码,
|
// 用当前 JDK(25)编译,通过 --release 21 产出兼容 Java 21 的字节码,
|
||||||
// 避免 toolchain 触发额外的 JDK 下载。Folia 要求运行时为 Java 21+。
|
// 避免 toolchain 触发额外的 JDK 下载。Folia 要求运行时为 Java 21+。
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import com.craftbank.economy.VaultEconomyProvider;
|
|||||||
import com.craftbank.gui.BankGUI;
|
import com.craftbank.gui.BankGUI;
|
||||||
import com.craftbank.gui.PendingInput;
|
import com.craftbank.gui.PendingInput;
|
||||||
import com.craftbank.hooks.CraftBankExpansion;
|
import com.craftbank.hooks.CraftBankExpansion;
|
||||||
|
import com.craftbank.hooks.XConomyExpansion;
|
||||||
import com.craftbank.items.BankCardManager;
|
import com.craftbank.items.BankCardManager;
|
||||||
import com.craftbank.items.ChequeManager;
|
import com.craftbank.items.ChequeManager;
|
||||||
import com.craftbank.items.Keys;
|
import com.craftbank.items.Keys;
|
||||||
@@ -22,9 +23,9 @@ import com.craftbank.listeners.InteractListener;
|
|||||||
import com.craftbank.listeners.InventoryListener;
|
import com.craftbank.listeners.InventoryListener;
|
||||||
import com.craftbank.listeners.PlayerConnectionListener;
|
import com.craftbank.listeners.PlayerConnectionListener;
|
||||||
import com.craftbank.model.PlayerAccount;
|
import com.craftbank.model.PlayerAccount;
|
||||||
|
import com.craftbank.platform.PlatformScheduler;
|
||||||
import com.craftbank.util.Msg;
|
import com.craftbank.util.Msg;
|
||||||
import net.milkbowl.vault.economy.Economy;
|
import net.milkbowl.vault.economy.Economy;
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.command.PluginCommand;
|
import org.bukkit.command.PluginCommand;
|
||||||
import org.bukkit.command.TabExecutor;
|
import org.bukkit.command.TabExecutor;
|
||||||
import org.bukkit.plugin.ServicePriority;
|
import org.bukkit.plugin.ServicePriority;
|
||||||
@@ -50,6 +51,8 @@ public final class CraftBank extends JavaPlugin {
|
|||||||
private ChequeManager chequeManager;
|
private ChequeManager chequeManager;
|
||||||
private BankGUI bankGUI;
|
private BankGUI bankGUI;
|
||||||
private VaultEconomyProvider vaultProvider;
|
private VaultEconomyProvider vaultProvider;
|
||||||
|
private PlatformScheduler scheduler;
|
||||||
|
private XConomyExpansion xConomyExpansion;
|
||||||
private Msg msg;
|
private Msg msg;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -82,6 +85,7 @@ public final class CraftBank extends JavaPlugin {
|
|||||||
this.bankCardManager = new BankCardManager(this);
|
this.bankCardManager = new BankCardManager(this);
|
||||||
this.chequeManager = new ChequeManager(this);
|
this.chequeManager = new ChequeManager(this);
|
||||||
this.bankGUI = new BankGUI(this);
|
this.bankGUI = new BankGUI(this);
|
||||||
|
this.scheduler = new PlatformScheduler(this);
|
||||||
|
|
||||||
registerVault();
|
registerVault();
|
||||||
registerCommands();
|
registerCommands();
|
||||||
@@ -95,7 +99,7 @@ public final class CraftBank extends JavaPlugin {
|
|||||||
runAsync(() -> economyManager.loadBlocking(p.getUniqueId(), p.getName(), true));
|
runAsync(() -> economyManager.loadBlocking(p.getUniqueId(), p.getName(), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
getLogger().info("CraftBank 已启用 (Folia 模式)。");
|
getLogger().info("CraftBank 已启用 (" + (scheduler.isFolia() ? "Folia" : "Purpur/Paper") + " 模式)。");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -154,7 +158,10 @@ public final class CraftBank extends JavaPlugin {
|
|||||||
if (getServer().getPluginManager().getPlugin("PlaceholderAPI") != null) {
|
if (getServer().getPluginManager().getPlugin("PlaceholderAPI") != null) {
|
||||||
try {
|
try {
|
||||||
new CraftBankExpansion(this).register();
|
new CraftBankExpansion(this).register();
|
||||||
getLogger().info("已挂接 PlaceholderAPI。");
|
this.xConomyExpansion = new XConomyExpansion(this);
|
||||||
|
this.xConomyExpansion.register();
|
||||||
|
runAsync(this.xConomyExpansion::refreshCache);
|
||||||
|
getLogger().info("已挂接 PlaceholderAPI (craftbank + xconomy 兼容变量)。");
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
getLogger().warning("PlaceholderAPI 挂接失败: " + t.getMessage());
|
getLogger().warning("PlaceholderAPI 挂接失败: " + t.getMessage());
|
||||||
}
|
}
|
||||||
@@ -164,7 +171,7 @@ public final class CraftBank extends JavaPlugin {
|
|||||||
private void startBackgroundTasks() {
|
private void startBackgroundTasks() {
|
||||||
long periodMinutes = 60L;
|
long periodMinutes = 60L;
|
||||||
// 后台周期任务: 结算在线玩家活期利息 + 自动结算到期定期存款。
|
// 后台周期任务: 结算在线玩家活期利息 + 自动结算到期定期存款。
|
||||||
getServer().getAsyncScheduler().runAtFixedRate(this, task -> {
|
scheduler.runAsyncRepeating(() -> {
|
||||||
for (PlayerAccount account : economyManager.getAllCached()) {
|
for (PlayerAccount account : economyManager.getAllCached()) {
|
||||||
bankManager.settleSavingsInterest(account);
|
bankManager.settleSavingsInterest(account);
|
||||||
}
|
}
|
||||||
@@ -172,13 +179,19 @@ public final class CraftBank extends JavaPlugin {
|
|||||||
}, periodMinutes, periodMinutes, TimeUnit.MINUTES);
|
}, periodMinutes, periodMinutes, TimeUnit.MINUTES);
|
||||||
|
|
||||||
// 定期落库脏账户, 降低崩溃丢数据风险。
|
// 定期落库脏账户, 降低崩溃丢数据风险。
|
||||||
getServer().getAsyncScheduler().runAtFixedRate(this, task -> {
|
scheduler.runAsyncRepeating(() -> {
|
||||||
for (PlayerAccount account : economyManager.getAllCached()) {
|
for (PlayerAccount account : economyManager.getAllCached()) {
|
||||||
if (account.isDirty()) {
|
if (account.isDirty()) {
|
||||||
databaseManager.saveAccount(account);
|
databaseManager.saveAccount(account);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 5L, 5L, TimeUnit.MINUTES);
|
}, 5L, 5L, TimeUnit.MINUTES);
|
||||||
|
|
||||||
|
scheduler.runAsyncRepeating(() -> {
|
||||||
|
if (xConomyExpansion != null) {
|
||||||
|
xConomyExpansion.refreshCache();
|
||||||
|
}
|
||||||
|
}, 5L, 5L, TimeUnit.MINUTES);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
@@ -187,17 +200,17 @@ public final class CraftBank extends JavaPlugin {
|
|||||||
|
|
||||||
/** 在异步线程立即执行 (用于数据库读写等纯后台任务)。 */
|
/** 在异步线程立即执行 (用于数据库读写等纯后台任务)。 */
|
||||||
public void runAsync(Runnable runnable) {
|
public void runAsync(Runnable runnable) {
|
||||||
getServer().getAsyncScheduler().runNow(this, task -> runnable.run());
|
scheduler.runAsync(runnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 在指定玩家所在的实体区域线程执行 (用于操作物品、打开 GUI、发消息)。 */
|
/** 在指定玩家所在的实体区域线程执行 (用于操作物品、打开 GUI、发消息)。 */
|
||||||
public void runForPlayer(org.bukkit.entity.Player player, Runnable runnable) {
|
public void runForPlayer(org.bukkit.entity.Player player, Runnable runnable) {
|
||||||
player.getScheduler().run(this, task -> runnable.run(), null);
|
scheduler.runForPlayer(player, runnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 在全局区域线程执行。 */
|
/** 在全局区域线程执行。 */
|
||||||
public void runGlobal(Runnable runnable) {
|
public void runGlobal(Runnable runnable) {
|
||||||
getServer().getGlobalRegionScheduler().run(this, task -> runnable.run());
|
scheduler.runGlobal(runnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 异步持久化某账户 (脏标记已由数据变更方法设置)。 */
|
/** 异步持久化某账户 (脏标记已由数据变更方法设置)。 */
|
||||||
|
|||||||
@@ -187,6 +187,21 @@ public class DatabaseManager {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 全服现金总额。 */
|
||||||
|
public double getTotalCash() {
|
||||||
|
String sql = "SELECT SUM(cash) FROM craftbank_players";
|
||||||
|
try (Connection conn = dataSource.getConnection();
|
||||||
|
PreparedStatement ps = conn.prepareStatement(sql);
|
||||||
|
ResultSet rs = ps.executeQuery()) {
|
||||||
|
if (rs.next()) {
|
||||||
|
return rs.getDouble(1);
|
||||||
|
}
|
||||||
|
} catch (SQLException ex) {
|
||||||
|
plugin.getLogger().log(Level.SEVERE, "读取全服现金总额失败", ex);
|
||||||
|
}
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
// 定期存款
|
// 定期存款
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
package com.craftbank.hooks;
|
||||||
|
|
||||||
|
import com.craftbank.CraftBank;
|
||||||
|
import com.craftbank.model.PlayerAccount;
|
||||||
|
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XConomy PlaceholderAPI 兼容层, 让原有 %xconomy_*% 变量无需替换即可使用。
|
||||||
|
*/
|
||||||
|
public final class XConomyExpansion extends PlaceholderExpansion {
|
||||||
|
|
||||||
|
private static final int RANKING_SIZE = 100;
|
||||||
|
|
||||||
|
private final CraftBank plugin;
|
||||||
|
private final DecimalFormat numberFormat = new DecimalFormat("#,##0.00");
|
||||||
|
private final List<Map.Entry<String, Double>> top = new CopyOnWriteArrayList<>();
|
||||||
|
private volatile double sumBalance;
|
||||||
|
|
||||||
|
public XConomyExpansion(CraftBank plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String getIdentifier() {
|
||||||
|
return "xconomy";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String getAuthor() {
|
||||||
|
return "CraftBank";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String getVersion() {
|
||||||
|
return plugin.getDescription().getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean persist() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String onRequest(OfflinePlayer player, @NotNull String identifier) {
|
||||||
|
String id = identifier.toLowerCase();
|
||||||
|
|
||||||
|
if (id.equals("balance") || id.equals("balance_value") || id.equals("balance_formatted")) {
|
||||||
|
double cash = getCash(player);
|
||||||
|
return switch (id) {
|
||||||
|
case "balance_value" -> decimal(cash);
|
||||||
|
case "balance_formatted" -> formatCompact(cash);
|
||||||
|
default -> formatDisplay(cash);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith("top_player_")) {
|
||||||
|
Integer index = parseIndex(id, "top_player_");
|
||||||
|
if (index == null) {
|
||||||
|
return "[XConomy]Invalid index";
|
||||||
|
}
|
||||||
|
return topName(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.equals("top_rank") || id.startsWith("top_rank_")) {
|
||||||
|
String name = id.equals("top_rank") ? (player == null ? null : player.getName())
|
||||||
|
: identifier.substring("top_rank_".length());
|
||||||
|
return rankOf(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith("top_balance_formatted_")) {
|
||||||
|
Integer index = parseIndex(id, "top_balance_formatted_");
|
||||||
|
return index == null ? "[XConomy]Invalid index" : topBalance(index, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith("top_balance_value_")) {
|
||||||
|
Integer index = parseIndex(id, "top_balance_value_");
|
||||||
|
return index == null ? "[XConomy]Invalid index" : topBalance(index, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith("top_balance_")) {
|
||||||
|
Integer index = parseIndex(id, "top_balance_");
|
||||||
|
return index == null ? "[XConomy]Invalid index" : topBalance(index, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.contains("top_hidden")) {
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.contains("paypermission")) {
|
||||||
|
if (id.contains("_global")) {
|
||||||
|
return "1";
|
||||||
|
}
|
||||||
|
return player == null ? "Error" : "DEFAULT";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.equals("paytoggle")) {
|
||||||
|
return player == null ? "Error" : "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.contains("sum_balance")) {
|
||||||
|
return id.contains("sum_balance_value") ? decimal(sumBalance) : formatDisplay(sumBalance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshCache() {
|
||||||
|
top.clear();
|
||||||
|
top.addAll(plugin.getDatabaseManager().getTopCash(RANKING_SIZE));
|
||||||
|
sumBalance = plugin.getDatabaseManager().getTotalCash();
|
||||||
|
}
|
||||||
|
|
||||||
|
private double getCash(OfflinePlayer player) {
|
||||||
|
if (player == null) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
PlayerAccount account = plugin.getEconomyManager().getCached(player.getUniqueId());
|
||||||
|
return account == null ? 0.0 : account.getCash();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String topName(int oneBasedIndex) {
|
||||||
|
if (oneBasedIndex > top.size()) {
|
||||||
|
return "No data";
|
||||||
|
}
|
||||||
|
return top.get(oneBasedIndex - 1).getKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String topBalance(int oneBasedIndex, boolean compact, boolean raw) {
|
||||||
|
if (oneBasedIndex > top.size()) {
|
||||||
|
return "No data";
|
||||||
|
}
|
||||||
|
double balance = top.get(oneBasedIndex - 1).getValue();
|
||||||
|
if (raw) {
|
||||||
|
return decimal(balance);
|
||||||
|
}
|
||||||
|
return compact ? formatCompact(balance) : formatDisplay(balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String rankOf(String name) {
|
||||||
|
if (name == null) {
|
||||||
|
return "No data";
|
||||||
|
}
|
||||||
|
for (int i = 0; i < top.size(); i++) {
|
||||||
|
if (name.equalsIgnoreCase(top.get(i).getKey())) {
|
||||||
|
return Integer.toString(i + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "No data";
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer parseIndex(String id, String prefix) {
|
||||||
|
String value = id.substring(prefix.length());
|
||||||
|
if (!value.matches("\\d+") || value.matches("0+")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Integer.parseInt(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatDisplay(double amount) {
|
||||||
|
return ChatColor.translateAlternateColorCodes('&', numberFormat.format(amount) + " "
|
||||||
|
+ (round(amount).compareTo(BigDecimal.ONE) == 0 ? "dollar" : "dollars"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatCompact(double amount) {
|
||||||
|
double abs = Math.abs(amount);
|
||||||
|
if (abs >= 1_000_000.0) {
|
||||||
|
return numberFormat.format(amount / 1_000_000.0) + "m dollars";
|
||||||
|
}
|
||||||
|
if (abs >= 1_000.0) {
|
||||||
|
return numberFormat.format(amount / 1_000.0) + "k dollars";
|
||||||
|
}
|
||||||
|
return formatDisplay(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String decimal(double amount) {
|
||||||
|
return round(amount).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal round(double amount) {
|
||||||
|
return BigDecimal.valueOf(amount).setScale(2, RoundingMode.DOWN);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.craftbank.platform;
|
||||||
|
|
||||||
|
import com.craftbank.CraftBank;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在 Folia 上使用区域调度器, 在 Purpur/Paper 上回退到 Bukkit 调度器。
|
||||||
|
*/
|
||||||
|
public final class PlatformScheduler {
|
||||||
|
|
||||||
|
private final CraftBank plugin;
|
||||||
|
private final boolean folia;
|
||||||
|
|
||||||
|
public PlatformScheduler(CraftBank plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.folia = hasMethod(plugin.getServer().getClass(), "getGlobalRegionScheduler")
|
||||||
|
&& hasMethod(plugin.getServer().getClass(), "getAsyncScheduler");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFolia() {
|
||||||
|
return folia;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void runAsync(Runnable runnable) {
|
||||||
|
if (!folia) {
|
||||||
|
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, runnable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
invokeAsync("runNow", new Class<?>[] {org.bukkit.plugin.Plugin.class, java.util.function.Consumer.class},
|
||||||
|
plugin, (java.util.function.Consumer<Object>) task -> runnable.run());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void runForPlayer(Player player, Runnable runnable) {
|
||||||
|
if (!folia) {
|
||||||
|
plugin.getServer().getScheduler().runTask(plugin, runnable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Object scheduler = player.getClass().getMethod("getScheduler").invoke(player);
|
||||||
|
Method run = scheduler.getClass().getMethod("run",
|
||||||
|
org.bukkit.plugin.Plugin.class, java.util.function.Consumer.class, Runnable.class);
|
||||||
|
run.invoke(scheduler, plugin, (java.util.function.Consumer<Object>) task -> runnable.run(), null);
|
||||||
|
} catch (ReflectiveOperationException ex) {
|
||||||
|
throw new IllegalStateException("Folia player scheduler invocation failed", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void runGlobal(Runnable runnable) {
|
||||||
|
if (!folia) {
|
||||||
|
plugin.getServer().getScheduler().runTask(plugin, runnable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Object scheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler").invoke(plugin.getServer());
|
||||||
|
Method run = scheduler.getClass().getMethod("run",
|
||||||
|
org.bukkit.plugin.Plugin.class, java.util.function.Consumer.class);
|
||||||
|
run.invoke(scheduler, plugin, (java.util.function.Consumer<Object>) task -> runnable.run());
|
||||||
|
} catch (ReflectiveOperationException ex) {
|
||||||
|
throw new IllegalStateException("Folia global scheduler invocation failed", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void runAsyncRepeating(Runnable runnable, long initialDelay, long period, TimeUnit unit) {
|
||||||
|
if (!folia) {
|
||||||
|
long initialTicks = Math.max(1L, unit.toSeconds(initialDelay) * 20L);
|
||||||
|
long periodTicks = Math.max(1L, unit.toSeconds(period) * 20L);
|
||||||
|
plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, runnable, initialTicks, periodTicks);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
invokeAsync("runAtFixedRate",
|
||||||
|
new Class<?>[] {org.bukkit.plugin.Plugin.class, java.util.function.Consumer.class, long.class, long.class, TimeUnit.class},
|
||||||
|
plugin, (java.util.function.Consumer<Object>) task -> runnable.run(), initialDelay, period, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void invokeAsync(String name, Class<?>[] parameterTypes, Object... args) {
|
||||||
|
try {
|
||||||
|
Object scheduler = plugin.getServer().getClass().getMethod("getAsyncScheduler").invoke(plugin.getServer());
|
||||||
|
Method method = scheduler.getClass().getMethod(name, parameterTypes);
|
||||||
|
method.invoke(scheduler, args);
|
||||||
|
} catch (ReflectiveOperationException ex) {
|
||||||
|
throw new IllegalStateException("Folia async scheduler invocation failed", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasMethod(Class<?> type, String name) {
|
||||||
|
for (Method method : type.getMethods()) {
|
||||||
|
if (method.getName().equals(name)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ main: com.craftbank.CraftBank
|
|||||||
api-version: '1.21'
|
api-version: '1.21'
|
||||||
folia-supported: true
|
folia-supported: true
|
||||||
authors: [CraftBank]
|
authors: [CraftBank]
|
||||||
description: All-in-one economy and bank core plugin for Folia 1.21.8.
|
description: All-in-one economy and bank core plugin for Folia/Purpur 1.21.8.
|
||||||
softdepend: [Vault, PlaceholderAPI]
|
softdepend: [Vault, PlaceholderAPI]
|
||||||
loadbefore: [Vault]
|
loadbefore: [Vault]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user