From 0a6c8dbe20a948fb60af41ad7bab3e3e6b28d652 Mon Sep 17 00:00:00 2001 From: ypsdf1 Date: Fri, 22 May 2026 16:06:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E7=9B=B2=E7=9B=92?= =?UTF-8?q?=E7=A7=8D=E7=B1=BB=EF=BC=8C=E6=94=AF=E6=8C=81=E5=AF=B9=E6=8E=A5?= =?UTF-8?q?=20Sdf1=5Flogin=20=E5=80=BA=E5=88=B8=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/sdf1/Main.java | 728 ++++++++++++++++++++++++++----- 1 file changed, 611 insertions(+), 117 deletions(-) diff --git a/src/main/java/com/sdf1/Main.java b/src/main/java/com/sdf1/Main.java index b014092..38ee89b 100644 --- a/src/main/java/com/sdf1/Main.java +++ b/src/main/java/com/sdf1/Main.java @@ -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 ALIASES = new HashMap(); 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 actions = new ArrayList(); 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 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 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)); }