feat: CraftBank Folia 1.21.8 经济与银行核心插件初始实现
实现完整的现金/钱包、银行卡、支票、活期储蓄与定期存款系统, 实现并以 Highest 优先级注册 Vault Economy 接口,接入 PlaceholderAPI。 全程遵循 Folia 调度模型(AsyncScheduler / 实体区域线程), 数据缓存线程安全,支票兑现与定期领取做防刷处理。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
package com.craftbank.commands;
|
||||
|
||||
import com.craftbank.CraftBank;
|
||||
import com.craftbank.economy.EconomyManager;
|
||||
import com.craftbank.util.Msg;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabExecutor;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* /baltop [页码] —— 现金排行榜。数据库排序在异步线程执行。
|
||||
*/
|
||||
public final class BaltopCommand implements TabExecutor {
|
||||
|
||||
private static final int PAGE_SIZE = 10;
|
||||
|
||||
private final CraftBank plugin;
|
||||
private final Msg msg;
|
||||
|
||||
public BaltopCommand(CraftBank plugin) {
|
||||
this.plugin = plugin;
|
||||
this.msg = new Msg(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
int page = 1;
|
||||
if (args.length >= 1) {
|
||||
try {
|
||||
page = Math.max(1, Integer.parseInt(args[0]));
|
||||
} catch (NumberFormatException ignored) {
|
||||
page = 1;
|
||||
}
|
||||
}
|
||||
final int finalPage = page;
|
||||
EconomyManager econ = plugin.getEconomyManager();
|
||||
|
||||
plugin.runAsync(() -> {
|
||||
// 取足够多再分页, 数量级在排行榜场景可接受。
|
||||
List<Map.Entry<String, Double>> top = plugin.getDatabaseManager().getTopCash(finalPage * PAGE_SIZE);
|
||||
int totalShown = top.size();
|
||||
int start = (finalPage - 1) * PAGE_SIZE;
|
||||
if (start >= totalShown && finalPage > 1) {
|
||||
msg.raw(sender, "&c没有更多内容了。");
|
||||
return;
|
||||
}
|
||||
msg.raw(sender, "&b&l现金排行榜 &7- 第 " + finalPage + " 页");
|
||||
int end = Math.min(start + PAGE_SIZE, totalShown);
|
||||
for (int i = start; i < end; i++) {
|
||||
Map.Entry<String, Double> entry = top.get(i);
|
||||
sender.sendMessage(com.craftbank.util.Text.color(
|
||||
"&7#" + (i + 1) + " &f" + entry.getKey() + " &7- &a" + econ.format(entry.getValue())));
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package com.craftbank.commands;
|
||||
|
||||
import com.craftbank.CraftBank;
|
||||
import com.craftbank.economy.EconomyManager;
|
||||
import com.craftbank.util.Amounts;
|
||||
import com.craftbank.util.Msg;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabExecutor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* /bankadmin <reload|give|take|set|setinterest> —— 管理员指令。
|
||||
*/
|
||||
public final class BankAdminCommand implements TabExecutor {
|
||||
|
||||
private final CraftBank plugin;
|
||||
private final Msg msg;
|
||||
|
||||
public BankAdminCommand(CraftBank plugin) {
|
||||
this.plugin = plugin;
|
||||
this.msg = new Msg(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!sender.hasPermission("craftbank.admin")) {
|
||||
msg.key(sender, "no-permission", "&c你没有权限执行该操作。");
|
||||
return true;
|
||||
}
|
||||
if (args.length == 0) {
|
||||
sendHelp(sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "reload" -> {
|
||||
plugin.reloadConfig();
|
||||
plugin.getEconomyManager().reload();
|
||||
msg.key(sender, "reload-success", "&a配置文件已重载。");
|
||||
}
|
||||
case "give" -> modify(sender, args, ModifyType.GIVE);
|
||||
case "take" -> modify(sender, args, ModifyType.TAKE);
|
||||
case "set" -> modify(sender, args, ModifyType.SET);
|
||||
case "setinterest" -> setInterest(sender, args);
|
||||
default -> sendHelp(sender);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private enum ModifyType {
|
||||
GIVE, TAKE, SET
|
||||
}
|
||||
|
||||
private void modify(CommandSender sender, String[] args, ModifyType type) {
|
||||
if (args.length < 3) {
|
||||
msg.raw(sender, "&c用法: /bankadmin " + type.name().toLowerCase() + " <玩家> <金额>");
|
||||
return;
|
||||
}
|
||||
double amount = Amounts.parse(args[2]);
|
||||
if (amount < 0 || (type != ModifyType.SET && amount <= 0)) {
|
||||
msg.key(sender, "invalid-amount", "&c请输入一个有效的正数金额。");
|
||||
return;
|
||||
}
|
||||
String targetName = args[1];
|
||||
EconomyManager econ = plugin.getEconomyManager();
|
||||
|
||||
plugin.runAsync(() -> {
|
||||
@SuppressWarnings("deprecation")
|
||||
OfflinePlayer target = Bukkit.getOfflinePlayer(targetName);
|
||||
UUID uuid = target.getUniqueId();
|
||||
if (uuid == null || (!target.hasPlayedBefore() && !target.isOnline())) {
|
||||
msg.key(sender, "player-not-found", "&c找不到目标玩家。");
|
||||
return;
|
||||
}
|
||||
econ.loadBlocking(uuid, target.getName(), true);
|
||||
boolean ok;
|
||||
switch (type) {
|
||||
case GIVE -> ok = econ.deposit(uuid, amount);
|
||||
case TAKE -> ok = econ.withdraw(uuid, amount);
|
||||
case SET -> ok = econ.set(uuid, amount);
|
||||
default -> ok = false;
|
||||
}
|
||||
if (!ok) {
|
||||
msg.raw(sender, "&c操作失败 (可能是余额不足)。");
|
||||
return;
|
||||
}
|
||||
msg.raw(sender, "&a已" + label(type) + " &f" + targetName + " &a现金 " + econ.format(amount)
|
||||
+ " &7(当前: " + econ.format(econ.getCash(uuid)) + ")");
|
||||
});
|
||||
}
|
||||
|
||||
private String label(ModifyType type) {
|
||||
return switch (type) {
|
||||
case GIVE -> "给予";
|
||||
case TAKE -> "扣除";
|
||||
case SET -> "设置";
|
||||
};
|
||||
}
|
||||
|
||||
private void setInterest(CommandSender sender, String[] args) {
|
||||
if (args.length < 3) {
|
||||
msg.raw(sender, "&c用法: /bankadmin setinterest <savings|term_7d|term_15d|term_30d> <利率>");
|
||||
return;
|
||||
}
|
||||
String key = args[1].toLowerCase();
|
||||
List<String> valid = List.of("savings", "term_7d", "term_15d", "term_30d");
|
||||
if (!valid.contains(key)) {
|
||||
msg.raw(sender, "&c利率类型无效, 可选: " + String.join(", ", valid));
|
||||
return;
|
||||
}
|
||||
double rate;
|
||||
try {
|
||||
rate = Double.parseDouble(args[2]);
|
||||
} catch (NumberFormatException ex) {
|
||||
msg.raw(sender, "&c请输入一个有效的利率 (例如 0.005)。");
|
||||
return;
|
||||
}
|
||||
if (!Double.isFinite(rate) || rate < 0) {
|
||||
msg.raw(sender, "&c利率必须为非负数。");
|
||||
return;
|
||||
}
|
||||
plugin.getConfig().set("Interest_Rates." + key, rate);
|
||||
plugin.saveConfig();
|
||||
msg.raw(sender, "&a已将 &f" + key + " &a的日利率设置为 &e" + rate + " &7(" + (rate * 100) + "%)");
|
||||
}
|
||||
|
||||
private void sendHelp(CommandSender sender) {
|
||||
msg.raw(sender, "&b&l工艺银行管理 &7指令帮助:");
|
||||
sender.sendMessage(com.craftbank.util.Text.color("&7/bankadmin reload"));
|
||||
sender.sendMessage(com.craftbank.util.Text.color("&7/bankadmin give <玩家> <金额>"));
|
||||
sender.sendMessage(com.craftbank.util.Text.color("&7/bankadmin take <玩家> <金额>"));
|
||||
sender.sendMessage(com.craftbank.util.Text.color("&7/bankadmin set <玩家> <金额>"));
|
||||
sender.sendMessage(com.craftbank.util.Text.color("&7/bankadmin setinterest <类型> <利率>"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||
if (!sender.hasPermission("craftbank.admin")) {
|
||||
return List.of();
|
||||
}
|
||||
if (args.length == 1) {
|
||||
List<String> base = new ArrayList<>(List.of("reload", "give", "take", "set", "setinterest"));
|
||||
base.removeIf(s -> !s.startsWith(args[0].toLowerCase()));
|
||||
return base;
|
||||
}
|
||||
if (args.length == 2 && args[0].equalsIgnoreCase("setinterest")) {
|
||||
return new ArrayList<>(List.of("savings", "term_7d", "term_15d", "term_30d"));
|
||||
}
|
||||
if (args.length == 2 && List.of("give", "take", "set").contains(args[0].toLowerCase())) {
|
||||
return null; // 在线玩家名
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package com.craftbank.commands;
|
||||
|
||||
import com.craftbank.CraftBank;
|
||||
import com.craftbank.economy.EconomyManager;
|
||||
import com.craftbank.model.PlayerAccount;
|
||||
import com.craftbank.util.Amounts;
|
||||
import com.craftbank.util.Msg;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabExecutor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* /bank <open|card|deposit|withdraw> —— 玩家银行业务入口。
|
||||
*/
|
||||
public final class BankCommand implements TabExecutor {
|
||||
|
||||
private final CraftBank plugin;
|
||||
private final Msg msg;
|
||||
|
||||
public BankCommand(CraftBank plugin) {
|
||||
this.plugin = plugin;
|
||||
this.msg = new Msg(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
msg.key(sender, "player-only", "&c该指令只能由玩家执行。");
|
||||
return true;
|
||||
}
|
||||
if (!player.hasPermission("craftbank.use")) {
|
||||
msg.key(sender, "no-permission", "&c你没有权限执行该操作。");
|
||||
return true;
|
||||
}
|
||||
if (args.length == 0) {
|
||||
sendHelp(player);
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "open" -> open(player);
|
||||
case "card" -> card(player, args);
|
||||
case "deposit", "存款" -> deposit(player, args);
|
||||
case "withdraw", "取款" -> withdraw(player, args);
|
||||
default -> sendHelp(player);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void open(Player player) {
|
||||
if (!plugin.getConfig().getBoolean("Bank_Settings.allow_command_open", false)) {
|
||||
msg.raw(player, "&c请手持银行卡右键银行方块来打开银行。");
|
||||
return;
|
||||
}
|
||||
PlayerAccount account = plugin.getEconomyManager().getCached(player.getUniqueId());
|
||||
if (account == null) {
|
||||
msg.raw(player, "&c账户尚未加载完成, 请稍后再试。");
|
||||
return;
|
||||
}
|
||||
plugin.getBankGUI().openMain(player, account);
|
||||
}
|
||||
|
||||
private void card(Player player, String[] args) {
|
||||
if (args.length < 2) {
|
||||
msg.raw(player, "&c用法: /bank card <apply|revoke>");
|
||||
return;
|
||||
}
|
||||
PlayerAccount account = plugin.getEconomyManager().getCached(player.getUniqueId());
|
||||
if (account == null) {
|
||||
msg.raw(player, "&c账户尚未加载完成, 请稍后再试。");
|
||||
return;
|
||||
}
|
||||
switch (args[1].toLowerCase()) {
|
||||
case "apply" -> {
|
||||
if (account.hasValidCard()) {
|
||||
msg.raw(player, "&e你已经持有一张有效的银行卡。如已遗失请使用 &f/bank card revoke &e后重新申请。");
|
||||
return;
|
||||
}
|
||||
ItemStack card = plugin.getBankCardManager().issueCard(player, account);
|
||||
Map<Integer, ItemStack> leftover = player.getInventory().addItem(card);
|
||||
if (!leftover.isEmpty()) {
|
||||
player.getWorld().dropItem(player.getLocation(), card);
|
||||
}
|
||||
msg.raw(player, "&a已为你办理一张全新的银行卡, 请妥善保管。");
|
||||
}
|
||||
case "revoke" -> {
|
||||
account.setCardSerial(0);
|
||||
plugin.persistAsync(account);
|
||||
msg.raw(player, "&a已挂失当前银行卡, 旧卡立即作废。可使用 &f/bank card apply &a补办。");
|
||||
}
|
||||
default -> msg.raw(player, "&c用法: /bank card <apply|revoke>");
|
||||
}
|
||||
}
|
||||
|
||||
private void deposit(Player player, String[] args) {
|
||||
if (args.length < 2) {
|
||||
msg.raw(player, "&c用法: /bank deposit <金额>");
|
||||
return;
|
||||
}
|
||||
double amount = Amounts.parse(args[1]);
|
||||
if (amount <= 0) {
|
||||
msg.key(player, "invalid-amount", "&c请输入一个有效的正数金额。");
|
||||
return;
|
||||
}
|
||||
PlayerAccount account = plugin.getEconomyManager().getCached(player.getUniqueId());
|
||||
if (account == null) {
|
||||
msg.raw(player, "&c账户尚未加载完成, 请稍后再试。");
|
||||
return;
|
||||
}
|
||||
EconomyManager econ = plugin.getEconomyManager();
|
||||
synchronized (account) {
|
||||
if (!account.withdrawCash(amount)) {
|
||||
msg.key(player, "insufficient-funds", "&c钱包现金不足。");
|
||||
return;
|
||||
}
|
||||
account.depositSaving(amount);
|
||||
}
|
||||
plugin.persistAsync(account);
|
||||
msg.raw(player, "&a已存入活期 &f" + econ.format(amount) + " &a, 当前活期余额: &f" + econ.format(account.getBankSaving()));
|
||||
}
|
||||
|
||||
private void withdraw(Player player, String[] args) {
|
||||
if (args.length < 2) {
|
||||
msg.raw(player, "&c用法: /bank withdraw <金额>");
|
||||
return;
|
||||
}
|
||||
double amount = Amounts.parse(args[1]);
|
||||
if (amount <= 0) {
|
||||
msg.key(player, "invalid-amount", "&c请输入一个有效的正数金额。");
|
||||
return;
|
||||
}
|
||||
PlayerAccount account = plugin.getEconomyManager().getCached(player.getUniqueId());
|
||||
if (account == null) {
|
||||
msg.raw(player, "&c账户尚未加载完成, 请稍后再试。");
|
||||
return;
|
||||
}
|
||||
EconomyManager econ = plugin.getEconomyManager();
|
||||
synchronized (account) {
|
||||
if (!account.withdrawSaving(amount)) {
|
||||
msg.key(player, "insufficient-funds", "&c活期余额不足。");
|
||||
return;
|
||||
}
|
||||
account.depositCash(amount);
|
||||
}
|
||||
plugin.persistAsync(account);
|
||||
msg.raw(player, "&a已从活期取回 &f" + econ.format(amount) + " &a到钱包, 当前活期余额: &f" + econ.format(account.getBankSaving()));
|
||||
}
|
||||
|
||||
private void sendHelp(Player player) {
|
||||
msg.raw(player, "&b&l工艺银行 &7指令帮助:");
|
||||
player.sendMessage(com.craftbank.util.Text.color("&7/bank open &8- &f打开银行界面"));
|
||||
player.sendMessage(com.craftbank.util.Text.color("&7/bank card apply &8- &f申请一张银行卡"));
|
||||
player.sendMessage(com.craftbank.util.Text.color("&7/bank card revoke &8- &f挂失并作废银行卡"));
|
||||
player.sendMessage(com.craftbank.util.Text.color("&7/bank deposit <金额> &8- &f存入活期"));
|
||||
player.sendMessage(com.craftbank.util.Text.color("&7/bank withdraw <金额> &8- &f从活期取出"));
|
||||
player.sendMessage(com.craftbank.util.Text.color("&7/cheque <金额> &8- &f开具支票"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||
if (args.length == 1) {
|
||||
List<String> base = new ArrayList<>(List.of("open", "card", "deposit", "withdraw"));
|
||||
base.removeIf(s -> !s.startsWith(args[0].toLowerCase()));
|
||||
return base;
|
||||
}
|
||||
if (args.length == 2 && args[0].equalsIgnoreCase("card")) {
|
||||
List<String> sub = new ArrayList<>(List.of("apply", "revoke"));
|
||||
sub.removeIf(s -> !s.startsWith(args[1].toLowerCase()));
|
||||
return sub;
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.craftbank.commands;
|
||||
|
||||
import com.craftbank.CraftBank;
|
||||
import com.craftbank.economy.EconomyManager;
|
||||
import com.craftbank.util.Amounts;
|
||||
import com.craftbank.util.Msg;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabExecutor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* /cheque <金额> —— 将现金具现化为一张实体支票。
|
||||
*
|
||||
* <p>流程:异步扣除现金 → 切回玩家区域线程生成并发放支票物品。背包满时退款,
|
||||
* 避免吞钱。</p>
|
||||
*/
|
||||
public final class ChequeCommand implements TabExecutor {
|
||||
|
||||
private final CraftBank plugin;
|
||||
private final Msg msg;
|
||||
|
||||
public ChequeCommand(CraftBank plugin) {
|
||||
this.plugin = plugin;
|
||||
this.msg = new Msg(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
msg.key(sender, "player-only", "&c该指令只能由玩家执行。");
|
||||
return true;
|
||||
}
|
||||
if (args.length < 1) {
|
||||
msg.raw(sender, "&c用法: /cheque <金额>");
|
||||
return true;
|
||||
}
|
||||
double amount = Amounts.parse(args[0]);
|
||||
if (amount <= 0) {
|
||||
msg.key(sender, "invalid-amount", "&c请输入一个有效的正数金额。");
|
||||
return true;
|
||||
}
|
||||
|
||||
EconomyManager econ = plugin.getEconomyManager();
|
||||
|
||||
plugin.runAsync(() -> {
|
||||
econ.loadBlocking(player.getUniqueId(), player.getName(), true);
|
||||
if (!econ.withdraw(player.getUniqueId(), amount)) {
|
||||
msg.key(sender, "insufficient-funds", "&c余额不足。");
|
||||
return;
|
||||
}
|
||||
// 物品操作必须回到玩家所在区域线程。
|
||||
plugin.runForPlayer(player, () -> {
|
||||
ItemStack cheque = plugin.getChequeManager()
|
||||
.createCheque(player.getUniqueId(), player.getName(), amount);
|
||||
Map<Integer, ItemStack> leftover = player.getInventory().addItem(cheque);
|
||||
if (!leftover.isEmpty()) {
|
||||
// 背包放不下: 异步退款。
|
||||
plugin.runAsync(() -> {
|
||||
econ.deposit(player.getUniqueId(), amount);
|
||||
msg.raw(player, "&c背包已满, 无法生成支票, 金额已退回。");
|
||||
});
|
||||
return;
|
||||
}
|
||||
msg.raw(player, "&a已开具一张面额 &f" + econ.format(amount) + " &a的支票。");
|
||||
});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.craftbank.commands;
|
||||
|
||||
import com.craftbank.CraftBank;
|
||||
import com.craftbank.economy.EconomyManager;
|
||||
import com.craftbank.util.Msg;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabExecutor;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* /money [玩家] —— 查看自己或他人的现金余额。
|
||||
*/
|
||||
public final class MoneyCommand implements TabExecutor {
|
||||
|
||||
private final CraftBank plugin;
|
||||
private final Msg msg;
|
||||
|
||||
public MoneyCommand(CraftBank plugin) {
|
||||
this.plugin = plugin;
|
||||
this.msg = new Msg(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
EconomyManager econ = plugin.getEconomyManager();
|
||||
|
||||
if (args.length == 0) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
msg.key(sender, "player-only", "&c该指令只能由玩家执行。");
|
||||
return true;
|
||||
}
|
||||
double cash = econ.getCash(player.getUniqueId());
|
||||
msg.raw(sender, "&7你的现金余额: &a" + econ.format(cash));
|
||||
return true;
|
||||
}
|
||||
|
||||
// 查看他人余额需要权限。
|
||||
if (!sender.hasPermission("craftbank.money.others") && !sender.hasPermission("craftbank.admin")) {
|
||||
msg.key(sender, "no-permission", "&c你没有权限执行该操作。");
|
||||
return true;
|
||||
}
|
||||
|
||||
String target = args[0];
|
||||
// 余额查询涉及数据库读取, 放到异步线程。
|
||||
plugin.runAsync(() -> {
|
||||
@SuppressWarnings("deprecation")
|
||||
OfflinePlayer op = Bukkit.getOfflinePlayer(target);
|
||||
if (op.getUniqueId() == null || (!op.hasPlayedBefore() && !op.isOnline())) {
|
||||
msg.key(sender, "player-not-found", "&c找不到目标玩家。");
|
||||
return;
|
||||
}
|
||||
double cash = econ.getCash(op.getUniqueId());
|
||||
msg.raw(sender, "&7" + target + " 的现金余额: &a" + econ.format(cash));
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||
if (args.length == 1 && (sender.hasPermission("craftbank.money.others") || sender.hasPermission("craftbank.admin"))) {
|
||||
return null; // 返回 null 让服务端补全在线玩家名
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.craftbank.commands;
|
||||
|
||||
import com.craftbank.CraftBank;
|
||||
import com.craftbank.economy.EconomyManager;
|
||||
import com.craftbank.model.PlayerAccount;
|
||||
import com.craftbank.util.Amounts;
|
||||
import com.craftbank.util.Msg;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabExecutor;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* /pay <玩家> <金额> —— 现金转账。
|
||||
*
|
||||
* <p>资金操作经 {@link EconomyManager#transfer} 原子完成;目标若离线先异步加载其账户。</p>
|
||||
*/
|
||||
public final class PayCommand implements TabExecutor {
|
||||
|
||||
private final CraftBank plugin;
|
||||
private final Msg msg;
|
||||
|
||||
public PayCommand(CraftBank plugin) {
|
||||
this.plugin = plugin;
|
||||
this.msg = new Msg(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
msg.key(sender, "player-only", "&c该指令只能由玩家执行。");
|
||||
return true;
|
||||
}
|
||||
if (args.length < 2) {
|
||||
msg.raw(sender, "&c用法: /pay <玩家> <金额>");
|
||||
return true;
|
||||
}
|
||||
|
||||
double amount = Amounts.parse(args[1]);
|
||||
if (amount <= 0) {
|
||||
msg.key(sender, "invalid-amount", "&c请输入一个有效的正数金额。");
|
||||
return true;
|
||||
}
|
||||
|
||||
String targetName = args[0];
|
||||
EconomyManager econ = plugin.getEconomyManager();
|
||||
|
||||
plugin.runAsync(() -> {
|
||||
@SuppressWarnings("deprecation")
|
||||
OfflinePlayer target = Bukkit.getOfflinePlayer(targetName);
|
||||
UUID targetUuid = target.getUniqueId();
|
||||
if (targetUuid == null || (!target.hasPlayedBefore() && !target.isOnline())) {
|
||||
msg.key(sender, "player-not-found", "&c找不到目标玩家。");
|
||||
return;
|
||||
}
|
||||
if (targetUuid.equals(player.getUniqueId())) {
|
||||
msg.raw(sender, "&c你不能给自己转账。");
|
||||
return;
|
||||
}
|
||||
// 确保双方账户均已加载入缓存。
|
||||
econ.loadBlocking(player.getUniqueId(), player.getName(), true);
|
||||
PlayerAccount targetAcc = econ.loadBlocking(targetUuid, target.getName(), true);
|
||||
if (targetAcc == null) {
|
||||
msg.key(sender, "player-not-found", "&c找不到目标玩家。");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!econ.has(player.getUniqueId(), amount)) {
|
||||
msg.key(sender, "insufficient-funds", "&c余额不足。");
|
||||
return;
|
||||
}
|
||||
if (econ.transfer(player.getUniqueId(), targetUuid, amount)) {
|
||||
msg.raw(sender, "&a成功向 &f" + targetName + " &a转账 " + econ.format(amount));
|
||||
Player onlineTarget = Bukkit.getPlayer(targetUuid);
|
||||
if (onlineTarget != null) {
|
||||
msg.raw(onlineTarget, "&a你收到来自 &f" + player.getName() + " &a的转账 " + econ.format(amount));
|
||||
}
|
||||
} else {
|
||||
msg.key(sender, "insufficient-funds", "&c余额不足。");
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||
if (args.length == 1) {
|
||||
return null; // 在线玩家名
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user