fix/feat: 修复 PVP 区域实时榜显示为总榜的问题,新增虚拟货币债券系统(BondManager/BondPrinter/CDKManager/UserGroupManager)
This commit is contained in:
@@ -33,6 +33,7 @@
|
||||
<version>1.21.4-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.MilkBowl</groupId>
|
||||
<artifactId>VaultAPI</artifactId>
|
||||
|
||||
@@ -0,0 +1,593 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import java.io.File;
|
||||
import java.sql.*;
|
||||
import java.util.*;
|
||||
|
||||
public class BondManager {
|
||||
|
||||
private final Main plugin;
|
||||
private Connection db;
|
||||
|
||||
// ===== 交易类型常量 =====
|
||||
public static final String TX_ADMIN_GIVE = "admin_give";
|
||||
public static final String TX_ADMIN_DEDUCT = "admin_deduct";
|
||||
public static final String TX_TRANSFER_OUT = "transfer_out";
|
||||
public static final String TX_TRANSFER_IN = "transfer_in";
|
||||
public static final String TX_REDEEM = "redeem";
|
||||
public static final String TX_SHOP_BUY = "shop_buy";
|
||||
public static final String TX_DAILY_SIGN = "daily_sign";
|
||||
public static final String TX_FREEZE = "freeze";
|
||||
public static final String TX_UNFREEZE = "unfreeze";
|
||||
|
||||
public static final int MAX_WATER_DAYS = 14;
|
||||
public static final int ADMIN_MAX_WATER_DAYS = 30;
|
||||
|
||||
public BondManager(Main plugin) {
|
||||
this.plugin = plugin;
|
||||
initDB();
|
||||
}
|
||||
|
||||
// ======================== 数据库初始化 ========================
|
||||
|
||||
private void initDB() {
|
||||
try {
|
||||
Class.forName("org.sqlite.JDBC");
|
||||
File dbFile = new File(
|
||||
plugin.getDataFolder(), "bond.db");
|
||||
db = DriverManager.getConnection(
|
||||
"jdbc:sqlite:" + dbFile.getAbsolutePath());
|
||||
Statement st = db.createStatement();
|
||||
st.execute("PRAGMA journal_mode=WAL");
|
||||
|
||||
// 债券余额(含冻结状态)
|
||||
st.execute("CREATE TABLE IF NOT EXISTS bonds ("
|
||||
+ "player_name TEXT PRIMARY KEY,"
|
||||
+ "amount INTEGER DEFAULT 0,"
|
||||
+ "status TEXT DEFAULT 'normal')");
|
||||
|
||||
// 兼容旧表
|
||||
try {
|
||||
st.execute("ALTER TABLE bonds "
|
||||
+ "ADD COLUMN status "
|
||||
+ "TEXT DEFAULT 'normal'");
|
||||
} catch (SQLException ignored) {}
|
||||
|
||||
// 旧流水(保留兼容)
|
||||
st.execute("CREATE TABLE IF NOT EXISTS bond_log ("
|
||||
+ "id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
+ "player_name TEXT NOT NULL,"
|
||||
+ "amount INTEGER NOT NULL,"
|
||||
+ "reason TEXT DEFAULT '',"
|
||||
+ "time INTEGER DEFAULT 0)");
|
||||
|
||||
// 新交易流水表
|
||||
st.execute("CREATE TABLE IF NOT EXISTS "
|
||||
+ "bond_transaction ("
|
||||
+ "id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
+ "player_name TEXT NOT NULL,"
|
||||
+ "type TEXT NOT NULL,"
|
||||
+ "amount INTEGER NOT NULL,"
|
||||
+ "target_player TEXT DEFAULT '',"
|
||||
+ "operator TEXT DEFAULT '',"
|
||||
+ "reason TEXT DEFAULT '',"
|
||||
+ "balance_after INTEGER DEFAULT 0,"
|
||||
+ "time INTEGER DEFAULT 0)");
|
||||
|
||||
try {
|
||||
st.execute("CREATE INDEX "
|
||||
+ "IF NOT EXISTS idx_tx_pt "
|
||||
+ "ON bond_transaction("
|
||||
+ "player_name, time)");
|
||||
} catch (SQLException ignored) {}
|
||||
try {
|
||||
st.execute("ALTER TABLE bond_transaction "
|
||||
+ "ADD COLUMN balance_before "
|
||||
+ "INTEGER DEFAULT 0");
|
||||
} catch (SQLException ignored) {}
|
||||
|
||||
|
||||
st.close();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(
|
||||
"[Bond] DB init failed: "
|
||||
+ e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 账户状态 ========================
|
||||
|
||||
public boolean isFrozen(String player) {
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"SELECT status FROM bonds "
|
||||
+ "WHERE player_name=?");
|
||||
ps.setString(1, player);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
String s = rs.getString("status");
|
||||
rs.close(); ps.close();
|
||||
return "frozen".equals(s);
|
||||
}
|
||||
rs.close(); ps.close();
|
||||
} catch (SQLException ignored) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getAccountStatus(String player) {
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"SELECT status FROM bonds "
|
||||
+ "WHERE player_name=?");
|
||||
ps.setString(1, player);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
String s = rs.getString("status");
|
||||
rs.close(); ps.close();
|
||||
return s != null ? s : "normal";
|
||||
}
|
||||
rs.close(); ps.close();
|
||||
} catch (SQLException ignored) {}
|
||||
return "normal";
|
||||
}
|
||||
|
||||
public void freezeAccount(String player) {
|
||||
ensureAccount(player);
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"UPDATE bonds SET status='frozen' "
|
||||
+ "WHERE player_name=?");
|
||||
ps.setString(1, player);
|
||||
ps.executeUpdate(); ps.close();
|
||||
} catch (SQLException e) { e.printStackTrace(); }
|
||||
}
|
||||
|
||||
public void unfreezeAccount(String player) {
|
||||
ensureAccount(player);
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"UPDATE bonds SET status='normal' "
|
||||
+ "WHERE player_name=?");
|
||||
ps.setString(1, player);
|
||||
ps.executeUpdate(); ps.close();
|
||||
} catch (SQLException e) { e.printStackTrace(); }
|
||||
}
|
||||
|
||||
private void ensureAccount(String player) {
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"INSERT OR IGNORE INTO bonds "
|
||||
+ "(player_name,amount,status) "
|
||||
+ "VALUES (?,0,'normal')");
|
||||
ps.setString(1, player);
|
||||
ps.executeUpdate(); ps.close();
|
||||
} catch (SQLException ignored) {}
|
||||
}
|
||||
|
||||
// ======================== 余额查询 ========================
|
||||
|
||||
public int getBonds(String player) {
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"SELECT amount FROM bonds "
|
||||
+ "WHERE player_name=?");
|
||||
ps.setString(1, player);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
int v = 0;
|
||||
if (rs.next()) v = rs.getInt("amount");
|
||||
rs.close(); ps.close();
|
||||
return v;
|
||||
} catch (SQLException e) { return 0; }
|
||||
}
|
||||
|
||||
// ======================== 增加债券 ========================
|
||||
|
||||
public int addBonds(String player, int amount) {
|
||||
return addBondsInternal(player, amount,
|
||||
TX_ADMIN_GIVE, "", "",
|
||||
"管理员发放");
|
||||
}
|
||||
|
||||
public int addBonds(String player, int amount,
|
||||
String type,
|
||||
String targetPlayer,
|
||||
String operator,
|
||||
String reason) {
|
||||
if (isFrozen(player)) return -1;
|
||||
return addBondsInternal(player, amount,
|
||||
type, targetPlayer, operator, reason);
|
||||
}
|
||||
|
||||
private int addBondsInternal(String player,
|
||||
int amount,
|
||||
String type,
|
||||
String targetPlayer,
|
||||
String operator,
|
||||
String reason) {
|
||||
int before = getBonds(player);
|
||||
int after = before + amount;
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"INSERT INTO bonds "
|
||||
+ "(player_name,amount,status)"
|
||||
+ " VALUES (?,?, 'normal') "
|
||||
+ "ON CONFLICT(player_name) "
|
||||
+ "DO UPDATE SET amount=?");
|
||||
ps.setString(1, player);
|
||||
ps.setInt(2, after);
|
||||
ps.setInt(3, after);
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
logTransaction(player, type, amount,
|
||||
targetPlayer, operator, reason,
|
||||
before, after);
|
||||
logLegacy(player, amount);
|
||||
return after;
|
||||
}
|
||||
|
||||
// ======================== 扣除债券 ========================
|
||||
|
||||
|
||||
|
||||
private boolean deductBondsInternal(String player,
|
||||
int amount,
|
||||
String type,
|
||||
String targetPlayer,
|
||||
String operator,
|
||||
String reason) {
|
||||
int before = getBonds(player);
|
||||
if (before < amount) return false;
|
||||
int after = before - amount;
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"UPDATE bonds SET amount=? "
|
||||
+ "WHERE player_name=?");
|
||||
ps.setInt(1, after);
|
||||
ps.setString(2, player);
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
logTransaction(player, type, amount,
|
||||
targetPlayer, operator, reason,
|
||||
before, after);
|
||||
logLegacy(player, -amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ======================== 流水写入 ========================
|
||||
|
||||
private void logTransaction(String player,
|
||||
String type,
|
||||
int amount,
|
||||
String targetPlayer,
|
||||
String operator,
|
||||
String reason,
|
||||
int balanceBefore,
|
||||
int balanceAfter) {
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"INSERT INTO bond_transaction "
|
||||
+ "(player_name, type, amount,"
|
||||
+ " target_player, operator,"
|
||||
+ " reason, balance_before,"
|
||||
+ " balance_after, time)"
|
||||
+ " VALUES (?,?,?,?,?,?,?,?,?)");
|
||||
ps.setString(1, player);
|
||||
ps.setString(2, type);
|
||||
ps.setInt(3, amount);
|
||||
ps.setString(4,
|
||||
targetPlayer != null
|
||||
? targetPlayer : "");
|
||||
ps.setString(5,
|
||||
operator != null ? operator : "");
|
||||
ps.setString(6,
|
||||
reason != null ? reason : "");
|
||||
ps.setInt(7, balanceBefore);
|
||||
ps.setInt(8, balanceAfter);
|
||||
ps.setLong(9,
|
||||
System.currentTimeMillis());
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ======================== 扣除债券 ========================
|
||||
|
||||
/** 完整版:含冻结检查 + 流水记录 */
|
||||
public boolean deductBonds(String player, int amount,
|
||||
String type,
|
||||
String targetPlayer,
|
||||
String operator,
|
||||
String reason) {
|
||||
if (isFrozen(player)) return false;
|
||||
return deductBondsInternal(player, amount,
|
||||
type, targetPlayer, operator, reason);
|
||||
}
|
||||
|
||||
|
||||
public boolean setBonds(String player, int amount) {
|
||||
ensureAccount(player);
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"INSERT INTO bonds "
|
||||
+ "(player_name,amount,status) "
|
||||
+ "VALUES (?,?, 'normal') "
|
||||
+ "ON CONFLICT(player_name) "
|
||||
+ "DO UPDATE SET amount=?");
|
||||
ps.setString(1, player);
|
||||
ps.setInt(2, amount);
|
||||
ps.setInt(3, amount);
|
||||
ps.executeUpdate(); ps.close();
|
||||
} catch (SQLException e) { e.printStackTrace(); }
|
||||
return true;
|
||||
}
|
||||
|
||||
// ======================== 转账 ========================
|
||||
|
||||
public TransferResult transfer(String from,
|
||||
String to,
|
||||
int amount) {
|
||||
if (from.equalsIgnoreCase(to))
|
||||
return new TransferResult(false,
|
||||
"不能给自己转账");
|
||||
if (isFrozen(from))
|
||||
return new TransferResult(false,
|
||||
"你的账户已被冻结");
|
||||
if (isFrozen(to))
|
||||
return new TransferResult(false,
|
||||
to + " 的账户已被冻结");
|
||||
if (isSameIP(from, to))
|
||||
return new TransferResult(false,
|
||||
"不能与同IP玩家转账");
|
||||
|
||||
if (!deductBondsInternal(from, amount,
|
||||
TX_TRANSFER_OUT, to, from,
|
||||
"转账给" + to))
|
||||
return new TransferResult(false, "债券不足");
|
||||
|
||||
addBondsInternal(to, amount,
|
||||
TX_TRANSFER_IN, from, from,
|
||||
"收到来自" + from + "的转账");
|
||||
|
||||
return new TransferResult(true, "转账成功");
|
||||
}
|
||||
|
||||
// ======================== 同IP检测 ========================
|
||||
|
||||
public boolean isSameIP(String p1, String p2) {
|
||||
String ip1 = getPlayerIPAny(p1);
|
||||
String ip2 = getPlayerIPAny(p2);
|
||||
if (ip1 == null || ip2 == null) return false;
|
||||
return ip1.equals(ip2);
|
||||
}
|
||||
|
||||
/** 获取同IP下的所有其他在线玩家 */
|
||||
public List<String> getSameIPPlayers(String player) {
|
||||
String ip = getPlayerIPAny(player);
|
||||
if (ip == null) return Collections.emptyList();
|
||||
List<String> list = new ArrayList<>();
|
||||
for (Player p :
|
||||
plugin.getServer().getOnlinePlayers()) {
|
||||
String pIp = plugin.getPlayerIP(p);
|
||||
if (pIp != null && pIp.equals(ip)
|
||||
&& !p.getName()
|
||||
.equalsIgnoreCase(player)) {
|
||||
list.add(p.getName());
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private String getPlayerIPAny(String player) {
|
||||
// 在线:直接取
|
||||
Player p = plugin.getServer()
|
||||
.getPlayerExact(player);
|
||||
if (p != null) {
|
||||
String ip = plugin.getPlayerIP(p);
|
||||
if (ip != null) return ip;
|
||||
}
|
||||
// 离线:查主库
|
||||
try {
|
||||
Object v = plugin.getDb()
|
||||
.getField(player, "last_login_ip");
|
||||
if (v != null) return v.toString();
|
||||
} catch (Exception ignored) {}
|
||||
try {
|
||||
Object v = plugin.getDb()
|
||||
.getField(player, "register_ip");
|
||||
if (v != null) return v.toString();
|
||||
} catch (Exception ignored) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ======================== 流水查询 ========================
|
||||
|
||||
public List<Map<String, Object>> getTransactions(
|
||||
String player, int days) {
|
||||
days = Math.min(days, MAX_WATER_DAYS);
|
||||
long cutoff = System.currentTimeMillis()
|
||||
- (long) days * 24 * 60 * 60 * 1000;
|
||||
List<Map<String, Object>> list = new ArrayList<>();
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"SELECT * FROM bond_transaction "
|
||||
+ "WHERE player_name=? "
|
||||
+ "AND time>=? "
|
||||
+ "ORDER BY time DESC");
|
||||
ps.setString(1, player);
|
||||
ps.setLong(2, cutoff);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
Map<String, Object> row =
|
||||
new LinkedHashMap<>();
|
||||
row.put("id", rs.getInt("id"));
|
||||
row.put("type", rs.getString("type"));
|
||||
row.put("amount",
|
||||
rs.getInt("amount"));
|
||||
row.put("target_player",
|
||||
rs.getString("target_player"));
|
||||
row.put("operator",
|
||||
rs.getString("operator"));
|
||||
row.put("reason",
|
||||
rs.getString("reason"));
|
||||
row.put("balance_after",
|
||||
rs.getInt("balance_after"));
|
||||
row.put("balance_before", rs.getInt("balance_before"));
|
||||
row.put("time", rs.getLong("time"));
|
||||
list.add(row);
|
||||
}
|
||||
rs.close(); ps.close();
|
||||
} catch (SQLException e) { e.printStackTrace(); }
|
||||
return list;
|
||||
}
|
||||
// 放在 getTransactions 方法后面:
|
||||
|
||||
/**
|
||||
* 获取该玩家全部流水(不限时间,供打印使用)
|
||||
*/
|
||||
public List<Map<String, Object>>
|
||||
getAllTransactions(String player) {
|
||||
List<Map<String, Object>> list =
|
||||
new ArrayList<>();
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"SELECT * FROM bond_transaction "
|
||||
+ "WHERE player_name=? "
|
||||
+ "ORDER BY time ASC");
|
||||
ps.setString(1, player);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
Map<String, Object> row =
|
||||
new LinkedHashMap<>();
|
||||
row.put("id", rs.getInt("id"));
|
||||
row.put("player_name",
|
||||
rs.getString("player_name"));
|
||||
row.put("type", rs.getString("type"));
|
||||
row.put("amount",
|
||||
rs.getInt("amount"));
|
||||
row.put("target_player",
|
||||
rs.getString("target_player"));
|
||||
row.put("operator",
|
||||
rs.getString("operator"));
|
||||
row.put("reason",
|
||||
rs.getString("reason"));
|
||||
row.put("balance_after",
|
||||
rs.getInt("balance_after"));
|
||||
row.put("balance_before", rs.getInt("balance_before"));
|
||||
row.put("time", rs.getLong("time"));
|
||||
list.add(row);
|
||||
}
|
||||
rs.close(); ps.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return list;
|
||||
}
|
||||
// 放在 getAllTransactions 方法后面:
|
||||
|
||||
/**
|
||||
* 获取所有有流水记录的玩家名(去重)
|
||||
*/
|
||||
public List<String> getAllPlayerNames() {
|
||||
List<String> list = new ArrayList<>();
|
||||
try {
|
||||
Statement st = db.createStatement();
|
||||
ResultSet rs = st.executeQuery(
|
||||
"SELECT DISTINCT player_name "
|
||||
+ "FROM bond_transaction "
|
||||
+ "ORDER BY player_name ASC");
|
||||
while (rs.next()) {
|
||||
list.add(rs.getString("player_name"));
|
||||
}
|
||||
rs.close(); st.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public List<Map<String, Object>>
|
||||
getTransferTransactions(
|
||||
String player, int days) {
|
||||
days = Math.min(days, MAX_WATER_DAYS);
|
||||
long cutoff = System.currentTimeMillis()
|
||||
- (long) days * 24 * 60 * 60 * 1000;
|
||||
List<Map<String, Object>> list = new ArrayList<>();
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"SELECT * FROM bond_transaction "
|
||||
+ "WHERE player_name=? "
|
||||
+ "AND time>=? "
|
||||
+ "AND (type=? OR type=?) "
|
||||
+ "ORDER BY time DESC");
|
||||
ps.setString(1, player);
|
||||
ps.setLong(2, cutoff);
|
||||
ps.setString(3, TX_TRANSFER_OUT);
|
||||
ps.setString(4, TX_TRANSFER_IN);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
Map<String, Object> row =
|
||||
new LinkedHashMap<>();
|
||||
row.put("id", rs.getInt("id"));
|
||||
row.put("type", rs.getString("type"));
|
||||
row.put("amount",
|
||||
rs.getInt("amount"));
|
||||
row.put("target_player",
|
||||
rs.getString("target_player"));
|
||||
row.put("reason",
|
||||
rs.getString("reason"));
|
||||
row.put("balance_after",
|
||||
rs.getInt("balance_after"));
|
||||
row.put("balance_before", rs.getInt("balance_before"));
|
||||
row.put("time", rs.getLong("time"));
|
||||
list.add(row);
|
||||
}
|
||||
rs.close(); ps.close();
|
||||
} catch (SQLException e) { e.printStackTrace(); }
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
private void logLegacy(String player, int amount) {
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"INSERT INTO bond_log "
|
||||
+ "(player_name,amount,time) "
|
||||
+ "VALUES (?,?,?)");
|
||||
ps.setString(1, player);
|
||||
ps.setInt(2, amount);
|
||||
ps.setLong(3,
|
||||
System.currentTimeMillis());
|
||||
ps.executeUpdate(); ps.close();
|
||||
} catch (SQLException ignored) {}
|
||||
}
|
||||
|
||||
public Map<String, Object> getLog(
|
||||
String player, int limit) {
|
||||
Map<String, Object> info = new LinkedHashMap<>();
|
||||
info.put("balance", getBonds(player));
|
||||
return info;
|
||||
}
|
||||
|
||||
// ======================== 转账结果 ========================
|
||||
|
||||
public static class TransferResult {
|
||||
public final boolean success;
|
||||
public final String message;
|
||||
public TransferResult(boolean success,
|
||||
String message) {
|
||||
this.success = success;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class BondPrinter {
|
||||
|
||||
private final Main plugin;
|
||||
private final File outputDir;
|
||||
|
||||
private static final SimpleDateFormat FILE_SDF =
|
||||
new SimpleDateFormat(
|
||||
"yyyy-MM-dd_HH-mm-ss");
|
||||
private static final SimpleDateFormat CELL_SDF =
|
||||
new SimpleDateFormat(
|
||||
"yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
private static final LinkedHashMap<String, String>
|
||||
TYPE_NAMES = new LinkedHashMap<>();
|
||||
|
||||
static {
|
||||
TYPE_NAMES.put("admin_give", "管理员发放");
|
||||
TYPE_NAMES.put("admin_deduct", "管理员扣除");
|
||||
TYPE_NAMES.put("transfer_out", "转出");
|
||||
TYPE_NAMES.put("transfer_in", "转入");
|
||||
TYPE_NAMES.put("redeem", "CDK兑换");
|
||||
TYPE_NAMES.put("shop_buy", "商城购买");
|
||||
TYPE_NAMES.put("daily_sign", "签到奖励");
|
||||
TYPE_NAMES.put("freeze", "冻结");
|
||||
TYPE_NAMES.put("unfreeze", "解冻");
|
||||
}
|
||||
|
||||
public BondPrinter(Main plugin) {
|
||||
this.plugin = plugin;
|
||||
this.outputDir = new File(
|
||||
plugin.getDataFolder(), "printer");
|
||||
if (!this.outputDir.exists()) {
|
||||
this.outputDir.mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 打印单个玩家 ============
|
||||
|
||||
public void printPlayer(String player,
|
||||
CommandSender sender) {
|
||||
Bukkit.getScheduler()
|
||||
.runTaskAsynchronously(plugin,
|
||||
() -> doPrintPlayer(
|
||||
player, sender));
|
||||
}
|
||||
|
||||
private void doPrintPlayer(String player,
|
||||
CommandSender sender) {
|
||||
try {
|
||||
List<Map<String, Object>> txs =
|
||||
plugin.getBonds()
|
||||
.getAllTransactions(player);
|
||||
|
||||
String fileName = player + "_"
|
||||
+ FILE_SDF.format(new Date())
|
||||
+ ".xls";
|
||||
File file = new File(outputDir, fileName);
|
||||
|
||||
deleteOld(player);
|
||||
|
||||
PrintWriter pw = new PrintWriter(
|
||||
new OutputStreamWriter(
|
||||
new FileOutputStream(file),
|
||||
StandardCharsets.UTF_8));
|
||||
try {
|
||||
writeXmlHeader(pw,
|
||||
escXml(player));
|
||||
writeXmlRow(pw,
|
||||
new String[]{
|
||||
"时间", "类型",
|
||||
"金额", "对象",
|
||||
"操作者", "理由",
|
||||
"操作前余额",
|
||||
"操作后余额"});
|
||||
|
||||
for (Map<String, Object> tx : txs) {
|
||||
writeXmlRow(pw, new String[]{
|
||||
txTime(tx),
|
||||
txTypeName(tx),
|
||||
String.valueOf(
|
||||
txInt(tx,
|
||||
"amount")),
|
||||
txStr(tx,
|
||||
"target_player"),
|
||||
txStr(tx, "operator"),
|
||||
txStr(tx, "reason"),
|
||||
String.valueOf(
|
||||
txInt(tx,
|
||||
"balance_before")),
|
||||
String.valueOf(
|
||||
txInt(tx,
|
||||
"balance_after"))
|
||||
});
|
||||
}
|
||||
|
||||
writeXmlFooter(pw);
|
||||
pw.flush();
|
||||
} finally {
|
||||
pw.close();
|
||||
}
|
||||
|
||||
if (sender != null) {
|
||||
sender.sendMessage("§a[打印] §f"
|
||||
+ player + " §a流水已导出");
|
||||
sender.sendMessage("§7文件: §f"
|
||||
+ fileName);
|
||||
sender.sendMessage("§7共 §f"
|
||||
+ txs.size()
|
||||
+ " §7条,§724小时后删除");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (sender != null) {
|
||||
sender.sendMessage("§c导出失败: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
plugin.getLogger().severe(
|
||||
"[Printer] " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 打印全服 ============
|
||||
|
||||
public void printAll(CommandSender sender) {
|
||||
Bukkit.getScheduler()
|
||||
.runTaskAsynchronously(plugin,
|
||||
() -> doPrintAll(sender));
|
||||
}
|
||||
|
||||
private void doPrintAll(CommandSender sender) {
|
||||
try {
|
||||
List<String> players =
|
||||
plugin.getBonds()
|
||||
.getAllPlayerNames();
|
||||
if (players.isEmpty()) {
|
||||
if (sender != null) {
|
||||
sender.sendMessage(
|
||||
"§c没有流水数据");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String fileName = "全服流水_"
|
||||
+ FILE_SDF.format(new Date())
|
||||
+ ".xls";
|
||||
File file = new File(outputDir, fileName);
|
||||
|
||||
PrintWriter pw = new PrintWriter(
|
||||
new OutputStreamWriter(
|
||||
new FileOutputStream(file),
|
||||
StandardCharsets.UTF_8));
|
||||
try {
|
||||
writeXmlHeader(pw, "全服流水");
|
||||
writeXmlRow(pw,
|
||||
new String[]{
|
||||
"玩家", "时间",
|
||||
"类型", "金额",
|
||||
"对象", "操作者",
|
||||
"理由",
|
||||
"操作前余额",
|
||||
"操作后余额"});
|
||||
|
||||
Set<String> written = new HashSet<>();
|
||||
int totalRows = 0;
|
||||
|
||||
for (String player : players) {
|
||||
List<Map<String, Object>> txs =
|
||||
plugin.getBonds()
|
||||
.getAllTransactions(
|
||||
player);
|
||||
for (Map<String, Object> tx : txs) {
|
||||
int txId = ((Number) tx
|
||||
.get("id"))
|
||||
.intValue();
|
||||
if (!written.add(
|
||||
String.valueOf(
|
||||
txId))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
writeXmlRow(pw, new String[]{
|
||||
player,
|
||||
txTime(tx),
|
||||
txTypeName(tx),
|
||||
String.valueOf(
|
||||
txInt(tx,
|
||||
"amount")),
|
||||
txStr(tx,
|
||||
"target_player"),
|
||||
txStr(tx, "operator"),
|
||||
txStr(tx, "reason"),
|
||||
String.valueOf(
|
||||
txInt(tx,
|
||||
"balance_before")),
|
||||
String.valueOf(
|
||||
txInt(tx,
|
||||
"balance_after"))
|
||||
});
|
||||
totalRows++;
|
||||
}
|
||||
}
|
||||
|
||||
writeXmlFooter(pw);
|
||||
pw.flush();
|
||||
|
||||
if (sender != null) {
|
||||
sender.sendMessage(
|
||||
"§a[打印] §f全服流水"
|
||||
+ " §a已导出");
|
||||
sender.sendMessage("§7文件: §f"
|
||||
+ fileName);
|
||||
sender.sendMessage("§7共 §f"
|
||||
+ players.size()
|
||||
+ " 名玩家,§f"
|
||||
+ totalRows + " §7条");
|
||||
sender.sendMessage(
|
||||
"§724小时后自动删除");
|
||||
}
|
||||
} finally {
|
||||
pw.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (sender != null) {
|
||||
sender.sendMessage("§c导出失败: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
plugin.getLogger().severe(
|
||||
"[Printer] " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 清理 ============
|
||||
|
||||
public void cleanupOldFiles() {
|
||||
long now = System.currentTimeMillis();
|
||||
long maxAge = 24L * 60 * 60 * 1000;
|
||||
File[] files = outputDir.listFiles(
|
||||
(d, n) -> n.endsWith(".xls"));
|
||||
if (files == null) {
|
||||
return;
|
||||
}
|
||||
int count = 0;
|
||||
for (File f : files) {
|
||||
if (now - f.lastModified() > maxAge) {
|
||||
if (f.delete()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count > 0) {
|
||||
plugin.getLogger().info(
|
||||
"[Printer] 清理 "
|
||||
+ count + " 个过期文件");
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteOld(String player) {
|
||||
File[] old = outputDir.listFiles(
|
||||
(d, n) -> n.startsWith(player + "_")
|
||||
&& n.endsWith(".xls"));
|
||||
if (old != null) {
|
||||
for (File f : old) {
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============ XML工具 ============
|
||||
|
||||
private void writeXmlHeader(PrintWriter pw,
|
||||
String sheetName) {
|
||||
pw.println("<?xml version=\"1.0\"?>");
|
||||
pw.println("<?mso-application "
|
||||
+ "progid=\"Excel.Sheet\"?>");
|
||||
pw.println("<Workbook xmlns="
|
||||
+ "\"urn:schemas-microsoft-com"
|
||||
+ ":office:spreadsheet\"");
|
||||
pw.println(" xmlns:ss="
|
||||
+ "\"urn:schemas-microsoft-com"
|
||||
+ ":office:spreadsheet\">");
|
||||
pw.println(" <Worksheet ss:Name=\""
|
||||
+ sheetName + "\">");
|
||||
pw.println(" <Table>");
|
||||
}
|
||||
|
||||
private void writeXmlRow(PrintWriter pw,
|
||||
String[] cells) {
|
||||
pw.println(" <Row>");
|
||||
for (String cell : cells) {
|
||||
pw.println(" <Cell>"
|
||||
+ "<Data ss:Type=\"String\">"
|
||||
+ escXml(cell != null ? cell : "")
|
||||
+ "</Data></Cell>");
|
||||
}
|
||||
pw.println(" </Row>");
|
||||
}
|
||||
|
||||
private void writeXmlFooter(PrintWriter pw) {
|
||||
pw.println(" </Table>");
|
||||
pw.println(" </Worksheet>");
|
||||
pw.println("</Workbook>");
|
||||
}
|
||||
|
||||
private String escXml(String val) {
|
||||
if (val == null) {
|
||||
return "";
|
||||
}
|
||||
return val.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("\"", """);
|
||||
}
|
||||
|
||||
private String txTime(Map<String, Object> tx) {
|
||||
Object t = tx.get("time");
|
||||
if (t == null) return "";
|
||||
long ms = ((Number) t).longValue();
|
||||
return CELL_SDF.format(new Date(ms));
|
||||
}
|
||||
|
||||
private String txTypeName(
|
||||
Map<String, Object> tx) {
|
||||
String type = (String) tx.get("type");
|
||||
return TYPE_NAMES.getOrDefault(type, type);
|
||||
}
|
||||
|
||||
private int txInt(Map<String, Object> tx,
|
||||
String key) {
|
||||
Object v = tx.get(key);
|
||||
if (v == null) return 0;
|
||||
return ((Number) v).intValue();
|
||||
}
|
||||
|
||||
private String txStr(Map<String, Object> tx,
|
||||
String key) {
|
||||
Object v = tx.get(key);
|
||||
return v != null ? v.toString() : "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,551 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scoreboard.Scoreboard;
|
||||
|
||||
import java.io.*;
|
||||
import java.sql.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
public class CDKManager {
|
||||
|
||||
private final Main plugin;
|
||||
private Connection db;
|
||||
private final Map<String, CDKInput> pendingInputs
|
||||
= new ConcurrentHashMap<>();
|
||||
|
||||
public CDKManager(Main plugin) {
|
||||
this.plugin = plugin;
|
||||
initDB();
|
||||
}
|
||||
|
||||
private void initDB() {
|
||||
try {
|
||||
Class.forName("org.sqlite.JDBC");
|
||||
File dbFile = new File(
|
||||
plugin.getDataFolder(), "bond.db");
|
||||
db = DriverManager.getConnection(
|
||||
"jdbc:sqlite:" + dbFile.getAbsolutePath());
|
||||
Statement st = db.createStatement();
|
||||
st.execute("PRAGMA journal_mode=WAL");
|
||||
st.execute("CREATE TABLE IF NOT EXISTS cdk ("
|
||||
+ "code TEXT PRIMARY KEY,"
|
||||
+ "amount INTEGER DEFAULT 0,"
|
||||
+ "type TEXT DEFAULT 'bond',"
|
||||
+ "used INTEGER DEFAULT 0,"
|
||||
+ "used_by TEXT DEFAULT '',"
|
||||
+ "created_time INTEGER DEFAULT 0,"
|
||||
+ "used_time INTEGER DEFAULT 0)");
|
||||
st.close();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(
|
||||
"[CDK] DB init failed: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadCDKsFromDir() {
|
||||
File dir = new File(
|
||||
plugin.getDataFolder(), "cdk");
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
plugin.getLogger().info(
|
||||
"[CDK] 已创建 cdk/ 目录");
|
||||
return;
|
||||
}
|
||||
File[] files = dir.listFiles(
|
||||
(d, n) -> n.endsWith(".txt"));
|
||||
if (files == null || files.length == 0) {
|
||||
plugin.getLogger().warning(
|
||||
"[CDK] cdk/ 目录下"
|
||||
+ "无 .txt 文件");
|
||||
return;
|
||||
}
|
||||
int count = 0;
|
||||
for (File f : files) {
|
||||
int loaded = loadCDKFile(f);
|
||||
plugin.getLogger().info(
|
||||
"[CDK] 文件: " + f.getName()
|
||||
+ " 加载 " + loaded + " 条");
|
||||
count += loaded;
|
||||
}
|
||||
plugin.getLogger().info(
|
||||
"[CDK] 共导入 " + count + " 条");
|
||||
}
|
||||
|
||||
/** 单文件加载,返回成功数 */
|
||||
private int loadCDKFile(File f) {
|
||||
int count = 0;
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(
|
||||
new InputStreamReader(
|
||||
new FileInputStream(f),
|
||||
StandardCharsets.UTF_8));
|
||||
String line;
|
||||
int lineNum = 0;
|
||||
while ((line = br.readLine()) != null) {
|
||||
lineNum++;
|
||||
line = line.trim();
|
||||
if (line.isEmpty()) continue;
|
||||
if (line.startsWith("#")) continue;
|
||||
if (line.startsWith("//")) continue;
|
||||
|
||||
// 支持格式:
|
||||
// CODE 100
|
||||
// CODE,100
|
||||
// CODE 100 bond
|
||||
// CODE,100,bond
|
||||
String[] parts = line
|
||||
.split("[,\\s]+");
|
||||
if (parts.length < 2) {
|
||||
plugin.getLogger().warning(
|
||||
"[CDK] " + f.getName()
|
||||
+ " L" + lineNum
|
||||
+ " 格式错误: "
|
||||
+ line);
|
||||
continue;
|
||||
}
|
||||
|
||||
String code = parts[0].trim();
|
||||
if (code.isEmpty()) continue;
|
||||
|
||||
int amount;
|
||||
try {
|
||||
amount = Integer.parseInt(
|
||||
parts[1].trim());
|
||||
} catch (NumberFormatException e) {
|
||||
plugin.getLogger().warning(
|
||||
"[CDK] " + f.getName()
|
||||
+ " L" + lineNum
|
||||
+ " 金额格式错误: "
|
||||
+ parts[1]);
|
||||
continue;
|
||||
}
|
||||
|
||||
String type = parts.length >= 3
|
||||
? parts[2].trim() : "bond";
|
||||
|
||||
boolean ok = addCDK(code, amount,
|
||||
type);
|
||||
if (ok) {
|
||||
count++;
|
||||
} else {
|
||||
plugin.getLogger().warning(
|
||||
"[CDK] " + f.getName()
|
||||
+ " L" + lineNum
|
||||
+ " 重复跳过: "
|
||||
+ code);
|
||||
}
|
||||
}
|
||||
br.close();
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning(
|
||||
"[CDK] 读取失败: " + f.getName()
|
||||
+ " " + e.getMessage());
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
public int importFromFile(String fileName) {
|
||||
File dir = new File(
|
||||
plugin.getDataFolder(), "cdk");
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
|
||||
// 先在 cdk/ 目录下找
|
||||
File f = new File(dir, fileName);
|
||||
if (!f.exists()) {
|
||||
// 再在插件根目录找
|
||||
f = new File(
|
||||
plugin.getDataFolder(), fileName);
|
||||
}
|
||||
if (!f.exists()) {
|
||||
plugin.getLogger().warning(
|
||||
"[CDK] 文件不存在: "
|
||||
+ fileName);
|
||||
return -1;
|
||||
}
|
||||
|
||||
plugin.getLogger().info(
|
||||
"[CDK] 开始导入: "
|
||||
+ f.getAbsolutePath()
|
||||
+ " 大小:" + f.length()
|
||||
+ " bytes");
|
||||
|
||||
int count = loadCDKFile(f);
|
||||
plugin.getLogger().info(
|
||||
"[CDK] 导入完成: " + count + " 条");
|
||||
return count;
|
||||
}
|
||||
|
||||
public int importFromSdf1(String fileName) {
|
||||
int count = 0;
|
||||
Scoreboard board = Bukkit.getScoreboardManager()
|
||||
.getMainScoreboard();
|
||||
// 尝试从sdf1的计分板读取口令
|
||||
// 这部分依赖sdf1插件的配置
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
private boolean addCDK(String code, int amount,
|
||||
String type) {
|
||||
try {
|
||||
// 先检查是否已存在
|
||||
PreparedStatement check =
|
||||
db.prepareStatement(
|
||||
"SELECT code FROM cdk "
|
||||
+ "WHERE code=?");
|
||||
check.setString(1, code);
|
||||
ResultSet rs = check.executeQuery();
|
||||
if (rs.next()) {
|
||||
rs.close();
|
||||
check.close();
|
||||
return false;
|
||||
}
|
||||
rs.close();
|
||||
check.close();
|
||||
|
||||
PreparedStatement ps =
|
||||
db.prepareStatement(
|
||||
"INSERT INTO cdk "
|
||||
+ "(code, amount, type,"
|
||||
+ " created_time)"
|
||||
+ " VALUES (?,?,?,?)");
|
||||
ps.setString(1, code);
|
||||
ps.setInt(2, amount);
|
||||
ps.setString(3,
|
||||
type != null ? type : "bond");
|
||||
ps.setLong(4,
|
||||
System.currentTimeMillis());
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().warning(
|
||||
"[CDK] addCDK失败: " + code
|
||||
+ " " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 替换整个 redeem 方法 =====
|
||||
public String redeem(String code, String player) {
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"SELECT amount,type,used FROM cdk "
|
||||
+ "WHERE code=?");
|
||||
ps.setString(1, code);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (!rs.next()) {
|
||||
rs.close(); ps.close();
|
||||
return null;
|
||||
}
|
||||
int amount = rs.getInt("amount");
|
||||
String type = rs.getString("type");
|
||||
int used = rs.getInt("used");
|
||||
rs.close(); ps.close();
|
||||
|
||||
if (used == 1) return "该兑换码已被使用";
|
||||
|
||||
// [新增] 冻结检查
|
||||
if (plugin.getBonds().isFrozen(player)) {
|
||||
return "账户已被冻结,无法兑换";
|
||||
}
|
||||
|
||||
PreparedStatement up = db.prepareStatement(
|
||||
"UPDATE cdk SET used=1,"
|
||||
+ "used_by=?,used_time=? "
|
||||
+ "WHERE code=?");
|
||||
up.setString(1, player);
|
||||
up.setLong(2, System.currentTimeMillis());
|
||||
up.setString(3, code);
|
||||
up.executeUpdate(); up.close();
|
||||
|
||||
if ("bond".equals(type)) {
|
||||
// [修改] 使用带流水的重载方法
|
||||
plugin.getBonds().addBonds(player, amount,
|
||||
BondManager.TX_REDEEM, "",
|
||||
"system", "CDK兑换: " + code);
|
||||
return "兑换成功!获得 " + amount + " 债券";
|
||||
}
|
||||
return null;
|
||||
} catch (SQLException e) {
|
||||
return "兑换失败";
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 聊天输入系统(主插件转发调用)=====
|
||||
|
||||
public void requestInput(Player p, String type, String context) {
|
||||
CDKInput input = new CDKInput();
|
||||
input.type = type;
|
||||
input.context = context;
|
||||
input.time = System.currentTimeMillis();
|
||||
pendingInputs.put(p.getName(), input);
|
||||
}
|
||||
|
||||
public boolean isListening(String playerName) {
|
||||
return pendingInputs.containsKey(playerName);
|
||||
}
|
||||
|
||||
public void onChat(Player p, String msg) {
|
||||
CDKInput input = pendingInputs.remove(p.getName());
|
||||
if (input == null) return;
|
||||
|
||||
if (System.currentTimeMillis() - input.time > 60000) {
|
||||
p.sendMessage("§c输入超时,请重新操作");
|
||||
return;
|
||||
}
|
||||
|
||||
if ("cdk".equals(input.type)) {
|
||||
String result = redeem(msg.trim(), p.getName());
|
||||
p.sendMessage(result != null
|
||||
? "§e§l[CDK] §f" + result
|
||||
: "§c§l[CDK] §f无效的兑换码");
|
||||
return;
|
||||
}
|
||||
|
||||
if ("econ".equals(input.type)) {
|
||||
handleEconInput(p, msg.trim(), input.context);
|
||||
return;
|
||||
}
|
||||
|
||||
if ("bond".equals(input.type)) {
|
||||
handleBondInput(p, msg.trim(), input.context);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEconInput(Player admin, String msg, String target) {
|
||||
net.milkbowl.vault.economy.Economy econ = null;
|
||||
try {
|
||||
var reg = plugin.getServer().getServicesManager()
|
||||
.getRegistration(net.milkbowl.vault.economy.Economy.class);
|
||||
if (reg != null) econ = reg.getProvider();
|
||||
} catch (Exception ignored) {}
|
||||
if (econ == null) {
|
||||
admin.sendMessage("§c经济系统不可用");
|
||||
return;
|
||||
}
|
||||
if (!msg.startsWith("+") && !msg.startsWith("-")) {
|
||||
admin.sendMessage("§c格式: §a+100 §7给钱 / §c-50 §7扣钱");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String numStr = msg.substring(1).trim();
|
||||
double amount = Double.parseDouble(numStr);
|
||||
if (amount <= 0) {
|
||||
admin.sendMessage("§c金额必须大于0");
|
||||
return;
|
||||
}
|
||||
if (msg.startsWith("-")) {
|
||||
Player tp = plugin.getServer().getPlayerExact(target);
|
||||
if (tp == null) {
|
||||
admin.sendMessage("§c" + target + " 不在线");
|
||||
return;
|
||||
}
|
||||
if (!econ.has(tp, amount)) {
|
||||
admin.sendMessage("§c" + target + " 余额不足");
|
||||
return;
|
||||
}
|
||||
double oldBal = econ.getBalance(tp);
|
||||
econ.withdrawPlayer(tp, amount);
|
||||
double newBal = econ.getBalance(tp);
|
||||
admin.sendMessage("§7============== §c余额扣款 §7==============");
|
||||
admin.sendMessage("§7目标: §e" + target);
|
||||
admin.sendMessage("§c扣款: §e-$" + String.format("%.2f", amount));
|
||||
admin.sendMessage("§7余额: §e" + String.format("%.2f", oldBal) + " → §c" + String.format("%.2f", newBal));
|
||||
admin.sendMessage("§7============== §c余额扣款 §7==============");
|
||||
tp.sendMessage("§7============== §c余额扣款 §7==============");
|
||||
tp.sendMessage("§c扣款: §e-$" + String.format("%.2f", amount));
|
||||
tp.sendMessage("§7余额: §e" + String.format("%.2f", oldBal) + " → §c" + String.format("%.2f", newBal));
|
||||
tp.sendMessage("§7操作者: §e" + admin.getName());
|
||||
tp.sendMessage("§7============== §c余额扣款 §7==============");
|
||||
} else {
|
||||
double oldBal = econ.getBalance(target);
|
||||
econ.depositPlayer(target, amount);
|
||||
double newBal = econ.getBalance(target);
|
||||
Player tp = plugin.getServer().getPlayerExact(target);
|
||||
admin.sendMessage("§7============== §6余额转账 §7==============");
|
||||
admin.sendMessage("§7目标: §e" + target);
|
||||
admin.sendMessage("§a到账: §e$" + String.format("%.2f", amount));
|
||||
admin.sendMessage("§7余额: §e" + String.format("%.2f", oldBal) + " → §a" + String.format("%.2f", newBal));
|
||||
admin.sendMessage("§7============== §6余额转账 §7==============");
|
||||
if (tp != null) {
|
||||
tp.sendMessage("§7============== §a收到转账 §7==============");
|
||||
tp.sendMessage("§a到账: §e$" + String.format("%.2f", amount));
|
||||
tp.sendMessage("§7余额: §e" + String.format("%.2f", oldBal) + " → §a" + String.format("%.2f", newBal));
|
||||
tp.sendMessage("§7来自: §e" + admin.getName());
|
||||
tp.sendMessage("§7============== §a收到转账 §7==============");
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
admin.sendMessage("§c格式错误,请输入 §a+数字 §7或 §c-数字");
|
||||
}
|
||||
}
|
||||
// ===== 替换整个 handleBondInput 方法 =====
|
||||
private void handleBondInput(Player admin,
|
||||
String msg,
|
||||
String target) {
|
||||
if (!msg.startsWith("+")
|
||||
&& !msg.startsWith("-")) {
|
||||
admin.sendMessage(
|
||||
"§c格式: §a+100 §7给债券"
|
||||
+ " / §c-50 §7扣债券");
|
||||
admin.sendMessage(
|
||||
"§7可附加理由: §a+100 活动奖励");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String sign = msg.substring(0, 1);
|
||||
String rest = msg.substring(1).trim();
|
||||
|
||||
// 拆分金额和理由
|
||||
String[] parts = rest.split("\\s+", 2);
|
||||
int amount = Integer.parseInt(parts[0]);
|
||||
String reason = parts.length >= 2
|
||||
? parts[1].trim()
|
||||
: "管理员手动调整";
|
||||
|
||||
if (amount <= 0) {
|
||||
admin.sendMessage("§c金额必须大于0");
|
||||
return;
|
||||
}
|
||||
String opName = admin.getName();
|
||||
String txType = "-".equals(sign)
|
||||
? BondManager.TX_ADMIN_DEDUCT
|
||||
: BondManager.TX_ADMIN_GIVE;
|
||||
|
||||
if ("-".equals(sign)) {
|
||||
if (plugin.getBonds()
|
||||
.isFrozen(target)) {
|
||||
admin.sendMessage("§c" + target
|
||||
+ " 账户已被冻结");
|
||||
return;
|
||||
}
|
||||
int oldBal = plugin.getBonds()
|
||||
.getBonds(target);
|
||||
if (plugin.getBonds().deductBonds(
|
||||
target, amount, txType,
|
||||
"", opName, reason)) {
|
||||
int newBal = plugin.getBonds()
|
||||
.getBonds(target);
|
||||
admin.sendMessage(
|
||||
"§7============== §c"
|
||||
+ "债券扣款 §7"
|
||||
+ "=============");
|
||||
admin.sendMessage(
|
||||
"§7目标: §e" + target);
|
||||
admin.sendMessage("§c扣款: §c-"
|
||||
+ amount + " §6枚");
|
||||
admin.sendMessage("§7理由: §f"
|
||||
+ reason);
|
||||
admin.sendMessage("§7债券: §e"
|
||||
+ oldBal + " → §c"
|
||||
+ newBal);
|
||||
admin.sendMessage(
|
||||
"§7============== §c"
|
||||
+ "债券扣款 §7"
|
||||
+ "=============");
|
||||
Player tp = plugin.getServer()
|
||||
.getPlayerExact(target);
|
||||
if (tp != null) {
|
||||
tp.sendMessage(
|
||||
"§7============== §c"
|
||||
+ "债券扣款 §7"
|
||||
+ "=============");
|
||||
tp.sendMessage("§c扣款: §c-"
|
||||
+ amount + " §6枚");
|
||||
tp.sendMessage("§7理由: §f"
|
||||
+ reason);
|
||||
tp.sendMessage("§7债券: §e"
|
||||
+ oldBal + " → §c"
|
||||
+ newBal);
|
||||
tp.sendMessage("§7操作者: §e"
|
||||
+ opName);
|
||||
tp.sendMessage(
|
||||
"§7============== §c"
|
||||
+ "债券扣款 §7"
|
||||
+ "=============");
|
||||
}
|
||||
} else {
|
||||
admin.sendMessage("§c" + target
|
||||
+ " 债券不足(当前: "
|
||||
+ oldBal + ")");
|
||||
}
|
||||
} else {
|
||||
if (plugin.getBonds()
|
||||
.isFrozen(target)) {
|
||||
admin.sendMessage("§c" + target
|
||||
+ " 账户已被冻结");
|
||||
return;
|
||||
}
|
||||
int oldBal = plugin.getBonds()
|
||||
.getBonds(target);
|
||||
plugin.getBonds().addBonds(target,
|
||||
amount, txType,
|
||||
"", opName, reason);
|
||||
int newBal = plugin.getBonds()
|
||||
.getBonds(target);
|
||||
admin.sendMessage(
|
||||
"§7============== §6"
|
||||
+ "债券转账 §7"
|
||||
+ "=============");
|
||||
admin.sendMessage(
|
||||
"§7目标: §e" + target);
|
||||
admin.sendMessage("§a到账: §a+"
|
||||
+ amount + " §6枚");
|
||||
admin.sendMessage("§7理由: §f"
|
||||
+ reason);
|
||||
admin.sendMessage("§7债券: §e"
|
||||
+ oldBal + " → §a"
|
||||
+ newBal);
|
||||
admin.sendMessage(
|
||||
"§7============== §6"
|
||||
+ "债券转账 §7"
|
||||
+ "=============");
|
||||
Player tp = plugin.getServer()
|
||||
.getPlayerExact(target);
|
||||
if (tp != null) {
|
||||
tp.sendMessage(
|
||||
"§7============== §a"
|
||||
+ "收到债券 §7"
|
||||
+ "=============");
|
||||
tp.sendMessage("§a到账: §a+"
|
||||
+ amount + " §6枚");
|
||||
tp.sendMessage("§7理由: §f"
|
||||
+ reason);
|
||||
tp.sendMessage("§7债券: §e"
|
||||
+ oldBal + " → §a"
|
||||
+ newBal);
|
||||
tp.sendMessage("§7来自: §e"
|
||||
+ opName);
|
||||
tp.sendMessage(
|
||||
"§7============== §a"
|
||||
+ "收到债券 §7"
|
||||
+ "=============");
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
admin.sendMessage("§c格式错误,请输入 "
|
||||
+ "§a+数字 §7或 §c-数字");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public void cancelInput(String player) {
|
||||
pendingInputs.remove(player);
|
||||
}
|
||||
|
||||
static class CDKInput {
|
||||
String type;
|
||||
String context;
|
||||
long time;
|
||||
}
|
||||
}
|
||||
@@ -35,36 +35,28 @@ public class CheckInManager {
|
||||
}
|
||||
|
||||
Object streakObj = plugin.getDb()
|
||||
.getField(name,
|
||||
"checkin_streak");
|
||||
.getField(name, "checkin_streak");
|
||||
int streak = streakObj != null
|
||||
? ((Number) streakObj)
|
||||
.intValue() : 0;
|
||||
? ((Number) streakObj).intValue()
|
||||
: 0;
|
||||
|
||||
String lastDate = (String) plugin
|
||||
.getDb().getField(name,
|
||||
"last_checkin_date");
|
||||
String today = new SimpleDateFormat(
|
||||
"yyyy-MM-dd")
|
||||
String lastDate = (String) plugin.getDb()
|
||||
.getField(name, "last_checkin_date");
|
||||
String today = new SimpleDateFormat("yyyy-MM-dd")
|
||||
.format(new Date());
|
||||
|
||||
if (lastDate != null
|
||||
&& !lastDate.isEmpty()) {
|
||||
if (lastDate != null && !lastDate.isEmpty()) {
|
||||
try {
|
||||
java.util.Date lastD =
|
||||
new SimpleDateFormat(
|
||||
"yyyy-MM-dd")
|
||||
new SimpleDateFormat("yyyy-MM-dd")
|
||||
.parse(lastDate);
|
||||
java.util.Date todayD =
|
||||
new SimpleDateFormat(
|
||||
"yyyy-MM-dd")
|
||||
new SimpleDateFormat("yyyy-MM-dd")
|
||||
.parse(today);
|
||||
long diff = todayD.getTime()
|
||||
- lastD.getTime();
|
||||
long diff = todayD.getTime() - lastD.getTime();
|
||||
if (diff > 86400000L * 2) {
|
||||
streak = 0;
|
||||
} else if (diff
|
||||
> 86400000L) {
|
||||
} else if (diff > 86400000L) {
|
||||
streak++;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
@@ -79,41 +71,35 @@ public class CheckInManager {
|
||||
else if (streak >= 14) multi = 1.3;
|
||||
else if (streak >= 7) multi = 1.1;
|
||||
|
||||
int basePoints = plugin.getConfig2()
|
||||
.checkinPoints;
|
||||
int points =
|
||||
(int) (basePoints * multi);
|
||||
int basePoints = plugin.getConfig2().checkinPoints;
|
||||
int points = (int) (basePoints * multi);
|
||||
|
||||
plugin.getDb().addPoints(
|
||||
name, points);
|
||||
plugin.getDb().setField(
|
||||
name, "checkin_streak",
|
||||
streak);
|
||||
plugin.getDb().setField(
|
||||
name,
|
||||
"last_checkin_date", today);
|
||||
plugin.getDb().addPoints(name, points);
|
||||
plugin.getDb().setField(name, "checkin_streak", streak);
|
||||
plugin.getDb().setField(name, "last_checkin_date", today);
|
||||
|
||||
Object totalObj = plugin.getDb()
|
||||
.getField(name,
|
||||
"total_checkin_days");
|
||||
.getField(name, "total_checkin_days");
|
||||
int total = totalObj != null
|
||||
? ((Number) totalObj)
|
||||
.intValue() : 0;
|
||||
plugin.getDb().setField(
|
||||
name,
|
||||
"total_checkin_days",
|
||||
? ((Number) totalObj).intValue()
|
||||
: 0;
|
||||
plugin.getDb().setField(name, "total_checkin_days",
|
||||
total + 1);
|
||||
|
||||
return plugin.getConfig2()
|
||||
.msg("checkin_success")
|
||||
.replace("{points}",
|
||||
String.valueOf(points))
|
||||
.replace("{streak}",
|
||||
String.valueOf(streak))
|
||||
.replace("{multi}",
|
||||
String.valueOf(multi));
|
||||
int bondReward = 1;
|
||||
if (streak >= 21) bondReward = 3;
|
||||
else if (streak >= 14) bondReward = 2;
|
||||
else if (streak >= 7) bondReward = 2;
|
||||
plugin.getBonds().addBonds(name, bondReward);
|
||||
|
||||
return plugin.getConfig2().msg("checkin_success")
|
||||
.replace("{points}", String.valueOf(points))
|
||||
.replace("{streak}", String.valueOf(streak))
|
||||
.replace("{multi}", String.valueOf(multi))
|
||||
+ " §e+" + bondReward + "债券";
|
||||
}
|
||||
|
||||
|
||||
// ===== 获取连续天数 =====
|
||||
public int getStreak(String name) {
|
||||
Object val = plugin.getDb()
|
||||
@@ -257,8 +243,12 @@ public class CheckInManager {
|
||||
plugin.getDb().setField(name,
|
||||
"checkin_streak",
|
||||
streak + 1);
|
||||
int bondReward = 1;
|
||||
if (streak >= 21) bondReward = 3;
|
||||
else if (streak >= 14) bondReward = 2;
|
||||
else if (streak >= 7) bondReward = 2;
|
||||
plugin.getBonds().addBonds(name, bondReward);
|
||||
}
|
||||
|
||||
// ===== 标准化日期 =====
|
||||
private String normalizeDate(
|
||||
String input) {
|
||||
|
||||
@@ -0,0 +1,569 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CypayCommand
|
||||
implements CommandExecutor, TabCompleter {
|
||||
|
||||
private final Main plugin;
|
||||
private static final SimpleDateFormat SDF =
|
||||
new SimpleDateFormat(
|
||||
"yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public CypayCommand(Main plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
private boolean hasAdminPerm(CommandSender s) {
|
||||
if (s.isOp()) return true;
|
||||
if (!(s instanceof Player)) return true;
|
||||
Player p = (Player) s;
|
||||
String tag =
|
||||
plugin.getConfig2().adminTag;
|
||||
return !tag.isEmpty()
|
||||
&& p.getScoreboardTags().contains(tag);
|
||||
}
|
||||
|
||||
private int parseAmount(String s) {
|
||||
return Integer.parseInt(
|
||||
s.trim().replaceAll("[+]", ""));
|
||||
}
|
||||
|
||||
private List<String> filter(
|
||||
List<String> list, String prefix) {
|
||||
String low = prefix.toLowerCase();
|
||||
return list.stream()
|
||||
.filter(x -> x.toLowerCase()
|
||||
.startsWith(low))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender,
|
||||
Command cmd,
|
||||
String label,
|
||||
String[] args) {
|
||||
|
||||
// ===== /cypay — 查自己 =====
|
||||
if (args.length == 0) {
|
||||
String name = sender instanceof Player
|
||||
? sender.getName() : "Console";
|
||||
int bonds =
|
||||
plugin.getBonds().getBonds(name);
|
||||
String status = plugin.getBonds()
|
||||
.getAccountStatus(name);
|
||||
sender.sendMessage(
|
||||
"§e§l[债券] §f" + name
|
||||
+ " 余额: §a" + bonds);
|
||||
sender.sendMessage(
|
||||
"§e§l[债券] §f状态: "
|
||||
+ ("frozen".equals(status)
|
||||
? "§c冻结中"
|
||||
: "§a正常"));
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== /cypay import =====
|
||||
if (args.length == 2
|
||||
&& "import".equalsIgnoreCase(
|
||||
args[0])) {
|
||||
if (!hasAdminPerm(sender)) {
|
||||
sender.sendMessage("§c权限不足");
|
||||
return true;
|
||||
}
|
||||
String file = args[1];
|
||||
if (!file.endsWith(".txt"))
|
||||
file += ".txt";
|
||||
int result = plugin.getCDK()
|
||||
.importFromFile(file);
|
||||
if (result == -1)
|
||||
sender.sendMessage("§c文件不存在: "
|
||||
+ file);
|
||||
else
|
||||
sender.sendMessage("§a导入完成: "
|
||||
+ result + " 条");
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== /cypay freeze =====
|
||||
if (args.length == 2
|
||||
&& "freeze".equalsIgnoreCase(
|
||||
args[0])) {
|
||||
if (!hasAdminPerm(sender)) {
|
||||
sender.sendMessage("§c权限不足");
|
||||
return true;
|
||||
}
|
||||
String target = args[1];
|
||||
if (plugin.getBonds().isFrozen(target)) {
|
||||
sender.sendMessage("§c" + target
|
||||
+ " 已处于冻结状态");
|
||||
return true;
|
||||
}
|
||||
plugin.getBonds().freezeAccount(target);
|
||||
sender.sendMessage("§a已冻结 " + target
|
||||
+ " 的债券账户");
|
||||
Player tp = plugin.getServer()
|
||||
.getPlayerExact(target);
|
||||
if (tp != null)
|
||||
tp.sendMessage(
|
||||
"§c§l[系统] 你的债券账户"
|
||||
+ "已被管理员冻结");
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== /cypay unfreeze =====
|
||||
if (args.length == 2
|
||||
&& "unfreeze".equalsIgnoreCase(
|
||||
args[0])) {
|
||||
if (!hasAdminPerm(sender)) {
|
||||
sender.sendMessage("§c权限不足");
|
||||
return true;
|
||||
}
|
||||
String target = args[1];
|
||||
if (!plugin.getBonds()
|
||||
.isFrozen(target)) {
|
||||
sender.sendMessage("§c" + target
|
||||
+ " 未被冻结");
|
||||
return true;
|
||||
}
|
||||
plugin.getBonds().unfreezeAccount(target);
|
||||
sender.sendMessage("§a已解冻 " + target
|
||||
+ " 的债券账户");
|
||||
Player tp = plugin.getServer()
|
||||
.getPlayerExact(target);
|
||||
if (tp != null)
|
||||
tp.sendMessage(
|
||||
"§a§l[系统] 你的债券账户"
|
||||
+ "已被解冻");
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== /cypay water [days] =====
|
||||
if (args.length >= 2
|
||||
&& "water".equalsIgnoreCase(
|
||||
args[0])) {
|
||||
if (!hasAdminPerm(sender)) {
|
||||
sender.sendMessage("§c权限不足");
|
||||
return true;
|
||||
}
|
||||
String target = args[1];
|
||||
int days = 7;
|
||||
if (args.length >= 3) {
|
||||
try {
|
||||
days = Integer.parseInt(args[2]);
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
days = Math.min(days,
|
||||
BondManager.ADMIN_MAX_WATER_DAYS);
|
||||
|
||||
if (days <= 0) days = 1;
|
||||
|
||||
List<Map<String, Object>> txs =
|
||||
plugin.getBonds()
|
||||
.getTransactions(
|
||||
target, days);
|
||||
|
||||
sender.sendMessage(
|
||||
"§e§l===== " + target
|
||||
+ " 流水记录 (近"
|
||||
+ days + "天) =====");
|
||||
sender.sendMessage("§f当前余额: §a"
|
||||
+ plugin.getBonds()
|
||||
.getBonds(target)
|
||||
+ " §f状态: "
|
||||
+ (plugin.getBonds()
|
||||
.isFrozen(target)
|
||||
? "§c冻结" : "§a正常"));
|
||||
|
||||
if (txs.isEmpty()) {
|
||||
sender.sendMessage("§7 (暂无记录)");
|
||||
} else {
|
||||
for (Map<String, Object> tx : txs) {
|
||||
sender.sendMessage(
|
||||
formatTxLine(tx));
|
||||
}
|
||||
}
|
||||
sender.sendMessage(
|
||||
"§e§l=========================="
|
||||
+ "==========");
|
||||
return true;
|
||||
}
|
||||
// ===== /cypay printer [player] =====
|
||||
if ("printer".equalsIgnoreCase(args[0])) {
|
||||
if (!hasAdminPerm(sender)
|
||||
&& !(sender instanceof Player)
|
||||
== false) {
|
||||
// 控制台也可以用
|
||||
if (!(sender instanceof Player))
|
||||
; // 放行
|
||||
else if (!hasAdminPerm(sender)) {
|
||||
sender.sendMessage("§c权限不足");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (args.length == 1) {
|
||||
// 打印全服
|
||||
sender.sendMessage(
|
||||
"§e正在导出全服流水...");
|
||||
plugin.getBonds(); // 确保实例就绪
|
||||
if (plugin.getBondPrinter() != null) {
|
||||
plugin.getBondPrinter()
|
||||
.printAll(sender);
|
||||
} else {
|
||||
sender.sendMessage("§cPrinter未初始化");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (args.length == 2) {
|
||||
// 打印指定玩家
|
||||
String target = args[1];
|
||||
sender.sendMessage("§e正在导出 "
|
||||
+ target + " 的流水...");
|
||||
if (plugin.getBondPrinter() != null) {
|
||||
plugin.getBondPrinter()
|
||||
.printPlayer(target, sender);
|
||||
} else {
|
||||
sender.sendMessage("§cPrinter未初始化");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== /cypay give/remove =====
|
||||
if (args.length == 3) {
|
||||
String action = args[0].toLowerCase();
|
||||
if ("give".equals(action)
|
||||
|| "remove".equals(action)) {
|
||||
if (!hasAdminPerm(sender)) {
|
||||
sender.sendMessage("§c权限不足");
|
||||
return true;
|
||||
}
|
||||
String target = args[1];
|
||||
try {
|
||||
int amount =
|
||||
parseAmount(args[2]);
|
||||
if (amount <= 0) {
|
||||
sender.sendMessage(
|
||||
"§c金额必须大于0");
|
||||
return true;
|
||||
}
|
||||
String txType =
|
||||
"give".equals(action)
|
||||
? BondManager
|
||||
.TX_ADMIN_GIVE
|
||||
: BondManager
|
||||
.TX_ADMIN_DEDUCT;
|
||||
String opName =
|
||||
sender instanceof Player
|
||||
? sender.getName()
|
||||
: "Console";
|
||||
|
||||
if ("give".equals(action)) {
|
||||
plugin.getBonds().addBonds(
|
||||
target, amount,
|
||||
txType, "",
|
||||
opName, "管理员发放");
|
||||
sender.sendMessage(
|
||||
"§a已给 " + target
|
||||
+ " " + amount
|
||||
+ " 债券");
|
||||
Player tp = plugin.getServer()
|
||||
.getPlayerExact(target);
|
||||
if (tp != null)
|
||||
tp.sendMessage(
|
||||
"§a管理员给了你 "
|
||||
+ amount
|
||||
+ " 债券");
|
||||
} else {
|
||||
if (plugin.getBonds()
|
||||
.deductBonds(target,
|
||||
amount,
|
||||
txType, "",
|
||||
opName,
|
||||
"管理员扣除")) {
|
||||
sender.sendMessage(
|
||||
"§a已扣除 " + target
|
||||
+ " " + amount
|
||||
+ " 债券");
|
||||
Player tp = plugin
|
||||
.getServer()
|
||||
.getPlayerExact(
|
||||
target);
|
||||
if (tp != null)
|
||||
tp.sendMessage(
|
||||
"§c管理员扣除了"
|
||||
+ "你的 "
|
||||
+ amount
|
||||
+ " 债券");
|
||||
} else {
|
||||
sender.sendMessage(
|
||||
"§c" + target
|
||||
+ " 债券不足"
|
||||
+ "或账户冻结");
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
sender.sendMessage(
|
||||
"§c金额格式错误");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== /cypay <player> <amount> — 仅玩家转账 =====
|
||||
if (args.length == 2) {
|
||||
try {
|
||||
int amount = parseAmount(args[1]);
|
||||
if (amount <= 0) {
|
||||
sender.sendMessage(
|
||||
"§c金额必须大于0");
|
||||
return true;
|
||||
}
|
||||
// ★ 控制台禁止转账
|
||||
if (!(sender instanceof Player)) {
|
||||
sender.sendMessage(
|
||||
"§c控制台不能转账,请使用 "
|
||||
+ "/cypay give <玩家> <金额>");
|
||||
return true;
|
||||
}
|
||||
Player p = (Player) sender;
|
||||
String target = args[0];
|
||||
BondManager.TransferResult r =
|
||||
plugin.getBonds().transfer(
|
||||
p.getName(),
|
||||
target, amount);
|
||||
if (r.success) {
|
||||
p.sendMessage("§e§l[债券转账] §f向 §a"
|
||||
+ target + " §f转出 §c"
|
||||
+ amount + " §f枚债券");
|
||||
Player tp = plugin.getServer()
|
||||
.getPlayerExact(target);
|
||||
if (tp != null)
|
||||
tp.sendMessage("§e§l[债券转账] §f收到 §a"
|
||||
+ p.getName()
|
||||
+ " §f转入 §a"
|
||||
+ amount + " §f枚债券");
|
||||
} else {
|
||||
p.sendMessage("§c" + r.message
|
||||
+ ",当前: "
|
||||
+ plugin.getBonds()
|
||||
.getBonds(p.getName()));
|
||||
}
|
||||
return true;
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== /cypay <player> — 查指定玩家 =====
|
||||
if (args.length == 1) {
|
||||
String target = args[0];
|
||||
if (hasAdminPerm(sender)) {
|
||||
int bonds = plugin.getBonds()
|
||||
.getBonds(target);
|
||||
String status = plugin.getBonds()
|
||||
.getAccountStatus(target);
|
||||
sender.sendMessage(
|
||||
"§e§l[债券] §f" + target
|
||||
+ " 余额: §a"
|
||||
+ bonds);
|
||||
sender.sendMessage(
|
||||
"§e§l[债券] §f状态: "
|
||||
+ ("frozen".equals(
|
||||
status)
|
||||
? "§c冻结中"
|
||||
: "§a正常"));
|
||||
} else {
|
||||
int bonds = plugin.getBonds()
|
||||
.getBonds(
|
||||
sender.getName());
|
||||
sender.sendMessage(
|
||||
"§e§l[债券] §f你的余额: §a"
|
||||
+ bonds);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
showHelp(sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== 流水行格式化 =====
|
||||
|
||||
private String formatTxLine(
|
||||
Map<String, Object> tx) {
|
||||
String type = (String) tx.get("type");
|
||||
int amount = ((Number) tx.get("amount"))
|
||||
.intValue();
|
||||
String tgt = (String) tx.get("target_player");
|
||||
String reason = (String) tx.get("reason");
|
||||
int bal = ((Number) tx.get("balance_after"))
|
||||
.intValue();
|
||||
long time = ((Number) tx.get("time"))
|
||||
.longValue();
|
||||
|
||||
boolean pos = amount > 0;
|
||||
String sign = pos ? "+" : "";
|
||||
String color = pos ? "§a" : "§c";
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("§7[")
|
||||
.append(SDF.format(new Date(time)))
|
||||
.append("] ")
|
||||
.append(formatTxType(type))
|
||||
.append(" ")
|
||||
.append(color).append(sign)
|
||||
.append(amount).append("§7");
|
||||
if (tgt != null && !tgt.isEmpty())
|
||||
sb.append(" §e↔ ").append(tgt);
|
||||
if (reason != null && !reason.isEmpty())
|
||||
sb.append(" §7(").append(reason)
|
||||
.append(")");
|
||||
sb.append(" §7余额:§f").append(bal);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String formatTxType(String type) {
|
||||
if (type == null) return "§7未知";
|
||||
switch (type) {
|
||||
case BondManager.TX_ADMIN_GIVE:
|
||||
return "§a管理员发放";
|
||||
case BondManager.TX_ADMIN_DEDUCT:
|
||||
return "§c管理员扣除";
|
||||
case BondManager.TX_TRANSFER_OUT:
|
||||
return "§c转出";
|
||||
case BondManager.TX_TRANSFER_IN:
|
||||
return "§a转入";
|
||||
case BondManager.TX_REDEEM:
|
||||
return "§aCDK兑换";
|
||||
case BondManager.TX_SHOP_BUY:
|
||||
return "§e商城购买";
|
||||
case BondManager.TX_DAILY_SIGN:
|
||||
return "§a签到奖励";
|
||||
case BondManager.TX_FREEZE:
|
||||
return "§c冻结";
|
||||
case BondManager.TX_UNFREEZE:
|
||||
return "§a解冻";
|
||||
default:
|
||||
return "§7" + type;
|
||||
}
|
||||
}
|
||||
|
||||
private void showHelp(CommandSender sender) {
|
||||
sender.sendMessage("§e§l债券命令帮助:");
|
||||
sender.sendMessage(
|
||||
"§e/cypay §7- 查询自己余额");
|
||||
sender.sendMessage(
|
||||
"§e/cypay <玩家> §7- 查余额(管理员)");
|
||||
sender.sendMessage(
|
||||
"§e/cypay <玩家> <金额> §7- 转账");
|
||||
if (hasAdminPerm(sender)) {
|
||||
sender.sendMessage(
|
||||
"§e/cypay give <玩家> <金额> §7- 给债券");
|
||||
sender.sendMessage(
|
||||
"§e/cypay remove <玩家> <金额> §7- 扣债券");
|
||||
sender.sendMessage(
|
||||
"§e/cypay import <文件> §7- 导入CDK");
|
||||
sender.sendMessage(
|
||||
"§e/cypay freeze <玩家> §7- 冻结");
|
||||
sender.sendMessage(
|
||||
"§e/cypay unfreeze <玩家> §7- 解冻");
|
||||
sender.sendMessage(
|
||||
"§e/cypay water <玩家> [天数] §7- 流水");
|
||||
sender.sendMessage(
|
||||
"§e/cypay printer §7- 导出全服流水xlsx");
|
||||
sender.sendMessage(
|
||||
"§e/cypay printer <玩家> §7- 导出指定玩家xlsx");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(
|
||||
CommandSender sender, Command command,
|
||||
String label, String[] args) {
|
||||
List<String> list = new ArrayList<>();
|
||||
|
||||
if (args.length == 1) {
|
||||
String last = args[0].toLowerCase();
|
||||
if (hasAdminPerm(sender)) {
|
||||
list.add("give");
|
||||
list.add("remove");
|
||||
list.add("import");
|
||||
list.add("freeze");
|
||||
list.add("unfreeze");
|
||||
list.add("water");
|
||||
list.add("printer");
|
||||
|
||||
File cdkDir = new File(
|
||||
plugin.getDataFolder(),
|
||||
"cdk");
|
||||
if (cdkDir.exists()) {
|
||||
File[] files = cdkDir.listFiles(
|
||||
(d, n) -> n
|
||||
.endsWith(".txt"));
|
||||
if (files != null)
|
||||
for (File f : files)
|
||||
list.add(f.getName());
|
||||
}
|
||||
}
|
||||
for (Player o :
|
||||
Bukkit.getOnlinePlayers())
|
||||
list.add(o.getName());
|
||||
return filter(list, last);
|
||||
}
|
||||
|
||||
if (args.length == 2) {
|
||||
String first = args[0].toLowerCase();
|
||||
if ("give".equals(first)
|
||||
|| "remove".equals(first)
|
||||
|| "freeze".equals(first)
|
||||
|| "unfreeze".equals(first)
|
||||
|| "water".equals(first)) {
|
||||
for (Player o :
|
||||
Bukkit.getOnlinePlayers())
|
||||
list.add(o.getName());
|
||||
return filter(list,
|
||||
args[1].toLowerCase());
|
||||
}
|
||||
list.add("10");
|
||||
list.add("50");
|
||||
list.add("100");
|
||||
return filter(list,
|
||||
args[1].toLowerCase());
|
||||
}
|
||||
|
||||
if (args.length == 3) {
|
||||
String first = args[0].toLowerCase();
|
||||
if ("give".equals(first)
|
||||
|| "remove".equals(first)) {
|
||||
list.add("10");
|
||||
list.add("50");
|
||||
list.add("100");
|
||||
list.add("999");
|
||||
return filter(list,
|
||||
args[2].toLowerCase());
|
||||
}
|
||||
if ("water".equals(first)) {
|
||||
list.add("1");
|
||||
list.add("3");
|
||||
list.add("7");
|
||||
list.add("14");
|
||||
return filter(list,
|
||||
args[2].toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -189,10 +189,10 @@ public class GUIManager implements Listener {
|
||||
myBal = reg.getProvider()
|
||||
.getBalance(p);
|
||||
} catch (Exception ignored) {}
|
||||
g.setItem(32, mkItem(Material.DIAMOND,
|
||||
"§a§l余额查询",
|
||||
"§7我的余额: §a$"
|
||||
+ String.format("%.2f", myBal)));
|
||||
int myBonds = plugin.getBonds().getBonds(p.getName());
|
||||
g.setItem(32, mkItem(Material.EMERALD,
|
||||
"§b§l我的钱包",
|
||||
"§7查看债券余额与流水记录"));
|
||||
|
||||
// ★ 余额操作 - 仅OP和tag玩家 ★
|
||||
if (isAdmin(p)) {
|
||||
@@ -309,50 +309,34 @@ public class GUIManager implements Listener {
|
||||
}
|
||||
|
||||
public void openBalanceOps(Player p) {
|
||||
Inventory g = Bukkit.createInventory(
|
||||
null, 54,
|
||||
"§d§l余额操作");
|
||||
Inventory g = Bukkit.createInventory(null, 54, "§d§l余额操作");
|
||||
fillBg(g);
|
||||
|
||||
net.milkbowl.vault.economy.Economy eco = null;
|
||||
try {
|
||||
var reg = plugin.getServer()
|
||||
.getServicesManager()
|
||||
.getRegistration(
|
||||
net.milkbowl
|
||||
.vault
|
||||
.economy
|
||||
.Economy
|
||||
.class);
|
||||
var reg = plugin.getServer().getServicesManager()
|
||||
.getRegistration(net.milkbowl.vault.economy.Economy.class);
|
||||
if (reg != null) eco = reg.getProvider();
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
int slot = 0;
|
||||
for (Player op :
|
||||
Bukkit.getOnlinePlayers()) {
|
||||
for (Player op : Bukkit.getOnlinePlayers()) {
|
||||
if (slot >= 45) break;
|
||||
double bal = 0;
|
||||
if (eco != null) bal =
|
||||
eco.getBalance(op);
|
||||
|
||||
boolean isSelf =
|
||||
op.getName().equals(p.getName());
|
||||
|
||||
if (eco != null) bal = eco.getBalance(op);
|
||||
boolean isSelf = op.getName().equals(p.getName());
|
||||
int bondBal = plugin.getBonds().getBonds(op.getName());
|
||||
g.setItem(slot, mkItem(
|
||||
isSelf ? Material.EMERALD_BLOCK
|
||||
: Material.PLAYER_HEAD,
|
||||
isSelf ? Material.EMERALD_BLOCK : Material.PLAYER_HEAD,
|
||||
"§e" + op.getName(),
|
||||
"§7余额: §a$"
|
||||
+ String.format(
|
||||
"%.2f", bal),
|
||||
"§7金币: §a$" + String.format("%.2f", bal),
|
||||
"§7债券: §e" + bondBal + " §6枚",
|
||||
"",
|
||||
"§a左键给钱 §c右键扣钱"));
|
||||
"§a左键经济(±) §6右键债券(±)"));
|
||||
slot++;
|
||||
}
|
||||
|
||||
g.setItem(49, mkItem(Material.ARROW,
|
||||
"§7返回"));
|
||||
|
||||
g.setItem(49, mkItem(Material.ARROW, "§7返回"));
|
||||
p.openInventory(g);
|
||||
}
|
||||
|
||||
@@ -750,7 +734,67 @@ public class GUIManager implements Listener {
|
||||
e.getOldCursor().clone();
|
||||
}
|
||||
}
|
||||
@EventHandler
|
||||
public void onInvClick(InventoryClickEvent event) {
|
||||
if (!(event.getWhoClicked() instanceof Player)) return;
|
||||
Player p = (Player) event.getWhoClicked();
|
||||
String title = event.getView().getTitle();
|
||||
int raw = event.getRawSlot();
|
||||
|
||||
// 我的信息 — CDK兑换 (slot 16)
|
||||
if (T_MY_INFO.equals(title) && raw == 16) {
|
||||
event.setCancelled(true);
|
||||
p.closeInventory();
|
||||
p.sendMessage("§e§l[CDK] §f请输入兑换码(聊天输入):");
|
||||
plugin.getCDK().requestInput(p, "cdk", "");
|
||||
return;
|
||||
}
|
||||
if (T_MAIN.equals(title) && raw == 32) {
|
||||
event.setCancelled(true);
|
||||
plugin.openMyWallet(p);
|
||||
return;
|
||||
}
|
||||
// 余额操作界面
|
||||
// 余额操作界面
|
||||
if ("§d§l余额操作".equals(title)) {
|
||||
event.setCancelled(true);
|
||||
if (raw == 49) {
|
||||
openAdmin(p);
|
||||
return;
|
||||
}
|
||||
if (raw < 0 || raw >= 45) return;
|
||||
ItemStack item = event.getView().getTopInventory().getItem(raw);
|
||||
if (item == null) return;
|
||||
Material mat = item.getType();
|
||||
if (mat != Material.PLAYER_HEAD && mat != Material.EMERALD_BLOCK) return;
|
||||
ItemMeta im = item.getItemMeta();
|
||||
if (im == null || im.getDisplayName() == null) return;
|
||||
String targetName = im.getDisplayName().replaceAll("§[0-9a-fk-orA-FK-OR]", "");
|
||||
Player target = plugin.getServer().getPlayerExact(targetName);
|
||||
if (target == null) return;
|
||||
String adminTag = plugin.getConfig2().adminTag;
|
||||
boolean isAdmin = p.isOp() || (!adminTag.isEmpty() && p.getScoreboardTags().contains(adminTag));
|
||||
if (!isAdmin) return;
|
||||
|
||||
boolean leftOnly = event.isLeftClick() && !event.isRightClick();
|
||||
boolean rightOnly = event.isRightClick() && !event.isLeftClick();
|
||||
|
||||
if (leftOnly) {
|
||||
event.setCancelled(true);
|
||||
p.closeInventory();
|
||||
p.sendMessage("§e§l[经济操作] §f对 §a" + targetName + " §f进行经济操作");
|
||||
p.sendMessage("§7输入 §a+数字 §7给钱,§c-数字 §7扣钱");
|
||||
plugin.getCDK().requestInput(p, "econ", targetName);
|
||||
} else if (rightOnly) {
|
||||
event.setCancelled(true);
|
||||
p.closeInventory();
|
||||
p.sendMessage("§e§l[债券操作] §f对 §a" + targetName + " §f进行债券操作");
|
||||
p.sendMessage("§7输入 §a+数字 §7给债券,§c-数字 §7扣债券");
|
||||
plugin.getCDK().requestInput(p, "bond", targetName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void openEditor(
|
||||
Player p, int idx) {
|
||||
@@ -842,6 +886,19 @@ public class GUIManager implements Listener {
|
||||
g.setItem(15, mkItem(Material.EMERALD_BLOCK,
|
||||
"§a§l积分商城",
|
||||
"§7当前: " + points + "积分"));
|
||||
// CDK兑换入口
|
||||
g.setItem(16, mkItem(Material.BOOK,
|
||||
"§6§lCDK兑换",
|
||||
"§7输入兑换码获取债券",
|
||||
"",
|
||||
"§e点击输入兑换码"));
|
||||
|
||||
// 显示当前债券余额
|
||||
int bondBal = plugin.getBonds().getBonds(p.getName());
|
||||
g.setItem(20, mkItem(Material.EMERALD,
|
||||
"§e§l我的债券: " + bondBal + "枚",
|
||||
"§7债券可通过签到、CDK、转账获得"));
|
||||
|
||||
|
||||
|
||||
// 图标说明
|
||||
@@ -874,6 +931,15 @@ public class GUIManager implements Listener {
|
||||
g.setItem(32, mkItem(Material.WOODEN_SHOVEL,
|
||||
"§c清除图标",
|
||||
"§7点击可清除已保存的图标"));
|
||||
String ugLabel = "§7(玩家)";
|
||||
if (plugin.getUserGroup() != null) {
|
||||
UserGroupManager.UserGroupConfig ugCfg =
|
||||
plugin.getUserGroup().getHighestGroup(p.getName());
|
||||
if (ugCfg != null) {
|
||||
ugLabel = ugCfg.displayColor + ugCfg.displayName;
|
||||
}
|
||||
}
|
||||
g.setItem(22, mkItem(Material.PAPER, "§e§l我的用户组", "§7当前: " + ugLabel));
|
||||
|
||||
g.setItem(26, mkItem(Material.ARROW, "§7返回"));
|
||||
p.openInventory(g);
|
||||
@@ -956,6 +1022,30 @@ public class GUIManager implements Listener {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* 处理"我的信息"GUI中CDK兑换按钮的点击
|
||||
* 返回 true 表示已处理
|
||||
*/
|
||||
public boolean handleCDKClick(Player p,
|
||||
InventoryClickEvent event) {
|
||||
String title = event.getView().getTitle();
|
||||
int raw = event.getRawSlot();
|
||||
|
||||
if (!T_MY_INFO.equals(title)) return false;
|
||||
|
||||
// CDK兑换按钮 (slot 16)
|
||||
if (raw == 16) {
|
||||
event.setCancelled(true);
|
||||
p.closeInventory();
|
||||
p.sendMessage(
|
||||
"§e§l[CDK] §f请输入兑换码"
|
||||
+ "(直接输入聊天即可):");
|
||||
plugin.getCDK().requestInput(
|
||||
p, "cdk", "");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ===== 放入自定义图标 =====
|
||||
private void doPlaceIcon(Player p,
|
||||
@@ -1557,14 +1647,22 @@ public class GUIManager implements Listener {
|
||||
"§7左键: 切换开关",
|
||||
"§7右键: 设置时长",
|
||||
"§7当前: " + afkMin + "分钟"));
|
||||
g.setItem(22, mkItem(Material.ARROW, "§7返回"));
|
||||
// openAdmin 方法中加:
|
||||
g.setItem(15, mkItem(Material.EMERALD_BLOCK,
|
||||
"§d§l余额操作",
|
||||
"§7给钱、扣钱"));
|
||||
|
||||
p.openInventory(g);
|
||||
String ugLabel = "§7(玩家)";
|
||||
if (plugin.getUserGroup() != null) {
|
||||
UserGroupManager.UserGroupConfig ugCfg =
|
||||
plugin.getUserGroup().getHighestGroup(p.getName());
|
||||
if (ugCfg != null) {
|
||||
ugLabel = ugCfg.displayColor + ugCfg.displayName;
|
||||
}
|
||||
}
|
||||
g.setItem(22, mkItem(Material.NETHERITE_UPGRADE_SMITHING_TEMPLATE,
|
||||
"§e§l我的用户组",
|
||||
"§7当前: " + ugLabel));
|
||||
|
||||
g.setItem(24, mkItem(Material.ARROW, "§7返回"));
|
||||
// openAdmin 方法中加:
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void openInvite(Player p) {
|
||||
@@ -1593,6 +1691,16 @@ public class GUIManager implements Listener {
|
||||
"§7绑定他人的邀请码",
|
||||
"",
|
||||
"§e点击输入"));
|
||||
String ugLabel = "§7(玩家)";
|
||||
UserGroupManager.UserGroupConfig ugCfg =
|
||||
plugin.getUserGroup().getHighestGroup(p.getName());
|
||||
if (ugCfg != null) {
|
||||
ugLabel = ugCfg.displayColor + ugCfg.displayName;
|
||||
}
|
||||
g.setItem(22, mkItem(Material.ENCHANTED_BOOK,
|
||||
"§e§l我的用户组",
|
||||
"§7当前: " + ugLabel));
|
||||
|
||||
g.setItem(26, mkItem(Material.ARROW, "§7返回"));
|
||||
p.openInventory(g);
|
||||
}
|
||||
|
||||
+558
-207
@@ -16,6 +16,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
|
||||
@@ -126,6 +127,29 @@ public class Main extends JavaPlugin
|
||||
// 余额操作待处理
|
||||
private String balanceTarget = null;
|
||||
private boolean balanceGiveMode = false;
|
||||
private BondManager bondManager;
|
||||
private CDKManager cdkManager;
|
||||
private UserGroupManager userGroupManager;
|
||||
private CypayCommand cypayCommand;
|
||||
public BondManager getBonds() { return bondManager; }
|
||||
public CDKManager getCDK() { return cdkManager; }
|
||||
public UserGroupManager getUserGroup() { return userGroupManager; }
|
||||
public CypayCommand getCypay() { return cypayCommand; }
|
||||
// 在 Main.java 字段声明区域(bondManager 附近)添加:
|
||||
// 在 bondManager 字段附近添加:
|
||||
private BondPrinter bondPrinter;
|
||||
|
||||
// 钱包流水查看目标(玩家名 → 查看目标)
|
||||
private final Map<String, String> walletViewTarget =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
// 流水格式化
|
||||
private static final java.text.SimpleDateFormat
|
||||
TX_SDF = new java.text.SimpleDateFormat(
|
||||
"yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public CommissionManager getCommission() {
|
||||
@@ -345,6 +369,10 @@ private PVPManager pvpManager;
|
||||
getCommand("oa").setExecutor(this);
|
||||
if (getCommand("menu") != null)
|
||||
getCommand("menu").setExecutor(this);
|
||||
if (getCommand("printer") != null) {
|
||||
getCommand("printer").setExecutor(this);
|
||||
}
|
||||
|
||||
|
||||
// ===== 10. 注册事件 =====
|
||||
getServer().getPluginManager()
|
||||
@@ -368,6 +396,13 @@ private PVPManager pvpManager;
|
||||
}
|
||||
}
|
||||
}.runTaskTimer(this, 600L, 600L);
|
||||
// [新增] Printer 文件清理(每小时检测一次)
|
||||
new BukkitRunnable() {
|
||||
public void run() {
|
||||
if (bondPrinter != null)
|
||||
bondPrinter.cleanupOldFiles();
|
||||
}
|
||||
}.runTaskTimer(this, 1200L, 72000L);
|
||||
|
||||
// 12 ===PVP===
|
||||
pvpManager = new PVPManager(this);
|
||||
@@ -380,8 +415,28 @@ private PVPManager pvpManager;
|
||||
20L, 20L);
|
||||
setupPVPTab();
|
||||
|
||||
// 13 ====cypay债券====
|
||||
// 债券系统(独立DB)
|
||||
bondManager = new BondManager(this);
|
||||
cdkManager = new CDKManager(this);
|
||||
userGroupManager = new UserGroupManager(this);
|
||||
cypayCommand = new CypayCommand(this);
|
||||
// 加载CDK文件
|
||||
cdkManager.loadCDKsFromDir();
|
||||
// cypay 命令
|
||||
if (getCommand("cypay") != null) {
|
||||
getCommand("cypay").setExecutor(cypayCommand);
|
||||
getCommand("cypay").setTabCompleter(cypayCommand);
|
||||
}
|
||||
bondPrinter = new BondPrinter(this);
|
||||
cdkManager.loadCDKsFromDir();
|
||||
|
||||
|
||||
getLogger().info("Sdf1_login v1.0 | 就绪");
|
||||
}
|
||||
public BondPrinter getBondPrinter() {
|
||||
return bondPrinter;
|
||||
}
|
||||
|
||||
private void setupPVPTab() {
|
||||
if (getCommand("pvp") != null) {
|
||||
@@ -583,6 +638,15 @@ private PVPManager pvpManager;
|
||||
public boolean isFrozen(Player p) {
|
||||
return !loggedIn.contains(p.getName());
|
||||
}
|
||||
private String debugCodePoints(String s) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
if (i > 0) sb.append(" ");
|
||||
sb.append(String.format("U+%04X",
|
||||
(int) s.charAt(i)));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private boolean isAdmin(CommandSender sender) {
|
||||
if (sender instanceof Player)
|
||||
@@ -1323,16 +1387,23 @@ private PVPManager pvpManager;
|
||||
// 强制重发资源包(清掉客户端拒绝记录)
|
||||
Bukkit.getScheduler()
|
||||
.runTaskLater(this, () -> {
|
||||
if (p.isOnline()
|
||||
&& radio != null) {
|
||||
radio.sendResourcePack(p);
|
||||
// ★ 资源包:只发送一次,延迟3秒确保客户端就绪
|
||||
if (radio != null) {
|
||||
final Player jp = p;
|
||||
Bukkit.getScheduler().runTaskLater(
|
||||
this, () -> {
|
||||
if (jp.isOnline()) {
|
||||
radio.sendResourcePack(jp);
|
||||
}
|
||||
}, 60L);
|
||||
}
|
||||
|
||||
}, 20L);
|
||||
// 重置聊天输入状态(防止上次退出时残留)
|
||||
chatInput.reset(p);
|
||||
if (needsPasswordChange.contains(name))
|
||||
needsPasswordChange.remove(name);
|
||||
|
||||
// 改完了直接打包,要打sdf1一起
|
||||
lastActivity.put(p.getUniqueId(),
|
||||
System.currentTimeMillis());
|
||||
if (needsPasswordChange.contains(name))
|
||||
@@ -1371,6 +1442,7 @@ private PVPManager pvpManager;
|
||||
}
|
||||
}
|
||||
// [ADDED] 资源包(延迟发送,不强制)
|
||||
// ★ 资源包:只发送一次,延迟3秒确保客户端就绪
|
||||
if (radio != null) {
|
||||
final Player jp = p;
|
||||
Bukkit.getScheduler().runTaskLater(
|
||||
@@ -1397,20 +1469,32 @@ private PVPManager pvpManager;
|
||||
afk.recordAction(p.getUniqueId());
|
||||
hideInventoryDelayed(p, 0);
|
||||
// [ADDED] 发送资源包
|
||||
if (radio != null) radio.sendResourcePack(p);
|
||||
|
||||
// [ADDED] 发送资源包(延迟5秒确保客户端就绪)
|
||||
// ★ 资源包:只发送一次,延迟3秒确保客户端就绪
|
||||
if (radio != null) {
|
||||
final Player jp = p;
|
||||
Bukkit.getScheduler().runTaskLater(this,
|
||||
() -> {
|
||||
Bukkit.getScheduler().runTaskLater(
|
||||
this, () -> {
|
||||
if (jp.isOnline()) {
|
||||
radio.sendResourcePack(jp);
|
||||
}
|
||||
}, 100L);
|
||||
}, 60L);
|
||||
}
|
||||
|
||||
|
||||
// [ADDED] 发送资源包(延迟5秒确保客户端就绪)
|
||||
// ★ 资源包:只发送一次,延迟3秒确保客户端就绪
|
||||
if (radio != null) {
|
||||
final Player jp = p;
|
||||
Bukkit.getScheduler().runTaskLater(
|
||||
this, () -> {
|
||||
if (jp.isOnline()) {
|
||||
radio.sendResourcePack(jp);
|
||||
}
|
||||
}, 60L);
|
||||
}
|
||||
|
||||
|
||||
|
||||
verification.verifyPremiumAsync(p,
|
||||
isPremium -> {
|
||||
if (isPremium) {
|
||||
@@ -1903,130 +1987,20 @@ private PVPManager pvpManager;
|
||||
public void onChat(AsyncPlayerChatEvent e) {
|
||||
Player p = e.getPlayer();
|
||||
String msg = e.getMessage();
|
||||
// ★ CDK/经济/债券输入拦截 — 必须在所有聊天处理之前
|
||||
if (this.getCDK() != null && this.getCDK().isListening(p.getName())) {
|
||||
e.setCancelled(true);
|
||||
this.getCDK().onChat(p, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// ===== 菜单聊天输入 =====
|
||||
if (getMenu().isEditing(p.getName())) {
|
||||
e.setCancelled(true);
|
||||
getMenu().onChat(p, msg);
|
||||
return;
|
||||
}
|
||||
// ===== 余额操作输入 =====
|
||||
if (balanceTarget != null) {
|
||||
e.setCancelled(true);
|
||||
String input = msg.trim();
|
||||
String[] parts = input.split("\\s+", 2);
|
||||
if (parts.length < 2
|
||||
|| parts[0].isEmpty()
|
||||
|| parts[1].isEmpty()) {
|
||||
p.sendMessage("§c格式: 金额 理由");
|
||||
p.sendMessage("§7例: 100 测试扣款");
|
||||
balanceTarget = null;
|
||||
return;
|
||||
}
|
||||
double amount;
|
||||
try {
|
||||
amount = Double.parseDouble(parts[0]);
|
||||
} catch (NumberFormatException ex) {
|
||||
p.sendMessage("§c金额格式错误");
|
||||
balanceTarget = null;
|
||||
return;
|
||||
}
|
||||
if (amount <= 0) {
|
||||
p.sendMessage("§c金额必须大于0");
|
||||
balanceTarget = null;
|
||||
return;
|
||||
}
|
||||
String reason = parts[1];
|
||||
String target = balanceTarget;
|
||||
boolean giveMode = balanceGiveMode;
|
||||
balanceTarget = null;
|
||||
var reg = getServer().getServicesManager()
|
||||
.getRegistration(
|
||||
net.milkbowl.vault.economy
|
||||
.Economy.class);
|
||||
if (reg == null) {
|
||||
p.sendMessage("§c经济系统未加载");
|
||||
return;
|
||||
}
|
||||
net.milkbowl.vault.economy.Economy eco =
|
||||
reg.getProvider();
|
||||
Player tp = Bukkit.getPlayer(target);
|
||||
if (tp == null || !tp.isOnline()) {
|
||||
p.sendMessage(
|
||||
"§c玩家不在线: "
|
||||
+ target);
|
||||
return;
|
||||
}
|
||||
if (giveMode) {
|
||||
var resp =
|
||||
eco.depositPlayer(target, amount);
|
||||
if (resp.transactionSuccess()) {
|
||||
double oldBal = resp.balance
|
||||
- amount;
|
||||
double newBal = resp.balance;
|
||||
// 操作者看到的
|
||||
p.sendMessage("");
|
||||
p.sendMessage("§6【余额转账】正在执行...");
|
||||
p.sendMessage("§7============== §6余额转账 §7==============");
|
||||
p.sendMessage("§7目标: §e" + target);
|
||||
p.sendMessage("§a到账: §e$" + String.format("%.2f", amount));
|
||||
p.sendMessage("§7余额: §e" + String.format("%.2f", oldBal) + " → §a" + String.format("%.2f", newBal));
|
||||
p.sendMessage("§7理由: §f" + reason);
|
||||
p.sendMessage("§7============== §6余额转账 §7==============");
|
||||
p.sendMessage("");
|
||||
// 被转账者看到的
|
||||
tp.sendMessage("");
|
||||
tp.sendMessage("§a【收到转账】一笔新入账...");
|
||||
tp.sendMessage("§7============== §a收到转账 §7==============");
|
||||
tp.sendMessage("§a到账: §e$" + String.format("%.2f", amount));
|
||||
tp.sendMessage("§7余额: §e" + String.format("%.2f", oldBal) + " → §a" + String.format("%.2f", newBal));
|
||||
tp.sendMessage("§7来自: §e" + p.getName());
|
||||
tp.sendMessage("§7理由: §f" + reason);
|
||||
tp.sendMessage("§7============== §a收到转账 §7==============");
|
||||
tp.sendMessage("");
|
||||
} else {
|
||||
p.sendMessage("§c转账失败: "
|
||||
+ resp.errorMessage);
|
||||
}
|
||||
} else {
|
||||
double bal = eco.getBalance(target);
|
||||
if (bal < amount) {
|
||||
p.sendMessage("§c" + target
|
||||
+ " 余额不足");
|
||||
p.sendMessage("§7当前余额: §e$"
|
||||
+ String.format("%.2f", bal));
|
||||
return;
|
||||
}
|
||||
var resp =
|
||||
eco.withdrawPlayer(target, amount);
|
||||
if (resp.transactionSuccess()) {
|
||||
double oldBal = resp.balance + amount;
|
||||
double newBal = resp.balance;
|
||||
// 操作者看到的
|
||||
p.sendMessage("");
|
||||
p.sendMessage("§c【余额扣款】正在执行...");
|
||||
p.sendMessage("§7============== §c余额扣款 §7==============");
|
||||
p.sendMessage("§7目标: §e" + target);
|
||||
p.sendMessage("§c扣款: §e-$" + String.format("%.2f", amount));
|
||||
p.sendMessage("§7余额: §e" + String.format("%.2f", oldBal) + " → §c" + String.format("%.2f", newBal));
|
||||
p.sendMessage("§7理由: §f" + reason);
|
||||
p.sendMessage("§7============== §c余额扣款 §7==============");
|
||||
p.sendMessage("");
|
||||
// 被扣款者看到的
|
||||
tp.sendMessage("");
|
||||
tp.sendMessage("§c【余额扣款】你的账户收到扣款...");
|
||||
tp.sendMessage("§7============== §c余额扣款 §7==============");
|
||||
tp.sendMessage("§c扣款: §e-$" + String.format("%.2f", amount));
|
||||
tp.sendMessage("§7余额: §e" + String.format("%.2f", oldBal) + " → §c" + String.format("%.2f", newBal));
|
||||
tp.sendMessage("§7操作者: §e" + p.getName());
|
||||
tp.sendMessage("§7理由: §f" + reason);
|
||||
tp.sendMessage("§7============== §c余额扣款 §7==============");
|
||||
tp.sendMessage("");
|
||||
} else {
|
||||
p.sendMessage("§c扣款失败: "
|
||||
+ resp.errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 冻结检查 =====
|
||||
if (isFrozen(p)) {
|
||||
@@ -2176,11 +2150,251 @@ private PVPManager pvpManager;
|
||||
public PVPManager getPVPManager() {
|
||||
return pvpManager;
|
||||
}
|
||||
// ==================== 我的钱包 ====================
|
||||
|
||||
public void openMyWallet(Player p) {
|
||||
openWalletGUI(p, p.getName());
|
||||
}
|
||||
|
||||
private void openWalletGUI(Player p, String target) {
|
||||
Inventory g = Bukkit.createInventory(
|
||||
null, 27, "§6§l我的钱包");
|
||||
|
||||
|
||||
ItemStack glass = new ItemStack(
|
||||
Material.GRAY_STAINED_GLASS_PANE);
|
||||
ItemMeta gm = glass.getItemMeta();
|
||||
if (gm != null) {
|
||||
gm.setDisplayName(" ");
|
||||
glass.setItemMeta(gm);
|
||||
}
|
||||
for (int i = 0; i < 27; i++)
|
||||
g.setItem(i, glass);
|
||||
|
||||
// 债券余额 (slot 4)
|
||||
int balance = bondManager.getBonds(target);
|
||||
g.setItem(4, mkItem(Material.GOLD_INGOT,
|
||||
"§6§l债券余额: §e" + balance + " §6枚",
|
||||
"§7当前持有的债券数量"));
|
||||
|
||||
// 账户状态 (slot 10)
|
||||
String status = bondManager
|
||||
.getAccountStatus(target);
|
||||
if ("frozen".equals(status)) {
|
||||
g.setItem(10, mkItem(Material.REDSTONE,
|
||||
"§c§l账户状态: 冻结中",
|
||||
"§7无法进行任何资金交易"));
|
||||
} else {
|
||||
g.setItem(10, mkItem(Material.EMERALD,
|
||||
"§a§l账户状态: 正常",
|
||||
"§7账户运行正常"));
|
||||
}
|
||||
|
||||
// 同IP玩家 (slot 11)
|
||||
if (target.equals(p.getName())) {
|
||||
List<String> sameIP = bondManager
|
||||
.getSameIPPlayers(target);
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add("§7同IP下注册的其他玩家:");
|
||||
if (sameIP.isEmpty()) {
|
||||
lore.add("§a 无");
|
||||
} else {
|
||||
for (String n : sameIP)
|
||||
lore.add("§c - " + n);
|
||||
}
|
||||
lore.add("§7");
|
||||
lore.add("§7转账限制:");
|
||||
lore.add("§7 不能与同IP玩家转账");
|
||||
g.setItem(11, mkItem(Material.PAPER,
|
||||
"§e§l同IP玩家", lore.toArray(
|
||||
new String[0])));
|
||||
}
|
||||
|
||||
// 近7天流水 (slot 12)
|
||||
g.setItem(12, mkItem(Material.BOOK,
|
||||
"§e近7天流水",
|
||||
"§7点击查看最近7天交易记录"));
|
||||
|
||||
// 近14天流水 (slot 13)
|
||||
g.setItem(13, mkItem(Material.BOOK,
|
||||
"§e近14天流水",
|
||||
"§7点击查看最近14天交易记录",
|
||||
"§8(系统最大支持14天)"));
|
||||
|
||||
// 转账记录 (slot 14)
|
||||
g.setItem(14, mkItem(Material.PAPER,
|
||||
"§e转账记录",
|
||||
"§7点击查看所有转账明细"));
|
||||
|
||||
// 返回 (slot 22)
|
||||
g.setItem(22, mkItem(Material.ARROW,
|
||||
"§7← 返回"));
|
||||
|
||||
walletViewTarget.put(p.getName(), target);
|
||||
p.openInventory(g);
|
||||
}
|
||||
|
||||
// ==================== 流水记录GUI ====================
|
||||
|
||||
public void openWalletTransactions(
|
||||
Player p, int days) {
|
||||
String target = walletViewTarget.getOrDefault(
|
||||
p.getName(), p.getName());
|
||||
openWalletTxGUI(p, target, days, false);
|
||||
}
|
||||
|
||||
public void openWalletTransfers(Player p) {
|
||||
String target = walletViewTarget.getOrDefault(
|
||||
p.getName(), p.getName());
|
||||
openWalletTxGUI(p, target, 14, true);
|
||||
}
|
||||
|
||||
private void openWalletTxGUI(Player p,
|
||||
String target,
|
||||
int days,
|
||||
boolean transfersOnly) {
|
||||
days = Math.min(days, 14); // 普通玩家最大14天
|
||||
String title = transfersOnly
|
||||
? "§e§l转账记录 - " + target
|
||||
: "§e§l流水记录 - 近" + days
|
||||
+ "天 - " + target;
|
||||
|
||||
Inventory g = Bukkit.createInventory(
|
||||
null, 54, title);
|
||||
|
||||
List<Map<String, Object>> txs = transfersOnly
|
||||
? bondManager.getTransferTransactions(
|
||||
target, days)
|
||||
: bondManager.getTransactions(
|
||||
target, days);
|
||||
|
||||
int slot = 0;
|
||||
for (Map<String, Object> tx : txs) {
|
||||
if (slot >= 45) break;
|
||||
|
||||
String type = (String) tx.get("type");
|
||||
int amount = ((Number) tx.get("amount"))
|
||||
.intValue();
|
||||
String tgt = (String) tx.get("target_player");
|
||||
String reason = (String) tx.get("reason");
|
||||
int bal = ((Number) tx.get("balance_after"))
|
||||
.intValue();
|
||||
long time = ((Number) tx.get("time"))
|
||||
.longValue();
|
||||
|
||||
boolean pos = amount > 0;
|
||||
String sign = pos ? "+" : "";
|
||||
String color = pos ? "§a" : "§c";
|
||||
String typeName = walletFormatType(type);
|
||||
|
||||
// 用 if-else 替代 switch 避免编译问题
|
||||
Material mat = Material.PAPER;
|
||||
if (BondManager.TX_TRANSFER_OUT.equals(type)) {
|
||||
mat = Material.RED_WOOL;
|
||||
} else if (BondManager.TX_TRANSFER_IN
|
||||
.equals(type)) {
|
||||
mat = Material.LIME_WOOL;
|
||||
} else if (BondManager.TX_ADMIN_GIVE
|
||||
.equals(type)) {
|
||||
mat = Material.GOLD_BLOCK;
|
||||
} else if (BondManager.TX_ADMIN_DEDUCT
|
||||
.equals(type)) {
|
||||
mat = Material.REDSTONE_BLOCK;
|
||||
} else if (BondManager.TX_REDEEM
|
||||
.equals(type)) {
|
||||
mat = Material.NAME_TAG;
|
||||
} else if (BondManager.TX_SHOP_BUY
|
||||
.equals(type)) {
|
||||
mat = Material.CHEST;
|
||||
} else if (BondManager.TX_FREEZE
|
||||
.equals(type)) {
|
||||
mat = Material.BARRIER;
|
||||
} else if (BondManager.TX_UNFREEZE
|
||||
.equals(type)) {
|
||||
mat = Material.EMERALD_BLOCK;
|
||||
} else if (pos) {
|
||||
mat = Material.LIME_WOOL;
|
||||
} else {
|
||||
mat = Material.RED_WOOL;
|
||||
}
|
||||
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add("§7类型: " + typeName);
|
||||
lore.add("§7金额: " + color + sign + amount
|
||||
+ " §6枚");
|
||||
if (tgt != null && !tgt.isEmpty())
|
||||
lore.add("§7对象: §e" + tgt);
|
||||
if (reason != null && !reason.isEmpty())
|
||||
lore.add("§7理由: §7" + reason);
|
||||
lore.add("§7余额: §f" + bal + " §6枚");
|
||||
lore.add("§8" + TX_SDF.format(
|
||||
new Date(time)));
|
||||
|
||||
ItemStack item = new ItemStack(mat);
|
||||
ItemMeta im = item.getItemMeta();
|
||||
if (im != null) {
|
||||
im.setDisplayName(color + sign + amount
|
||||
+ " §6枚 - " + typeName);
|
||||
im.setLore(lore);
|
||||
item.setItemMeta(im);
|
||||
}
|
||||
g.setItem(slot, item);
|
||||
slot++;
|
||||
}
|
||||
|
||||
if (txs.isEmpty()) {
|
||||
ItemStack empty = new ItemStack(
|
||||
Material.BARRIER);
|
||||
ItemMeta em = empty.getItemMeta();
|
||||
if (em != null) {
|
||||
em.setDisplayName("§7暂无记录");
|
||||
empty.setItemMeta(em);
|
||||
}
|
||||
g.setItem(22, empty);
|
||||
} else if (txs.size() > 45) {
|
||||
g.setItem(49, mkItem(Material.PAPER,
|
||||
"§7还有 " + (txs.size() - 45)
|
||||
+ " 条未显示"));
|
||||
}
|
||||
|
||||
g.setItem(53, mkItem(Material.ARROW,
|
||||
"§7← 返回钱包"));
|
||||
|
||||
p.openInventory(g);
|
||||
}
|
||||
|
||||
|
||||
private String walletFormatType(String type) {
|
||||
if (type == null) return "未知";
|
||||
switch (type) {
|
||||
case BondManager.TX_ADMIN_GIVE:
|
||||
return "管理员发放";
|
||||
case BondManager.TX_ADMIN_DEDUCT:
|
||||
return "管理员扣除";
|
||||
case BondManager.TX_TRANSFER_OUT:
|
||||
return "转出";
|
||||
case BondManager.TX_TRANSFER_IN:
|
||||
return "转入";
|
||||
case BondManager.TX_REDEEM:
|
||||
return "CDK兑换";
|
||||
case BondManager.TX_SHOP_BUY:
|
||||
return "商城购买";
|
||||
case BondManager.TX_DAILY_SIGN:
|
||||
return "签到奖励";
|
||||
case BondManager.TX_FREEZE:
|
||||
return "冻结";
|
||||
case BondManager.TX_UNFREEZE:
|
||||
return "解冻";
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ===== Inventory Click(旧逻辑 + 新主菜单布局 + 新功能) =====
|
||||
@EventHandler
|
||||
public void onInvClick(InventoryClickEvent e) {
|
||||
if (e.isCancelled()) return;
|
||||
if (!(e.getWhoClicked() instanceof Player))
|
||||
return;
|
||||
Player p = (Player) e.getWhoClicked();
|
||||
@@ -2393,26 +2607,134 @@ private PVPManager pvpManager;
|
||||
return;
|
||||
}
|
||||
|
||||
// ===== 余额操作 —— 移到前面 =====
|
||||
// ===== [DEBUG] 标题追踪 =====
|
||||
{
|
||||
String dt = title;
|
||||
if (dt.contains("钱包")
|
||||
|| dt.contains("余额")
|
||||
|| dt.contains("流水")
|
||||
|| dt.contains("转账")) {
|
||||
getLogger().info(
|
||||
"[DEBUG-CLICK] "
|
||||
+ "player=" + p.getName()
|
||||
+ " title=[" + dt + "]"
|
||||
+ " slot=" + slot
|
||||
+ " codePoints="
|
||||
+ debugCodePoints(dt));
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 二级菜单(.txt)—— 排除所有固定GUI =====
|
||||
if (title.startsWith("§6§l")
|
||||
&& !title.equals(GUIManager.T_MAIN)
|
||||
&& !title.equals(GUIManager.T_ADMIN)
|
||||
&& !title.equals(GUIManager.T_MY_INFO)
|
||||
&& !title.equals(GUIManager.T_INVITE)
|
||||
&& !title.equals(
|
||||
GUIManager.T_TASK_CENTER)
|
||||
&& !title.equals(
|
||||
GUIManager.T_GIFT_STAGES)
|
||||
&& !title.equals("§6§l任务面板")
|
||||
&& !title.equals("§6§l垃圾回收站")
|
||||
&& !title.equals("§6§l我的钱包")
|
||||
&& !title.equals("§6§l余额操作")) {
|
||||
e.setCancelled(true);
|
||||
if (slot == 49) {
|
||||
gui.openMain(p);
|
||||
return;
|
||||
}
|
||||
if (slot < 45
|
||||
&& slot < e.getInventory()
|
||||
.getSize()
|
||||
&& e.getCurrentItem() != null) {
|
||||
ItemMeta subLm = e.getCurrentItem()
|
||||
.getItemMeta();
|
||||
if (subLm != null
|
||||
&& subLm.getLore() != null) {
|
||||
for (String subLn
|
||||
: subLm.getLore()) {
|
||||
if (subLn.contains(
|
||||
"§7指令: §f")) {
|
||||
String cmd = subLn
|
||||
.replace("§7指令: §f", "")
|
||||
.trim();
|
||||
if (cmd.isEmpty()
|
||||
|| cmd.equals("/")
|
||||
|| cmd.equals("null")) {
|
||||
p.sendMessage("§c指令为空");
|
||||
return;
|
||||
}
|
||||
p.closeInventory();
|
||||
if (cmd.startsWith("/")) {
|
||||
cmd = cmd.substring(1);
|
||||
}
|
||||
Bukkit.dispatchCommand(p, cmd);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ===== 我的钱包(唯一handler) =====
|
||||
if (title.equals("§6§l我的钱包")) {
|
||||
e.setCancelled(true);
|
||||
if (slot == 22) {
|
||||
gui.openMain(p);
|
||||
} else if (slot == 12) {
|
||||
openWalletTransactions(p, 7);
|
||||
} else if (slot == 13) {
|
||||
openWalletTransactions(p, 14);
|
||||
} else if (slot == 14) {
|
||||
openWalletTransfers(p);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ===== 流水/转账记录(唯一handler) =====
|
||||
if (title.startsWith("§e§l流水记录")
|
||||
|| title.startsWith("§e§l转账记录")) {
|
||||
e.setCancelled(true);
|
||||
if (slot == 53) {
|
||||
openMyWallet(p);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ===== 余额操作(唯一handler) =====
|
||||
if (title.equals("§d§l余额操作")) {
|
||||
e.setCancelled(true);
|
||||
if (slot == 49) {
|
||||
gui.openMain(p);
|
||||
return;
|
||||
}
|
||||
// ★ bounds检查 ★
|
||||
if (slot >= 0
|
||||
&& slot < e.getInventory()
|
||||
.getSize()) {
|
||||
ItemStack item =
|
||||
e.getInventory().getItem(slot);
|
||||
&& slot < e.getInventory().getSize()) {
|
||||
ItemStack item = e.getInventory()
|
||||
.getItem(slot);
|
||||
if (item == null
|
||||
|| !item.hasItemMeta()
|
||||
|| !item.getItemMeta()
|
||||
.hasDisplayName())
|
||||
.hasDisplayName()) {
|
||||
return;
|
||||
String targetName =
|
||||
item.getItemMeta()
|
||||
}
|
||||
// 在 slot == 49 的 return 之后、读取 item 之前
|
||||
// ItemStack item = e.getInventory().getItem(slot);
|
||||
if (item == null) return;
|
||||
Material mat = item.getType();
|
||||
// 以下是常见的装饰性玻璃板材质,按需扩展
|
||||
if (mat.name().contains("STAINED_GLASS")
|
||||
|| mat.name().contains("GLASS_PANE")
|
||||
|| mat == Material.LIGHT_BLUE_STAINED_GLASS_PANE
|
||||
|| mat == Material.GRAY_STAINED_GLASS_PANE
|
||||
// ... 其他装饰物
|
||||
|| mat == Material.BLACK_STAINED_GLASS_PANE) {
|
||||
e.setCancelled(true);
|
||||
return; // 装饰物品,不处理
|
||||
}
|
||||
|
||||
String targetName = item.getItemMeta()
|
||||
.getDisplayName()
|
||||
.replace("§e", "")
|
||||
.replace("§a", "");
|
||||
@@ -2423,18 +2745,37 @@ private PVPManager pvpManager;
|
||||
if (e.isLeftClick()) {
|
||||
balanceTarget = targetName;
|
||||
balanceGiveMode = true;
|
||||
} else if (e.isRightClick()) {
|
||||
balanceTarget = targetName;
|
||||
balanceGiveMode = false;
|
||||
}
|
||||
p.closeInventory();
|
||||
p.sendMessage(
|
||||
"§e请输入金额和理由");
|
||||
p.sendMessage(
|
||||
"§7例: 100 测试扣款");
|
||||
"§7例: +100 给款 / -100 扣款");
|
||||
} else if (e.isRightClick()) {
|
||||
if (this.getCDK() != null) {
|
||||
this.getCDK().requestInput(
|
||||
p, "bond",
|
||||
targetName);
|
||||
}
|
||||
p.closeInventory();
|
||||
p.sendMessage(
|
||||
"§e【债券操作】对 §a"
|
||||
+ targetName
|
||||
+ " §e进行债券操作");
|
||||
p.sendMessage(
|
||||
"§e输入 +金额 给债券,"
|
||||
+ " -金额 扣债券");
|
||||
p.sendMessage(
|
||||
"§7可附加理由: §a+100 "
|
||||
+ "活动奖励 §7不填默认:"
|
||||
+ "管理员手动调整");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// ===== 菜单管理 =====
|
||||
if (title.equals("§c§l菜单管理")) {
|
||||
e.setCancelled(true);
|
||||
@@ -2562,7 +2903,9 @@ private PVPManager pvpManager;
|
||||
p.sendMessage("§c已取消");
|
||||
gui.openMain(p);
|
||||
break;
|
||||
}}
|
||||
}
|
||||
}
|
||||
// ==================== 主菜单(新布局) ====================
|
||||
// ==================== 主菜单(新布局) ====================
|
||||
if (title.equals(GUIManager.T_MAIN)) {
|
||||
e.setCancelled(true);
|
||||
@@ -2573,27 +2916,24 @@ private PVPManager pvpManager;
|
||||
.getItemMeta();
|
||||
if (meta != null && meta.getLore() != null) {
|
||||
for (String ln : meta.getLore()) {
|
||||
if (ln.contains(
|
||||
"§7指令: §f")) {
|
||||
if (ln.contains("§7指令: §f")) {
|
||||
String cmd = ln.replace(
|
||||
"§7指令: §f",
|
||||
"").trim();
|
||||
"§7指令: §f", "").trim();
|
||||
if (cmd.isEmpty()
|
||||
|| cmd.equals("/")
|
||||
|| cmd.equals("null")) {
|
||||
p.sendMessage(
|
||||
"§c指令为空");
|
||||
p.sendMessage("§c指令为空");
|
||||
return;
|
||||
}
|
||||
p.closeInventory();
|
||||
if (cmd.startsWith("/"))
|
||||
if (cmd.startsWith("/")) {
|
||||
cmd = cmd.substring(1);
|
||||
}
|
||||
if (cmd.endsWith(".txt")) {
|
||||
gui.openSubMenu(p,
|
||||
cmd.substring(1));
|
||||
} else {
|
||||
Bukkit.dispatchCommand(
|
||||
p, cmd);
|
||||
Bukkit.dispatchCommand(p, cmd);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -2602,47 +2942,45 @@ private PVPManager pvpManager;
|
||||
}
|
||||
|
||||
// 固定按钮
|
||||
switch (slot) {
|
||||
case 10: gui.openMyInfo(p); break;
|
||||
case 12: gui.openInvite(p); break;
|
||||
case 14:
|
||||
p.openInventory(
|
||||
points.createShopGUI(p));
|
||||
break;
|
||||
case 16: gui.openBiomeMenu(p); break;
|
||||
case 19: ticket.openMain(p); break;
|
||||
case 21:
|
||||
if (slot == 10) {
|
||||
gui.openMyInfo(p);
|
||||
} else if (slot == 12) {
|
||||
gui.openInvite(p);
|
||||
} else if (slot == 14) {
|
||||
p.openInventory(points.createShopGUI(p));
|
||||
} else if (slot == 16) {
|
||||
gui.openBiomeMenu(p);
|
||||
} else if (slot == 19) {
|
||||
ticket.openMain(p);
|
||||
} else if (slot == 20) {
|
||||
openMyWallet(p);
|
||||
} else if (slot == 21) {
|
||||
dailySignWithReward(p);
|
||||
gui.openMain(p);
|
||||
break;
|
||||
case 23: gui.openTaskCenter(p); break;
|
||||
case 25:
|
||||
} else if (slot == 23) {
|
||||
gui.openTaskCenter(p);
|
||||
} else if (slot == 25) {
|
||||
if (p.isOp() || isAdmin(p)) {
|
||||
garbage.openRecycle(p);
|
||||
}
|
||||
break;
|
||||
case 29:
|
||||
} else if (slot == 29) {
|
||||
if (isAdmin(p)) {
|
||||
p.closeInventory();
|
||||
chatInput.getState(p).type =
|
||||
ChatInputManager.InputType
|
||||
.ADMIN_AUTH;
|
||||
p.sendMessage(
|
||||
"请输入管理密码:");
|
||||
ChatInputManager.InputType.ADMIN_AUTH;
|
||||
p.sendMessage("请输入管理密码:");
|
||||
}
|
||||
break;
|
||||
case 31:
|
||||
} else if (slot == 31) {
|
||||
if (p.isOp()) {
|
||||
gui.openMenuManager(p);
|
||||
}
|
||||
break;
|
||||
|
||||
case 34:
|
||||
} else if (slot == 34) {
|
||||
if (p.isOp() || isAdmin(p)) {
|
||||
gui.openBalanceOps(p);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2723,10 +3061,13 @@ private PVPManager pvpManager;
|
||||
// ==================== 我的信息 ====================
|
||||
if (title.equals(GUIManager.T_MY_INFO)) {
|
||||
e.setCancelled(true);
|
||||
// 先处理图标槽
|
||||
if (gui.handleMyInfoIconClick(p, e))
|
||||
// ★ 只在图标槽(slot 4)调用,不干扰按钮
|
||||
if (slot == 4
|
||||
&& gui.handleMyInfoIconClick(p, e)) {
|
||||
return;
|
||||
}
|
||||
if (slot == 26) gui.openMain(p);
|
||||
|
||||
else if (slot == 10) {
|
||||
if (checkIn.isCheckedInToday(
|
||||
p.getName()))
|
||||
@@ -3263,8 +3604,41 @@ private PVPManager pvpManager;
|
||||
Player p = (Player) sender;
|
||||
return pvpManager.onCommand(p, args);
|
||||
}
|
||||
if (cmdName.equals("cypay")) {
|
||||
return cypayCommand.onCommand(sender, cmd, label, args);
|
||||
}
|
||||
|
||||
|
||||
// ===== /printer [玩家名] =====
|
||||
if (label.equalsIgnoreCase("printer")) {
|
||||
if (bondPrinter == null) {
|
||||
sender.sendMessage("§cPrinter未初始化");
|
||||
return true;
|
||||
}
|
||||
if (!sender.isOp()
|
||||
&& !isAdmin(sender)) {
|
||||
sender.sendMessage("§c权限不足");
|
||||
return true;
|
||||
}
|
||||
if (args.length == 0) {
|
||||
// 打印全服
|
||||
sender.sendMessage(
|
||||
"§e正在导出全服流水...");
|
||||
bondPrinter.printAll(sender);
|
||||
return true;
|
||||
}
|
||||
if (args.length == 1) {
|
||||
// 打印指定玩家
|
||||
String target = args[0];
|
||||
sender.sendMessage("§e正在导出 "
|
||||
+ target + " 的流水...");
|
||||
bondPrinter.printPlayer(
|
||||
target, sender);
|
||||
return true;
|
||||
}
|
||||
sender.sendMessage(
|
||||
"§7用法: /printer [玩家名]");
|
||||
return true;
|
||||
}
|
||||
// ===== /chat 独立命令 =====
|
||||
if (cmdName.equals("chat")
|
||||
|| cmdName.equals("聊天")) {
|
||||
@@ -3610,33 +3984,6 @@ private PVPManager pvpManager;
|
||||
}
|
||||
|
||||
|
||||
// /sdf1debug
|
||||
if (cmdName.equals("sdf1debug")) {
|
||||
if (!(sender instanceof Player)) {
|
||||
sender.sendMessage("§c仅玩家可用");
|
||||
return true;
|
||||
}
|
||||
Player p2 = (Player) sender;
|
||||
if (!isAdmin(sender)) {
|
||||
p2.sendMessage("§c权限不足");
|
||||
return true;
|
||||
}
|
||||
if (args.length < 2) {
|
||||
p2.sendMessage("§e用法: /sdf1debug <玩家> <阶段>");
|
||||
return true;
|
||||
}
|
||||
String tgt = args[0];
|
||||
try {
|
||||
int stage = Integer.parseInt(args[1]);
|
||||
db.setField(tgt, "gift_stage", stage);
|
||||
p2.sendMessage("§a已将 " + tgt
|
||||
+ " 的任务阶段设为 " + stage);
|
||||
} catch (NumberFormatException ex) {
|
||||
p2.sendMessage("§c请输入有效数字");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// /oa
|
||||
if (cmdName.equals("oa")) {
|
||||
if (!(sender instanceof Player)) {
|
||||
@@ -4765,6 +5112,10 @@ private PVPManager pvpManager;
|
||||
"addplayer", "takeplayer",
|
||||
"unmute", "reset"));
|
||||
}
|
||||
if (cmd.getName().equalsIgnoreCase("cypay")) {
|
||||
return cypayCommand.onTabComplete(sender, cmd, label, args);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.scoreboard.*;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.*;
|
||||
import java.util.*;
|
||||
@@ -39,6 +40,10 @@ public class PVPManager implements Listener {
|
||||
private final Map<String, KillSession>
|
||||
killSessions =
|
||||
new ConcurrentHashMap<>();
|
||||
private final Set<String> tripleAnnounced =
|
||||
ConcurrentHashMap.newKeySet();
|
||||
private final Map<String, Long> lastScoreboardRefresh = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
// 边框显示
|
||||
private final Set<String>
|
||||
@@ -48,6 +53,7 @@ public class PVPManager implements Listener {
|
||||
tempBorders =
|
||||
ConcurrentHashMap.newKeySet();
|
||||
|
||||
|
||||
public static class KillSession {
|
||||
public int count = 0;
|
||||
public long firstKill = 0;
|
||||
@@ -576,13 +582,10 @@ public class PVPManager implements Listener {
|
||||
}
|
||||
String was = inRegion.get(pn);
|
||||
|
||||
if (inside != null
|
||||
&& !inside
|
||||
.equals(was)) {
|
||||
if (inside != null && !inside.equals(was)) {
|
||||
inRegion.put(pn, inside);
|
||||
if (was == null) {
|
||||
String reg = inside;
|
||||
initSession(pn, reg);
|
||||
final Player pp = p;
|
||||
Bukkit.getScheduler()
|
||||
.runTaskLater(plugin,
|
||||
@@ -591,22 +594,20 @@ public class PVPManager implements Listener {
|
||||
pp, reg),
|
||||
1L);
|
||||
}
|
||||
|
||||
} else if (inside == null
|
||||
&& was != null) {
|
||||
inRegion.remove(pn);
|
||||
// 不删 sessionKills,等倒计时结束再清
|
||||
removeScoreboard(p);
|
||||
final Player pp = p;
|
||||
Bukkit.getScheduler()
|
||||
.runTaskLater(plugin,
|
||||
() -> plugin
|
||||
.giveMenuSnowball(
|
||||
pp),
|
||||
.giveMenuSnowball(pp),
|
||||
5L);
|
||||
} else if (inside != null
|
||||
&& inside
|
||||
.equals(was)) {
|
||||
ensureScoreboard(p, inside);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 5分钟超时重置连杀session
|
||||
@@ -624,19 +625,28 @@ public class PVPManager implements Listener {
|
||||
}
|
||||
|
||||
// 5分钟无击杀 → 计数榜切总榜
|
||||
// 5分钟无击杀 → 归档并清零
|
||||
for (String rName :
|
||||
new ArrayList<>(
|
||||
lastKillTime
|
||||
.keySet())) {
|
||||
long last = lastKillTime
|
||||
.getOrDefault(rName, 0L);
|
||||
if (now - last
|
||||
> 5 * 60 * 1000) {
|
||||
if (now - last > 5 * 60 * 1000) {
|
||||
// 清零该区域所有sessionKills
|
||||
sessionKills.keySet()
|
||||
.removeIf(k -> k
|
||||
.startsWith(
|
||||
rName + ":"));
|
||||
// 切换到总榜
|
||||
refreshScoreboard(rName);
|
||||
lastKillTime.remove(rName);
|
||||
lastScoreboardRefresh.remove(rName);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 边框渲染
|
||||
for (Player p :
|
||||
Bukkit.getOnlinePlayers()) {
|
||||
@@ -653,12 +663,38 @@ public class PVPManager implements Listener {
|
||||
.contains(pr)) {
|
||||
drawBorder(p, r);
|
||||
}
|
||||
// 每秒刷新活跃区域的计分板倒计时
|
||||
for (String rName : new ArrayList<>(lastKillTime.keySet())) {
|
||||
long last = lastScoreboardRefresh.getOrDefault(rName, 0L);
|
||||
if (now - last >= 1000) {
|
||||
lastScoreboardRefresh.put(rName, now);
|
||||
for (Player tp : Bukkit.getOnlinePlayers()) {
|
||||
String inR = inRegion.get(tp.getName());
|
||||
if (rName.equals(inR)) {
|
||||
showCounter(tp, rName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastWithSound(String msg, Sound sound) {
|
||||
Bukkit.broadcastMessage(msg);
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
p.playSound(p.getLocation(), sound, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void initSession(String player,
|
||||
String region) {
|
||||
killSessions.remove(player);
|
||||
tripleAnnounced.remove(
|
||||
region + ":" + player);
|
||||
KillSession s = new KillSession();
|
||||
s.region = region;
|
||||
s.firstKill =
|
||||
@@ -694,7 +730,7 @@ public class PVPManager implements Listener {
|
||||
addKill(kName, region);
|
||||
addDeath(vName, region);
|
||||
|
||||
// sessionKills(计数榜用)
|
||||
// 计数榜
|
||||
String kKey =
|
||||
region + ":" + kName;
|
||||
sessionKills.merge(kKey, 1,
|
||||
@@ -702,12 +738,12 @@ public class PVPManager implements Listener {
|
||||
lastKillTime.put(region,
|
||||
System.currentTimeMillis());
|
||||
|
||||
// 终结检测
|
||||
// 终结检测
|
||||
KillSession victimSession =
|
||||
killSessions.get(vName);
|
||||
if (victimSession != null
|
||||
&& victimSession.count
|
||||
>= 3) {
|
||||
&& victimSession.count >= 3) {
|
||||
String endMsg =
|
||||
"§b§l" + kName
|
||||
+ " §e终结了 "
|
||||
@@ -716,27 +752,22 @@ public class PVPManager implements Listener {
|
||||
+ "连杀!§a恭喜终结者 "
|
||||
+ kName
|
||||
+ "§e!§7快来挑战!";
|
||||
Bukkit.broadcastMessage(endMsg);
|
||||
for (Player pp :
|
||||
Bukkit.getOnlinePlayers()) {
|
||||
pp.playSound(
|
||||
pp.getLocation(),
|
||||
Sound
|
||||
.UI_TOAST_CHALLENGE_COMPLETE,
|
||||
2.5f, 0.8f);
|
||||
}
|
||||
killSessions.remove(vName);
|
||||
broadcastWithSound(endMsg, Sound.ENTITY_ENDER_DRAGON_GROWL);
|
||||
|
||||
killSessions.remove(vName);
|
||||
}
|
||||
|
||||
// 击杀者连杀计数
|
||||
|
||||
// 击杀者连杀
|
||||
KillSession ks =
|
||||
killSessions.get(kName);
|
||||
if (ks == null
|
||||
|| !ks.region
|
||||
.equals(region)
|
||||
|| !ks.region.equals(region)
|
||||
|| System.currentTimeMillis()
|
||||
- ks.lastKill
|
||||
> 5 * 60 * 1000) {
|
||||
- ks.lastKill > 5 * 60 * 1000) {
|
||||
tripleAnnounced.remove(
|
||||
region + ":" + kName);
|
||||
KillSession ns =
|
||||
new KillSession();
|
||||
ns.region = region;
|
||||
@@ -756,6 +787,11 @@ public class PVPManager implements Listener {
|
||||
KillSession killerSession =
|
||||
killSessions.get(kName);
|
||||
if (killerSession != null) {
|
||||
plugin.getLogger().info(
|
||||
"[PVP] checkMultiKill: "
|
||||
+ kName + " count="
|
||||
+ killerSession.count
|
||||
+ " region=" + region);
|
||||
checkMultiKill(killer,
|
||||
region, killerSession);
|
||||
}
|
||||
@@ -783,6 +819,7 @@ public class PVPManager implements Listener {
|
||||
KillSession session) {
|
||||
int count = session.count;
|
||||
String kName = killer.getName();
|
||||
String key = region + ":" + kName;
|
||||
String msg = null;
|
||||
|
||||
if (count >= 5) {
|
||||
@@ -791,8 +828,7 @@ public class PVPManager implements Listener {
|
||||
+ "」达成 " + count
|
||||
+ "连杀!§7§l快来挑战!";
|
||||
} else if (count == 3) {
|
||||
if (!session.tripleAnnounced) {
|
||||
session.tripleAnnounced = true;
|
||||
if (tripleAnnounced.add(key)) {
|
||||
msg = "§6§l三杀!"
|
||||
+ kName
|
||||
+ " 势不可挡!"
|
||||
@@ -801,29 +837,28 @@ public class PVPManager implements Listener {
|
||||
}
|
||||
|
||||
if (msg != null) {
|
||||
Bukkit.broadcastMessage(msg);
|
||||
for (Player pp :
|
||||
Bukkit.getOnlinePlayers()) {
|
||||
pp.playSound(
|
||||
pp.getLocation(),
|
||||
Sound
|
||||
.UI_TOAST_CHALLENGE_COMPLETE,
|
||||
2.0f, 1.0f);
|
||||
if (count >= 5) {
|
||||
broadcastWithSound(msg, Sound.ENTITY_ENDER_DRAGON_GROWL);
|
||||
} else {
|
||||
broadcastWithSound(msg, Sound.BLOCK_END_PORTAL_SPAWN);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===== 计分板(英文ID) =====
|
||||
|
||||
private void ensureScoreboard(
|
||||
Player p, String region) {
|
||||
if (lastKillTime
|
||||
.containsKey(region)) {
|
||||
// 活跃击杀:每秒强制刷新(更新倒计时)
|
||||
if (lastKillTime.containsKey(region)) {
|
||||
showCounter(p, region);
|
||||
return;
|
||||
}
|
||||
Scoreboard sb =
|
||||
p.getScoreboard();
|
||||
// 无活跃击杀:只在没有scoreboard时创建总榜
|
||||
Scoreboard sb = p.getScoreboard();
|
||||
if (sb == null
|
||||
|| sb.getObjective(
|
||||
"sdf1pvp") == null) {
|
||||
@@ -831,6 +866,7 @@ public class PVPManager implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void updateScoreboard(
|
||||
Player p, String region) {
|
||||
if (lastKillTime
|
||||
@@ -885,13 +921,11 @@ public class PVPManager implements Listener {
|
||||
}
|
||||
p.setScoreboard(sb);
|
||||
}
|
||||
|
||||
private void showCounter(Player p,
|
||||
String region) {
|
||||
ScoreboardManager mgr =
|
||||
Bukkit.getScoreboardManager();
|
||||
Scoreboard sb =
|
||||
mgr.getNewScoreboard();
|
||||
Scoreboard sb = mgr.getNewScoreboard();
|
||||
Objective obj =
|
||||
sb.registerNewObjective(
|
||||
"sdf1pvp",
|
||||
@@ -940,17 +974,19 @@ public class PVPManager implements Listener {
|
||||
Long last =
|
||||
lastKillTime.get(region);
|
||||
if (last != null) {
|
||||
long elapsed =
|
||||
System.currentTimeMillis()
|
||||
- last;
|
||||
long remaining =
|
||||
Math.max(0,
|
||||
5 * 60 * 1000
|
||||
- (System
|
||||
.currentTimeMillis()
|
||||
- last));
|
||||
5 * 60 * 1000 - elapsed);
|
||||
long secs = remaining / 1000;
|
||||
obj.getScore(
|
||||
"§7冷却: " + secs + "s")
|
||||
.setScore(-1);
|
||||
}
|
||||
|
||||
// 关键:强制赋值新scoreboard
|
||||
p.setScoreboard(sb);
|
||||
}
|
||||
|
||||
@@ -1286,6 +1322,7 @@ public class PVPManager implements Listener {
|
||||
.startsWith(
|
||||
name + ":"));
|
||||
lastKillTime.remove(name);
|
||||
lastScoreboardRefresh.remove(name);
|
||||
p.sendMessage(
|
||||
"§a已删除: " + name);
|
||||
return true;
|
||||
|
||||
@@ -111,34 +111,39 @@ public class RadioDownloadListener
|
||||
}
|
||||
|
||||
private void handleFailure(Player p, UUID uuid) {
|
||||
int failNum = failCount.getOrDefault(uuid, 0) + 1;
|
||||
int failNum = failCount.getOrDefault(
|
||||
uuid, 0) + 1;
|
||||
failCount.put(uuid, failNum);
|
||||
|
||||
log.warning("[Radio] " + p.getName()
|
||||
+ " 失败次数: " + failNum + "/" + MAX_FAILED);
|
||||
+ " 失败次数: " + failNum
|
||||
+ "/" + MAX_FAILED);
|
||||
|
||||
if (failNum >= MAX_FAILED) {
|
||||
blocked.put(uuid, true);
|
||||
log.severe("[Radio] " + p.getName()
|
||||
+ " 已达最大失败次数,停止重试,"
|
||||
+ "玩家继续留在服务器");
|
||||
// 不踢出,只记录熔断,玩家正常游玩
|
||||
p.sendMessage("§e§l[Radio] 资源包加载失败"
|
||||
+ ",跳过资源包,部分画面、音频可能出现问题"
|
||||
+ "§d§l若想继续下载,请退出重进");
|
||||
|
||||
+ " 达到最大失败,停止重试");
|
||||
p.sendMessage(
|
||||
"§e§l[Radio] 资源包加载失败"
|
||||
+ ",部分音频可能异常");
|
||||
p.sendMessage(
|
||||
"§d§l若想重试,请退出重进");
|
||||
return;
|
||||
}
|
||||
// 打了包没测试
|
||||
p.sendMessage("§e§l[Radio] 下载失败,"
|
||||
+ "重试... (" + failNum + "/" + MAX_FAILED + ")");
|
||||
|
||||
// 5秒后重试
|
||||
p.sendMessage("§e§l[Radio] 下载失败,"
|
||||
+ "第 " + failNum + " 次重试"
|
||||
+ " (" + failNum + "/"
|
||||
+ +MAX_FAILED + ")");
|
||||
|
||||
// ★ 3秒后重试(之前是5秒)
|
||||
Bukkit.getScheduler()
|
||||
.runTaskLater(plugin, () -> {
|
||||
if (p.isOnline() && plugin.radio != null) {
|
||||
plugin.radio.sendResourcePack(p);
|
||||
if (p.isOnline()
|
||||
&& plugin.radio != null) {
|
||||
plugin.radio
|
||||
.sendResourcePack(p);
|
||||
}
|
||||
}, 100L);
|
||||
}, 60L);
|
||||
}
|
||||
}
|
||||
@@ -515,30 +515,32 @@ public class RadioManager {
|
||||
// ========== 资源包发送 ==========
|
||||
|
||||
/**
|
||||
* 发送资源包(可选,不强制)
|
||||
* 优先用HTTP URL发送,兜底用byte[]
|
||||
* 发送资源包(带限流,不禁重试)
|
||||
*/
|
||||
private final Map<UUID, Long> lastSendTime =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
public void sendResourcePack(Player p) {
|
||||
UUID uuid = p.getUniqueId();
|
||||
if (pendingPack.contains(uuid)) {
|
||||
|
||||
// 最短间隔 5秒(不是10秒的全锁死)
|
||||
Long last = lastSendTime.get(uuid);
|
||||
if (last != null
|
||||
&& System.currentTimeMillis() - last
|
||||
< 5000L) {
|
||||
plugin.getLogger().info(
|
||||
"[Radio] 跳过(已等待): "
|
||||
"[Radio] 跳过(5秒内): "
|
||||
+ p.getName());
|
||||
return;
|
||||
}
|
||||
pendingPack.add(uuid);
|
||||
Bukkit.getScheduler()
|
||||
.runTaskLater(plugin, () -> {
|
||||
pendingPack.remove(uuid);
|
||||
}, 200L);
|
||||
lastSendTime.put(uuid,
|
||||
System.currentTimeMillis());
|
||||
|
||||
String url = getResourcePackUrl();
|
||||
if (url == null || url.isEmpty()) {
|
||||
plugin.getLogger().warning(
|
||||
"[Radio] URL为空,"
|
||||
+ "跳过发送: "
|
||||
"[Radio] URL为空,跳过: "
|
||||
+ p.getName());
|
||||
pendingPack.remove(uuid);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -549,7 +551,6 @@ public class RadioManager {
|
||||
plugin.getLogger().warning(
|
||||
"[Radio] radiopack.zip不存在,"
|
||||
+ "跳过: " + p.getName());
|
||||
pendingPack.remove(uuid);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -560,22 +561,10 @@ public class RadioManager {
|
||||
long fileSize = zip.length();
|
||||
|
||||
plugin.getLogger().info(
|
||||
"[Radio] === 发送资源包 ===");
|
||||
plugin.getLogger().info(
|
||||
"[Radio] 玩家: " + p.getName());
|
||||
plugin.getLogger().info(
|
||||
"[Radio] URL: " + url);
|
||||
plugin.getLogger().info(
|
||||
"[Radio] Hash: " + hash);
|
||||
plugin.getLogger().info(
|
||||
"[Radio] 文件大小: "
|
||||
+ fileSize + " bytes ("
|
||||
+ String.format("%.1f",
|
||||
fileSize / 1024.0)
|
||||
+ " KB)");
|
||||
plugin.getLogger().info(
|
||||
"[Radio] 客户端版本: "
|
||||
+ p.getProtocolVersion());
|
||||
"[Radio] 发送: " + p.getName()
|
||||
+ " url=" + url
|
||||
+ " size=" + fileSize
|
||||
+ " hash=" + hash);
|
||||
|
||||
sendPackWithHash(p, url, hash);
|
||||
|
||||
@@ -584,7 +573,6 @@ public class RadioManager {
|
||||
"[Radio] 发送异常: "
|
||||
+ p.getName());
|
||||
e.printStackTrace();
|
||||
pendingPack.remove(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,37 +582,38 @@ public class RadioManager {
|
||||
Player p,
|
||||
String url,
|
||||
String hash) {
|
||||
|
||||
UUID uuid = UUID.fromString(
|
||||
"a1b2c3d4-e5f6-7890-abcd-"
|
||||
+ "ef1234567890");
|
||||
|
||||
// 尝试5参 + Component
|
||||
try {
|
||||
Object component =
|
||||
net.kyori.adventure.text
|
||||
.Component.text(
|
||||
"§d§l服务器需要自定义资源包,请您点击'继续'由客户端自动下载即可");
|
||||
"§d§l请下载自定义资源包");
|
||||
p.getClass().getMethod(
|
||||
"setResourcePack",
|
||||
UUID.class,
|
||||
String.class,
|
||||
String.class,
|
||||
net.kyori.adventure
|
||||
.text
|
||||
.Component.class,
|
||||
.text.Component
|
||||
.class,
|
||||
boolean.class)
|
||||
.invoke(
|
||||
p, uuid, url,
|
||||
hash, component,
|
||||
true);
|
||||
plugin.getLogger().info(
|
||||
"[Radio] 发送成功: "
|
||||
+ p.getName()
|
||||
+ " url="
|
||||
+ url);
|
||||
"[Radio] 成功(5参): "
|
||||
+ p.getName());
|
||||
return;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// 尝试5参 + String描述
|
||||
try {
|
||||
p.getClass().getMethod(
|
||||
"setResourcePack",
|
||||
@@ -639,12 +628,13 @@ public class RadioManager {
|
||||
"Sdf1 Radio",
|
||||
true);
|
||||
plugin.getLogger().info(
|
||||
"[Radio] 旧版签名成功: "
|
||||
"[Radio] 成功(5参String): "
|
||||
+ p.getName());
|
||||
return;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// 尝试3参
|
||||
try {
|
||||
p.getClass().getMethod(
|
||||
"setResourcePack",
|
||||
@@ -655,18 +645,27 @@ public class RadioManager {
|
||||
p, uuid, url,
|
||||
hash);
|
||||
plugin.getLogger().info(
|
||||
"[Radio] 3参签名成功: "
|
||||
"[Radio] 成功(3参): "
|
||||
+ p.getName());
|
||||
return;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// ★ 全部失败 → 通知玩家 + 熔断
|
||||
plugin.getLogger().severe(
|
||||
"[Radio] 所有签名失败: "
|
||||
+ p.getName());
|
||||
+ p.getName()
|
||||
+ " 客户端版本: "
|
||||
+ p.getProtocolVersion());
|
||||
p.sendMessage(
|
||||
"§e§l[Radio] §f资源包发送失败,"
|
||||
+ "§7部分功能可能异常");
|
||||
p.sendMessage(
|
||||
"§7可尝试: §e/rejoin §7或重启客户端");
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void sendPackFromLocal(
|
||||
Player p, String url) {
|
||||
File zip = new File(
|
||||
@@ -718,7 +717,7 @@ public class RadioManager {
|
||||
}
|
||||
|
||||
public void clearPending(UUID uuid) {
|
||||
pendingPack.remove(uuid);
|
||||
lastSendTime.remove(uuid);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.*;
|
||||
import java.util.*;
|
||||
import java.util.Base64;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class UserGroupManager {
|
||||
|
||||
private final Main plugin;
|
||||
private Connection db;
|
||||
private final Map<String, UserGroupConfig> groupConfigs = new ConcurrentHashMap<>();
|
||||
|
||||
public UserGroupManager(Main plugin) {
|
||||
this.plugin = plugin;
|
||||
initDB();
|
||||
loadGroupConfigs();
|
||||
startExpiryChecker();
|
||||
}
|
||||
|
||||
// ===== DB =====
|
||||
|
||||
private void initDB() {
|
||||
try {
|
||||
Class.forName("org.sqlite.JDBC");
|
||||
File dbFile = new File(plugin.getDataFolder(), "usergroup.db");
|
||||
db = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath());
|
||||
Statement st = db.createStatement();
|
||||
st.execute("PRAGMA journal_mode=WAL");
|
||||
st.execute("CREATE TABLE IF NOT EXISTS user_group_config ("
|
||||
+ "group_name TEXT PRIMARY KEY,"
|
||||
+ "display_name TEXT DEFAULT '',"
|
||||
+ "display_color TEXT DEFAULT '§f',"
|
||||
+ "display_emoji TEXT DEFAULT '',"
|
||||
+ "priority INTEGER DEFAULT 0,"
|
||||
+ "is_permanent INTEGER DEFAULT 1,"
|
||||
+ "duration_minutes INTEGER DEFAULT 0,"
|
||||
+ "pack_url TEXT DEFAULT '',"
|
||||
+ "pack_hash TEXT DEFAULT '',"
|
||||
+ "pack_prompt TEXT DEFAULT 'Custom Resource Pack',"
|
||||
+ "pack_size INTEGER DEFAULT 0)");
|
||||
st.execute("CREATE TABLE IF NOT EXISTS user_group_member ("
|
||||
+ "player_name TEXT NOT NULL,"
|
||||
+ "group_name TEXT NOT NULL,"
|
||||
+ "added_by TEXT DEFAULT 'system',"
|
||||
+ "added_time INTEGER DEFAULT 0,"
|
||||
+ "expiry_time INTEGER DEFAULT 0,"
|
||||
+ "PRIMARY KEY(player_name, group_name))");
|
||||
st.close();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("[UserGroup] DB init failed: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 配置加载 =====
|
||||
|
||||
public void loadGroupConfigs() {
|
||||
groupConfigs.clear();
|
||||
File dir = new File(plugin.getDataFolder(), "用户组");
|
||||
if (!dir.exists()) dir.mkdirs();
|
||||
File[] files = dir.listFiles((d, n) -> n.endsWith(".txt"));
|
||||
if (files == null) return;
|
||||
|
||||
for (File f : files) {
|
||||
try {
|
||||
List<String> lines = java.nio.file.Files.readAllLines(
|
||||
f.toPath(), StandardCharsets.UTF_8);
|
||||
UserGroupConfig cfg = parseGroupConfig(lines);
|
||||
if (cfg != null) {
|
||||
groupConfigs.put(cfg.name, cfg);
|
||||
saveGroupConfigToDB(cfg);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[UserGroup] Failed to load: " + f.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private UserGroupConfig parseGroupConfig(List<String> lines) {
|
||||
UserGroupConfig cfg = new UserGroupConfig();
|
||||
boolean inBlock = false;
|
||||
|
||||
for (String raw : lines) {
|
||||
String l = raw;
|
||||
|
||||
// 块注释
|
||||
if (l.contains("<!--")) { inBlock = true; continue; }
|
||||
if (l.contains("-->")) { inBlock = false; continue; }
|
||||
if (l.contains("/*")) { inBlock = true; continue; }
|
||||
if (l.contains("*/")) { inBlock = false; continue; }
|
||||
if (inBlock) continue;
|
||||
|
||||
// 行注释
|
||||
l = l.trim();
|
||||
if (l.isEmpty()) continue;
|
||||
if (l.startsWith("#") || l.startsWith("//")) continue;
|
||||
|
||||
// 行内注释
|
||||
int h = l.indexOf('#');
|
||||
int s = l.indexOf("//");
|
||||
if (h >= 0) l = l.substring(0, h).trim();
|
||||
if (s >= 0 && (h < 0 || s < h)) l = l.substring(0, s).trim();
|
||||
if (l.isEmpty()) continue;
|
||||
|
||||
// 解析键值对
|
||||
String key = l;
|
||||
String val = "";
|
||||
int idx = l.indexOf('\uff1a');
|
||||
if (idx < 0) idx = l.indexOf(':');
|
||||
if (idx >= 0) {
|
||||
key = l.substring(0, idx).trim();
|
||||
val = l.substring(idx + 1).trim();
|
||||
if (val.startsWith("\"") && val.endsWith("\""))
|
||||
val = val.substring(1, val.length() - 1);
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case "组名":
|
||||
case "name": cfg.name = val; break;
|
||||
case "显示名":
|
||||
case "display": cfg.displayName = val; break;
|
||||
case "颜色":
|
||||
case "color": cfg.displayColor = val; break;
|
||||
case "符号":
|
||||
case "emoji": cfg.displayEmoji = val; break;
|
||||
case "优先级":
|
||||
case "priority":
|
||||
try { cfg.priority = Integer.parseInt(val); } catch (Exception ignored) {}
|
||||
break;
|
||||
case "类型":
|
||||
case "type":
|
||||
cfg.permanent = val.equalsIgnoreCase("永久") || val.equalsIgnoreCase("permanent");
|
||||
break;
|
||||
case "时长":
|
||||
case "duration":
|
||||
try { cfg.durationMinutes = Integer.parseInt(val); } catch (Exception ignored) {}
|
||||
break;
|
||||
case "资源包URL":
|
||||
case "pack_url": cfg.packUrl = val; break;
|
||||
case "资源包Hash":
|
||||
case "pack_hash": cfg.packHash = val; break;
|
||||
case "资源包提示":
|
||||
case "pack_prompt": cfg.packPrompt = val; break;
|
||||
case "资源包大小":
|
||||
case "pack_size":
|
||||
try { cfg.packSize = Integer.parseInt(val); } catch (Exception ignored) {}
|
||||
break;
|
||||
case "本地文件":
|
||||
case "local_file": cfg.localFile = val; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cfg.name == null || cfg.name.isEmpty()) return null;
|
||||
return cfg;
|
||||
}
|
||||
|
||||
private void saveGroupConfigToDB(UserGroupConfig cfg) {
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"INSERT OR REPLACE INTO user_group_config "
|
||||
+ "(group_name, display_name, display_color, display_emoji,"
|
||||
+ " priority, is_permanent, duration_minutes,"
|
||||
+ " pack_url, pack_hash, pack_prompt, pack_size)"
|
||||
+ " VALUES (?,?,?,?,?,?,?,?,?,?,?)");
|
||||
ps.setString(1, cfg.name);
|
||||
ps.setString(2, cfg.displayName);
|
||||
ps.setString(3, cfg.displayColor);
|
||||
ps.setString(4, cfg.displayEmoji);
|
||||
ps.setInt(5, cfg.priority);
|
||||
ps.setInt(6, cfg.permanent ? 1 : 0);
|
||||
ps.setInt(7, cfg.durationMinutes);
|
||||
ps.setString(8, cfg.packUrl);
|
||||
ps.setString(9, cfg.packHash);
|
||||
ps.setString(10, cfg.packPrompt);
|
||||
ps.setInt(11, cfg.packSize);
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 玩家操作 =====
|
||||
|
||||
public boolean addPlayer(String player, String groupName, String addedBy, long durationMinutes) {
|
||||
UserGroupConfig cfg = groupConfigs.get(groupName);
|
||||
if (cfg == null) return false;
|
||||
long now = System.currentTimeMillis();
|
||||
long expiry = cfg.permanent ? 0 : now + durationMinutes * 60 * 1000;
|
||||
if (!cfg.permanent && durationMinutes > 0) expiry = now + durationMinutes * 60 * 1000;
|
||||
else if (!cfg.permanent) expiry = now + cfg.durationMinutes * 60 * 1000;
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"INSERT OR REPLACE INTO user_group_member "
|
||||
+ "(player_name, group_name, added_by, added_time, expiry_time)"
|
||||
+ " VALUES (?,?,?,?,?)");
|
||||
ps.setString(1, player);
|
||||
ps.setString(2, groupName);
|
||||
ps.setString(3, addedBy);
|
||||
ps.setLong(4, now);
|
||||
ps.setLong(5, expiry);
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
Player p = plugin.getServer().getPlayerExact(player);
|
||||
if (p != null) sendResourcePack(p);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean removePlayer(String player, String groupName) {
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"DELETE FROM user_group_member WHERE player_name=? AND group_name=?");
|
||||
ps.setString(1, player);
|
||||
ps.setString(2, groupName);
|
||||
int rows = ps.executeUpdate();
|
||||
ps.close();
|
||||
return rows > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getPlayerGroups(String player) {
|
||||
List<Map<String, Object>> list = new ArrayList<>();
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"SELECT * FROM user_group_member WHERE player_name=?");
|
||||
ps.setString(1, player);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
long now = System.currentTimeMillis();
|
||||
while (rs.next()) {
|
||||
Map<String, Object> row = new LinkedHashMap<>();
|
||||
row.put("group_name", rs.getString("group_name"));
|
||||
row.put("added_by", rs.getString("added_by"));
|
||||
long expiry = rs.getLong("expiry_time");
|
||||
row.put("expiry_time", expiry);
|
||||
row.put("expired", expiry > 0 && expiry <= now);
|
||||
list.add(row);
|
||||
}
|
||||
rs.close(); ps.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public UserGroupConfig getHighestGroup(String player) {
|
||||
List<Map<String, Object>> groups = getPlayerGroups(player);
|
||||
long now = System.currentTimeMillis();
|
||||
UserGroupConfig best = null;
|
||||
for (Map<String, Object> g : groups) {
|
||||
long expiry = (long) g.get("expiry_time");
|
||||
if (expiry > 0 && expiry <= now) continue;
|
||||
String name = (String) g.get("group_name");
|
||||
UserGroupConfig cfg = groupConfigs.get(name);
|
||||
if (cfg != null && (best == null || cfg.priority > best.priority)) {
|
||||
best = cfg;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
public boolean isPlayerInGroup(String player, String groupName) {
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"SELECT 1 FROM user_group_member WHERE player_name=? AND group_name=?");
|
||||
ps.setString(1, player);
|
||||
ps.setString(2, groupName);
|
||||
boolean exists = ps.executeQuery().next();
|
||||
ps.close();
|
||||
return exists;
|
||||
} catch (SQLException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 过期自动移除 =====
|
||||
|
||||
private void startExpiryChecker() {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
checkExpiry();
|
||||
}
|
||||
}.runTaskTimerAsynchronously(plugin, 1200L, 1200L); // 每分钟检查
|
||||
}
|
||||
|
||||
private void checkExpiry() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"SELECT player_name, group_name FROM user_group_member "
|
||||
+ "WHERE expiry_time > 0 AND expiry_time <= ?");
|
||||
ps.setLong(1, now);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
List<String[]> expired = new ArrayList<>();
|
||||
while (rs.next()) {
|
||||
expired.add(new String[]{
|
||||
rs.getString("player_name"),
|
||||
rs.getString("group_name")});
|
||||
}
|
||||
rs.close(); ps.close();
|
||||
|
||||
for (String[] e : expired) {
|
||||
PreparedStatement del = db.prepareStatement(
|
||||
"DELETE FROM user_group_member WHERE player_name=? AND group_name=?");
|
||||
del.setString(1, e[0]);
|
||||
del.setString(2, e[1]);
|
||||
del.executeUpdate();
|
||||
del.close();
|
||||
plugin.getLogger().info("[UserGroup] 过期移除: " + e[0] + " -> " + e[1]);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 资源包分发 =====
|
||||
|
||||
public void sendResourcePack(Player p) {
|
||||
UserGroupConfig cfg = getHighestGroup(p.getName());
|
||||
if (cfg == null || cfg.packUrl == null || cfg.packUrl.isEmpty()) return;
|
||||
|
||||
String url = cfg.packUrl;
|
||||
String prompt = cfg.packPrompt != null
|
||||
? cfg.packPrompt : "Custom Resource Pack";
|
||||
|
||||
plugin.getServer().getScheduler().runTaskLater(plugin, () -> {
|
||||
try {
|
||||
p.setResourcePack(url, new byte[0], prompt, false);
|
||||
plugin.getLogger().info("[UserGroup] 资源包已发送: "
|
||||
+ p.getName() + " -> " + cfg.displayName);
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning(
|
||||
"[UserGroup] 资源包发送失败: " + e.getMessage());
|
||||
}
|
||||
}, 40L);
|
||||
}
|
||||
|
||||
public Map<String, UserGroupConfig> getGroupConfigs() {
|
||||
return groupConfigs;
|
||||
}
|
||||
|
||||
// ===== 配置类 =====
|
||||
|
||||
public static class UserGroupConfig {
|
||||
public String name;
|
||||
public String displayName = "";
|
||||
public String displayColor = "§f";
|
||||
public String displayEmoji = "";
|
||||
public int priority = 0;
|
||||
public boolean permanent = true;
|
||||
public int durationMinutes = 0;
|
||||
public String packUrl = "";
|
||||
public String packHash = "";
|
||||
public String packPrompt = "Custom Resource Pack";
|
||||
public int packSize = 0;
|
||||
public String localFile = "";
|
||||
}
|
||||
}
|
||||
@@ -60,9 +60,9 @@ commands:
|
||||
deny:
|
||||
description: 拒绝OA请求
|
||||
usage: /deny <编号> [理由]
|
||||
sdf1debug:
|
||||
description: 调试指令
|
||||
usage: /sdf1debug <玩家> take.<阶段>
|
||||
#sdf1debug:
|
||||
# description: 调试指令
|
||||
# usage: /sdf1debug <玩家> take.<阶段>
|
||||
email:
|
||||
description: 绑定邮箱
|
||||
usage: /email <邮箱>
|
||||
@@ -90,3 +90,9 @@ commands:
|
||||
pvp :
|
||||
description: PVP榜单
|
||||
usage: /pvp
|
||||
cypay :
|
||||
description: 草原探险债券
|
||||
usage: /cypay
|
||||
printer :
|
||||
description: 打印债券流水
|
||||
usage: /printer
|
||||
Reference in New Issue
Block a user