feat: 新增盲盒种类,支持对接 Sdf1_login 债券系统

This commit is contained in:
ypsdf1
2026-05-22 16:06:15 +08:00
parent 359f4956e1
commit 0a6c8dbe20
+611 -117
View File
@@ -37,6 +37,8 @@ import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.sql.*;
public class Main extends JavaPlugin implements Listener, CommandExecutor {
@@ -69,6 +71,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
private long lastCyDiscover = 0;
private static final long DISCOVER_INTERVAL = 30000L;
private static final Map<String, String> ALIASES = new HashMap<String, String>();
static {
String[][] pairs = {
@@ -128,17 +131,52 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
private static class ActionEntry {
String type;
double money, moneyMin, moneyMax;
int slots, days, slotsMin, slotsMax, daysMin, daysMax;
int slots, days, slotsMin, slotsMax,
daysMin, daysMax;
int bondMin, bondMax;
int decimalPlaces = -1;
String rawText = "";
ActionEntry(String t) { type = t; }
}
private static class ScoreAction {
List<ActionEntry> actions = new ArrayList<ActionEntry>();
boolean deleteCode = false;
boolean recordName = false;
}
// ===== 债券DB(跨插件访问Sdf1_login的bond.db)=====
private Connection bondDb;
private Connection getBondDb() {
if (bondDb != null) {
try {
if (!bondDb.isClosed()) return bondDb;
} catch (Exception ignored) {}
bondDb = null;
}
try {
Plugin sdf1Login = Bukkit.getPluginManager()
.getPlugin("Sdf1_login");
if (sdf1Login == null) return null;
File dbFile = new File(
sdf1Login.getDataFolder(),
"bond.db");
if (!dbFile.exists()) return null;
Class.forName("org.sqlite.JDBC");
bondDb = DriverManager.getConnection(
"jdbc:sqlite:"
+ dbFile.getAbsolutePath());
return bondDb;
} catch (Exception e) {
log("[Bond] DB连接失败: "
+ e.getMessage());
return null;
}
}
// ===== 撤销记录 =====
private static class UndoRecord {
@@ -432,12 +470,14 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
cfgNameBoard = safeStr(globals.get("记名板"));
cfgAdminTeam = safeStr(globals.get("管理团队"));
cfgAdminTag = safeStr(globals.get("管理标签"));
for (Map<String, String> block : blocks) {
int scoreKey = safeInt(block.get("分值"));
if (scoreKey <= 0) continue;
ScoreAction sa = new ScoreAction();
sa.deleteCode = "true".equals(block.get("_删除口令"));
sa.recordName = "true".equals(block.get("_记名"));
if ("true".equals(block.get("_经济盲盒"))) {
ActionEntry e = new ActionEntry("经济盲盒");
e.moneyMin = safeDouble(block.get("_经济盲盒_min"));
@@ -447,6 +487,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
e.decimalPlaces = safeInt(block.getOrDefault("_小数位", "-1"));
e.rawText = "经济盲盒 " + fmtMoney(e.moneyMin) + "~" + fmtMoney(e.moneyMax);
sa.actions.add(e);
} else if ("true".equals(block.get("_联控盲盒"))) {
ActionEntry e = new ActionEntry("联控盲盒");
e.daysMin = safeInt(block.getOrDefault("_天数min", "7"));
@@ -459,11 +500,19 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
if (e.slotsMax <= 0) e.slotsMax = e.slotsMin;
if (e.daysMax < e.daysMin) e.daysMax = e.daysMin;
if (e.slotsMax < e.slotsMin) e.slotsMax = e.slotsMin;
e.rawText = "联控盲盒 " + e.daysMin + "~" + e.daysMax + "天 "
+ e.slotsMin + "~" + e.slotsMax + "格";
log("[配置] 联控盲盒范围: " + e.daysMin + "~" + e.daysMax + "天 "
+ e.slotsMin + "~" + e.slotsMax + "格");
e.rawText = "联控盲盒 " + e.daysMin + "~" + e.daysMax + "天 " + e.slotsMin + "~" + e.slotsMax + "格";
sa.actions.add(e);
} else if ("true".equals(block.get("_债券盲盒"))) {
ActionEntry e = new ActionEntry("债券盲盒");
e.bondMin = safeInt(block.getOrDefault("_债券盲盒_min", "1"));
e.bondMax = safeInt(block.getOrDefault("_债券盲盒_max", "10"));
if (e.bondMin <= 0) e.bondMin = 1;
if (e.bondMax <= 0) e.bondMax = e.bondMin;
if (e.bondMax < e.bondMin) e.bondMax = e.bondMin;
e.rawText = "债券盲盒 " + e.bondMin + "~" + e.bondMax + "个";
sa.actions.add(e);
} else {
if (block.containsKey("_发钱")) {
ActionEntry e = new ActionEntry("发钱");
@@ -471,6 +520,12 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
e.rawText = "发钱 $" + fmtMoney(e.money);
sa.actions.add(e);
}
if (block.containsKey("_发债券")) {
ActionEntry e = new ActionEntry("发债券");
e.money = safeDouble(block.get("_发债券"));
e.rawText = "发债券 " + (int) e.money;
sa.actions.add(e);
}
if (block.containsKey("_扣钱")) {
ActionEntry e = new ActionEntry("扣钱");
e.money = safeDouble(block.get("_扣钱"));
@@ -485,6 +540,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
sa.actions.add(e);
}
}
if (block.containsKey("_小数位")) {
int dp = safeInt(block.get("_小数位"));
for (ActionEntry ae : sa.actions) ae.decimalPlaces = dp;
@@ -504,17 +560,8 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
text = text.replaceAll("^['\"](.*)['\"]$", "$1").trim();
text = normalizeText(text);
log("[SDF1-诊断-动作] 输入: [" + text + "]");
StringBuilder actionHex = new StringBuilder();
for (int i = 0; i < Math.min(60, text.length()); i++) {
actionHex.append(String.format("U+%04X ", (int) text.charAt(i)));
}
log("[SDF1-诊断-动作] 码点: " + actionHex.toString().trim());
text = text.replace('\uff0d', '-').replace('\u2013', '-').replace('\u2014', '-').replace('\uff5e', '~');
// ★ 在所有检测之前,先归一化数字格式
// 这样 "V千块-2万块" → "5000块-20000块"
// 确保 extractMoneyRange 和所有 contains 检查都能正确工作
String norm = normalizeAllDigits(text);
norm = normalizeEnglishNumbers(norm);
norm = normalizeRomanNumerals(norm);
@@ -522,7 +569,47 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
norm = normalizeChineseNumbers(norm);
log("[SDF1-诊断-动作] 归一化: [" + norm + "]");
// ★ 用归一化后的文本做所有检测
// ★ 债券检测(含债券盲盒)
if (norm.contains("债券")) {
// 先检测范围:X~Y个债券盲盒
Matcher bondBlindM = Pattern.compile(
"(\\d+)\\s*[~\\-到至]+\\s*(\\d+)\\s*个?\\s*债券盲盒"
).matcher(norm);
if (bondBlindM.find()) {
int bmin = Integer.parseInt(bondBlindM.group(1));
int bmax = Integer.parseInt(bondBlindM.group(2));
if (bmin > bmax) { int t2 = bmin; bmin = bmax; bmax = t2; }
block.put("_债券盲盒", "true");
block.put("_债券盲盒_min", String.valueOf(bmin));
block.put("_债券盲盒_max", String.valueOf(bmax));
diag.add(" -> 识别: 债券盲盒 " + bmin + "~" + bmax);
} else if (norm.contains("盲盒") || norm.contains("抽")) {
// 盲盒关键词 + X~Y个债券
Matcher bondR = Pattern.compile(
"(\\d+)\\s*[~\\-到至]+\\s*(\\d+)\\s*个?\\s*债券"
).matcher(norm);
if (bondR.find()) {
int bmin = Integer.parseInt(bondR.group(1));
int bmax = Integer.parseInt(bondR.group(2));
if (bmin > bmax) { int t2 = bmin; bmin = bmax; bmax = t2; }
block.put("_债券盲盒", "true");
block.put("_债券盲盒_min", String.valueOf(bmin));
block.put("_债券盲盒_max", String.valueOf(bmax));
diag.add(" -> 识别: 债券盲盒 " + bmin + "~" + bmax);
}
}
// 固定发债券(非盲盒)
if (!"true".equals(block.get("_债券盲盒"))) {
Matcher bondM = Pattern.compile("(\\d+)\\s*个?\\s*债券").matcher(norm);
if (bondM.find()) {
int bondAmt = Integer.parseInt(bondM.group(1));
block.put("_发债券", String.valueOf(bondAmt));
diag.add(" -> 识别: 发债券 " + bondAmt);
}
}
}
// ★ 删除口令检测
if (norm.contains("删") || norm.contains("删除") || norm.contains("销毁")
|| norm.contains("作废") || norm.contains("清除") || norm.contains("清空")
|| norm.contains("抹除") || norm.contains("删掉") || norm.contains("移除")
@@ -532,70 +619,115 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
block.put("_删除口令", "true");
diag.add(" -> 识别: 删除口令");
}
// ★ 记名检测
if (norm.contains("记名") || norm.contains("永久一次性") || norm.contains("记录")
|| norm.contains("标记已用") || norm.contains("防重复") || norm.contains("查重")
|| norm.contains("已领") || norm.contains("已兑换") || norm.contains("去重")) {
block.put("_记名", "true");
diag.add(" -> 识别: 记名");
}
// ★ 小数位检测
Matcher decMatch = Pattern.compile("(\\d+)\\s*位\\s*小数").matcher(norm);
if (decMatch.find()) {
block.put("_小数位", decMatch.group(1));
diag.add(" -> 识别: 小数位=" + decMatch.group(1));
}
// ★ 盲盒检测:用归一化后的文本
// ★ 盲盒检测
if (norm.contains("盲盒") || norm.contains("抽")) {
// 第一优先: 明确的金额范围 -> 经济盲盒
// ★★ 新增:优先检测债券盲盒
if (norm.contains("债券")) {
java.util.List<Integer> bnums =
new java.util.ArrayList<>();
Matcher bnm = Pattern.compile(
"(\\d+)").matcher(norm);
while (bnm.find()) {
bnums.add(Integer.parseInt(
bnm.group(1)));
}
if (!bnums.isEmpty()) {
int bmin = java.util.Collections
.min(bnums);
int bmax = java.util.Collections
.max(bnums);
block.put("_债券盲盒", "true");
block.put("_债券盲盒_min",
String.valueOf(bmin));
block.put("_债券盲盒_max",
String.valueOf(bmax));
diag.add(" -> 识别: 债券盲盒 "
+ bmin + "~" + bmax);
return;
}
}
// 原有经济盲盒
double[] mr = extractMoneyRange(norm);
if (mr != null) {
block.put("_经济盲盒", "true");
block.put("_经济盲盒_min", String.valueOf(mr[0]));
block.put("_经济盲盒_max", String.valueOf(mr[1]));
diag.add(" -> 识别: 经济盲盒 " + mr[0] + "~" + mr[1]);
block.put("_经济盲盒_min",
String.valueOf(mr[0]));
block.put("_经济盲盒_max",
String.valueOf(mr[1]));
diag.add(" -> 识别: 经济盲盒 "
+ mr[0] + "~" + mr[1]);
return;
}
// 第二优先: 明确的天+格组合范围 -> 联控盲盒
int[] sr = extractSlotDayRange(norm);
if (sr != null) {
block.put("_联控盲盒", "true");
block.put("_天数min", String.valueOf(sr[0]));
block.put("_格子min", String.valueOf(sr[1]));
block.put("_天数max", String.valueOf(sr[2]));
block.put("_格子max", String.valueOf(sr[3]));
diag.add(" -> 识别: 联控盲盒 天数=" + sr[0] + "~" + sr[2]
+ " 格子=" + sr[1] + "~" + sr[3]);
block.put("_天数min",
String.valueOf(sr[0]));
block.put("_格子min",
String.valueOf(sr[1]));
block.put("_天数max",
String.valueOf(sr[2]));
block.put("_格子max",
String.valueOf(sr[3]));
diag.add(" -> 识别: 联控盲盒 天数="
+ sr[0] + "~" + sr[2]
+ " 格子=" + sr[1]
+ "~" + sr[3]);
return;
}
// 第三优先: 独立的天数范围或格子范围 -> 联控盲盒
int[] dayR = extractDayRange(norm);
int[] slotR = extractSlotRange(norm);
if (dayR != null || slotR != null) {
block.put("_联控盲盒", "true");
if (dayR != null) {
block.put("_天数min", String.valueOf(dayR[0]));
block.put("_天数max", String.valueOf(dayR[1]));
diag.add(" -> 识别: 天数范围 " + dayR[0] + "~" + dayR[1]);
block.put("_天数min",
String.valueOf(dayR[0]));
block.put("_天数max",
String.valueOf(dayR[1]));
}
if (slotR != null) {
block.put("_格子min", String.valueOf(slotR[0]));
block.put("_格子max", String.valueOf(slotR[1]));
diag.add(" -> 识别: 格子范围 " + slotR[0] + "~" + slotR[1]);
block.put("_格子min",
String.valueOf(slotR[0]));
block.put("_格子max",
String.valueOf(slotR[1]));
}
return;
}
// 第四优先: 关键词判断
boolean hasCyKw = norm.contains("格") || norm.contains("天")
|| norm.contains("背包") || norm.contains("空间")
|| norm.contains("会员") || norm.contains("激活")
boolean hasCyKw = norm.contains("格")
|| norm.contains("天")
|| norm.contains("背包")
|| norm.contains("空间")
|| norm.contains("会员")
|| norm.contains("激活")
|| norm.contains("存储");
boolean hasMoneyKw = norm.contains("给") || norm.contains("钱")
|| norm.contains("块") || norm.contains("元")
|| norm.contains("奖") || norm.contains("金额")
boolean hasMoneyKw = norm.contains("给")
|| norm.contains("钱")
|| norm.contains("块")
|| norm.contains("元")
|| norm.contains("奖")
|| norm.contains("金额")
|| norm.contains("金");
if (hasCyKw && !hasMoneyKw) {
block.put("_联控盲盒", "true");
diag.add(" -> 识别: 联控盲盒(联控关键词)");
diag.add(" -> 识别: 联控盲盒(关键词)");
} else {
block.put("_经济盲盒", "true");
diag.add(" -> 识别: 经济盲盒(默认)");
@@ -603,7 +735,8 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
return;
}
// ★ 非盲盒路径也用归一化文本
// ★ 非盲盒:联控检测
boolean hasSlotKw = norm.contains("背包") || norm.contains("空间")
|| norm.contains("格") || norm.contains("激活") || norm.contains("会员")
|| norm.contains("存储");
@@ -616,6 +749,8 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
diag.add(" -> 识别: 联控 " + sd[0] + "天" + sd[1] + "格");
}
}
// ★ 扣钱检测
boolean isTake = norm.contains("扣") || norm.contains("减") || norm.contains("扣除")
|| norm.contains("罚") || norm.contains("没收");
if (isTake) {
@@ -625,6 +760,8 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
diag.add(" -> 识别: 扣钱 " + amt);
}
}
// ★ 发钱检测(排除债券,债券已独立处理)
boolean isGive = !isTake && (norm.contains("给") || norm.contains("奖")
|| norm.contains("发") || norm.contains("加") || norm.contains("赏")
|| norm.contains("赠送") || norm.contains("返"));
@@ -637,6 +774,7 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
}
}
private String safeStr(String s) { return s == null ? "" : s.trim(); }
private int safeInt(String s) {
@@ -1194,36 +1332,253 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerChat(AsyncPlayerChatEvent event) {
Player p = event.getPlayer(); UUID u = p.getUniqueId(); String msg = event.getMessage().trim();
Player p = event.getPlayer();
UUID u = p.getUniqueId();
String msg = event.getMessage().trim();
if (msg.isEmpty() || !listening.containsKey(u)) return;
event.setCancelled(true); stopListening(u, true);
event.setCancelled(true);
stopListening(u, true);
p.sendMessage(colorize("&a[SDF1] &f已拦截,比对中..."));
Long last = chatCd.get(u);
if (last != null && System.currentTimeMillis() - last < 500) return;
chatCd.put(u, System.currentTimeMillis());
// ★ 优先检查债券CDK
if (tryBondRedeem(p, msg)) {
return;
}
int scoreVal = lookupScore(msg);
if (scoreVal < 0) { p.sendMessage(colorize("&c[SDF1] 口令无效")); log("[拦截] " + p.getName() + " 无效: \"" + msg + "\""); return; }
if (scoreVal < 0) {
p.sendMessage(colorize("&c[SDF1] 口令无效"));
log("[拦截] " + p.getName() + " 无效: \"" + msg + "\"");
return;
}
ScoreAction sa = scoreMap.get(scoreVal);
if (sa == null) { p.sendMessage(colorize("&c[SDF1] 规则未配置")); log("[拦截] " + p.getName() + " 分值=" + scoreVal + " 无规则"); return; }
log("[拦截] " + p.getName() + " 分值=" + scoreVal + " 动作数=" + sa.actions.size() + (sa.recordName ? " 记名" : "") + (sa.deleteCode ? " 删口令" : ""));
if (sa == null) {
p.sendMessage(colorize("&c[SDF1] 规则未配置"));
log("[拦截] " + p.getName() + " 分值=" + scoreVal + " 无规则");
return;
}
log("[拦截] " + p.getName() + " 分值=" + scoreVal + " 动作数=" + sa.actions.size()
+ (sa.recordName ? " 记名" : "") + (sa.deleteCode ? " 删口令" : ""));
// 记名检查
if (sa.recordName && cfgNameBoard != null && !cfgNameBoard.isEmpty()) {
String nameKey = msg + "_" + p.getName();
try {
Scoreboard board = Bukkit.getScoreboardManager().getMainScoreboard();
Objective nameObj = board.getObjective(cfgNameBoard);
if (nameObj != null) { Score ns = nameObj.getScore(nameKey); if (ns.isScoreSet() && ns.getScore() > 0) { p.sendMessage(colorize("&c[SDF1] 你已领取过此口令")); log("[记名] " + p.getName() + " 重复: " + nameKey); return; } }
if (nameObj != null) {
Score ns = nameObj.getScore(nameKey);
if (ns.isScoreSet() && ns.getScore() > 0) {
p.sendMessage(colorize("&c[SDF1] 你已领取过此口令"));
log("[记名] " + p.getName() + " 重复: " + nameKey);
return;
}
}
} catch (Exception ignored) {}
}
p.sendMessage(colorize("&a[SDF1] 执行: " + sa.actions.size() + "个动作"));
// ===== 快照:执行前的经济/债券 =====
double econBefore = 0;
if (economy != null) econBefore = economy.getBalance(p);
int bondBefore = 0;
Connection bdb = getBondDb();
if (bdb != null) {
try {
PreparedStatement q = bdb.prepareStatement(
"SELECT amount FROM bonds WHERE player_name=?");
q.setString(1, p.getName());
ResultSet rs = q.executeQuery();
if (rs.next()) bondBefore = rs.getInt("amount");
rs.close();
q.close();
} catch (Exception ignored) {}
}
// ===== 执行动作(静默) =====
boolean hasEcon = false;
boolean hasBond = false;
boolean hasSlots = false;
String econMsg = "";
String bondMsg = "";
String slotsMsg = "";
for (ActionEntry ae : sa.actions) {
log("[执行] [" + ae.type + "] " + ae.rawText);
if ("发钱".equals(ae.type)) execGiveMoney(p, ae, sa.deleteCode, msg);
else if ("扣钱".equals(ae.type)) execTakeMoney(p, ae, sa.deleteCode, msg);
else if ("经济盲盒".equals(ae.type)) execMoneyBox(p, ae, sa.deleteCode, msg);
else if ("联控盲盒".equals(ae.type)) execSlotBox(p, ae, sa.deleteCode, msg);
else if ("联控".equals(ae.type)) execCy(p, ae, sa.deleteCode, msg);
else if ("删除口令".equals(ae.type)) execDelete(p, ae, msg);
if ("发钱".equals(ae.type)) {
if (economy != null) {
economy.depositPlayer(p, ae.money);
hasEcon = true;
econMsg = "+$" + fmtAmount(ae.money, ae.decimalPlaces);
}
} else if ("扣钱".equals(ae.type)) {
if (economy != null) {
double amt = Math.abs(ae.money);
economy.withdrawPlayer(p, amt);
hasEcon = true;
econMsg = "-$" + fmtAmount(amt, ae.decimalPlaces);
}
} else if ("经济盲盒".equals(ae.type)) {
if (economy != null) {
double amount = ae.moneyMin
+ rng.nextDouble()
* (ae.moneyMax - ae.moneyMin);
amount = Math.round(amount * 100.0)
/ 100.0;
economy.depositPlayer(p, amount);
hasEcon = true;
econMsg = "+$" + fmtAmount(amount,
ae.decimalPlaces);
}
} else if ("债券盲盒".equals(ae.type)) {
int range = Math.max(0, ae.bondMax - ae.bondMin);
int bondAmt = ae.bondMin
+ (range > 0 ? rng.nextInt(range + 1) : 0);
log("[债券盲盒] 金额=" + bondAmt + " bdb="
+ (bdb != null ? "有" : "无"));
if (bondAmt > 0 && bdb != null) {
int bef = 0;
try {
PreparedStatement q = bdb.prepareStatement(
"SELECT amount FROM bonds"
+ " WHERE player_name=?");
q.setString(1, p.getName());
ResultSet r = q.executeQuery();
if (r.next()) bef = r.getInt("amount");
r.close(); q.close();
} catch (Exception ex) {
log("[债券盲盒] 查询余额失败: " + ex);
}
try {
PreparedStatement ps = bdb.prepareStatement(
"INSERT INTO bonds"
+ "(player_name,amount)"
+ " VALUES(?,?) "
+ "ON CONFLICT(player_name)"
+ " DO UPDATE SET "
+ "amount=amount+?");
ps.setString(1, p.getName());
ps.setInt(2, bondAmt);
ps.setInt(3, bondAmt);
ps.executeUpdate();
ps.close();
} catch (SQLException ex) {
log("[债券盲盒] 写入失败: " + ex);
}
log("[债券盲盒] 写入后余额=" + (bef + bondAmt)
+ ",准备写流水");
logBondTx(p.getName(), "redeem", bondAmt,
"", "口令系统", "口令奖励-债券盲盒",
bef, bef + bondAmt);
hasBond = true;
bondMsg = "+" + bondAmt + "枚(盲盒)";
}
} else if ("联控盲盒".equals(ae.type)) {
int dayRange = Math.max(0, ae.daysMax - ae.daysMin);
int slotRange = Math.max(0, ae.slotsMax - ae.slotsMin);
int rd = ae.daysMin + (dayRange > 0 ? rng.nextInt(dayRange + 1) : 0);
int rs = ae.slotsMin + (slotRange > 0 ? rng.nextInt(slotRange + 1) : 0);
if (tryCyActivate(p, rs, rd)) {
hasSlots = true;
slotsMsg = "+" + rd + "天 +" + rs + "格(盲盒)";
} else {
p.sendMessage(colorize("&c[SDF1] 联控连接失败,盲盒作废"));
}
} else if ("联控".equals(ae.type)) {
if (tryCyActivate(p, ae.slots, ae.days)) {
hasSlots = true;
slotsMsg = "+" + ae.slots + "格 " + ae.days + "天";
}
} else if ("发债券".equals(ae.type)) {
int bondAmt = (int) ae.money;
if (bondAmt > 0 && bdb != null) {
int bef = 0;
try {
PreparedStatement q = bdb.prepareStatement(
"SELECT amount FROM bonds WHERE player_name=?");
q.setString(1, p.getName());
ResultSet r = q.executeQuery();
if (r.next()) bef = r.getInt("amount");
r.close(); q.close();
} catch (Exception ignored) {}
try {
PreparedStatement ps = bdb.prepareStatement(
"INSERT INTO bonds(player_name, amount) "
+ "VALUES(?,?) "
+ "ON CONFLICT(player_name) "
+ "DO UPDATE SET amount=amount+?");
ps.setString(1, p.getName());
ps.setInt(2, bondAmt);
ps.setInt(3, bondAmt);
ps.executeUpdate();
ps.close();
} catch (SQLException ignored) {}
logBondTx(p.getName(), "admin_give", bondAmt,
"", "口令系统", "口令奖励-发债券",
bef, bef + bondAmt);
hasBond = true;
bondMsg = "+" + bondAmt + "枚";
}
} else if ("删除口令".equals(ae.type)) {
consumeCode(msg, true);
}
}
// ===== 快照:执行后的经济/债券 =====
double econAfter = 0;
if (economy != null) econAfter = economy.getBalance(p);
int bondAfter = 0;
if (bdb != null) {
try {
PreparedStatement q2 = bdb.prepareStatement(
"SELECT amount FROM bonds WHERE player_name=?");
q2.setString(1, p.getName());
ResultSet rs2 = q2.executeQuery();
if (rs2.next()) bondAfter = rs2.getInt("amount");
rs2.close();
q2.close();
} catch (Exception ignored) {}
}
// ===== 统一输出 =====
p.sendMessage(colorize("&7============== &e" + sa.actions.size() + "个动作 &7=============="));
p.sendMessage(colorize("&7玩家: &e" + p.getName()));
p.sendMessage(colorize("&7口令: &e" + msg));
if (hasEcon) {
p.sendMessage(colorize("&7------------- &a经济 &7-------------"));
p.sendMessage(colorize("&7变动: &e" + econMsg));
p.sendMessage(colorize("&7余额: &e$" + String.format("%.2f", econBefore)
+ " &7-> &a$" + String.format("%.2f", econAfter)));
}
if (hasBond) {
p.sendMessage(colorize("&7------------- &6债券 &7-------------"));
p.sendMessage(colorize("&7变动: &e" + bondMsg));
p.sendMessage(colorize("&7余额: &e" + bondBefore + " &7-> &a" + bondAfter));
}
if (hasSlots) {
p.sendMessage(colorize("&7------------- &b联控 &7-------------"));
p.sendMessage(colorize("&7获得: &e" + slotsMsg));
}
if (!hasEcon && !hasBond && !hasSlots) {
p.sendMessage(colorize("&7无经济/债券/背包变动"));
}
p.sendMessage(colorize("&7============== &e执行完毕 &7=============="));
// 消耗口令(非删除口令类型统一处理)
if (sa.deleteCode) {
consumeCode(msg, true);
}
// 记名
if (sa.recordName && cfgNameBoard != null && !cfgNameBoard.isEmpty()) {
final String nameKey = msg + "_" + p.getName();
final String boardName = cfgNameBoard;
@@ -1233,80 +1588,62 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
try {
Scoreboard board = Bukkit.getScoreboardManager().getMainScoreboard();
Objective obj = board.getObjective(boardName);
if (obj == null) { log("[记名] 记名板不存在: " + boardName); fp.sendMessage(colorize("&c[SDF1] 记名板不存在,记名失败")); return; }
if (obj == null) {
log("[记名] 记名板不存在: " + boardName);
fp.sendMessage(colorize("&c[SDF1] 记名板不存在,记名失败"));
return;
}
obj.getScore(nameKey).setScore(1);
log("[记名] 写入: " + nameKey);
fp.sendMessage(colorize("&a[SDF1] 记名成功"));
} catch (Exception e) { log("[记名] 写入失败: " + e.getMessage()); }
} catch (Exception e) {
log("[记名] 写入失败: " + e.getMessage());
}
}
});
}
}
private void execGiveMoney(Player p, ActionEntry a, boolean deleteCode, String code) {
if (economy == null) { p.sendMessage(colorize("&c[SDF1] 经济不可用")); return; }
double before = economy.getBalance(p);
economy.depositPlayer(p, a.money);
double after = economy.getBalance(p);
consumeCode(code, deleteCode);
p.sendMessage(colorize("&a[SDF1] 获得 &e$" + fmtAmount(a.money, a.decimalPlaces)));
p.sendMessage(colorize("&7余额: &a$" + fmtMoney(before) + " &7-> &a$" + fmtMoney(after)));
/** 向 bond_transaction 表写入一条流水 */
private void logBondTx(String player, String type,
int amount, String target,
String operator, String reason,
int before, int after) {
Connection bdb = getBondDb();
if (bdb == null) {
log("[Bond] logBondTx: bdb=null, 跳过");
return;
}
try {
PreparedStatement ps = bdb.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, target != null ? target : "");
ps.setString(5, operator != null ? operator : "");
ps.setString(6, reason != null ? reason : "");
ps.setInt(7, before);
ps.setInt(8, after);
ps.setLong(9, System.currentTimeMillis());
ps.executeUpdate();
ps.close();
log("[Bond] logBondTx成功: " + player
+ " " + type + " " + amount);
} catch (Exception e) {
log("[Bond] logBondTx失败: " + e.getMessage());
e.printStackTrace();
}
}
private void execTakeMoney(Player p, ActionEntry a, boolean deleteCode, String code) {
if (economy == null) { p.sendMessage(colorize("&c[SDF1] 经济不可用")); return; }
double amount = Math.abs(a.money);
if (!economy.has(p, amount)) { p.sendMessage(colorize("&c[SDF1] 余额不足! 需要: $" + fmtAmount(amount, a.decimalPlaces))); return; }
double before = economy.getBalance(p);
economy.withdrawPlayer(p, amount);
double after = economy.getBalance(p);
consumeCode(code, deleteCode);
p.sendMessage(colorize("&c[SDF1] 扣除 &e$" + fmtAmount(amount, a.decimalPlaces)));
p.sendMessage(colorize("&7余额: &a$" + fmtMoney(before) + " &7-> &a$" + fmtMoney(after)));
}
private void execMoneyBox(final Player p, final ActionEntry a, final boolean deleteCode, final String code) {
if (economy == null) { p.sendMessage(colorize("&c[SDF1] 经济不可用")); return; }
p.sendMessage(colorize("&6&l[SDF1] &e&l盲盒开启中..."));
Bukkit.getScheduler().runTaskLater(this, new Runnable() { public void run() {
p.sendMessage(colorize("&6&l[SDF1] &e&l正在摇晃盲盒..."));
Bukkit.getScheduler().runTaskLater(Main.this, new Runnable() { public void run() {
p.sendMessage(colorize("&6&l[SDF1] &e&l即将揭晓..."));
Bukkit.getScheduler().runTaskLater(Main.this, new Runnable() { public void run() {
double amount = a.moneyMin + rng.nextDouble() * (a.moneyMax - a.moneyMin);
amount = Math.round(amount * 100.0) / 100.0;
double before = economy.getBalance(p); economy.depositPlayer(p, amount); double after = economy.getBalance(p);
consumeCode(code, deleteCode);
p.sendMessage(colorize("&6&l[SDF1] &e&l恭喜!获得 $&c&l" + fmtAmount(amount, a.decimalPlaces) + " &e&l!"));
p.sendMessage(colorize("&7余额: &a$" + fmtMoney(before) + " &7-> &a$" + fmtMoney(after)));
}}, 20L);
}}, 20L);
}}, 20L);
}
private void execSlotBox(final Player p, final ActionEntry a, final boolean deleteCode, final String code) {
log("[联控盲盒] 玩家=" + p.getName() + " 天数范围=" + a.daysMin + "~" + a.daysMax + " 格子范围=" + a.slotsMin + "~" + a.slotsMax);
p.sendMessage(colorize("&6&l[SDF1] &e&l盲盒开启中..."));
Bukkit.getScheduler().runTaskLater(this, new Runnable() { public void run() {
p.sendMessage(colorize("&6&l[SDF1] &e&l正在摇晃盲盒..."));
Bukkit.getScheduler().runTaskLater(Main.this, new Runnable() { public void run() {
p.sendMessage(colorize("&6&l[SDF1] &e&l即将揭晓..."));
Bukkit.getScheduler().runTaskLater(Main.this, new Runnable() { public void run() {
int dayRange = Math.max(0, a.daysMax - a.daysMin);
int slotRange = Math.max(0, a.slotsMax - a.slotsMin);
int rd = a.daysMin + (dayRange > 0 ? rng.nextInt(dayRange + 1) : 0);
int rs = a.slotsMin + (slotRange > 0 ? rng.nextInt(slotRange + 1) : 0);
log("[联控盲盒] 随机结果: " + rd + "天 " + rs + "格");
if (tryCyActivate(p, rs, rd)) {
consumeCode(code, deleteCode);
p.sendMessage(colorize("&6&l[SDF1] &e&l恭喜!获得 &c&l" + rd + "天会员 + " + rs + "格空间 &e&l!"));
} else {
p.sendMessage(colorize("&c[SDF1] 联控连接失败,盲盒作废"));
}
}}, 20L);
}}, 20L);
}}, 20L);
}
private void execCy(Player p, ActionEntry a, boolean deleteCode, String code) {
if (tryCyActivate(p, a.slots, a.days)) {
@@ -1321,11 +1658,139 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
catch (Exception e) { p.sendMessage(colorize("&c[SDF1] 联控失败")); log("[联控] 失败: " + e.getMessage()); return false; }
}
private void execDelete(Player p, ActionEntry a, String code) {
log("[删除] " + p.getName() + " 口令: \"" + code + "\"");
consumeCode(code, true);
}
private boolean tryBondRedeem(Player p, String code) {
Connection bdb = getBondDb();
if (bdb == null) return false;
try {
PreparedStatement ps = bdb.prepareStatement(
"SELECT amount, used FROM cdk WHERE code=?");
ps.setString(1, code);
ResultSet rs = ps.executeQuery();
if (!rs.next()) {
rs.close();
ps.close();
return false;
}
int amount = rs.getInt("amount");
int used = rs.getInt("used");
rs.close();
ps.close();
if (used == 1) {
p.sendMessage(colorize("&c此兑换码已被使用"));
return true;
}
PreparedStatement up = bdb.prepareStatement(
"UPDATE cdk SET used=1, used_by=?, used_time=? WHERE code=?");
up.setString(1, p.getName());
up.setLong(2, System.currentTimeMillis());
up.setString(3, code);
up.executeUpdate();
up.close();
int beforeBal = 0;
PreparedStatement q0 = bdb.prepareStatement(
"SELECT amount FROM bonds WHERE player_name=?");
q0.setString(1, p.getName());
ResultSet rs0 = q0.executeQuery();
if (rs0.next()) beforeBal = rs0.getInt("amount");
rs0.close(); q0.close();
int befBal = 0;
PreparedStatement q1 = bdb.prepareStatement(
"SELECT amount FROM bonds WHERE player_name=?");
q0.setString(1, p.getName());
ResultSet rs1 = q0.executeQuery();
if (rs0.next()) befBal = rs0.getInt("amount");
rs0.close(); q0.close();
PreparedStatement add = bdb.prepareStatement(
"INSERT INTO bonds(player_name, amount) VALUES(?,?) "
+ "ON CONFLICT(player_name) "
+ "DO UPDATE SET amount=amount+?");
add.setString(1, p.getName());
add.setInt(2, amount);
add.setInt(3, amount);
add.executeUpdate();
add.close();
logBondTx(p.getName(), "redeem", amount,
"", "CDK系统", "兑换码兑换",
befBal, befBal + amount);
p.sendMessage(colorize("&6&l[债券] &e&l恭喜!获得 &c&l" + amount + " &e&l债券!"));
log("[债券] " + p.getName() + " 兑换 " + amount + " 债券");
return true;
} catch (Exception e) {
log("[债券] 兑换异常: " + e.getMessage());
return false;
}
}
private void execGiveBond(Player p, ActionEntry a, boolean deleteCode, String code) {
int amount = (int) a.money;
if (amount <= 0) { p.sendMessage(colorize("&c[SDF1] 债券金额无效")); return; }
double oldEcon = 0;
if (economy != null) oldEcon = economy.getBalance(p);
int oldBonds = 0;
Connection bdb = getBondDb();
if (bdb != null) {
try {
PreparedStatement q = bdb.prepareStatement("SELECT amount FROM bonds WHERE player_name=?");
q.setString(1, p.getName());
ResultSet rs = q.executeQuery();
if (rs.next()) oldBonds = rs.getInt("amount");
rs.close();
q.close();
} catch (Exception ignored) {}
}
if (bdb != null) {
try {
PreparedStatement ps = bdb.prepareStatement(
"INSERT INTO bonds(player_name, amount) VALUES(?,?) "
+ "ON CONFLICT(player_name) DO UPDATE SET amount=amount+?");
ps.setString(1, p.getName());
ps.setInt(2, amount);
ps.setInt(3, amount);
ps.executeUpdate();
ps.close();
} catch (SQLException ignored) {}
}
consumeCode(code, deleteCode);
double newEcon = 0;
if (economy != null) newEcon = economy.getBalance(p);
int newBonds = 0;
if (bdb != null) {
try {
PreparedStatement q2 = bdb.prepareStatement("SELECT amount FROM bonds WHERE player_name=?");
q2.setString(1, p.getName());
ResultSet rs2 = q2.executeQuery();
if (rs2.next()) newBonds = rs2.getInt("amount");
rs2.close();
q2.close();
} catch (Exception ignored) {}
}
p.sendMessage(colorize("&7============== &6获得债券 &7=============="));
p.sendMessage(colorize("&7玩家: &e" + p.getName()));
p.sendMessage(colorize("&7口令: &e" + code));
p.sendMessage(colorize("&6&l获得 &c&l" + amount + " &6&l枚债券"));
p.sendMessage(colorize("&7-------------"));
p.sendMessage(colorize("&7经济余额: &e$" + String.format("%.2f", oldEcon) + " &7-> &a$" + String.format("%.2f", newEcon)));
p.sendMessage(colorize("&7债券余额: &e" + oldBonds + " &7-> &a" + newBonds));
p.sendMessage(colorize("&7============== &6获得债券 &7=============="));
}
// ★ 修改:权限不足直接返回帮助,不报"权限不足"
@Override
public boolean onCommand(CommandSender s, Command c, String l, String[] a) {
@@ -1514,7 +1979,36 @@ public class Main extends JavaPlugin implements Listener, CommandExecutor {
}
} else {
String[] parts = trimmed.split("[|,,、;;]+");
boolean isBondLine = trimmed.contains("债券");
for (String part : parts) {
if (isBondLine) {
String[] bondFields = part.split("\\s+");
if (bondFields.length >= 2) {
String bondCode = bondFields[0];
try {
int bondAmt = Integer.parseInt(bondFields[1]);
Connection bdb = getBondDb();
if (bdb != null) {
PreparedStatement bps = bdb.prepareStatement(
"INSERT OR IGNORE INTO cdk(code,amount,type,created_time) VALUES(?,?,?,?)");
bps.setString(1, bondCode);
bps.setInt(2, bondAmt);
bps.setString(3, "bond");
bps.setLong(4, System.currentTimeMillis());
int rows = bps.executeUpdate();
bps.close();
if (rows > 0) {
count++;
undoBuf.add(new UndoRecord(bondCode, currentScore));
} else {
skip++;
}
}
} catch (Exception ignored) {}
continue;
}
}
part = cleanImportCode(part); if (part.isEmpty()) continue;
int result = tryImportCode(obj, board, part, currentScore);
if (result == 1) { count++; undoBuf.add(new UndoRecord(part, currentScore)); }