diff --git a/.gitignore b/.gitignore index cc2bdba..fe405f6 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,5 @@ build/ txt/ *.xlsx *.patch -新建*.txt \ No newline at end of file +新建*.txt +群戏归类.txt \ No newline at end of file diff --git a/src/main/java/Sdf1_login/ChatFilterManager.java b/src/main/java/Sdf1_login/ChatFilterManager.java index 91dabc3..59cb5de 100644 --- a/src/main/java/Sdf1_login/ChatFilterManager.java +++ b/src/main/java/Sdf1_login/ChatFilterManager.java @@ -37,6 +37,16 @@ public class ChatFilterManager { private boolean notifyAdmin = true; private boolean notifyAll = false; private int muteDuration = 300; + private boolean enabled = true; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean v) { + enabled = v; + } + private static final Pattern URL_PATTERN = Pattern.compile( @@ -106,6 +116,7 @@ public class ChatFilterManager { if (line.startsWith("\uFEFF")) line = line.substring(1); String t = line.trim(); + if (t.isEmpty() || t.startsWith("#")) continue; @@ -128,6 +139,12 @@ public class ChatFilterManager { String[] kv = t.split(":", 2); String k = kv[0].trim(); String v = kv[1].trim(); + if (equals("启用过滤") + || equals("enabled")) { + enabled = parseBool(v); + continue; + } + if (k.equals("通知管理员")) { notifyAdmin = parseBool(v); continue; @@ -238,6 +255,7 @@ public class ChatFilterManager { new FileOutputStream(f), StandardCharsets.UTF_8))) { pw.println("# Sdf1_chat 聊天过滤配置"); + pw.println("启用过滤: " + enabled); pw.println("通知管理员: true"); pw.println("全服通报: false"); pw.println("禁言时长: 300"); diff --git a/src/main/java/Sdf1_login/ChatInputManager.java b/src/main/java/Sdf1_login/ChatInputManager.java index 233546a..3134d04 100644 --- a/src/main/java/Sdf1_login/ChatInputManager.java +++ b/src/main/java/Sdf1_login/ChatInputManager.java @@ -36,7 +36,8 @@ public class ChatInputManager { TICKET_ADMIN_TYPE, TICKET_ADMIN_TITLE, TICKET_ADMIN_DESC, - TICKET_ADMIN_PROVIDER + TICKET_ADMIN_PROVIDER, + BACK_CHECK_ECONOMY_CONFIRM, } public static class PlayerState { @@ -63,6 +64,11 @@ public class ChatInputManager { public void reset(Player p) { states.remove(p.getName()); } + public boolean isInFlow(Player p) { + PlayerState state = states.get(p.getName()); + return state != null + && state.type != InputType.NONE; + } public void handleInput(Player p, String msg, Main mainPlugin) { @@ -542,24 +548,33 @@ public class ChatInputManager { } case BACK_CHECKIN: { - int cost = 20; - if (!mainPlugin.getDb() - .deductPoints( - p.getName(), cost)) { - p.sendMessage( - "§c积分不足,补签需 " - + cost + " 积分"); - reset(p); + String result = + mainPlugin.getCheckIn() + .backCheckIn(p, msg); + if (result == null) { + // 等待选择(NEED_CONFIRM) return; } - p.sendMessage( - "§a补签成功!扣除 " - + cost + " 积分。" - + "日期: " + msg); + if (result.startsWith( + "NEED_CONFIRM:")) { + String[] parts = + result.split(":"); + // 存储到Main的Map + mainPlugin.pendingBackCheck.put( + p.getName(), + parts[1] + ":" + parts[2]); + return; + } + if (result.startsWith("§c")) { + p.sendMessage(result); + } else { + p.sendMessage(result); + } reset(p); return; } + case INVITE_INPUT_CODE: { String code = msg.trim(); String myName = p.getName(); diff --git a/src/main/java/Sdf1_login/CheckInManager.java b/src/main/java/Sdf1_login/CheckInManager.java index fa0fdfb..e5bc4bc 100644 --- a/src/main/java/Sdf1_login/CheckInManager.java +++ b/src/main/java/Sdf1_login/CheckInManager.java @@ -1,114 +1,286 @@ -package Sdf1_login; - -import org.bukkit.entity.Player; - -import java.text.SimpleDateFormat; -import java.util.Date; - -public class CheckInManager { - - private final Main plugin; - - public CheckInManager(Main plugin) { - this.plugin = plugin; - } - - public boolean isCheckedInToday(String name) { - String last = (String) plugin.getDb() - .getField(name, "last_checkin_date"); - String today = new SimpleDateFormat("yyyy-MM-dd") - .format(new Date()); - return today.equals(last); - } - - public String checkIn(Player p) { - String name = p.getName(); - if (isCheckedInToday(name)) { - return plugin.getConfig2() - .msg("checkin_already"); - } - - Object streakObj = plugin.getDb() - .getField(name, "checkin_streak"); - int streak = streakObj != null - ? ((Number) streakObj).intValue() : 0; - - 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()) { - try { - java.util.Date lastD = new SimpleDateFormat( - "yyyy-MM-dd").parse(lastDate); - java.util.Date todayD = new SimpleDateFormat( - "yyyy-MM-dd").parse(today); - long diff = todayD.getTime() - lastD.getTime(); - if (diff > 86400000L * 2) { - streak = 0; - } else if (diff > 86400000L) { - streak++; - } - } catch (Exception ignored) { - } - } - - if (streak == 0) streak = 1; - else streak++; - - double multi = 1.0; - if (streak >= 21) multi = 1.5; - else if (streak >= 14) multi = 1.3; - else if (streak >= 7) multi = 1.1; - - int points = (int) (1 * multi); - 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"); - int total = totalObj != null - ? ((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)); - } - - public String backCheckIn(Player p, String dateStr) { - String name = p.getName(); - if (!plugin.getDb().deductPoints(name, 10)) { - return plugin.getConfig2() - .msg("checkin_already"); - } - try { - java.util.Date target = new SimpleDateFormat( - "yyyy-MM-dd").parse(dateStr); - java.util.Date now = new SimpleDateFormat( - "yyyy-MM-dd").parse( - new SimpleDateFormat("yyyy-MM-dd") - .format(new Date())); - long diff = now.getTime() - target.getTime(); - if (diff < 0 || diff > 3 * 86400000L) { - plugin.getDb().addPoints(name, 10); - return plugin.getConfig2() - .msg("checkin_already"); - } - plugin.getDb().addPoints(name, 1); - return "§a补签成功!日期: " + dateStr; - } catch (Exception e) { - plugin.getDb().addPoints(name, 10); - return "§c日期格式错误,请用 yyyy-MM-dd"; - } - } -} +package Sdf1_login; + +import org.bukkit.entity.Player; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +public class CheckInManager { + + private final Main plugin; + + public CheckInManager(Main plugin) { + this.plugin = plugin; + } + + // ===== 今日是否已签到 ===== + public boolean isCheckedInToday( + String name) { + String last = (String) plugin.getDb() + .getField(name, + "last_checkin_date"); + String today = new SimpleDateFormat( + "yyyy-MM-dd") + .format(new Date()); + return today.equals(last); + } + + // ===== 签到 ===== + public String checkIn(Player p) { + String name = p.getName(); + if (isCheckedInToday(name)) { + return plugin.getConfig2() + .msg("checkin_already"); + } + + Object streakObj = plugin.getDb() + .getField(name, + "checkin_streak"); + int streak = streakObj != null + ? ((Number) streakObj) + .intValue() : 0; + + 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()) { + try { + java.util.Date lastD = + new SimpleDateFormat( + "yyyy-MM-dd") + .parse(lastDate); + java.util.Date todayD = + new SimpleDateFormat( + "yyyy-MM-dd") + .parse(today); + long diff = todayD.getTime() + - lastD.getTime(); + if (diff > 86400000L * 2) { + streak = 0; + } else if (diff + > 86400000L) { + streak++; + } + } catch (Exception ignored) { + } + } + + if (streak == 0) streak = 1; + else streak++; + + double multi = 1.0; + if (streak >= 21) multi = 1.5; + else if (streak >= 14) multi = 1.3; + else if (streak >= 7) multi = 1.1; + + 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); + + Object totalObj = plugin.getDb() + .getField(name, + "total_checkin_days"); + int total = totalObj != null + ? ((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)); + } + + // ===== 获取连续天数 ===== + public int getStreak(String name) { + Object val = plugin.getDb() + .getField(name, + "checkin_streak"); + if (val instanceof Number) { + return ((Number) val) + .intValue(); + } + return 0; + } + + // ===== 补签入口 ===== + public String backCheckIn( + Player p, String dateStr) { + String name = p.getName(); + String normalized = + normalizeDate(dateStr); + + // 严格日期验证 + java.text.SimpleDateFormat sdf = + new java.text.SimpleDateFormat( + "yyyy-MM-dd"); + sdf.setLenient(false); + java.util.Date target; + try { + target = sdf.parse(normalized); + } catch (Exception ex) { + return "§c无效日期: " + dateStr; + } + String reformat = + sdf.format(target); + if (!reformat.equals(normalized)) { + return "§c无效日期: " + dateStr; + } + + String today = sdf.format(new Date()); + + if (normalized.compareTo(today) >= 0) { + return "§c今天不需要补签"; + } + + String threeDaysAgo = getPastDate(3); + if (normalized.compareTo( + threeDaysAgo) < 0) { + return "§c只能补签3天内" + + "(最多到 " + + threeDaysAgo + ")"; + } + + Object val = plugin.getDb() + .getField(name, + "backcheck_dates"); + String existing = val != null + ? val.toString() : ""; + boolean alreadyBacked = false; + if (!existing.isEmpty()) { + for (String d + : existing.split(",")) { + if (normalizeDate(d.trim()) + .equals(normalized)) { + alreadyBacked = true; + break; + } + } + } + if (alreadyBacked) { + return "§c该日期已补签: " + + normalized; + } + + Object normalCheck = + plugin.getDb().getField( + name, + "last_checkin_date"); + String lastCheckin = normalCheck + != null + ? normalCheck.toString() + : ""; + if (lastCheckin.equals(normalized)) { + return "§c该日期已签到: " + + normalized; + } + + if (!plugin.getDb() + .deductPoints(name, 10)) { + return "§c积分不足,补签需 10 积分"; + } + + net.milkbowl.vault.economy + .Economy econ = + plugin.getEconomy(); + if (econ != null) { + double bal = econ.getBalance(p); + if (bal < 10.0) { + plugin.getDb() + .addPoints(name, 10); + int mult = plugin.getConfig2() + .backCheckPointMultiplier; + int needed = 10 * mult; + p.sendMessage("§e§l[补签] " + + "§f经济不足($" + + String.format( + "%.2f", bal) + + " < $10)"); + p.sendMessage("§7是否用 §a§l" + + needed + "积分 " + + "§7代替 10经济?"); + p.sendMessage("§7输入 §a1 " + + "§7同意," + + "§c其他 §7取消"); + return "NEED_CONFIRM:" + + normalized + + ":" + needed; + } + econ.withdrawPlayer(p, 10); + } + + doBackCheckIn(p, normalized); + return "§a补签成功!日期: " + + normalized + + " §7(扣除10积分+10经济)"; + } + + + // ===== 执行补签 ===== + public void doBackCheckIn( + Player p, String dateStr) { + String name = p.getName(); + Object val = plugin.getDb() + .getField(name, + "backcheck_dates"); + String existing = val != null + ? val.toString() : ""; + String updated = existing.isEmpty() + ? dateStr + : existing + "," + dateStr; + plugin.getDb().setField(name, + "backcheck_dates", updated); + int streak = getStreak(name); + plugin.getDb().setField(name, + "checkin_streak", + streak + 1); + } + + // ===== 标准化日期 ===== + private String normalizeDate( + String input) { + String s = input.trim() + .replace("/", "-"); + if (s.matches("\\d{8}")) { + return s.substring(0, 4) + "-" + + s.substring(4, 6) + "-" + + s.substring(6, 8); + } + return s; + } + + // ===== 获取N天前日期 ===== + private String getPastDate( + int daysAgo) { + Calendar cal = + Calendar.getInstance(); + cal.add(Calendar.DAY_OF_MONTH, + -daysAgo); + return new SimpleDateFormat( + "yyyy-MM-dd") + .format(cal.getTime()); + } +} diff --git a/src/main/java/Sdf1_login/ConfigManager.java b/src/main/java/Sdf1_login/ConfigManager.java index 5bb0609..13b7cfa 100644 --- a/src/main/java/Sdf1_login/ConfigManager.java +++ b/src/main/java/Sdf1_login/ConfigManager.java @@ -4,14 +4,15 @@ import java.io.*; import java.nio.charset.StandardCharsets; import java.util.*; -import static java.lang.Double.parseDouble; - public class ConfigManager { private final File dataFolder; - private final Map messages = new LinkedHashMap<>(); - private final Map smtpSettings = new LinkedHashMap<>(); + private final Map messages = + new LinkedHashMap<>(); + private final Map smtpSettings = + new LinkedHashMap<>(); + // ===== 公共字段 ===== public String adminTag = "admin"; public String adminPassword = "qweasd"; public int maxAccountsPerIP = 3; @@ -27,9 +28,26 @@ public class ConfigManager { public String smtpConfiguringPlayer = ""; public String broadcastServerUrl = ""; public int httpPort = 8080; - public String garbageTag= "admin"; + public String garbageTag = "admin"; + // 签到经济奖励 + public double checkinRewardFixed = 0; + public double checkinRewardMin = 0; + public double checkinRewardMax = 0; + public boolean checkinGivePoints = true; + public int checkinPoints = 10; + public String checkinRewardType = "none"; + public int backCheckPointMultiplier = 5; + // 提成/工单 + public double providerPointsPerScore = 2.0; + public int baseEconomyReward = 100; + public double providerEconomyPerScore = 50.0; + public int requesterPointsReward = 10; + // 垃圾站 + public boolean garbageEnabled = true; + public int garbageInterval = 300; + public int garbageMaxRounds = 1; public ConfigManager(File dataFolder) { this.dataFolder = dataFolder; @@ -39,232 +57,132 @@ public class ConfigManager { public void loadMessages() { messages.clear(); - File file = new File(dataFolder, "消息.txt"); - if (!file.exists()) createDefaultMessages(file); + File file = + new File(dataFolder, "消息.txt"); + if (!file.exists()) + createDefaultMessages(file); loadMap(file, messages); } public String msg(String key) { - String val = messages.getOrDefault(key, key); + String val = + messages.getOrDefault(key, key); val = val.replace("", "&l"); val = val.replace("", "&r"); val = val.replace("
", "\n"); val = val.replace("\\n", "\n"); - // &颜色码 转换为 §颜色码 val = val.replaceAll( "&([0-9a-fk-orA-FK-OR])", "§$1"); return val; } - private void createDefaultMessages(File f) { List L = new ArrayList<>(); - L.add("# =========================================="); - L.add("# Sdf1_login 消息配置文件"); - L.add("# =========================================="); - L.add("# 颜色代码使用 § 符号"); - L.add("# §0黑色 §1深蓝 §2深绿 §3深青"); - L.add("# §4深红 §5深紫 §6金色 §7灰色"); - L.add("# §8深灰 §9蓝色 §a绿色 §b浅蓝"); - L.add("# §c红色 §d粉色 §e黄色 §f白色"); - L.add("# §l粗体 §n下划线 §o斜体 §r重置"); - L.add("#"); - L.add("# 占位符用 {xxx} 表示,运行时自动替换"); - L.add("# 支持 粗体 标签"); - L.add("# 支持 \\n 或
换行"); - L.add("# 修改后执行 /sdf1_login reload 重载"); - L.add("# =========================================="); + L.add("# ===== Sdf1_login 消息配置 ====="); + L.add("# 占位符用 {xxx} 表示"); + L.add("# 修改后执行 /sdf1_login reload"); L.add(""); - // --- 登录/注册 --- - L.add("# ---- 登录注册相关 ----"); - L.add("# login_timeout: 登录超时被踢出时的提示"); + L.add("# ---- 登录注册 ----"); L.add("login_timeout=§c登录超时,请重新登录"); - L.add("# not_registered: 未注册玩家收到的提示"); L.add("not_registered=§e您尚未注册,请使用 /reg <密码>"); - L.add("# not_logged_in: 已注册但未登录的提示"); L.add("not_logged_in=§e您尚未登录,请使用 /login <密码>"); - L.add("# already_registered: 已注册玩家再次注册时的提示"); L.add("already_registered=§c您已注册过账号"); - L.add("# already_logged_in: 已登录玩家再次登录时的提示"); L.add("already_logged_in=§c您已登录"); - L.add("# reg_success: 注册成功提示"); L.add("reg_success=§a注册成功!欢迎来到服务器"); - L.add("# login_success: 登录成功提示"); L.add("login_success=§a登录成功!"); - L.add("# login_failed: 密码错误提示"); L.add("login_failed=§c密码错误,请重试"); L.add(""); - // --- 密码 --- - L.add("# ---- 密码相关 ----"); - L.add("# password_wrong: 旧密码验证失败"); + L.add("# ---- 密码 ----"); L.add("password_wrong=§c密码错误"); - L.add("# password_changed: 密码修改成功"); L.add("password_changed=§a密码修改成功!"); - L.add("# password_format_error: 密码格式不符合要求"); L.add("password_format_error=§c密码需6位以上,含大小写字母和数字"); - L.add("# password_same: 新密码与旧密码相同"); L.add("password_same=§c新密码不能与旧密码相同"); - L.add("# reset_no_email: 找回密码时未绑定邮箱"); L.add("reset_no_email=§c您未绑定邮箱,无法找回密码"); - L.add("# reset_sent: 临时密码已发送到邮箱"); - L.add("reset_sent=§a临时密码已发送到您的邮箱,请查收"); + L.add("reset_sent=§a临时密码已发送到您的邮箱"); L.add(""); - // --- 签到积分 --- - L.add("# ---- 签到积分相关 ----"); - L.add("# checkin_already: 今日已签到"); + L.add("# ---- 签到积分 ----"); L.add("checkin_already=§c您今日已签到"); - L.add("# checkin_success: 签到成功,{points}=获得积分 {streak}=连续天数 {multi}=倍率"); - L.add("checkin_success=§a签到成功!获得 {points} 积分,连续 {streak} 天,倍率 x{multi}"); - L.add("# points_insufficient: 积分不足"); + L.add("checkin_success=§a签到成功!获得 {points} 积分"); L.add("points_insufficient=§c积分不足,当前: {points}"); - L.add("# points_purchase_success: 积分购买成功"); L.add("points_purchase_success=§a购买成功!消耗 {count} 积分"); L.add(""); - // --- 挂机 --- - L.add("# ---- 挂机检测相关 ----"); - L.add("# afk_set_enabled: 挂机检测开启"); + L.add("# ---- 挂机检测 ----"); L.add("afk_set_enabled=§a挂机检测已开启"); - L.add("# afk_set_disabled: 挂机检测关闭"); L.add("afk_set_disabled=§c挂机检测已关闭"); - L.add("# afk_kick: 因挂机被踢出"); L.add("afk_kick=§c因挂机过久被踢出"); L.add(""); - // --- 邮箱 --- - L.add("# ---- 邮箱相关 ----"); - L.add("# email_set: 邮箱设置成功"); + L.add("# ---- 邮箱 ----"); L.add("email_set=§a邮箱设置成功:{email}"); - L.add("# email_format_error: 邮箱格式错误"); L.add("email_format_error=§c邮箱格式不正确"); L.add(""); - // --- 邀请 --- - L.add("# ---- 邀请相关 ----"); - L.add("# invite_code_generated: 邀请码生成"); + L.add("# ---- 邀请 ----"); L.add("invite_code_generated=§a邀请码:{code}"); - L.add("# invite_used: 使用邀请码成功"); L.add("invite_used=§a邀请成功,邀请人:{player}"); - L.add("# invite_self: 使用自己的邀请码"); L.add("invite_self=§c不能使用自己的邀请码"); - L.add("# invite_not_found: 邀请码不存在"); L.add("invite_not_found=§c邀请码不存在"); - L.add("# invite_referral_bonus: 被邀请人签到后邀请人获得奖励"); L.add("invite_referral_bonus=§a邀请奖励!获得 {points} 积分"); L.add(""); - // --- 礼包 --- - L.add("# ---- 新人礼包相关 ----"); - L.add("# gift_not_ready: 礼包条件未满足"); + L.add("# ---- 礼包 ----"); L.add("gift_not_ready=§c礼包条件未满足"); - L.add("# gift_claimed: 礼包领取成功"); L.add("gift_claimed=§a礼包领取成功!"); L.add(""); - // --- 审批/IP --- - L.add("# ---- 审批与IP限制 ----"); - L.add("# need_approval: 注册需审批"); + L.add("# ---- 审批与IP ----"); L.add("need_approval=§e注册需审批 (工单#{id})"); - L.add("# ip_max_accounts: IP注册已达上限"); L.add("ip_max_accounts=§cIP注册已达上限"); L.add(""); - // --- 管理员 --- - L.add("# ---- 管理员操作 ----"); - L.add("# admin_delete_confirm: 管理员删除账号确认提示"); + L.add("# ---- 管理员 ----"); L.add("admin_delete_confirm=§c请在聊天中输入玩家名确认删除"); L.add(""); - // --- 聊天过滤 --- L.add("# ---- 聊天过滤 ----"); - L.add("# chat_muted: 禁言玩家尝试聊天"); - L.add("chat_muted=§c§l[Sdf1_chat] §f你已被禁言,无法聊天"); - L.add("# chat_url_blocked: 发送违规链接被拦截"); - L.add("# {url}=检测到的链接地址"); - L.add("chat_url_blocked=§c§l[Sdf1_chat] §f检测到违规第三方链接: §e{url}"); - L.add("# chat_url_violation: 当前违规次数"); - L.add("# {count}=当前违规次数"); + L.add("chat_muted=§c§l[Sdf1_chat] §f你已被禁言"); + L.add("chat_url_blocked=§c§l[Sdf1_chat] §f检测到违规链接: §e{url}"); L.add("chat_url_violation=§7当前违规次数: §e{count}"); - L.add("# chat_url_admin_notify: 通知管理员,{player}=玩家名 {url}=链接 {count}=次数"); - L.add("chat_url_admin_notify=§e§l[Sdf1_chat] §f{player} 发送违规链接: {url} (第{count}次)"); - L.add("# chat_url_broadcast: 全服通报,{player}=玩家名"); + L.add("chat_url_admin_notify=§e§l[Sdf1_chat] §f{player} 发送违规链接: {url}"); L.add("chat_url_broadcast=§c§l[Sdf1_chat] §e{player} §f因发送违规链接被处罚"); L.add(""); - // --- 通用 --- L.add("# ---- 通用 ----"); - L.add("# 未知参数: 未知命令或权限不足时的提示"); L.add("未知参数=§c未知参数或权限不足"); - L.add("# only_player: 仅玩家可用的命令"); - L.add("# only_player=§c此命令仅玩家可用"); writeLines(f, L); } - // ==================== SMTP ==================== public void loadSmtp() { smtpSettings.clear(); - File file = new File(dataFolder, "SMTP设置.txt"); - if (!file.exists()) createDefaultSmtp(file); + File file = + new File(dataFolder, "SMTP设置.txt"); + if (!file.exists()) + createDefaultSmtp(file); loadMap(file, smtpSettings); } - private double parseDouble(String s, double def) { - try { return Double.parseDouble(s.trim()); } - catch (Exception e) { return def; } - } public String getSmtp(String key) { return smtpSettings.getOrDefault(key, ""); } - /** 获取挂机白名单列表 */ - public List getAfkWhitelist() { - List list = new ArrayList<>(); - File file = new File(dataFolder, "挂机白名单.txt"); - if (!file.exists()) { - try { - file.createNewFile(); - try (BufferedWriter w = new BufferedWriter( - new OutputStreamWriter( - new FileOutputStream(file), - StandardCharsets.UTF_8))) { - w.write("# 每行一个玩家名,前面加 # 为注释"); - w.newLine(); - w.write("# 以下玩家不会被挂机踢出"); - w.newLine(); - } - } catch (IOException ignored) {} - } - try (BufferedReader r = new BufferedReader( - new InputStreamReader( - new FileInputStream(file), - StandardCharsets.UTF_8))) { - String line; - while ((line = r.readLine()) != null) { - line = line.trim(); - if (!line.isEmpty() && !line.startsWith("#")) { - list.add(line); - } - } - } catch (IOException ignored) {} - return list; - } - /** 设置SMTP单项(聊天输入用) */ public void setSmtp(String key, String value) { smtpSettings.put(key, value); } - /** 将当前SMTP设置写回文件 */ public void saveSmtp() { - File file = new File(dataFolder, "SMTP设置.txt"); + File file = + new File(dataFolder, "SMTP设置.txt"); List L = new ArrayList<>(); L.add("# ===== SMTP 邮件服务器配置 ====="); - for (Map.Entry e : smtpSettings.entrySet()) { + for (Map.Entry e + : smtpSettings.entrySet()) { L.add(e.getKey() + "=" + e.getValue()); } writeLines(file, L); @@ -285,17 +203,25 @@ public class ConfigManager { // ==================== 插件设置 ==================== public void loadSettings() { - File file = new File(dataFolder, "插件设置.txt"); - if (!file.exists()) createDefaultSettings(file); - Map m = new LinkedHashMap<>(); + File file = + new File(dataFolder, "插件设置.txt"); + if (!file.exists()) + createDefaultSettings(file); + Map m = + new LinkedHashMap<>(); loadMap(file, m); - adminTag = m.getOrDefault("管理标签", adminTag); - adminPassword = m.getOrDefault("管理密码", adminPassword); + + adminTag = m.getOrDefault( + "管理标签", adminTag); + adminPassword = m.getOrDefault( + "管理密码", adminPassword); maxAccountsPerIP = parseInt( m.getOrDefault("每IP最大账号数", "3"), 3); - approvalMode = m.getOrDefault("审批模式", approvalMode); + approvalMode = m.getOrDefault( + "审批模式", approvalMode); autoApproveDelayMinutes = parseInt( - m.getOrDefault("自动审批延迟分钟", "30"), 30); + m.getOrDefault("自动审批延迟分钟", "30"), + 30); loginTimeout = parseInt( m.getOrDefault("登录超时秒数", "60"), 60); afkEnabled = Boolean.parseBoolean( @@ -304,56 +230,106 @@ public class ConfigManager { m.getOrDefault("挂机超时秒数", "300"), 300); smtpSsl = Boolean.parseBoolean( m.getOrDefault("smtp加密", "true")); + httpPort = parseInt( + m.getOrDefault("http-port", "8080"), + 8080); + broadcastServerUrl = m.getOrDefault( + "resource-pack-url", ""); + + // 提成 providerPointsPerScore = parseDouble( - m.getOrDefault("服务商积分倍率", "2.0"), - 2.0); + m.getOrDefault( + "服务商积分倍率", "2.0"), 2.0); baseEconomyReward = parseInt( - m.getOrDefault("基础经济奖励", "100"), - 100); + m.getOrDefault( + "基础经济奖励", "100"), 100); providerEconomyPerScore = parseDouble( - m.getOrDefault("经济奖励每评分", "50.0"), - 50.0); + m.getOrDefault( + "经济奖励每评分", "50.0"), 50.0); requesterPointsReward = parseInt( - m.getOrDefault("报单人积分奖励", "10"), - 10); + m.getOrDefault( + "报单人积分奖励", "10"), 10); + + // 垃圾站 garbageEnabled = Boolean.parseBoolean( - m.getOrDefault("垃圾站_启用", "true")); + m.getOrDefault( + "垃圾站_启用", "true")); garbageInterval = parseInt( - m.getOrDefault("垃圾站_清理间隔秒", - "300"), 300); + m.getOrDefault( + "垃圾站_清理间隔秒", "300"), 300); garbageMaxRounds = parseInt( - m.getOrDefault("垃圾站_保留轮数", - "1"), 1); - // loadSettings() 末尾添加: + m.getOrDefault( + "垃圾站_保留轮数", "1"), 1); + + // 签到 + checkinRewardFixed = parseDouble( + m.getOrDefault( + "签到固定金额", "0"), 0); + checkinRewardMin = parseDouble( + m.getOrDefault( + "签到最小金额", "0"), 0); + checkinRewardMax = parseDouble( + m.getOrDefault( + "签到最大金额", "0"), 0); + checkinGivePoints = Boolean.parseBoolean( + m.getOrDefault( + "签到给积分", "true")); + checkinPoints = parseInt( + m.getOrDefault( + "签到积分数量", "10"), 10); + backCheckPointMultiplier = parseInt( + m.getOrDefault( + "补签积分倍率", "5"), 5); + + + // 自动判断模式 + if (checkinRewardFixed > 0) { + checkinRewardType = "fixed"; + } else if (checkinRewardMin > 0 + && checkinRewardMax > 0 + && checkinRewardMax + > checkinRewardMin) { + checkinRewardType = "range"; + } else { + checkinRewardType = "none"; + } } + public void saveSettings() { - File file = new File(dataFolder, "插件设置.txt"); + File file = + new File(dataFolder, "插件设置.txt"); List L = new ArrayList<>(); L.add("# ===== Sdf1_login 插件设置 ====="); L.add("管理标签=" + adminTag); L.add("管理密码=" + adminPassword); L.add("每IP最大账号数=" + maxAccountsPerIP); L.add("审批模式=" + approvalMode); - L.add("自动审批延迟分钟=" + autoApproveDelayMinutes); + L.add("自动审批延迟分钟=" + + autoApproveDelayMinutes); L.add("登录超时秒数=" + loginTimeout); L.add("挂机踢出=" + afkEnabled); L.add("挂机超时秒数=" + afkTimeout); - // 垃圾站设置 + L.add("# ---- 资源包 ----"); + L.add("resource-pack-url=" + + broadcastServerUrl); + L.add("http-port=" + httpPort); L.add("# ---- 垃圾站 ----"); L.add("垃圾站_启用=" + garbageEnabled); - L.add("垃圾站_清理间隔秒=" + garbageInterval); + L.add("垃圾站_清理间隔秒=" + + garbageInterval); L.add("垃圾站_保留轮数=" + garbageMaxRounds); + L.add("# ---- 签到盲盒奖励 ----"); + L.add("签到固定金额=" + checkinRewardFixed); + L.add("签到最小金额=" + checkinRewardMin); + L.add("签到最大金额=" + checkinRewardMax); + L.add("签到给积分=" + checkinGivePoints); + L.add("签到积分数量=" + checkinPoints); + L.add("补签积分倍率=" + + backCheckPointMultiplier); - writeLines(file, L); + + writeLines(file, L); } - public double providerPointsPerScore = 2.0; - public int baseEconomyReward = 100; - public double providerEconomyPerScore = 50.0; - public int requesterPointsReward = 10; - public boolean garbageEnabled = true; - public int garbageInterval = 300; - public int garbageMaxRounds = 1; - private void createDefaultSettings(File f) { List L = new ArrayList<>(); @@ -366,100 +342,177 @@ public class ConfigManager { L.add("登录超时秒数=60"); L.add("挂机踢出=false"); L.add("挂机超时秒数=300"); - L.add("服务商积分倍率=" + providerPointsPerScore); - L.add("基础经济奖励=" + baseEconomyReward); - L.add("经济奖励每评分=" + providerEconomyPerScore); - L.add("报单人积分奖励=" + requesterPointsReward); + L.add("# ---- 资源包 ----"); + L.add("resource-pack-url="); + L.add("http-port=8080"); L.add("# ---- 垃圾站 ----"); L.add("垃圾站_启用=true"); L.add("垃圾站_清理间隔秒=300"); L.add("垃圾站_保留轮数=1"); - L.add("# ---- 资源包(HTTP) ----"); - L.add("resource-pack-url="); - L.add("http-port=8080"); + L.add("# ---- 签到盲盒奖励 ----"); + L.add("签到固定金额=0"); + L.add("签到最小金额=50"); + L.add("签到最大金额=500"); + L.add("签到给积分=true"); + L.add("签到积分数量=10"); + L.add("补签积分倍率=5"); writeLines(f, L); } - // ==================== 通用读写 ==================== + // ==================== 通用工具 ==================== - private void loadMap(File file, Map map) { - try (BufferedReader r = new BufferedReader( - new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) { + public List getAfkWhitelist() { + List list = new ArrayList<>(); + File file = new File( + dataFolder, "挂机白名单.txt"); + if (!file.exists()) { + try { + file.createNewFile(); + try (BufferedWriter w = + new BufferedWriter( + new OutputStreamWriter( + new FileOutputStream( + file), + StandardCharsets + .UTF_8))) { + w.write("# 每行一个玩家名"); + w.newLine(); + } + } catch (IOException ignored) {} + } + try (BufferedReader r = + new BufferedReader( + new InputStreamReader( + new FileInputStream( + file), + StandardCharsets + .UTF_8))) { String line; while ((line = r.readLine()) != null) { line = line.trim(); - if (line.isEmpty() || line.startsWith("#")) continue; - int eq = line.indexOf('='); - if (eq > 0) map.put(line.substring(0, eq).trim(), line.substring(eq + 1).trim()); + if (!line.isEmpty() + && !line.startsWith("#")) { + list.add(line); + } } - } catch (IOException e) { - System.err.println("[Sdf1_login] 读取失败: " + file.getName()); - } + } catch (IOException ignored) {} + return list; } - private void writeLines(File file, List lines) { - try (BufferedWriter w = new BufferedWriter( - new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) { - for (String l : lines) { w.write(l); w.newLine(); } - } catch (IOException e) { - System.err.println("[Sdf1_login] 写入失败: " + file.getName()); - } - } - - private int parseInt(String s, int def) { - try { return Integer.parseInt(s.trim()); } catch (Exception e) { return def; } - } - /** - * 生成6位验证码(字母+数字) - */ public String generateSmtpVerifyCode() { String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789"; StringBuilder sb = new StringBuilder(); - java.util.Random r = new java.util.Random(); + java.util.Random r = + new java.util.Random(); for (int i = 0; i < 6; i++) { - sb.append(chars.charAt(r.nextInt( - chars.length()))); + sb.append(chars.charAt( + r.nextInt(chars.length()))); } smtpVerifyCode = sb.toString(); smtpVerifyExpire = - System.currentTimeMillis() + 300000L; + System.currentTimeMillis() + + 300000L; return smtpVerifyCode; } - /** - * 检查验证码是否有效 - */ - public boolean checkSmtpVerifyCode(String code) { - if (System.currentTimeMillis() > smtpVerifyExpire) { + public boolean checkSmtpVerifyCode( + String code) { + if (System.currentTimeMillis() + > smtpVerifyExpire) { smtpVerifyCode = ""; return false; } - return smtpVerifyCode.equalsIgnoreCase(code); + return smtpVerifyCode + .equalsIgnoreCase(code); } - /** - * 清除验证码 - */ public void clearSmtpVerifyCode() { smtpVerifyCode = ""; smtpVerifyExpire = 0; } - /** - * 检查发送间隔是否>=1分钟 - */ public boolean canSendSmtpVerify() { return System.currentTimeMillis() - smtpLastSendTime >= 60000L; } public void recordSmtpSendTime() { - smtpLastSendTime = System.currentTimeMillis(); + smtpLastSendTime = + System.currentTimeMillis(); } + // ==================== 文件读写 ==================== + + private void loadMap(File file, + Map map) { + try (BufferedReader r = + new BufferedReader( + new InputStreamReader( + new FileInputStream( + file), + StandardCharsets + .UTF_8))) { + String line; + while ((line = r.readLine()) != null) { + line = line.trim(); + if (line.isEmpty() + || line.startsWith("#")) + continue; + int eq = line.indexOf('='); + if (eq > 0) { + map.put( + line.substring(0, eq) + .trim(), + line.substring(eq + 1) + .trim()); + } + } + } catch (IOException e) { + System.err.println( + "[Sdf1_login] 读取失败: " + + file.getName()); + } + } + + private void writeLines(File file, + List lines) { + try (BufferedWriter w = + new BufferedWriter( + new OutputStreamWriter( + new FileOutputStream( + file), + StandardCharsets + .UTF_8))) { + for (String l : lines) { + w.write(l); + w.newLine(); + } + } catch (IOException e) { + System.err.println( + "[Sdf1_login] 写入失败: " + + file.getName()); + } + } + + private double parseDouble( + String s, double def) { + try { + return Double.parseDouble(s.trim()); + } catch (Exception e) { + return def; + } + } + + private int parseInt(String s, int def) { + try { + return Integer.parseInt(s.trim()); + } catch (Exception e) { + return def; + } + } } diff --git a/src/main/java/Sdf1_login/GUIManager.java b/src/main/java/Sdf1_login/GUIManager.java index 8651270..117bbe0 100644 --- a/src/main/java/Sdf1_login/GUIManager.java +++ b/src/main/java/Sdf1_login/GUIManager.java @@ -30,7 +30,6 @@ public class GUIManager { public GUIManager(Main plugin) { this.plugin = plugin; } - public void openMain(Player p) { Inventory g = Bukkit.createInventory( null, 27, T_MAIN); @@ -42,17 +41,21 @@ public class GUIManager { g.setItem(14, mkItem(Material.EMERALD, "§d§l积分商城")); g.setItem(16, mkItem(Material.CHEST, - "§e§l新人礼包")); - g.setItem(18, mkItem(Material.BOOK, + "§e§l任务中心")); + g.setItem(18, mkItem(Material.EMERALD_BLOCK, + "§a§l每日签到", + "§7签到获取积分和惊喜奖励")); + g.setItem(20, mkItem(Material.BOOK, "§6工单系统", "§7提交bug、求助、举报")); if (p.isOp() || isAdmin(p)) { - g.setItem(20, mkItem(Material.CHEST, + g.setItem(22, mkItem(Material.CHEST, "§6垃圾回收站", "§7查看和取回清理的物品")); } if (isAdmin(p)) { - g.setItem(22, mkItem(Material.REDSTONE_BLOCK, + g.setItem(24, mkItem( + Material.REDSTONE_BLOCK, "§c§l管理员面板")); } p.openInventory(g); @@ -107,6 +110,70 @@ public class GUIManager { } // 在 openGiftStages 方法之前添加: + public void openGiftStages(Player p) { + Inventory g = Bukkit.createInventory( + null, 27, T_GIFT_STAGES); + fillBg(g); + + QuestTracker qt = + plugin.getQuestTracker(); + if (qt == null) { + g.setItem(22, mkItem( + Material.BARRIER, + "§7任务系统未加载")); + p.openInventory(g); + return; + } + + List quests = + qt.getQuests("新人任务"); + String playerName = p.getName(); + + int max = quests.size(); + for (int i = 0; + i < max && i < 9; i++) { + QuestTracker.QuestFile qf = + quests.get(i); + boolean completed = + qt.isStageCompleted( + playerName, qf); + boolean claimed = + qt.hasClaimed( + playerName, qf); + + Material mat; + String status; + if (completed && claimed) { + mat = Material.LIME_WOOL; + status = "§a已领取"; + } else if (completed) { + mat = Material.YELLOW_WOOL; + status = "§e可领取"; + } else { + mat = Material.RED_WOOL; + status = "§c未达标"; + } + + List lore = + new ArrayList<>(); + lore.add("§7" + status); + for (String cond : + qf.conditions) { + String progress = + qt.getProgress( + playerName, cond); + lore.add("§7" + progress); + } + + g.setItem(10 + i, mkItem(mat, + "§e" + qf.displayName, + lore.toArray( + new String[0]))); + } + g.setItem(26, mkItem(Material.ARROW, + "§7返回")); + p.openInventory(g); + } /** * 获取任务列表(带真实完成状态) @@ -408,72 +475,27 @@ public class GUIManager { g.setItem(10, mkItem(Material.BOOK, "§e§l新人任务", "§7完成新手阶段获取奖励")); - g.setItem(12, mkItem(Material.COMPASS, + g.setItem(12, mkItem(Material.MAP, + "§a§l群系打卡", + "§7在不同群系打卡完成任务", + "§7打卡范围: 32格去重")); + g.setItem(14, mkItem(Material.COMPASS, "§b§l主线任务", "§7查看主线任务列表")); - g.setItem(14, mkItem(Material.PAPER, + g.setItem(16, mkItem(Material.PAPER, "§a§l支线任务", "§7查看支线任务列表")); - g.setItem(16, mkItem(Material.EMERALD, - "§d§l每日签到", - "§7签到获取积分")); - g.setItem(22, mkItem(Material.ARROW, "§7返回")); + g.setItem(18, mkItem(Material.BOOK, + "§6工单系统", + "§7提交bug、求助、举报")); + g.setItem(20, mkItem(Material.EMERALD, + "§a§l每日签到", + "§7签到获取积分和奖励")); + g.setItem(22, mkItem(Material.ARROW, + "§7返回")); p.openInventory(g); } - public void openGiftStages(Player p) { - Inventory g = Bukkit.createInventory( - null, 27, T_GIFT_STAGES); - fillBg(g); - QuestTracker qt = plugin.getQuestTracker(); - if (qt == null) { - g.setItem(22, mkItem(Material.BARRIER, - "§7任务系统未加载")); - p.openInventory(g); - return; - } - - List quests = - qt.getQuests("新人任务"); - String playerName = p.getName(); - - int max = quests.size(); - for (int i = 0; i < max && i < 9; i++) { - QuestTracker.QuestFile qf = quests.get(i); - boolean completed = - qt.isStageCompleted(playerName, qf); - boolean claimed = - qt.hasClaimed(playerName, qf); - - Material mat; - String status; - if (completed && claimed) { - mat = Material.LIME_WOOL; - status = "§a已领取"; - } else if (completed) { - mat = Material.YELLOW_WOOL; - status = "§e可领取"; - } else { - mat = Material.RED_WOOL; - status = "§c未达标"; - } - - // 显示进度 - List lore = new ArrayList<>(); - lore.add("§7" + status); - for (String cond : qf.conditions) { - String progress = - qt.getProgress(playerName, cond); - lore.add("§7" + progress); - } - - g.setItem(10 + i, mkItem(mat, - "§e" + qf.displayName, - lore.toArray(new String[0]))); - } - g.setItem(26, mkItem(Material.ARROW, "§7返回")); - p.openInventory(g); - } public void openTaskList(Player p, String folderName) { @@ -552,6 +574,7 @@ public class GUIManager { slot++; } + g.setItem(53, mkItem(Material.ARROW, "§7返回")); p.openInventory(g); } diff --git a/src/main/java/Sdf1_login/GarbageManager.java b/src/main/java/Sdf1_login/GarbageManager.java index 26c379d..2a704fa 100644 --- a/src/main/java/Sdf1_login/GarbageManager.java +++ b/src/main/java/Sdf1_login/GarbageManager.java @@ -230,27 +230,26 @@ public class GarbageManager { } public void saveItem(ItemStack stack) { - { - try { - String data = - stackToBase64(stack); - if (data.isEmpty()) return; - PreparedStatement ps = db.prepareStatement( - "INSERT INTO items " - + "(item_data, amount, " - + "round, create_time) " - + "VALUES (?,?,?,?)"); - ps.setString(1, data); - ps.setInt(2, stack.getAmount()); - ps.setInt(3, currentRound); - ps.setLong(4, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (Exception e) { - e.printStackTrace(); - } - }} + try { + String data = stackToBase64(stack); + if (data.isEmpty()) return; + PreparedStatement ps = db.prepareStatement( + "INSERT INTO items " + + "(item_data, amount, " + + "round, create_time) " + + "VALUES (?,?,?,?)"); + ps.setString(1, data); + ps.setInt(2, stack.getAmount()); + ps.setInt(3, currentRound); + ps.setLong(4, + System.currentTimeMillis()); + ps.executeUpdate(); + ps.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + private int deleteOldItems(int beforeRound) { try { @@ -344,11 +343,9 @@ public class GarbageManager { } public void openRecyclePage(Player p, int page) { - Inventory g = Bukkit.createInventory( null, 54, "§6§l垃圾回收站"); - // 获取全部存储物品 List> allItems = getStoredItems(999); int total = allItems.size(); @@ -381,38 +378,6 @@ public class GarbageManager { .get("amount")).intValue(); ItemStack stack = base64ToStack(data); if (stack != null) { - try { - byte[] bytes = - stack.serializeAsBytes(); - String b64 = java.util.Base64 - .getEncoder() - .encodeToString(bytes); - int len = b64.length(); - String f = len > 15 - ? b64.substring(10, 15) : "短"; - String m = len >= 5 - ? b64.substring(len / 2, - Math.min(len / 2 + 5, len)) - : "短"; - String b = len > 10 - ? b64.substring(len - 10, len - 5) - : "短"; - plugin.getLogger().info( - "[垃圾站加载] ID#" + id - + " " + stack.getType() - + " x" + amount - + " base64[" + len + "]" - + " 前=" + f - + " 中=" + m - + " 后=" + b); - } catch (Exception ex) { - plugin.getLogger().warning( - "[垃圾站加载] 序列化异常: " - + ex.getMessage()); - } - } - - if (stack != null) { stack.setAmount(amount); ItemMeta im = stack.getItemMeta(); @@ -429,10 +394,7 @@ public class GarbageManager { slot++; } - // 45-48 格:额外投放区(空位) - // 52-53 格:额外投放区(空位) - - // 49 格:翻页-上一页 + // 49 格:上一页 if (page > 0) { g.setItem(49, mk(Material.ARROW, "§e上一页 §7(" @@ -451,9 +413,8 @@ public class GarbageManager { "§7当前页: §f" + (page + 1) + "/" + totalPages)); - // 51 格:翻页-下一页 - boolean canNext = total >= pageSize - && page < totalPages - 1; + // 51 格:下一页 + boolean canNext = page < totalPages - 1; if (canNext) { g.setItem(51, mk(Material.ARROW, "§e下一页 §7(" @@ -463,13 +424,15 @@ public class GarbageManager { g.setItem(51, mk(Material.ARROW, "§7已是最后一页")); } - // 52格:返回按钮 + + // 52 格:返回 g.setItem(52, mk(Material.ARROW, "§7返回", "§7返回主菜单")); p.openInventory(g); } + public int getRecyclePage(String name) { return recyclePages .getOrDefault(name, 0); diff --git a/src/main/java/Sdf1_login/GiftManager.java b/src/main/java/Sdf1_login/GiftManager.java index 1e4f9a7..6577c25 100644 --- a/src/main/java/Sdf1_login/GiftManager.java +++ b/src/main/java/Sdf1_login/GiftManager.java @@ -22,7 +22,7 @@ public class GiftManager { public GiftManager(Main plugin) { this.plugin = plugin; this.giftFolder = new File( - plugin.getDataFolder(), "新人礼包"); + plugin.getDataFolder(), "任务面板"); } public void loadStages() { diff --git a/src/main/java/Sdf1_login/Main.java b/src/main/java/Sdf1_login/Main.java index 95b80e5..6392d5e 100644 --- a/src/main/java/Sdf1_login/Main.java +++ b/src/main/java/Sdf1_login/Main.java @@ -1,6 +1,6 @@ package Sdf1_login; -import java.io.File; + import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Arrays; @@ -14,7 +14,6 @@ import java.util.TreeSet; import java.util.UUID; import java.util.LinkedHashMap; import java.util.concurrent.ConcurrentHashMap; -import org.bukkit.SoundCategory; import net.milkbowl.vault.economy.Economy; @@ -48,12 +47,11 @@ import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.command.TabCompleter; import org.bukkit.scheduler.BukkitRunnable; -// 在现有import区域最后添加: -import java.util.HashSet; -import java.util.Collections; import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.inventory.CraftItemEvent; import org.bukkit.event.player.PlayerFishEvent; +import java.util.HashMap; + public class Main extends JavaPlugin @@ -90,6 +88,23 @@ public class Main extends JavaPlugin new ConcurrentHashMap<>(); // 在 private WelcomeManager welcome; 字段(若不存在)添加: private WelcomeManager welcome; + private final Set pendingAdminAuth = + new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + /* + 资源包下载部分控制,不经过RadioDownloadListener的熔断控制 + */ + private RadioDownloadListener radioDL; + private final Set chatInputActive = + ConcurrentHashMap.newKeySet(); + // 补签确认:玩家名 → "日期:总积分" + public final Map + pendingBackCheck = + new HashMap<>(); + + + + + private final Set loggedIn = @@ -125,6 +140,9 @@ public class Main extends JavaPlugin time = t; } } + // 待删除的玩家名(仅保留最后一个) + private String pendingDeleteName = null; + private BukkitRunnable pendingDeleteTask = null; private final Map pwdRollback = @@ -228,8 +246,9 @@ public class Main extends JavaPlugin public void onEnable() { radio = new RadioManager(this); radio.init(); - getServer().getPluginManager().registerEvents( - new RadioDownloadListener(this), this); + radioDL = new RadioDownloadListener(this); + getServer().getPluginManager() + .registerEvents(radioDL, this); getDataFolder().mkdirs(); config = new ConfigManager(getDataFolder()); config.loadMessages(); @@ -249,6 +268,7 @@ public class Main extends JavaPlugin points = new PointsManager(this); gui = new GUIManager(this); chatInput = new ChatInputManager(); + verification = new VerificationManager(this); ipGroup = new IPGroupManager(this, config.maxAccountsPerIP); @@ -338,6 +358,16 @@ public class Main extends JavaPlugin @Override public void onDisable() { + // 取消未执行的删除任务 + if (pendingDeleteTask != null) { + pendingDeleteTask.cancel(); + pendingDeleteTask = null; + getLogger().info( + "[Sdf1_login] 服务器关闭," + + "取消待删除: " + + pendingDeleteName); + pendingDeleteName = null; + } if (db != null) db.close(); if (garbage != null) garbage.close(); if (questTracker != null) @@ -345,6 +375,7 @@ public class Main extends JavaPlugin } + private void setupEconomy() { if (getServer().getPluginManager() .getPlugin("Vault") == null) return; @@ -811,7 +842,6 @@ public class Main extends JavaPlugin invite.onInviteeCheckIn(p.getName()); return true; } - public void openTaskPanel(Player p) { Inventory g = Bukkit.createInventory( null, 27, "§6§l任务面板"); @@ -824,21 +854,22 @@ public class Main extends JavaPlugin } for (int i = 0; i < 27; i++) g.setItem(i, gl); - Map user = - db.getUser(p.getName()); - int stage = ((Number) user.getOrDefault( - "gift_stage", 0)).intValue(); - String claimed = (String) user.getOrDefault( - "gift_claimed", ""); - g.setItem(11, mkItem(Material.BOOK, - "§e新人礼包进度", - "§7当前阶段: §e" + stage, - "§7已领取: §a" + (claimed.isEmpty() - ? "无" : claimed))); - g.setItem(15, mkItem(Material.CLOCK, - "§b签到", "§7今日签到获取积分")); + + // 打卡按钮(独立,slot 11) + g.setItem(11, mkItem(Material.COMPASS, + "§b§l群系打卡", + "§7在不同群系打卡完成任务", + "§7打卡范围: 32格去重")); + + // 新人任务(slot 15) + g.setItem(15, mkItem(Material.BOOK, + "§e新人任务", + "§7完成新手阶段获取奖励")); + + // 返回 g.setItem(22, mkItem(Material.ARROW, "§7返回")); + p.openInventory(g); } @@ -1207,6 +1238,15 @@ public class Main extends JavaPlugin Player p = e.getPlayer(); String name = p.getName(); String ip = getPlayerIP(p); + + // 强制重发资源包(清掉客户端拒绝记录) + Bukkit.getScheduler() + .runTaskLater(this, () -> { + if (p.isOnline() + && radio != null) { + radio.sendResourcePack(p); + } + }, 20L); // 重置聊天输入状态(防止上次退出时残留) chatInput.reset(p); if (needsPasswordChange.contains(name)) @@ -1277,6 +1317,7 @@ public class Main extends JavaPlugin hideInventoryDelayed(p, 0); // [ADDED] 发送资源包 if (radio != null) radio.sendResourcePack(p); + // [ADDED] 发送资源包(延迟5秒确保客户端就绪) if (radio != null) { final Player jp = p; @@ -1390,6 +1431,12 @@ public class Main extends JavaPlugin joinTime.remove(p.getUniqueId()); joinLoc.remove(p.getUniqueId()); needsPasswordChange.remove(name); + // [ADDED] 清除 Radio 熔断状态 + // [ADDED] 清除 Radio 熔断状态 + if (radioDL != null) + radioDL.onPlayerQuit( + p.getUniqueId()); + pendingAdminAuth.remove(name); } private static final String MENU_SNOWBALL_TAG = @@ -1557,6 +1604,26 @@ public class Main extends JavaPlugin } + @EventHandler + public void onBlockPlace2(BlockPlaceEvent e) { + Player p = e.getPlayer(); + if (isFrozen(p)) return; + afk.recordAction(p.getUniqueId()); + if (questTracker != null) + questTracker.onBlockPlaced( + p.getName(), + e.getBlock().getType().name()); + } + + @EventHandler + public void onChatTrack(AsyncPlayerChatEvent e) { + if (isFrozen(e.getPlayer())) return; + if (questTracker != null) + questTracker.onPlayerChat( + e.getPlayer().getName()); + } + + @EventHandler public void onBlockPlace(BlockPlaceEvent e) { if (isFrozen(e.getPlayer())) @@ -1637,148 +1704,210 @@ public class Main extends JavaPlugin questTracker.onFishCaught(p.getName()); } - @EventHandler(priority = EventPriority.LOWEST) + // [ADDED] 村民交易追踪 + @EventHandler(priority = EventPriority.MONITOR, + ignoreCancelled = true) + public void onVillagerTrade(InventoryClickEvent e) { + if (!(e.getWhoClicked() instanceof Player)) + return; + Player p = (Player) e.getWhoClicked(); + if (isFrozen(p)) return; + if (questTracker == null) return; + + Inventory topInv = + e.getView().getTopInventory(); + if (topInv == null + || topInv.getHolder() == null) + return; + if (!(topInv.getHolder() + instanceof org.bukkit.entity.Villager)) + return; + + // 结果槽 = 2 + if (e.getRawSlot() != 2) return; + ItemStack result = topInv.getItem(2); + if (result == null + || result.getType() == Material.AIR) + return; + + final Player fp = p; + Bukkit.getScheduler().runTaskLater(this, + () -> { + if (fp.isOnline()) + questTracker.onVillagerTrade( + fp.getName()); + }, 2L); + } + + + @EventHandler public void onChat(AsyncPlayerChatEvent e) { Player p = e.getPlayer(); String msg = e.getMessage(); - if (chatInput.getState(p).type - != ChatInputManager.InputType.NONE) { - e.setCancelled(true); - ChatInputManager.InputType inputType = - chatInput.getState(p).type; - if (ticket != null - && ticket.handleChatInput( - p, msg, inputType)) { - return; - } - chatInput.handleInput(p, msg, this); - return; - } - + // ===== 冻结检查 ===== if (isFrozen(p)) { e.setCancelled(true); return; } - // [ADDED] 任务聊天计数 - if (questTracker != null) - questTracker.onPlayerChat(p.getName()); - if (chatFilter == null) return; - if (chatFilter.isPlayerWhitelisted( - p.getName())) return; - if (chatFilter.isMuted(p.getName())) { + // ===== 聊天输入流程(改密码/绑邮箱等) ===== + if (chatInput.isInFlow(p)) { e.setCancelled(true); - p.sendMessage( - chatFilter.msg("chat_muted")); + chatInput.handleInput(p, msg, this); return; } + // ===== 管理员密码验证 ===== + if (pendingAdminAuth.contains(p.getName())) { + e.setCancelled(true); + pendingAdminAuth.remove(p.getName()); + if (msg.equals(config.adminPassword)) { + p.sendMessage("§a验证通过"); + p.sendMessage("§7输入的密码已验证"); + } else { + p.sendMessage("§c密码错误"); + } + return; + } + + // ===== 聊天过滤 - 开关检查 ===== + if (!chatFilter.isEnabled()) return; + + // ===== 白名单玩家 ===== + if (chatFilter.isPlayerWhitelisted( + p.getName())) return; + + // ===== 禁言检查 ===== + if (chatFilter.isMuted(p.getName())) { + e.setCancelled(true); + p.sendMessage(chatFilter.msg( + "chat_muted")); + return; + } + + // ===== URL检测 ===== List urls = chatFilter.extractUrls(msg); if (urls.isEmpty()) return; - for (String u : urls) { - if (chatFilter.isWhitelisted(u)) - continue; - // [FIX] 增强域名检测:支持中文域名 - if (!isLikelyUrl(u)) continue; + // 过滤白名单 + List blocked = new ArrayList<>(); + for (String url : urls) { + if (!chatFilter.isWhitelisted(url)) { + blocked.add(url); + } + } + if (blocked.isEmpty()) return; - e.setCancelled(true); - chatFilter.incrementViolation( - p.getName()); - int count = chatFilter - .getViolationCount(p.getName()); + // 取消消息 + e.setCancelled(true); - p.sendMessage(chatFilter - .msg("chat_url_blocked") - .replace("{url}", u)); - p.sendMessage(chatFilter - .msg("chat_url_violation") - .replace("{count}", - String.valueOf(count))); + for (String url : blocked) { + p.sendMessage(chatFilter.msg( + "chat_url_blocked", + "url", url)); + } - chatFilter.applyPunishment(p, count); + // 增加违规计数 + chatFilter.incrementViolation(p.getName()); + int count = chatFilter.getViolationCount( + p.getName()); + p.sendMessage(chatFilter.msg( + "chat_url_violation", + "count", String.valueOf(count))); - if (chatFilter.isNotifyAdmin()) { - String tag = config.adminTag; - for (Player op : - Bukkit.getOnlinePlayers()) { - if (op.getScoreboardTags() - .contains(tag) - && !op.equals(p)) { - op.sendMessage(chatFilter - .msg("chat_url_admin_notify") - .replace("{player}", - p.getName()) - .replace("{url}", u) - .replace("{count}", - String.valueOf( - count))); - } + // 通知管理员 + if (chatFilter.isNotifyAdmin()) { + String notify = chatFilter.msg( + "chat_url_admin_notify", + "player", p.getName(), + "url", blocked.get(0), + "count", String.valueOf(count)); + for (Player op : + Bukkit.getOnlinePlayers()) { + if (op.hasPermission( + "sdf1.admin") + || op.isOp()) { + op.sendMessage(notify); } } - if (chatFilter.isNotifyAll()) { - Bukkit.broadcastMessage( - chatFilter - .msg("chat_url_broadcast") - .replace("{player}", - p.getName())); - } } + + // 通报 + if (chatFilter.isNotifyAll()) { + Bukkit.broadcastMessage( + chatFilter.msg( + "chat_url_broadcast", + "player", + p.getName())); + } + + // 处罚 + chatFilter.applyPunishment(p, count); + // ===== 补签确认 ===== + if (pendingBackCheck.containsKey( + p.getName())) { + e.setCancelled(true); + String data = pendingBackCheck + .remove(p.getName()); + String[] parts = data.split(":"); + String dateStr = parts[0]; + int totalPoints = + Integer.parseInt(parts[1]); + + if (msg.trim().equals("1") + || msg.trim() + .equalsIgnoreCase("yes") + || msg.trim().contains("是") + || msg.trim().contains("愿意")) { + int extra = totalPoints - 10; + if (extra > 0 + && !db.deductPoints( + p.getName(), extra)) { + db.addPoints(p.getName(), 10); + p.sendMessage("§c积分也不足," + + "10积分已退还"); + return; + } + checkIn.doBackCheckIn(p, dateStr); + p.sendMessage("§a补签成功!日期: " + + dateStr + " §7(扣除" + + totalPoints + + "积分,免经济)"); + playSuccessSound(p); + } else { + db.addPoints(p.getName(), 10); + p.sendMessage("§c补签已取消," + + "10积分已退还"); + } + return; + } + } - /** - * 增强URL检测:支持中文域名+新后缀 - * 匹配 http(s)://xxx 或裸域名 xxx.xxx - */ - private boolean isLikelyUrl(String text) { - if (text.toLowerCase().startsWith("http://") - || text.toLowerCase() - .startsWith("https://")) { - return true; - } - boolean hasChinese = false; - for (int i = 0; i < text.length(); i++) { - int code = text.codePointAt(i); - if (code >= 0x4E00 && code <= 0x9FFF) { - hasChinese = true; - break; - } - if (code >= 0x3400 && code <= 0x4DBF) { - hasChinese = true; - break; - } - } - String[] commonTlds = { - ".com", ".net", ".org", ".cn", - ".top", ".xyz", ".cc", ".icu", - ".online", ".site", ".club", - ".vip", ".shop", ".live", - ".app", ".dev", ".me", ".io", - ".co", ".tk", ".ml" - }; - String lower = text.toLowerCase().trim(); - for (String tld : commonTlds) { - if (lower.contains(tld)) return true; - } - if (hasChinese - && lower.matches( - ".*\\.[a-z]{2,}.*")) { - return true; - } - if (lower.matches( - "[\\w\\u4e00-\\u9fff" - + "\\u3400-\\u4dbf" - + "\\.-]+\\.[a-z]{2,}")) { - return true; - } - return false; - } // ===== Inventory Click ===== @EventHandler - public void onInvClick(InventoryClickEvent e) { + public void onInvClick( + InventoryClickEvent e) { + + // 防止越界:检查slot是否在容器范围内 + if (e.getClickedInventory() == null) { + return; + } + if (e.getSlot() < 0 + || e.getSlot() + >= e.getClickedInventory() + .getSize()) { + return; + } + // 防止非本插件容器触发 + if (e.getInventory().getSize() != 54 + && e.getInventory().getSize() != 27) { + return; + } + if (!(e.getWhoClicked() instanceof Player)) return; Player p = (Player) e.getWhoClicked(); @@ -1814,22 +1943,88 @@ public class Main extends JavaPlugin ticket.handleClick(p, title, slot); return; } - + // 垃圾回收站 // 垃圾回收站 if (title.equals("§6§l垃圾回收站")) { - if (slot < 0 || slot >= 54) return; - e.setCancelled(true); + + // 玩家背包普通点击 → 不拦截 + if (e.getClickedInventory() != null + && e.getClickedInventory() + == p.getInventory() + && !e.isShiftClick()) { + return; + } + + // Shift+Click 存入(玩家背包→垃圾站) + if (e.isShiftClick() + && e.getClickedInventory() != null + && e.getClickedInventory() + == p.getInventory() + && e.getCurrentItem() != null + && e.getCurrentItem().getType() + != Material.AIR + && !isMenuSnowball( + e.getCurrentItem())) { + e.setCancelled(true); + long now = System.currentTimeMillis(); + Long last = garbageBusy + .get(p.getUniqueId()); + if (last != null + && now - last < 200L) { + return; + } + garbageBusy.put( + p.getUniqueId(), now); + ItemStack toSave = + e.getCurrentItem().clone(); + p.getInventory() + .setItem(e.getSlot(), null); + garbage.saveItem(toSave); + Bukkit.getScheduler() + .runTaskLater(this, () -> { + garbage.openRecyclePage( + p, 0); + }, 1L); + return; + } // 功能格 49-53 if (slot >= 49 && slot <= 53) { + e.setCancelled(true); garbage.handleClick(p, title, slot); return; } + // 玩家背包区域(非Shift)→ 不拦截 + if (slot >= 54 && !e.isShiftClick()) { + return; + } + + // ===== 光标点击存入(点击GUI空格) ===== + if (slot < 54 + && e.getCursor() != null + && e.getCursor().getType() + != Material.AIR + && !isMenuSnowball( + e.getCursor())) { + e.setCancelled(true); + ItemStack toSave = + e.getCursor().clone(); + p.getOpenInventory() + .setCursor(null); + garbage.saveItem(toSave); + Bukkit.getScheduler() + .runTaskLater(this, () -> { + garbage.openRecyclePage( + p, 0); + }, 2L); + return; + } + + // 取出物品(点击有ID的物品) + e.setCancelled(true); ItemStack inSlot = e.getInventory().getItem(slot); - - // ===== 取出物品(点击垃圾箱格子) ===== if (inSlot != null && inSlot.hasItemMeta() && inSlot.getItemMeta().hasLore()) { @@ -1847,10 +2042,13 @@ public class Main extends JavaPlugin if (garbage.removeItem(dbId)) { ItemStack give = inSlot.clone(); - ItemMeta gm = - give.getItemMeta(); + ItemMeta gm = give.getItemMeta(); + if (gm == null) gm = + Bukkit.getItemFactory() + .getItemMeta(give.getType()); gm.setLore(null); give.setItemMeta(gm); + p.getInventory() .addItem(give); e.getInventory() @@ -1859,42 +2057,8 @@ public class Main extends JavaPlugin } catch (Exception ex) { p.sendMessage("§c取出失败"); } - return; } } - - // ===== Shift+Click 存入(仅在垃圾箱界面生效) ===== - if (e.isShiftClick() - && e.getClickedInventory() != null - && e.getClickedInventory() - == p.getInventory() - && e.getCurrentItem() != null - && e.getCurrentItem().getType() - != Material.AIR - && !isMenuSnowball( - e.getCurrentItem())) { - long now = System.currentTimeMillis(); - Long last = garbageBusy - .get(p.getUniqueId()); - if (last != null - && now - last < 200L) { - return; - } - garbageBusy.put(p.getUniqueId(), now); - - ItemStack toSave = - e.getCurrentItem().clone(); - p.getInventory() - .setItem(e.getSlot(), null); - garbage.saveItem(toSave); - Bukkit.getScheduler() - .runTaskLater(this, () -> { - garbage.openRecyclePage( - p, - garbage.getRecyclePage( - p.getName())); - }, 1L); - } return; } @@ -1909,13 +2073,18 @@ public class Main extends JavaPlugin points.createShopGUI(p)); else if (slot == 16) gui.openTaskCenter(p); - else if (slot == 18) ticket.openMain(p); - else if (slot == 20) { + else if (slot == 18) { + dailySignWithReward(p); + gui.openMain(p); + } + else if (slot == 20) ticket.openMain(p); + else if (slot == 22) { if (p.isOp() || p.getScoreboardTags() .contains(config.adminTag)) { garbage.openRecycle(p); } - } else if (slot == 22 && isAdmin(p)) { + } + else if (slot == 24 && isAdmin(p)) { p.closeInventory(); chatInput.getState(p).type = ChatInputManager.InputType @@ -1924,6 +2093,7 @@ public class Main extends JavaPlugin } return; } + // 新人礼包(QuestTracker版) if (title.equals(GUIManager.T_GIFT_STAGES)) { e.setCancelled(true); @@ -2080,24 +2250,29 @@ public class Main extends JavaPlugin } // 任务中心 - if (title.equals( - GUIManager.T_TASK_CENTER)) { + if (title.equals(GUIManager.T_TASK_CENTER)) { e.setCancelled(true); if (slot == 22) gui.openMain(p); else if (slot == 10) gui.openGiftStages(p); - else if (slot == 12) - gui.openTaskList(p, "主线任务"); - else if (slot == 14) - gui.openTaskList(p, "支线任务"); - else if (slot == 16) { - if (dailySign(p)) { - gui.openTaskCenter(p); + else if (slot == 12) { + // ===== 打卡 ===== + if (questTracker != null) { + Location loc = p.getLocation(); + String result = + questTracker.checkInAtPosition( + p.getName(), + p.getWorld().getName(), + loc.getX(), + loc.getY(), + loc.getZ()); + p.sendMessage(result); playSuccessSound(p); - } else - p.sendMessage(config.msg( - "checkin_already")); - } + } + } else if (slot == 14) + gui.openTaskList(p, "主线任务"); + else if (slot == 16) + gui.openTaskList(p, "支线任务"); return; } @@ -2247,17 +2422,26 @@ public class Main extends JavaPlugin return; } + // 任务面板 // 任务面板 if (title.equals("§6§l任务面板")) { e.setCancelled(true); if (slot == 22) gui.openMain(p); - else if (slot == 15) { - if (dailySign(p)) { - openTaskPanel(p); - playSuccessSound(p); - } else - p.sendMessage(config.msg( - "checkin_already")); + else if (slot == 11) { + // ===== 打卡按钮:报告坐标 ===== + Location loc = p.getLocation(); + String result = + questTracker.checkInAtPosition( + p.getName(), + p.getWorld().getName(), + loc.getX(), + loc.getY(), + loc.getZ()); + p.sendMessage(result); + playSuccessSound(p); + openTaskPanel(p); + } else if (slot == 15) { + gui.openGiftStages(p); } return; } @@ -2290,6 +2474,159 @@ public class Main extends JavaPlugin } } + + + /** + * 签到 + 盲盒经济奖励 + */ + public void dailySignWithReward(Player p) { + String name = p.getName(); + + if (checkIn.isCheckedInToday(name)) { + p.sendMessage( + config.msg("checkin_already")); + return; + } + + // ===== 1. 执行签到(内部已加积分) ===== + p.sendMessage(checkIn.checkIn(p)); + invite.onInviteeCheckIn(name); + + // ===== 2. 获取连续天数 ===== + int streak = checkIn.getStreak(name); + double multiplier = 1.0; + if (streak >= 21) { + multiplier = 1.3; + } else if (streak >= 14) { + multiplier = 1.2; + } else if (streak >= 7) { + multiplier = 1.1; + } + + // ===== 3. 经济奖励(不再重复加积分) ===== + if (economy == null) { + playSuccessSound(p); + return; + } + + // 固定金额模式 + if ("fixed".equals( + config.checkinRewardType) + && config.checkinRewardFixed > 0) { + final double reward = + config.checkinRewardFixed; + final double m = multiplier; + p.sendMessage("§e§l[签到] §f" + + "正在生成今日奖励..."); + Bukkit.getScheduler() + .runTaskLater(this, () -> { + double rewardFinal = + Math.round( + reward * m + * 100.0) + / 100.0; + double before = + economy + .getBalance(p); + economy.depositPlayer( + p, rewardFinal); + double after = + economy + .getBalance(p); + p.sendMessage( + "§6§l[签到] §f" + + "获得奖励: §a§l$" + + String.format( + "%.2f", + rewardFinal)); + p.sendMessage("§7余额: §e" + + String.format( + "%.2f", before) + + " §7→ §a" + + String.format( + "%.2f", after)); + playSuccessSound(p); + }, 30L); + return; + } + + // 盲盒范围模式 + if ("range".equals( + config.checkinRewardType) + && config.checkinRewardMin > 0 + && config.checkinRewardMax + > config.checkinRewardMin) { + final double min = + config.checkinRewardMin; + final double max = + config.checkinRewardMax; + final double m = multiplier; + getLogger().info( + "[签到] 盲盒: min=" + + min + " max=" + max + + " multi=" + m); + p.sendMessage("§e§l[签到盲盒] " + + "§f正在生成今日惊喜..."); + Bukkit.getScheduler() + .runTaskLater(this, () -> { + double rand = + Math.random(); + double reward = min + + rand + * (max - min); + reward = Math.round( + reward * m * 100.0) + / 100.0; + getLogger().info( + "[签到] 盲盒结果: rand=" + + String.format( + "%.4f", rand) + + " reward=" + + reward); + double before = + economy + .getBalance(p); + economy.depositPlayer( + p, reward); + double after = + economy + .getBalance(p); + p.sendMessage( + "§6§l====== 签到盲盒 ======"); + p.sendMessage( + "§7开出: §a§l$" + + String.format( + "%.2f", + reward)); + p.sendMessage("§7余额: §e" + + String.format( + "%.2f", before) + + " §7→ §a" + + String.format( + "%.2f", after)); + p.sendMessage( + "§6§l========================"); + playSuccessSound(p); + }, 40L); + return; + } + + // 无经济奖励 + playSuccessSound(p); + } + + + + @EventHandler + public void onVillagerTrade( + org.bukkit.event.inventory.TradeSelectEvent e) { + // TradeSelectEvent 在玩家点击交易时触发 + if (!(e.getWhoClicked() instanceof Player)) return; + Player p = (Player) e.getWhoClicked(); + if (isFrozen(p) || questTracker == null) return; + questTracker.onVillagerTrade(p.getName()); + } + @EventHandler public void onInvDrag(InventoryDragEvent e) { if (!(e.getWhoClicked() instanceof Player)) @@ -2303,44 +2640,32 @@ public class Main extends JavaPlugin // 回收站拖拽 if (t.equals("§6§l垃圾回收站")) { + boolean toGUI = false; for (int rawSlot : e.getRawSlots()) { - if (rawSlot >= 49) { - e.setCancelled(true); - return; + if (rawSlot < 54) { + toGUI = true; + break; } } - // 取消所有拖拽,改用Shift+Click - e.setCancelled(true); - - final Player fp = p; - // 取出光标上的物品 - final ItemStack curItem = - fp.getOpenInventory() - .getCursor(); - if (curItem != null - && curItem.getType() - != Material.AIR - && !isMenuSnowball(curItem)) { - // 1. 保存到数据库 - garbage.saveItem(curItem.clone()); - // 2. 清空光标 - fp.getOpenInventory() - .setCursor(null); - // 3. 从背包移除 - fp.getInventory() - .removeItem( - curItem.clone()); - // 4. 刷新GUI - Bukkit.getScheduler() - .runTaskLater(this, - () -> { - garbage.openRecyclePage( - fp, - garbage - .getRecyclePage( - fp - .getName())); - }, 2L); + if (toGUI) { + ItemStack cur = e.getCursor(); + if (cur != null + && cur.getType() != Material.AIR + && !isMenuSnowball(cur)) { + e.setCancelled(true); + p.getOpenInventory().setCursor(null); + garbage.saveItem(cur.clone()); + Bukkit.getScheduler() + .runTaskLater(this, () -> { + garbage.openRecyclePage( + p, + garbage + .getRecyclePage( + p + .getName())); + }, 2L); + return; + } } return; } @@ -2374,6 +2699,101 @@ public class Main extends JavaPlugin String[] args) { String cmdName = cmd.getName().toLowerCase(); + // ===== /chat 独立命令 ===== + if (cmdName.equals("chat") + || cmdName.equals("聊天")) { + if (sender instanceof Player) { + Player chatP = (Player) sender; + if (!chatP.getScoreboardTags() + .contains(config.adminTag)) { + sender.sendMessage("§c权限不足"); + return true; + } + } + if (args.length < 1) { + sender.sendMessage( + "§e/chat "); + return true; + } + String cs = args[0].toLowerCase(); + if (cs.equals("reload")) { + chatFilter.loadConfig(); + sender.sendMessage("§a聊天配置已重载"); + return true; + } + if (cs.equals("开") || cs.equals("on")) { + chatFilter.setEnabled(true); + chatFilter.saveConfig(); + sender.sendMessage("§aURL过滤已开启"); + return true; + } + if (cs.equals("关") || cs.equals("off")) { + chatFilter.setEnabled(false); + chatFilter.saveConfig(); + sender.sendMessage("§cURL过滤已关闭"); + return true; + } + if (cs.equals("add")) { + if (args.length < 2) { + sender.sendMessage("§e/chat add "); + return true; + } + chatFilter.addUrl(args[1]); + sender.sendMessage("§a已添加白名单: " + args[1]); + return true; + } + if (cs.equals("remove")) { + if (args.length < 2) { + sender.sendMessage("§e/chat remove "); + return true; + } + if (chatFilter.removeUrl(args[1])) + sender.sendMessage("§c已移除: " + args[1]); + else + sender.sendMessage("§c未找到: " + args[1]); + return true; + } + if (cs.equals("addplayer")) { + if (args.length < 2) { + sender.sendMessage("§e/chat addplayer <玩家>"); + return true; + } + chatFilter.addWhitelistPlayer(args[1]); + sender.sendMessage("§a白名单玩家: " + args[1]); + return true; + } + if (cs.equals("takeplayer")) { + if (args.length < 2) { + sender.sendMessage("§e/chat takeplayer <玩家>"); + return true; + } + if (chatFilter.removeWhitelistPlayer(args[1])) + sender.sendMessage("§c已移除白名单: " + args[1]); + else + sender.sendMessage("§c未找到: " + args[1]); + return true; + } + if (cs.equals("unmute")) { + if (args.length < 2) { + sender.sendMessage("§e/chat unmute <玩家>"); + return true; + } + chatFilter.unmutePlayer(args[1]); + sender.sendMessage("§a已解禁: " + args[1]); + return true; + } + if (cs.equals("reset")) { + if (args.length < 2) { + sender.sendMessage("§e/chat reset <玩家>"); + return true; + } + chatFilter.resetPlayer(args[1]); + sender.sendMessage("§a已重置: " + args[1]); + return true; + } + sender.sendMessage("§c未知子命令"); + return true; + } if (cmdName.equals("recycle")) { if (!(sender instanceof Player)) { @@ -2664,56 +3084,154 @@ public class Main extends JavaPlugin gui.openMain((Player) sender); return true; } + // String cmdName = cmd.getName().toLowerCase(); + if (!cmdName.equals("sdf1_login")) return false; + + if (args.length == 0) { + if (sender instanceof Player) { + gui.openMain((Player) sender); + } else { + sender.sendMessage("§e用法: /sdf1_login <子命令>"); + } + return true; + } String sub = args[0].toLowerCase(); - // stop +// ===== del / confirm / cancel(最先处理) ===== + if (sub.equals("del")) { + if (!isAdmin(sender)) { + sender.sendMessage("§c权限不足"); + return true; + } + if (args.length < 2) { + sender.sendMessage( + "§e用法: /sdf1_login del <玩家>"); + return true; + } + String target = args[1]; + if (!db.userExists(target)) { + sender.sendMessage( + "§c玩家 " + target + " 不存在"); + return true; + } + if (pendingDeleteTask != null) { + pendingDeleteTask.cancel(); + String old = pendingDeleteName; + pendingDeleteTask = null; + pendingDeleteName = null; + getLogger().info( + "[Sdf1_login] 取消旧待删除: " + old); + } + pendingDeleteName = target; + pendingDeleteTask = new BukkitRunnable() { + @Override + public void run() { + String name = pendingDeleteName; + pendingDeleteName = null; + pendingDeleteTask = null; + if (name == null) return; + db.deleteUser(name); + getLogger().info( + "[Sdf1_login] 已删除: " + name); + Player tp = Bukkit.getPlayer(name); + if (tp != null && tp.isOnline()) + tp.kickPlayer( + "§c账号已被管理员删除"); + } + }; + pendingDeleteTask.runTaskLater(this, 1200L); + sender.sendMessage("§e§l[删除确认] 目标: §c" + + target); + sender.sendMessage("§760秒后执行," + + "§a/sdf1_login confirm §7确认," + + "§c/sdf1_login cancel §7取消"); + getLogger().warning("[Sdf1_login] 待删除: " + + target + " by " + sender.getName()); + return true; + } + + if (sub.equals("confirm")) { + if (!isAdmin(sender)) { + sender.sendMessage("§c权限不足"); + return true; + } + if (pendingDeleteName == null + || pendingDeleteTask == null) { + sender.sendMessage( + "§c当前没有待删除的账号"); + return true; + } + pendingDeleteTask.cancel(); + String name = pendingDeleteName; + pendingDeleteName = null; + pendingDeleteTask = null; + db.deleteUser(name); + sender.sendMessage("§a已立即删除: " + name); + Player tp = Bukkit.getPlayer(name); + if (tp != null && tp.isOnline()) + tp.kickPlayer("§c账号已被管理员删除"); + return true; + } + + if (sub.equals("cancel")) { + if (!isAdmin(sender)) { + sender.sendMessage("§c权限不足"); + return true; + } + if (pendingDeleteName == null + || pendingDeleteTask == null) { + sender.sendMessage( + "§c当前没有待删除的账号"); + return true; + } + pendingDeleteTask.cancel(); + String name = pendingDeleteName; + pendingDeleteName = null; + pendingDeleteTask = null; + sender.sendMessage("§a已取消删除: " + name); + return true; + } + +// ===== stop ===== if (sub.equals("stop")) { if (!(sender instanceof Player)) { - // 控制台 if (args.length >= 2) { db.clearTempPassword(args[1]); sender.sendMessage("§a已报废 " + args[1] + " 的临时密码"); } else { db.clearAllTempPasswords(); - sender.sendMessage( - "§a已报废所有临时密码"); + sender.sendMessage("§a已报废所有临时密码"); } return true; } Player p2 = (Player) sender; if (args.length >= 2) { String tgt = args[1]; - String myIP = - getPlayerIP(p2); + String myIP = getPlayerIP(p2); String targetIP = db.getPlayerIPByName(tgt); if (targetIP == null) { - Player tp = - Bukkit.getPlayer(tgt); + Player tp = Bukkit.getPlayer(tgt); if (tp != null && tp.isOnline()) - targetIP = - getPlayerIP(tp); + targetIP = getPlayerIP(tp); } if (targetIP == null || !targetIP.equals(myIP)) { - p2.sendMessage( - "§c只能操作同IP下的玩家"); + p2.sendMessage("§c只能操作同IP下的玩家"); return true; } db.clearTempPassword(tgt); - p2.sendMessage("§a已报废 " + tgt - + " 的临时密码"); + p2.sendMessage("§a已报废 " + tgt + " 的临时密码"); } else { db.clearTempPassword(p2.getName()); - p2.sendMessage( - "§a已报废您的临时密码"); + p2.sendMessage("§a已报废您的临时密码"); } return true; } - // reload +// ===== reload ===== if (sub.equals("reload")) { if (!isAdmin(sender)) { sender.sendMessage("§c权限不足"); @@ -2727,146 +3245,260 @@ public class Main extends JavaPlugin sender.sendMessage("§a配置已重载!"); return true; } - // back - 恢复背包备份 + +// ===== back ===== if (sub.equals("back")) { if (!isAdmin(sender)) { sender.sendMessage("§c权限不足"); return true; } + // ... 保持原有back代码不变 ... if (args.length < 2) { - sender.sendMessage( - "§e用法: /sdf1_login back <玩家> [#编号]"); + sender.sendMessage("§e用法: /sdf1_login back <玩家> [#编号]"); return true; } + // 原有back逻辑 String backTarget = args[1]; if (args.length >= 3 && args[2].startsWith("#")) { - // /sdf1_login back Aplayer #1 int backupId; try { backupId = Integer.parseInt( args[2].replace("#", "")); } catch (NumberFormatException ex) { - sender.sendMessage( - "§c无效编号"); + sender.sendMessage("§c无效编号"); return true; } String[] backup = - db.getInventoryBackupById( - backupId); + db.getInventoryBackupById(backupId); if (backup == null) { - sender.sendMessage( - "§c备份 #" + backupId - + " 不存在"); + sender.sendMessage("§c备份 #" + backupId + " 不存在"); return true; } - // 如果玩家在线,直接恢复 - Player tp = Bukkit.getPlayer( - backTarget); + Player tp = Bukkit.getPlayer(backTarget); if (tp != null && tp.isOnline()) { try { - tp.getInventory() - .setContents( - InventorySerializer - .fromBase64( - backup[0])); - if (backup[1] != null - && !backup[1] - .isEmpty()) - tp.getInventory() - .setArmorContents( - InventorySerializer - .fromBase64( - backup[1])); - if (backup[2] != null - && !backup[2] - .isEmpty()) - tp.getInventory() - .setExtraContents( - InventorySerializer - .fromBase64( - backup[2])); - int lv = Integer.parseInt( - backup[3]); - if (lv > 0) - tp.setLevel(lv); - tp.sendMessage( - "§a管理员已恢复您的背包"); - sender.sendMessage( - "§a已恢复 " + backTarget - + " 的背包 (备份#" - + backupId + ")"); + tp.getInventory().setContents( + InventorySerializer.fromBase64(backup[0])); + if (backup[1] != null && !backup[1].isEmpty()) + tp.getInventory().setArmorContents( + InventorySerializer.fromBase64(backup[1])); + if (backup[2] != null && !backup[2].isEmpty()) + tp.getInventory().setExtraContents( + InventorySerializer.fromBase64(backup[2])); + int lv = Integer.parseInt(backup[3]); + if (lv > 0) tp.setLevel(lv); + tp.sendMessage("§a管理员已恢复您的背包"); + sender.sendMessage("§a已恢复 " + backTarget + " (备份#" + backupId + ")"); } catch (Exception e) { - sender.sendMessage( - "§c恢复失败: " - + e.getMessage()); + sender.sendMessage("§c恢复失败: " + e.getMessage()); } } else { - // 玩家离线:保存到数据库供下次登录恢复 - db.saveInventoryBackup( - backTarget, - backup[0], - backup[1], - backup[2], - Integer.parseInt( - backup[3]), - Double.parseDouble( - backup[4])); - sender.sendMessage( - "§a" + backTarget - + " 离线,备份已写入数据库," - + "下次登录时恢复"); + db.saveInventoryBackup(backTarget, + backup[0], backup[1], backup[2], + Integer.parseInt(backup[3]), + Double.parseDouble(backup[4])); + sender.sendMessage("§a" + backTarget + " 离线,备份已写入"); } } else { - // /sdf1_login back Aplayer - // 罗列最近5条备份 List> backups = - db.getInventoryBackups( - backTarget, 5); + db.getInventoryBackups(backTarget, 5); if (backups.isEmpty()) { - sender.sendMessage( - "§c" + backTarget - + " 没有背包备份"); + sender.sendMessage("§c" + backTarget + " 没有背包备份"); return true; } - sender.sendMessage( - "§e§l=== " + backTarget - + " 背包备份 ==="); + sender.sendMessage("§e§l=== " + backTarget + " 背包备份 ==="); java.text.SimpleDateFormat sdf = - new java.text.SimpleDateFormat( - "MM-dd HH:mm:ss"); - for (Map b : - backups) { - int bid = ((Number) - b.get("id")) - .intValue(); - Object stObj = - b.get("save_time"); + new java.text.SimpleDateFormat("MM-dd HH:mm:ss"); + for (Map b : backups) { + int bid = ((Number) b.get("id")).intValue(); + Object stObj = b.get("save_time"); long st = stObj != null - ? ((Number) stObj) - .longValue() : 0; + ? ((Number) stObj).longValue() : 0; String time = st > 0 - ? sdf.format( - new java.util.Date(st)) + ? sdf.format(new java.util.Date(st)) : "未知时间"; - String itemNames = - getBackupItemPreview( - bid); - sender.sendMessage( - "§7#" + bid - + " §f" + time - + " §7: " - + itemNames); + String itemNames = getBackupItemPreview(bid); + sender.sendMessage("§7#" + bid + " §f" + time + " §7: " + itemNames); } - sender.sendMessage( - "§7使用 §e/sdf1_login back " - + backTarget - + " #编号 §7恢复"); - sender.sendMessage( - "§e§l================================"); + sender.sendMessage("§7使用 §e/sdf1_login back " + backTarget + " #编号 §7恢复"); } + return true; } - // radio子命令(控制台+玩家) + +// ===== pw / password ===== + if (sub.equals("pw") || sub.equals("password")) { + if (!(sender instanceof Player)) { + sender.sendMessage("§c仅玩家可用"); + return true; + } + Player p2 = (Player) sender; + + if (args.length >= 3) { + String oldPwd = args[1]; + String newPwd = args[2]; + if (!PasswordUtils.validate(newPwd)) { + p2.sendMessage(config.msg("password_format_error")); + return true; + } + String salt = (String) db.getField( + p2.getName(), "password_salt"); + String hash = PasswordUtils.hash(oldPwd, salt); + boolean mainOk = db.checkPassword( + p2.getName(), hash); + boolean tempOk = db.checkPasswordOrTemp( + p2.getName(), hash); + if (!mainOk && !tempOk) { + p2.sendMessage(config.msg("password_wrong")); + return true; + } + String oldHash = (String) db.getField( + p2.getName(), "password_hash"); + String newSalt = PasswordUtils.generateSalt(); + String newHash = PasswordUtils.hash(newPwd, newSalt); + if (newHash.equals(oldHash)) { + p2.sendMessage(config.msg("password_same")); + return true; + } + recordPasswordChange(p2.getName(), oldHash, salt); + db.setField(p2.getName(), "password_hash", newHash); + db.setField(p2.getName(), "password_salt", newSalt); + if (!mainOk && tempOk) + db.clearTempPassword(p2.getName()); + needsPasswordChange.remove(p2.getName()); + p2.sendMessage(config.msg("password_changed")); + return true; + } + + if (args.length == 2) { + String oldPwd = args[1]; + String salt = (String) db.getField( + p2.getName(), "password_salt"); + String hash = PasswordUtils.hash(oldPwd, salt); + boolean mainOk = db.checkPassword( + p2.getName(), hash); + boolean tempOk = db.checkPasswordOrTemp( + p2.getName(), hash); + if (!mainOk && !tempOk) { + p2.sendMessage(config.msg("password_wrong")); + return true; + } + chatInput.getState(p2).type = + ChatInputManager.InputType.CHANGE_PWD_STEP2; + chatInput.getState(p2).ticketTitle = + mainOk ? "main" : "temp"; + p2.sendMessage("§e请输入新密码:"); + return true; + } + + chatInput.getState(p2).type = + ChatInputManager.InputType.CHANGE_PWD_STEP1; + p2.sendMessage("§e请输入当前密码:"); + return true; + } + +// ===== email ===== + if (sub.equals("email")) { + if (!(sender instanceof Player)) { + sender.sendMessage("§c仅玩家可用"); + return true; + } + Player p2 = (Player) sender; + if (args.length >= 2) { + String emailAddr = args[1]; + if (!emailAddr.contains("@") + || !emailAddr.contains(".")) { + p2.sendMessage("§c邮箱格式不正确"); + return true; + } + String oldEmail = (String) db.getField( + p2.getName(), "email"); + if (oldEmail != null && !oldEmail.isEmpty()) { + chatInput.getState(p2).type = + ChatInputManager.InputType.SET_EMAIL_VERIFY; + chatInput.getState(p2).targetPlayer = emailAddr; + chatInput.getState(p2).ticketTitle = "need_pwd"; + p2.sendMessage("§e您已绑定邮箱: " + oldEmail); + p2.sendMessage("§e请输入登录密码以确认修改:"); + } else { + String code = String.valueOf( + (int) (Math.random() * 900000 + 100000)); + chatInput.getState(p2).type = + ChatInputManager.InputType.SET_EMAIL_VERIFY; + chatInput.getState(p2).targetPlayer = emailAddr; + chatInput.getState(p2).ticketTitle = code; + final String to = emailAddr; + final String c = code; + final Player fp = p2; + Bukkit.getScheduler() + .runTaskAsynchronously(this, + () -> { + boolean sent = + email.sendVerifyCode(to, c); + Bukkit.getScheduler() + .runTask(this, () -> { + if (sent) { + fp.sendMessage("§a验证码已发送到 " + to); + fp.sendMessage("§e输入6位验证码(0取消):"); + } else { + fp.sendMessage("§c发送失败"); + } + }); + }); + } + } else { + chatInput.getState(p2).type = + ChatInputManager.InputType.SET_EMAIL; + p2.sendMessage("§e请输入邮箱:"); + } + return true; + } + +// ===== sign ===== + if (sub.equals("sign")) { + if (!(sender instanceof Player)) { + sender.sendMessage("§c仅玩家可用"); + return true; + } + dailySignWithReward((Player) sender); + return true; + } + +// ===== reset ===== + if (sub.equals("reset")) { + if (!(sender instanceof Player)) { + sender.sendMessage("§c仅玩家可用"); + return true; + } + loginMgr.handleReset((Player) sender); + return true; + } + +// ===== undo(密码撤销) ===== + if (sub.equals("undo")) { + if (!(sender instanceof Player)) { + sender.sendMessage("§c仅玩家可用"); + return true; + } + Player p2 = (Player) sender; + PwdRollback rb = pwdRollback.get(p2.getName()); + if (rb == null) { + p2.sendMessage("§c无可撤销记录"); + return true; + } + if (System.currentTimeMillis() - rb.time > 600000L) { + pwdRollback.remove(p2.getName()); + p2.sendMessage("§c撤销已过期"); + return true; + } + db.setField(p2.getName(), "password_hash", rb.hash); + db.setField(p2.getName(), "password_salt", rb.salt); + pwdRollback.remove(p2.getName()); + p2.sendMessage("§a密码已撤销!"); + return true; + } + + // radio子命令(控制台+玩家) // sdf1_login radio → 停止全服广播 // sdf1_login radio reload → 重载 // sdf1_login radio <歌名> → 全服播放 @@ -2909,6 +3541,7 @@ public class Main extends JavaPlugin } else { sender.sendMessage( "§c未找到歌曲: " + songName); + return true; } } else { Player targetP = @@ -3375,24 +4008,6 @@ public class Main extends JavaPlugin return true; } - // del - if (sub.equals("del")) { - if (!isAdmin(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - if (args.length < 2) { - sender.sendMessage( - "§e用法: /sdf1_login del <玩家>"); - return true; - } - db.deleteUser(args[1]); - sender.sendMessage("§a已删除 " + args[1]); - Player tp = Bukkit.getPlayer(args[1]); - if (tp != null && tp.isOnline()) - tp.kickPlayer("§c账号已被管理员删除"); - return true; - } // kick (挂机) if (sub.equals("kick")) { @@ -3504,6 +4119,7 @@ public class Main extends JavaPlugin p.sendMessage( "§e§l======== 聊天过滤帮助 =========="); p.sendMessage("§e/chat reload §7- 重载配置"); + p.sendMessage("§e/chat 开/关 §7- 开启/关闭聊天过滤"); p.sendMessage("§e/chat add §7- 添加白名单URL"); p.sendMessage("§e/chat remove §7- 移除白名单URL"); p.sendMessage("§e/chat addplayer <玩家> §7- 添加白名单玩家"); diff --git a/src/main/java/Sdf1_login/QuestTracker.java b/src/main/java/Sdf1_login/QuestTracker.java index 103284c..ea422d2 100644 --- a/src/main/java/Sdf1_login/QuestTracker.java +++ b/src/main/java/Sdf1_login/QuestTracker.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; +import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -178,20 +179,111 @@ public class QuestTracker { BIOME_CATEGORY.put("minecraft:warped_forest", "下界"); BIOME_CATEGORY.put("minecraft:soul_sand_valley", "下界"); BIOME_CATEGORY.put("minecraft:basalt_deltas", "下界"); + // 岩岸/沙滩 + BIOME_CATEGORY.put("minecraft:stony_shore", + "石岸"); +// 海岸 + BIOME_CATEGORY.put("minecraft:mushroom_fields", + "蘑菇岛"); +// 苍白之园 + BIOME_CATEGORY.put( + "minecraft:pale_garden", "森林"); +// 末地小型岛屿 + BIOME_CATEGORY.put( + "minecraft:small_end_islands", "末地"); +// 主世界结构归类 + BIOME_CATEGORY.put("minecraft:village", "平原"); + BIOME_CATEGORY.put( + "minecraft:desert_pyramid", "沙漠"); + BIOME_CATEGORY.put( + "minecraft:jungle_pyramid", "森林"); + BIOME_CATEGORY.put( + "minecraft:swamp_hut", "沼泽"); + // 末地群系 BIOME_CATEGORY.put("minecraft:the_end", "末地"); BIOME_CATEGORY.put("minecraft:the_void", "末地"); BIOME_CATEGORY.put("minecraft:end_highlands", "末地"); BIOME_CATEGORY.put("minecraft:end_midlands", "末地"); BIOME_CATEGORY.put("minecraft:end_barrens", "末地"); + + } // 所有群系大类列表 - private static final String[] ALL_CATEGORIES = - {"海洋", "平原", "森林", "沙漠", - "恶地", "雪原", "沙砾", "沼泽", - "河流", "蘑菇岛", "地下", - "下界", "末地"}; + private static final String[] + ALL_CATEGORIES = { + "海洋", "平原", "森林", "沙漠", + "恶地", "雪原", "沙砾", "沼泽", + "河流", "石岸", "蘑菇岛", "地下", + "下界", "末地" + }; + + // ===== 结构映射 ===== + private static final Map + STRUCTURE_CATEGORY = + new HashMap<>(); + static { + // 主世界结构 + STRUCTURE_CATEGORY.put( + "minecraft:village", "村庄"); + STRUCTURE_CATEGORY.put( + "minecraft:desert_pyramid", + "沙漠"); + STRUCTURE_CATEGORY.put( + "minecraft:jungle_pyramid", + "森林"); + STRUCTURE_CATEGORY.put( + "minecraft:swamp_hut", "沼泽"); + STRUCTURE_CATEGORY.put( + "minecraft:igloo", "雪原"); + STRUCTURE_CATEGORY.put( + "minecraft:mansion", "森林"); + STRUCTURE_CATEGORY.put( + "minecraft:pillager_outpost", + "平原"); + STRUCTURE_CATEGORY.put( + "minecraft:mineshaft", "地下"); + STRUCTURE_CATEGORY.put( + "minecraft:stronghold", "地下"); + STRUCTURE_CATEGORY.put( + "minecraft:ancient_city", + "地下"); + STRUCTURE_CATEGORY.put( + "minecraft:trail_ruins", + "平原"); + STRUCTURE_CATEGORY.put( + "minecraft:trial_chambers", + "地下"); + STRUCTURE_CATEGORY.put( + "minecraft:ocean_ruin", "海洋"); + STRUCTURE_CATEGORY.put( + "minecraft:shipwreck", "海洋"); + STRUCTURE_CATEGORY.put( + "minecraft:buried_treasure", + "海洋"); + STRUCTURE_CATEGORY.put( + "minecraft:ruined_portal", + "平原"); + STRUCTURE_CATEGORY.put( + "minecraft:fossil", "平原"); + STRUCTURE_CATEGORY.put( + "minecraft:ancient_city", + "地下"); + // 下界结构 + STRUCTURE_CATEGORY.put( + "minecraft:nether_fortress", + "下界"); + STRUCTURE_CATEGORY.put( + "minecraft:bastion_remnant", + "下界"); + STRUCTURE_CATEGORY.put( + "minecraft:nether_fossil", + "下界"); + STRUCTURE_CATEGORY.put( + "minecraft:basalt_pillars", + "下界"); + } // 所有群系分类的条件关键词映射 private static final Map @@ -214,6 +306,10 @@ public class QuestTracker { CATEGORY_KEYWORDS.put("地狱", "下界"); CATEGORY_KEYWORDS.put("下届", "下界"); CATEGORY_KEYWORDS.put("主岛", "末地"); + CATEGORY_KEYWORDS.put("石岸", "石岸"); + CATEGORY_KEYWORDS.put("沙滩", "石岸"); + CATEGORY_KEYWORDS.put("海岸", "石岸"); + } /** @@ -224,25 +320,53 @@ public class QuestTracker { */ public String mapBiomeCategory( String rawKey, String worldName) { - // 下界维度优先用世界名 + // 优先用世界名判断维度 if (worldName != null) { - String w = worldName.toLowerCase(); + String w = + worldName.toLowerCase(); if (w.contains("nether")) return "下界"; if (w.contains("end")) return "末地"; } + // 用群系映射表 if (rawKey != null) { + String lower = + rawKey.toLowerCase(); + // 带 minecraft: 前缀的直接查 String cat = - BIOME_CATEGORY.get( - rawKey.toLowerCase()); - if (cat != null) return cat; + BIOME_CATEGORY.get(lower); + if (cat != null) + return cat; + + // 不带前缀的尝试补全 + String prefixed = + "minecraft:" + lower; + cat = BIOME_CATEGORY.get(prefixed); + if (cat != null) + return cat; + + // 模糊匹配群系名 + for (Map.Entry entry + : BIOME_CATEGORY.entrySet()) { + if (entry.getKey() + .contains(lower) + || lower.contains( + entry.getKey() + .replace( + "minecraft:", + ""))) { + return entry.getValue(); + } + } } + return "主世界"; } + // ========== 解析为区块 ========== private List
parseIntoSections( @@ -1611,22 +1735,68 @@ public class QuestTracker { + ": §e" + cur + "/" + needed; } + if (key.contains("村民") + && key.contains("交易")) { + int needed = parseNumberFromText(key); + if (needed <= 0) needed = 1; + int cur = getCounter( + player, "VILLAGER_TRADE"); + return "§7村民交易: §e" + + cur + "/" + needed; + } + } + // ===== 击杀泛解析(支持中文列表) ===== +// 匹配 "击杀10只骷髅、僵尸、苦力怕" 等 + if (key.contains("击杀") && key.contains("只")) { + int needed = parseNumberFromText(key); + if (needed <= 0) needed = 1; + + String mobPart = key; + java.util.regex.Matcher killM = + java.util.regex.Pattern + .compile("击杀\\d+只(.+)") + .matcher(key); + if (killM.find()) { + mobPart = killM.group(1).trim(); + } + + String[] mobNames = mobPart + .split("[、,,,]+"); + int totalKills = 0; + StringBuilder names = new StringBuilder(); + for (String name : mobNames) { + String trimmed = name.trim(); + if (trimmed.isEmpty()) continue; + String entityType = + chineseMobToEntity(trimmed); + if (!entityType.isEmpty()) { + totalKills += getCounter( + player, + "KILL:" + entityType); + if (names.length() > 0) + names.append("/"); + names.append(trimmed); + } + } + return "§7击杀" + names + ": §e" + + totalKills + "/" + needed; + } + +// 单一KILL:xxx:N 格式(原有逻辑保留) if (key.startsWith("KILL:")) { - String[] parts = key.split(":"); - if (parts.length >= 3) { - int needed = - parseInt(parts[2]); - int cur = getCounter( - player, - "KILL:" + parts[1]); - return "§7杀 " + parts[1] - + ": §e" + cur - + "/" + needed; + String[] p = key.split(":"); + if (p.length >= 3) { + int needed = parseInt(p[2]); + int cur = getCounter(player, + "KILL:" + p[1]); + return "§7击杀" + p[1] + ": §e" + + cur + "/" + needed; } } + if (key.startsWith("KILL_PLAYER")) { String[] parts = key.split(":"); int needed = parts.length >= 2 @@ -1739,10 +1909,219 @@ public class QuestTracker { + done + "/" + quests.size(); } + // ===== 泛解析:击杀类进度 ===== + if (key.contains("击杀") || key.contains("猎杀") + || key.contains("消灭")) { + int needed = parseNumberFromText(key); + if (needed <= 0) needed = 1; + List mobTypes = extractMobTypes(key); + if (!mobTypes.isEmpty()) { + int total = 0; + StringBuilder names = + new StringBuilder(); + for (int i = 0; + i < mobTypes.size(); i++) { + total += getCounter(player, + "KILL:" + mobTypes.get(i)); + if (i > 0) names.append("/"); + names.append(reverseMobName( + mobTypes.get(i))); + } + return "§7击杀" + names + ": §e" + + total + "/" + needed; + } + } + + // ===== 泛解析:村民交易进度 ===== + if (key.contains("交易") + && (key.contains("村民") + || key.contains("与村民"))) { + int needed = parseNumberFromText(key); + if (needed <= 0) needed = 1; + int cur = getCounter(player, "TRADE"); + return "§7村民交易: §e" + + cur + "/" + needed; + } + // ===== 泛解析:采集/挖矿进度 ===== + if (key.contains("采集") + || key.contains("挖")) { + int needed = + parseNumberFromText(key); + if (needed <= 0) needed = 1; + List matTypes = + extractMaterialTypes(key); + if (!matTypes.isEmpty()) { + int total = 0; + StringBuilder names = + new StringBuilder(); + for (int i = 0; + i < matTypes.size(); i++) { + total += getCounter( + player, + "MINE:" + matTypes + .get(i)); + if (i > 0) + names.append("/"); + names.append( + translateMaterialName( + matTypes + .get(i))); + } + return "§7采集" + names + + ": §e" + total + + "/" + needed; + } + } + + // ===== 泛解析:合成进度 ===== + if (key.contains("合成") || key.contains("制作")) { + int needed = parseNumberFromText(key); + if (needed <= 0) needed = 1; + int cur = getCounter(player, "CRAFT"); + return "§7合成: §e" + cur + "/" + needed; + } + + // ===== 泛解析:钓鱼进度 ===== + if (key.contains("钓鱼") || key.contains("钓")) { + int needed = parseNumberFromText(key); + if (needed <= 0) needed = 1; + int cur = getCounter(player, "FISH"); + return "§7钓鱼: §e" + cur + "/" + needed; + } return "§7" + key; } + private String translateMaterialName( + String name) { + switch (name) { + case "COAL_ORE": + return "煤矿石"; + case "DEEPSLATE_COAL_ORE": + return "深层煤矿石"; + case "IRON_ORE": + return "铁矿石"; + case "DEEPSLATE_IRON_ORE": + return "深层铁矿石"; + case "COPPER_ORE": + return "铜矿石"; + case "DEEPSLATE_COPPER_ORE": + return "深层铜矿石"; + case "GOLD_ORE": + return "金矿石"; + case "DEEPSLATE_GOLD_ORE": + return "深层金矿石"; + case "DIAMOND_ORE": + return "钻石矿石"; + case "DEEPSLATE_DIAMOND_ORE": + return "深层钻石矿石"; + case "EMERALD_ORE": + return "绿宝石矿石"; + case "DEEPSLATE_EMERALD_ORE": + return "深层绿宝石矿石"; + case "LAPIS_ORE": + return "青金石矿石"; + case "DEEPSLATE_LAPIS_ORE": + return "深层青金石矿石"; + case "REDSTONE_ORE": + return "红石矿石"; + case "DEEPSLATE_REDSTONE_ORE": + return "深层红石矿石"; + case "NETHER_GOLD_ORE": + return "下界金矿石"; + case "NETHER_QUARTZ_ORE": + return "下界石英矿石"; + case "ANCIENT_DEBRIS": + return "远古残骸"; + case "COAL": + return "煤炭"; + case "RAW_IRON": + return "粗铁"; + case "RAW_GOLD": + return "粗金"; + case "RAW_COPPER": + return "粗铜"; + case "DIAMOND": + return "钻石"; + case "EMERALD": + return "绿宝石"; + case "LAPIS_LAZULI": + return "青金石"; + case "REDSTONE": + return "红石"; + case "QUARTZ": + return "石英"; + default: + return name; + } + } + /** + * 中文物种名 → Bukkit EntityType 名 + * 支持击杀泛解析的模糊匹配 + */ + private String chineseMobToEntity(String cn) { + String n = cn.toLowerCase().trim(); + + // 直接别名表 + Map mobMap = new HashMap<>(); + mobMap.put("骷髅", "SKELETON"); + mobMap.put("僵尸", "ZOMBIE"); + mobMap.put("苦力怕", "CREEPER"); + mobMap.put("蜘蛛", "SPIDER"); + mobMap.put("洞穴蜘蛛", "CAVE_SPIDER"); + mobMap.put("小白", "SKELETON"); + mobMap.put("末影人", "ENDERMAN"); + mobMap.put("enderman", "ENDERMAN"); + mobMap.put("猪灵", "PIGLIN"); + mobMap.put("猪灵蛮兵", "PIGLIN_BRUTE"); + mobMap.put("恶魂", "GHAST"); + mobMap.put("凋灵骷髅", "WITHER_SKELETON"); + mobMap.put("烈焰人", "BLAZE"); + mobMap.put("史莱姆", "SLIME"); + mobMap.put("岩浆怪", "MAGMA_CUBE"); + mobMap.put("女巫", "WITCH"); + mobMap.put("蠹虫", "SILVERFISH"); + mobMap.put("潜影贝", "SHULKER"); + mobMap.put("守卫者", "GUARDIAN"); + mobMap.put("远古守卫者", "ELDER_GUARDIAN"); + mobMap.put("溺尸", "DROWNED"); + mobMap.put("僵尸村民", "ZOMBIE_VILLAGER"); + mobMap.put("尸壳", "HUSK"); + mobMap.put("流浪者", "STRAY"); + mobMap.put("幻翼", "PHANTOM"); + mobMap.put("掠夺者", "PILLAGER"); + mobMap.put("卫道士", "VINDICATOR"); + mobMap.put("唤魔者", "EVOKER"); + mobMap.put("恼鬼", "VEX"); + mobMap.put("劫掠兽", "RAVAGER"); + mobMap.put("蜘蛛骑士", "SPIDER_JOCKEY"); + mobMap.put("凋灵", "WITHER"); + mobMap.put("末影龙", "ENDER_DRAGON"); + + // 精确匹配 + if (mobMap.containsKey(n)) { + return mobMap.get(n); + } + + // 模糊匹配(包含关系) + for (Map.Entry en + : mobMap.entrySet()) { + if (n.contains(en.getKey()) + || en.getKey().contains(n)) { + return en.getValue(); + } + } + + // 尝试直接用英文名 + try { + return org.bukkit.entity.EntityType + .valueOf(n.toUpperCase() + .replace(" ", "_")) + .name(); + } catch (Exception e) { + return ""; + } + } public List checkInBiome( String player, @@ -1776,6 +2155,23 @@ public class QuestTracker { String category = mapBiomeCategory( rawBiomeKey, worldName); + // 结构检测(覆盖纯生物群系分类) + // 地下检测:Y低于表面则归类为地下 +// 覆盖废弃矿井/试炼密室/要塞/远古城市等 + try { + org.bukkit.World uw = + Bukkit.getWorld(worldName); + if (uw != null) { + int surfaceY = uw + .getHighestBlockYAt( + (int) Math.floor(x), + (int) Math.floor(z)); + if (y < surfaceY - 8) { + category = "地下"; + } + } + } catch (Exception ignored) {} + // 记录总打卡 int allCur = getCounter( @@ -1851,6 +2247,344 @@ public class QuestTracker { return matched; } + /* + * 打卡入口:报告坐标 → 32格去重 → 匹配条件 → 记录 + */ + public String checkInAtPosition( + String player, String worldName, + double x, double y, double z) { + + List history = + loadCheckInPositions(player); + for (double[] pos : history) { + double dist = Math.sqrt( + Math.pow(x - pos[0], 2) + + Math.pow(y - pos[1], 2) + + Math.pow(z - pos[2], 2)); + if (dist <= 32) { + return "§c距离上次打卡点太近(" + + String.format( + "%.0f", dist) + + "格)"; + } + } + + // ===== 获取群系和分类 ===== + String biome = "minecraft:plains"; + String category = "平原"; + try { + org.bukkit.World w = + Bukkit.getWorld(worldName); + if (w != null) { + org.bukkit.Location loc = + new org.bukkit.Location( + w, + (int) Math.floor(x), + (int) Math.floor(y), + (int) Math.floor(z)); + biome = loc.getBlock() + .getBiome().getKey() + .toString(); + category = mapBiomeCategory( + biome, worldName); + + int surfaceY = w + .getHighestBlockYAt( + (int) Math.floor(x), + (int) Math.floor(z)); + if (y < surfaceY - 8) { + category = "地下"; + } + + if ("下界".equals(category) + || w.getEnvironment() + == org.bukkit.World + .Environment.NETHER) { + category = "下界"; + } else if ("末地".equals(category) + || w.getEnvironment() + == org.bukkit.World + .Environment.THE_END) { + category = "末地"; + } + } + } catch (Exception ex) { + plugin.getLogger().warning( + "[QuestTracker] 群系读取失败: " + + ex.getMessage()); + } + + plugin.getLogger().info( + "[QuestTracker] 打卡: " + + player + + " world=" + worldName + + " biome=" + biome + + " cat=" + category + + " pos=" + + String.format( + "%.0f,%.0f,%.0f", + x, y, z)); + + saveCheckInPosition( + player, x, y, z); + recordCheckIn( + player, biome, category, + worldName, x, y, z); + List matched = + checkInBiome( + player, biome, + worldName, x, y, z); + + StringBuilder sb = new StringBuilder(); + sb.append("§a打卡成功!群系: §e") + .append(category); + if (!matched.isEmpty()) { + sb.append("\n§a完成任务条件:"); + for (String m : matched) { + String qn = + findQuestName(m); + sb.append("\n§7• "); + if (qn != null) { + sb.append("§e[") + .append(qn) + .append("§7] §f"); + } + sb.append(m); + } + } + return sb.toString(); + } + + + /** + * 根据条件文本查找所属任务名 + */ + private String findQuestName( + String condition) { + for (String cat : Arrays.asList( + "新人任务", + "主线任务", + "支线任务")) { + for (QuestFile qf : + getQuests(cat)) { + if (qf.conditions.contains( + condition)) { + return qf.displayName; + } + } + } + return null; + } + + + /** + * 加载玩家所有历史打卡坐标 + * 格式: x1,y1,z1;x2,y2,z2;... + */ + private List loadCheckInPositions( + String player) { + List list = new ArrayList<>(); + Object val = storage.getCondition( + player, "checkin_all_pos"); + if (val == null) return list; + String data = val.toString(); + if (data.isEmpty()) return list; + String[] records = data.split(";"); + for (String r : records) { + String[] parts = r.split(","); + if (parts.length >= 3) { + try { + list.add(new double[]{ + Double.parseDouble(parts[0]), + Double.parseDouble(parts[1]), + Double.parseDouble(parts[2])}); + } catch (NumberFormatException e) { + // 跳过损坏数据 + } + } + } + return list; + } + + /** + * 保存打卡坐标(追加到已有记录) + */ + private void saveCheckInPosition( + String player, double x, + double y, double z) { + List list = + loadCheckInPositions(player); + list.add(new double[]{x, y, z}); + StringBuilder sb = new StringBuilder(); + for (double[] pos : list) { + if (sb.length() > 0) sb.append(";"); + sb.append(pos[0]).append(",") + .append(pos[1]).append(",") + .append(pos[2]); + } + storage.setConditionRaw( + player, + "checkin_all_pos", + sb.toString()); + } + // ========== 中文生物名 → EntityType 映射 ========== + private static final Map + MOB_NAME_MAP = new HashMap<>(); + static { + MOB_NAME_MAP.put("洞穴蜘蛛", "CAVE_SPIDER"); + MOB_NAME_MAP.put("骷髅", "SKELETON"); + MOB_NAME_MAP.put("僵尸", "ZOMBIE"); + MOB_NAME_MAP.put("苦力怕", "CREEPER"); + MOB_NAME_MAP.put("蜘蛛", "SPIDER"); + MOB_NAME_MAP.put("末影人", "ENDERMAN"); + MOB_NAME_MAP.put("女巫", "WITCH"); + MOB_NAME_MAP.put("烈焰人", "BLAZE"); + MOB_NAME_MAP.put("僵尸猪灵", + "ZOMBIFIED_PIGLIN"); + MOB_NAME_MAP.put("猪灵", "PIGLIN"); + MOB_NAME_MAP.put("恶魂", "GHAST"); + MOB_NAME_MAP.put("凋灵骷髅", + "WITHER_SKELETON"); + MOB_NAME_MAP.put("史莱姆", "SLIME"); + MOB_NAME_MAP.put("岩浆怪", "MAGMA_CUBE"); + MOB_NAME_MAP.put("溺尸", "DROWNED"); + MOB_NAME_MAP.put("尸壳", "HUSK"); + MOB_NAME_MAP.put("流髑", "STRAY"); + MOB_NAME_MAP.put("幻翼", "PHANTOM"); + MOB_NAME_MAP.put("守卫者", "GUARDIAN"); + MOB_NAME_MAP.put("远古守卫者", + "ELDER_GUARDIAN"); + MOB_NAME_MAP.put("掠夺者", "PILLAGER"); + MOB_NAME_MAP.put("卫道士", "VINDICATOR"); + MOB_NAME_MAP.put("唤魔者", "EVOKER"); + MOB_NAME_MAP.put("恼鬼", "VEX"); + MOB_NAME_MAP.put("劫掠兽", "RAVAGER"); + MOB_NAME_MAP.put("潜影贝", "SHULKER"); + MOB_NAME_MAP.put("末影螨", "ENDERMITE"); + MOB_NAME_MAP.put("凋灵", "WITHER"); + MOB_NAME_MAP.put("末影龙", "ENDER_DRAGON"); + MOB_NAME_MAP.put("监守者", "WARDEN"); + MOB_NAME_MAP.put("嗅探兽", "SNIFFER"); + MOB_NAME_MAP.put("僵尸村民", + "ZOMBIE_VILLAGER"); + } + + /** + * 从条件文本中提取所有匹配的生物类型 + * 按名称长度降序匹配,避免短名误匹配 + */ + private List extractMobTypes( + String text) { + List types = new ArrayList<>(); + List> sorted = + new ArrayList<>( + MOB_NAME_MAP.entrySet()); + sorted.sort((a, b) -> + b.getKey().length() + - a.getKey().length()); + String remaining = text; + for (Map.Entry entry + : sorted) { + if (remaining.contains( + entry.getKey())) { + if (!types.contains( + entry.getValue())) { + types.add(entry.getValue()); + } + remaining = remaining.replace( + entry.getKey(), ""); + } + } + return types; + } + + /** + * EntityType → 中文名 + */ + private String reverseMobName( + String entityType) { + for (Map.Entry entry + : MOB_NAME_MAP.entrySet()) { + if (entry.getValue() + .equals(entityType)) { + return entry.getKey(); + } + } + return entityType; + } + + /** + * 从条件文本中提取矿物名 + * "采集任意矿物(煤炭除外)1组" + * → [COAL_ORE, IRON_ORE, GOLD_ORE, ...] + */ + private List extractMaterialTypes( + String text) { + List types = new ArrayList<>(); + // 排除列表 + List excludes = + new ArrayList<>(); + if (text.contains("煤炭除外")) { + excludes.add("COAL"); + excludes.add("COAL_ORE"); + excludes.add("DEEPSLATE_COAL_ORE"); + } + // 通配符:"矿物" "矿石" + if (text.contains("矿物") + || text.contains("矿石")) { + String[] ores = { + "COAL_ORE", + "DEEPSLATE_COAL_ORE", + "IRON_ORE", + "DEEPSLATE_IRON_ORE", + "COPPER_ORE", + "DEEPSLATE_COPPER_ORE", + "GOLD_ORE", + "DEEPSLATE_GOLD_ORE", + "DIAMOND_ORE", + "DEEPSLATE_DIAMOND_ORE", + "EMERALD_ORE", + "DEEPSLATE_EMERALD_ORE", + "LAPIS_ORE", + "DEEPSLATE_LAPIS_ORE", + "REDSTONE_ORE", + "DEEPSLATE_REDSTONE_ORE", + "NETHER_GOLD_ORE", + "NETHER_QUARTZ_ORE", + "ANCIENT_DEBRIS" + }; + for (String ore : ores) { + if (!excludes.contains(ore)) { + types.add(ore); + } + } + return types; + } + // 单个矿物名 + if (text.contains("煤炭")) + types.add("COAL"); + if (text.contains("铁矿") + || text.contains("铁锭")) + types.add("RAW_IRON"); + if (text.contains("金矿") + || text.contains("金锭")) + types.add("RAW_GOLD"); + if (text.contains("钻石")) + types.add("DIAMOND"); + if (text.contains("绿宝石")) + types.add("EMERALD"); + if (text.contains("青金石")) + types.add("LAPIS_LAZULI"); + if (text.contains("红石")) + types.add("REDSTONE"); + if (text.contains("铜")) + types.add("RAW_COPPER"); + if (text.contains("下界石英") + || text.contains("石英")) + types.add("QUARTZ"); + return types; + } + /** * 从条件文本中提取目标群系分类 @@ -2108,6 +2842,16 @@ public class QuestTracker { } } + if (key.contains("村民") + && key.contains("交易")) { + int needed = parseNumberFromText(key); + if (needed <= 0) needed = 1; + return getCounter( + player, "VILLAGER_TRADE") + >= needed; + } + + if (key.startsWith("KILL_PLAYER")) { String[] p = key.split(":"); int n = p.length >= 2 @@ -2200,6 +2944,63 @@ public class QuestTracker { } return true; } + // ===== 泛解析:击杀类中文条件 ===== + if (key.contains("击杀") || key.contains("猎杀") + || key.contains("消灭")) { + int needed = parseNumberFromText(key); + if (needed <= 0) needed = 1; + List mobTypes = extractMobTypes(key); + if (!mobTypes.isEmpty()) { + int total = 0; + for (String mobType : mobTypes) { + total += getCounter(player, + "KILL:" + mobType); + } + return total >= needed; + } + } + + // ===== 泛解析:村民交易条件 ===== + if (key.contains("交易") + && (key.contains("村民") + || key.contains("与村民"))) { + int needed = parseNumberFromText(key); + if (needed <= 0) needed = 1; + return getCounter(player, "TRADE") + >= needed; + } + + // ===== 泛解析:采集/挖矿类条件 ===== + if (key.contains("采集") || key.contains("挖")) { + int needed = parseNumberFromText(key); + if (needed <= 0) needed = 1; + List matTypes = + extractMaterialTypes(key); + if (!matTypes.isEmpty()) { + int total = 0; + for (String mat : matTypes) { + total += getCounter(player, + "MINE:" + mat); + } + return total >= needed; + } + } + + // ===== 泛解析:合成类条件 ===== + if (key.contains("合成") || key.contains("制作")) { + int needed = parseNumberFromText(key); + if (needed <= 0) needed = 1; + return getCounter(player, "CRAFT") + >= needed; + } + + // ===== 泛解析:钓鱼类条件 ===== + if (key.contains("钓鱼") || key.contains("钓")) { + int needed = parseNumberFromText(key); + if (needed <= 0) needed = 1; + return getCounter(player, "FISH") + >= needed; + } return false; } @@ -2271,6 +3072,12 @@ public class QuestTracker { return 0; } + public void onVillagerTrade(String player) { + String key = "VILLAGER_TRADE"; + int cur = getCounter(player, key); + setCounter(player, key, cur + 1); + } + private long getCounterLong( String player, String key) { Object val = storage.getCondition( diff --git a/src/main/java/Sdf1_login/RadioDownloadListener.java b/src/main/java/Sdf1_login/RadioDownloadListener.java index 75dfb32..2689e35 100644 --- a/src/main/java/Sdf1_login/RadioDownloadListener.java +++ b/src/main/java/Sdf1_login/RadioDownloadListener.java @@ -4,30 +4,34 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerResourcePackStatusEvent; +import org.bukkit.event.player + .PlayerResourcePackStatusEvent; -import java.util.Map; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Logger; +import java.util.concurrent + .ConcurrentHashMap; public class RadioDownloadListener implements Listener { private final Main plugin; - private final Logger log; - private final Map retryCount = - new ConcurrentHashMap<>(); - private final Map totalAttempts = - new ConcurrentHashMap<>(); - private static final int MAX_RETRY = 3; - // 每位玩家最多总共尝试5次,防死循环 - private static final int MAX_TOTAL = 5; + private final java.util.logging.Logger log; + + private final ConcurrentHashMap + failCount = new ConcurrentHashMap<>(); + private final ConcurrentHashMap + blocked = new ConcurrentHashMap<>(); + + private static final int MAX_FAILED = 3; public RadioDownloadListener(Main plugin) { this.plugin = plugin; this.log = plugin.getLogger(); + } + public void onPlayerQuit(UUID uuid) { + failCount.remove(uuid); + blocked.remove(uuid); } @EventHandler @@ -35,84 +39,142 @@ public class RadioDownloadListener PlayerResourcePackStatusEvent e) { Player p = e.getPlayer(); UUID uuid = p.getUniqueId(); - String status = e.getStatus().name(); + String status = + e.getStatus().name(); - log.info("[Radio] " + p.getName() - + " 资源包: " + status); + log.info("[Radio] === 资源包状态 ==="); + log.info("[Radio] 玩家: " + + p.getName()); + log.info("[Radio] UUID: " + uuid); + log.info("[Radio] 状态: " + + status); + log.info("[Radio] 原始状态枚举: " + + e.getStatus().getClass() + .getSimpleName() + + "." + + e.getStatus().name()); + + if (blocked.containsKey(uuid)) { + log.info("[Radio] 已熔断,跳过: " + + p.getName()); + return; + } switch (status) { case "SUCCESS": - log.info("[Radio] " + p.getName() - + " 资源包加载成功"); - retryCount.remove(uuid); - totalAttempts.remove(uuid); + log.info("[Radio] " + + p.getName() + + " ✓ 资源包加载成功"); + failCount.remove(uuid); + blocked.remove(uuid); break; - case "FAILED_DOWNLOAD": - int retry = retryCount - .getOrDefault(uuid, 0) + 1; - int total = totalAttempts - .getOrDefault(uuid, 0) + 1; - retryCount.put(uuid, retry); - totalAttempts.put(uuid, total); - - log.warning("[Radio] " + p.getName() - + " 下载失败 (重试" - + retry + "/" + MAX_RETRY - + ", 总计" + total - + "/" + MAX_TOTAL + ")"); - - if (total >= MAX_TOTAL) { - p.sendMessage( - "§7[Radio] §f资源包加载失败,跳过"); - retryCount.remove(uuid); - totalAttempts.remove(uuid); - break; - } - - if (retry > MAX_RETRY) { - retryCount.put(uuid, 0); - Bukkit.getScheduler() - .runTaskLater(plugin, - () -> { - if (p.isOnline() - && plugin - .radio - != null) { - plugin.radio - .sendResourcePack( - p); - } - }, 100L); - break; - } - - p.sendMessage( - "§e[Radio] §f下载失败,重试中..."); - Bukkit.getScheduler() - .runTaskLater(plugin, - () -> { - if (p.isOnline() - && plugin.radio - != null) { - plugin.radio - .sendResourcePack( - p); - } - }, 60L); + case "ACCEPTED": + log.info("[Radio] " + + p.getName() + + " 客户端已接受," + + "等待下载..."); break; case "DECLINED": - log.info("[Radio] " + p.getName() - + " 拒绝下载"); - retryCount.remove(uuid); - totalAttempts.remove(uuid); + log.warning("[Radio] " + + p.getName() + + " ✗ 拒绝了资源包"); + log.warning("[Radio] " + + "可能原因: " + + "1.客户端设置禁用资源包" + + " 2.已缓存拒绝记录" + + " 3.SSL证书不匹配"); + handleFailure(p, uuid); + break; + + case "FAILED_DOWNLOAD": + log.warning("[Radio] " + + p.getName() + + " ✗ 下载失败"); + log.warning("[Radio] " + + "可能原因: " + + "1.URL无法访问" + + " 2.SSL证书错误" + + " 3.网络超时" + + " 4.文件不存在(404)"); + handleFailure(p, uuid); break; default: - retryCount.remove(uuid); - totalAttempts.remove(uuid); + log.info("[Radio] " + + p.getName() + + " 未知状态: " + + status); break; } } -} \ No newline at end of file + + private void handleFailure( + Player p, UUID uuid) { + int failNum = failCount + .getOrDefault(uuid, 0) + 1; + failCount.put(uuid, failNum); + + log.warning("[Radio] " + + p.getName() + + " 失败次数: " + + failNum + "/" + MAX_FAILED); + + if (failNum >= MAX_FAILED) { + blocked.put(uuid, true); + log.severe("[Radio] " + + p.getName() + + " 已达最大失败次数," + + "触发踢出"); + Bukkit.getScheduler() + .runTaskLater(plugin, + () -> { + Player kp = + Bukkit + .getPlayer( + uuid); + if (kp != null + && kp + .isOnline()) { + kp.kickPlayer( + "§c资源包" + + "加载失败" + + "(" + + MAX_FAILED + + "次)," + + "请联系管理员"); + } + }, 1L); + return; + } + + p.sendMessage( + "§e§l[Radio] 下载失败," + + "重试... (" + + failNum + "/" + + MAX_FAILED + ")"); + + // 5秒后重试 + Bukkit.getScheduler() + .runTaskLater(plugin, + () -> { + if (p.isOnline() + && plugin + .radio + != null) { + plugin.getLogger() + .info( + "[Radio] " + + "第" + + failNum + + "次重试: " + + p + .getName()); + plugin.radio + .sendResourcePack( + p); + } + }, 100L); + } +} diff --git a/src/main/java/Sdf1_login/RadioManager.java b/src/main/java/Sdf1_login/RadioManager.java index 631317f..01c6e7b 100644 --- a/src/main/java/Sdf1_login/RadioManager.java +++ b/src/main/java/Sdf1_login/RadioManager.java @@ -328,7 +328,7 @@ public class RadioManager { ("{\n \"pack\": {\n" + " \"pack_format\": 54,\n" + " \"description\":" - + " \"Sdf1 Radio\"\n }\n}") + + " \"为保证服务器正常运行,请勿拒绝资源包!\"\n }\n}") .getBytes("UTF-8")); } catch (Exception e) { // ignore @@ -412,15 +412,24 @@ public class RadioManager { */ public void sendResourcePack(Player p) { UUID uuid = p.getUniqueId(); - if (pendingPack.contains(uuid)) return; + if (pendingPack.contains(uuid)) { + plugin.getLogger().info( + "[Radio] 跳过(已等待): " + + p.getName()); + return; + } pendingPack.add(uuid); - Bukkit.getScheduler().runTaskLater( - plugin, () -> { + Bukkit.getScheduler() + .runTaskLater(plugin, () -> { pendingPack.remove(uuid); }, 200L); String url = getResourcePackUrl(); if (url == null || url.isEmpty()) { + plugin.getLogger().warning( + "[Radio] URL为空," + + "跳过发送: " + + p.getName()); pendingPack.remove(uuid); return; } @@ -429,98 +438,85 @@ public class RadioManager { plugin.getDataFolder(), "radiopack.zip"); if (!zip.exists()) { + plugin.getLogger().warning( + "[Radio] radiopack.zip不存在," + + "跳过: " + p.getName()); pendingPack.remove(uuid); return; } - // 异步:下载远程zip → 算hash → 比对本地hash - final Player fp = p; - final String finalUrl = url + "?t=" - + System.currentTimeMillis(); + try { + byte[] data = + Files.readAllBytes(zip.toPath()); + String hash = sha1(data); + long fileSize = zip.length(); - Bukkit.getScheduler() - .runTaskAsynchronously(plugin, () -> { - try { - // 1. 本地hash - byte[] localData = - Files.readAllBytes( - zip.toPath()); - String localHash = - sha1(localData); + 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()); - // 2. 远程hash - byte[] remoteData = - downloadBytes(finalUrl); - String remoteHash = - sha1(remoteData); + sendPackWithHash(p, url, hash); - // 3. 比对 - plugin.getLogger().info( - "[Radio] 本地hash: " - + localHash); - plugin.getLogger().info( - "[Radio] 远程hash: " - + remoteHash); - - if (!localHash.equals(remoteHash)) { - plugin.getLogger().warning( - "[Radio] hash不一致," - + "跳过发送资源包"); - plugin.getLogger().warning( - "[Radio] 请更新远程" - + "radiopack.zip"); - Bukkit.getScheduler() - .runTask(plugin, () -> { - fp.sendMessage( - "§7[Radio] §f资源包" - + "版本不一致," - + "跳过安装"); - }); - return; - } - - // 4. hash一致,发送给客户端 - Bukkit.getScheduler() - .runTask(plugin, () -> { - sendPackWithHash( - fp, finalUrl, - remoteHash); - }); - - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] 校验失败: " - + e.getMessage()); - } - }); + } catch (Exception e) { + plugin.getLogger().severe( + "[Radio] 发送异常: " + + p.getName()); + e.printStackTrace(); + pendingPack.remove(uuid); + } } - private void sendPackWithHash( - Player p, String url, String hash) { - UUID uuid = UUID.randomUUID(); - // Paper 1.21: setResourcePack(UUID, String, String, Component, boolean) + + private void sendPackWithHash( + Player p, + String url, + String hash) { + UUID uuid = UUID.fromString( + "a1b2c3d4-e5f6-7890-abcd-" + + "ef1234567890"); + try { - Object component = net.kyori.adventure - .text.Component.text("Sdf1 Radio"); + Object component = + net.kyori.adventure.text + .Component.text( + "§d§l服务器需要自定义资源包,请您点击'继续'由客户端自动下载即可"); p.getClass().getMethod( "setResourcePack", UUID.class, String.class, String.class, - net.kyori.adventure.text + net.kyori.adventure + .text .Component.class, boolean.class) - .invoke(p, uuid, url, - hash, component, false); + .invoke( + p, uuid, url, + hash, component, + true); plugin.getLogger().info( "[Radio] 发送成功: " - + p.getName()); + + p.getName() + + " url=" + + url); return; } catch (Exception ignored) { } - // 降级:旧版 String 签名 try { p.getClass().getMethod( "setResourcePack", @@ -529,8 +525,11 @@ public class RadioManager { String.class, String.class, boolean.class) - .invoke(p, uuid, url, - hash, "Sdf1 Radio", false); + .invoke( + p, uuid, url, + hash, + "Sdf1 Radio", + true); plugin.getLogger().info( "[Radio] 旧版签名成功: " + p.getName()); @@ -538,14 +537,15 @@ public class RadioManager { } catch (Exception ignored) { } - // 降级3参 try { p.getClass().getMethod( "setResourcePack", UUID.class, String.class, String.class) - .invoke(p, uuid, url, hash); + .invoke( + p, uuid, url, + hash); plugin.getLogger().info( "[Radio] 3参签名成功: " + p.getName()); @@ -692,19 +692,86 @@ public class RadioManager { } public String getResourcePackUrl() { + // 有外部配置 → 随机生成URL if (externalUrl != null - && !externalUrl.isEmpty()) { - plugin.getLogger().info( - "[Radio] 使用外部地址: " - + externalUrl); - return externalUrl; + && !externalUrl.trim().isEmpty()) { + return generateRandomUrl(externalUrl); } - plugin.getLogger().warning( - "[Radio] 未配置外部地址," - + "跳过资源包"); - return null; + // 无配置 → 用内置HTTP + int port = 8080; + if (plugin.getConfig2() != null) { + port = plugin.getConfig2().httpPort; + } + return "http://127.0.0.1:" + + port + "/radiopack.zip"; } + + /** + * 从配置的域名列表中随机选一个 + * 生成随机、带时间戳的完整资源包URL + * + * 规则: + * *.example.com + * → 随机子域+时间戳 + * 例: a3f7k2.example.com/pack.zip?t=xxx&v=b9c1e4 + * + * cdn.example.com (无*) + * → 直接使用+时间戳 + * 例: cdn.example.com/pack.zip?t=xxx + */ + private String generateRandomUrl( + String raw) { + String[] parts = raw.split("\\|"); + String pick = + parts[(int)(Math.random() + * parts.length)].trim(); + + // 去掉协议 https:// http:// + String cleaned = pick + .replaceFirst("^https?://", ""); + // 去掉路径 /path/a?v=1 + if (cleaned.contains("/")) { + cleaned = cleaned.substring( + 0, cleaned.indexOf('/')); + } + + // 随机子域 + String hex = Long.toHexString( + Double.doubleToLongBits( + Math.random())); + String rand = hex.length() > 6 + ? hex.substring(2, 8) : hex; + rand = rand.replaceAll( + "[^a-z0-9]", "x"); + + String host; + if (cleaned.startsWith("*")) { + host = rand + + cleaned.substring(1); + } else { + host = cleaned; + } + + String ts = String.valueOf( + System.currentTimeMillis()); + String token = Integer.toHexString( + (int)(Math.random() + * 0xFFFFFF)); + + String url = "https://" + host + + "/radiopack.zip" + + "?t=" + ts + + "&v=" + token; + + plugin.getLogger().info( + "[Radio] 生成URL: " + + pick + " → " + url); + return url; + } + + + // ========== 全服广播 ========== public void startMainRadio() { stopMainRadio(); @@ -847,6 +914,12 @@ public class RadioManager { } } +// 在 init() 或 registerEvents 中保存引用: +// this.listener = new RadioDownloadListener(plugin); +private RadioDownloadListener radioListener; + + + // ========== 重载 ========== public void reload() { stopMainRadio(); diff --git a/txt/AFKManager.txt b/txt/AFKManager.txt deleted file mode 100644 index a65c53f..0000000 --- a/txt/AFKManager.txt +++ /dev/null @@ -1,137 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitRunnable; - -import java.util.*; - -public class AFKManager { - - private final Map lastAction = new HashMap<>(); - private final Map lastStage = new HashMap<>(); - private final ConfigManager config; - private final JavaPlugin plugin; - - public AFKManager(ConfigManager config, JavaPlugin plugin) { - this.config = config; - this.plugin = plugin; - } - - public void startCheck() { - new BukkitRunnable() { - public void run() { tick(); } - }.runTaskTimer(plugin, 20L, 20L); - } - - private void tick() { - if (!config.afkEnabled) return; - long now = System.currentTimeMillis(); - - for (Player p : Bukkit.getOnlinePlayers()) { - String name = p.getName(); - if (!isLoggedIn(name)) continue; - if (config.getAfkWhitelist().contains(name)) continue; - - UUID uuid = p.getUniqueId(); - if (!lastAction.containsKey(uuid)) { - lastAction.put(uuid, now); - lastStage.put(uuid, 0); - continue; - } - - long last = lastAction.get(uuid); - long idle = (now - last) / 1000; - int timeout = config.afkTimeout; - long remain = timeout - idle; - - // 已超时:踢出 - if (remain <= 0) { - p.kickPlayer("§c§l你已挂机 " + fmtSec(idle) + ",被踢出"); - lastAction.remove(uuid); - lastStage.remove(uuid); - continue; - } - - int stage = lastStage.getOrDefault(uuid, 0); - - // === 阶段3:最后10秒 === - // 刷屏倒计时 + 大标题 + 每秒音效 - if (remain <= 10) { - if (stage < 3) { - lastStage.put(uuid, 3); - } - p.sendTitle("§e§l挂机了 " + fmtSec(idle), - "§c还剩 " + remain + " 秒踢出", - 0, 22, 3); - playSound(p); - continue; - } - - // === 阶段2:1分钟~10秒 === - // 持续音效(每秒)+ 标题,聊天仅1次 - if (remain <= 60) { - if (stage < 2) { - lastStage.put(uuid, 2); - p.sendMessage("§e§l⚠ 挂机警告 ⚠ §f你已挂机 §c" + fmtSec(idle) + "§f,还剩 §c" + fmtSec(remain) + "§f踢出"); - } - p.sendTitle("§e§l挂机了 " + fmtSec(idle), - "§c还剩 " + fmtSec(remain) + " 踢出", - 5, 25, 5); - playSound(p); - continue; - } - - // === 阶段1:3分钟提醒 === - // 仅1次:聊天 + 标题 + 3次音效循环 - if (remain <= 180 && stage < 1) { - lastStage.put(uuid, 1); - p.sendMessage("§e§l⚠ 挂机警告 ⚠ §f你已挂机 §c" + fmtSec(idle) + "§f,还剩 §c" + fmtSec(remain) + "§f踢出"); - p.sendTitle("§e§l挂机了 " + fmtSec(idle), - "§f还剩 " + fmtSec(remain) + " 踢出", - 10, 60, 20); - for (int i = 0; i < 3; i++) { - final int delay = i * 10; - Bukkit.getScheduler().runTaskLater(plugin, () -> { - if (p.isOnline()) playSound(p); - }, delay); - } - } - - // 超过3分钟:重置阶段 - if (remain > 180) { - lastStage.remove(uuid); - } - } - } - - - private void playSound(Player p) { - p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_CHIME, 1.0f, 0.79f); - p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1.0f, 0.79f); - p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 0.79f); - } - - public void recordAction(UUID uuid) { - lastAction.put(uuid, System.currentTimeMillis()); - lastStage.put(uuid, 0); - } - - public void remove(UUID uuid) { - lastAction.remove(uuid); - lastStage.remove(uuid); - } - - private boolean isLoggedIn(String name) { - return ((Main) plugin).getLoggedIn().contains(name); - } - - private String fmtSec(long sec) { - if (sec < 60) return sec + "秒"; - long m = sec / 60; - long s = sec % 60; - return m + "分" + (s > 0 ? s + "秒" : ""); - } -} diff --git a/txt/AccountRequestManager.txt b/txt/AccountRequestManager.txt deleted file mode 100644 index ae5eaad..0000000 --- a/txt/AccountRequestManager.txt +++ /dev/null @@ -1,146 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class AccountRequestManager { - - public enum ApprovalMode { AUTO, MANUAL, HYBRID } - - public static class Request { - public final int id; - public final String requester; - public final String target; - public final String ip; - public final long time; - public String status; - public String denyReason; - - public Request(int id, String requester, String target, String ip) { - this.id = id; - this.requester = requester; - this.target = target; - this.ip = ip; - this.time = System.currentTimeMillis(); - this.status = "PENDING"; - this.denyReason = ""; - } - } - - private final Main plugin; - private final Map requests = new ConcurrentHashMap<>(); - private final Map pendingByPlayer = new ConcurrentHashMap<>(); - private int nextId = 1; - private ApprovalMode mode = ApprovalMode.MANUAL; - private int autoDelayMinutes = 60; - - public AccountRequestManager(Main plugin) { - this.plugin = plugin; - } - - public ApprovalMode getMode() { return mode; } - - public void setMode(String m) { - try { this.mode = ApprovalMode.valueOf(m.toUpperCase()); } - catch (Exception e) { this.mode = ApprovalMode.MANUAL; } - } - - public void setAutoDelayMinutes(int min) { - this.autoDelayMinutes = min; - } - - public Request createRequest(String requester, String target, String ip) { - int id = nextId++; - Request req = new Request(id, requester, target, ip); - requests.put(id, req); - pendingByPlayer.put(requester, id); - - if (mode == ApprovalMode.AUTO) { - scheduleAutoApprove(id); - } else if (mode == ApprovalMode.HYBRID) { - String targetIp = (String) plugin.getDb().getField(target, "ip_address"); - if (ip != null && ip.equals(targetIp)) { - scheduleAutoApprove(id); - } - } - return req; - } - - private void scheduleAutoApprove(int id) { - long delayTicks = (long) autoDelayMinutes * 60L * 20L; - Request req = requests.get(id); - if (req == null) return; - String requesterName = req.requester; - String targetName = req.target; - plugin.getLogger().info("[Sdf1_login] OA#" + id + " 将在 " + autoDelayMinutes + " 分钟后自动通过"); - - Bukkit.getScheduler().runTaskLater(plugin, () -> { - Request r = requests.get(id); - if (r != null && "PENDING".equals(r.status)) { - approveRequest(id); - plugin.getLogger().info("[Sdf1_login] OA#" + id + " 自动通过"); - Player requester = Bukkit.getPlayer(requesterName); - if (requester != null && requester.isOnline()) { - requester.sendMessage("§a[Sdf1_login] §f您的删除请求 #" + id + "(§e" + targetName + "§a)已自动通过!"); - } - } - }, delayTicks); - } - - public boolean approveRequest(int id) { - Request req = requests.get(id); - if (req == null || !"PENDING".equals(req.status)) return false; - req.status = "APPROVED"; - plugin.getDb().deleteUser(req.target); - pendingByPlayer.remove(req.requester); - // 清背包+传送出生点+踢出 - Player target = Bukkit.getPlayer(req.target); - if (target != null && target.isOnline()) { - target.getInventory().clear(); - target.getInventory().setArmorContents(new org.bukkit.inventory.ItemStack[4]); - target.getInventory().setExtraContents(new org.bukkit.inventory.ItemStack[1]); - target.teleport(target.getWorld().getSpawnLocation()); - target.kickPlayer("§c§l你的账号已被管理员删除,请重新注册"); - } - return true; - } - - - public boolean denyRequest(int id, String reason) { - Request req = requests.get(id); - if (req == null || !"PENDING".equals(req.status)) return false; - req.status = "DENIED"; - req.denyReason = reason; - pendingByPlayer.remove(req.requester); - return true; - } - - public List getPendingRequests() { - List list = new ArrayList<>(); - for (Request r : requests.values()) { - if ("PENDING".equals(r.status)) list.add(r); - } - return list; - } - - public Request getRequest(int id) { - return requests.get(id); - } - - public boolean hasPending(String player) { - return pendingByPlayer.containsKey(player); - } - - public void cancelPending(String player) { - Integer id = pendingByPlayer.remove(player); - if (id != null) { - Request r = requests.get(id); - if (r != null) r.status = "CANCELLED"; - } - } -} diff --git a/txt/ChatFilterManager.txt b/txt/ChatFilterManager.txt deleted file mode 100644 index 1f92245..0000000 --- a/txt/ChatFilterManager.txt +++ /dev/null @@ -1,594 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; - -import java.io.*; -import java.net.InetSocketAddress; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class ChatFilterManager { - - private final JavaPlugin plugin; - private final List whitelistUrls = - new ArrayList<>(); - private final Set whitelistPlayers = - new TreeSet<>( - String.CASE_INSENSITIVE_ORDER); - private final Map - punishmentRules = - new LinkedHashMap<>(); - private final Map - punishmentDurations = - new LinkedHashMap<>(); - private final Map - violationCount = - new ConcurrentHashMap<>(); - private final Map - mutedPlayers = - new ConcurrentHashMap<>(); - private final Map messages = - new LinkedHashMap<>(); - private boolean notifyAdmin = true; - private boolean notifyAll = false; - private int muteDuration = 300; - - private static final Pattern URL_PATTERN = - Pattern.compile( - "(?i)(?:https?://\\S+)" - + "|(?:(?:[a-zA-Z0-9]" - + "(?:[a-zA-Z0-9\\-]*" - + "[a-zA-Z0-9])?\\.)+" - + "[a-zA-Z]{2,})" - + "(?:/\\S*)?"); - private static final Pattern DOT_VARIANTS = - Pattern.compile( - "[\uff0e\u3002\u2025\u2026\u00b7]"); - private static final Pattern COLOR_CODES = - Pattern.compile( - "\u00a7[0-9a-fk-orA-FK-OR]"); - - private static final Set FILE_EXT; - static { - Set s = new HashSet<>( - Arrays.asList( - "txt", "yml", "yaml", - "json", "xml", "java", - "class", "jar", "png", - "jpg", "jpeg", "gif", - "bmp", "svg", "ico", - "webp", "mp3", "mp4", - "avi", "mkv", "wav", - "flac", "ogg", "zip", - "rar", "7z", "tar", - "gz", "cfg", "conf", - "ini", "log", "db", - "sql", "sh", "bat", - "html", "htm", "css", - "js", "md", "pdf", - "dll", "so", "exe", - "bin", "c", "cpp", - "py", "rb", "go", - "rs", "kt", "lua", - "php", "asp", "jsp")); - FILE_EXT = Collections.unmodifiableSet(s); - } - - public ChatFilterManager(JavaPlugin plugin) { - this.plugin = plugin; - } - - // ========== 配置加载 ========== - - public void loadConfig() { - whitelistUrls.clear(); - whitelistPlayers.clear(); - punishmentRules.clear(); - punishmentDurations.clear(); - messages.clear(); - - File f = new File( - plugin.getDataFolder(), "chat.txt"); - if (!f.exists()) writeDefaultFile(f); - try (BufferedReader r = - new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets.UTF_8))) { - String section = ""; - String line; - while ((line = r.readLine()) != null) { - if (line.startsWith("\uFEFF")) - line = line.substring(1); - String t = line.trim(); - if (t.isEmpty() - || t.startsWith("#")) - continue; - if (t.equals("白名单") - || t.equals("白名单:")) { - section = "whitelist"; - continue; - } - if (t.equals("处罚规则") - || t.equals("处罚规则:")) { - section = "punishment"; - continue; - } - if (t.equals("白名单玩家") - || t.equals("白名单玩家:")) { - section = "players"; - continue; - } - if (t.contains(":")) { - String[] kv = t.split(":", 2); - String k = kv[0].trim(); - String v = kv[1].trim(); - if (k.equals("通知管理员")) { - notifyAdmin = parseBool(v); - continue; - } - if (k.equals("全服通报")) { - notifyAll = parseBool(v); - continue; - } - if (k.equals("禁言时长")) { - try { - muteDuration = - Integer.parseInt(v); - } catch (Exception ignored) { - } - continue; - } - } - switch (section) { - case "whitelist": - whitelistUrls.add( - t.toLowerCase()); - break; - case "punishment": - String[] parts = t.split(":"); - if (parts.length >= 2) { - try { - int cnt = - Integer.parseInt( - parts[0] - .trim()); - String type = - parts[1].trim() - .toLowerCase(); - int dur = - parts.length >= 3 - ? Integer.parseInt( - parts[2] - .trim()) - : muteDuration; - punishmentRules.put( - cnt, type); - punishmentDurations.put( - cnt, dur); - } catch (Exception ignored) { - } - } - break; - case "players": - whitelistPlayers.add(t); - break; - } - } - } catch (Exception e) { - plugin.getLogger().severe( - "[Sdf1_chat] 配置加载失败: " - + e.getMessage()); - } - - // 从消息.txt读取chat_开头的消息 - loadMessages(); - } - - private void loadMessages() { - File msgFile = new File( - plugin.getDataFolder(), - "消息.txt"); - if (!msgFile.exists()) return; - try (BufferedReader mr = - new BufferedReader( - new InputStreamReader( - new FileInputStream( - msgFile), - StandardCharsets.UTF_8))) { - String mline; - while ((mline = mr.readLine()) != null) { - mline = mline.trim(); - if (mline.isEmpty() - || mline.startsWith("#")) - continue; - // 支持 = 和 : 两种分隔符 - int eqIdx = mline.indexOf('='); - int coIdx = mline.indexOf(':'); - int splitIdx = -1; - if (eqIdx >= 0 && coIdx >= 0) { - splitIdx = Math.min(eqIdx, coIdx); - } else if (eqIdx >= 0) { - splitIdx = eqIdx; - } else if (coIdx >= 0) { - splitIdx = coIdx; - } - if (splitIdx < 0) continue; - String k = mline.substring(0, - splitIdx).trim(); - String v = mline.substring( - splitIdx + 1).trim(); - if (k.startsWith("chat_")) { - messages.put(k, v); - } - } - } catch (Exception ignored) { - } - } - - - private void writeDefaultFile(File f) { - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8))) { - pw.println("# Sdf1_chat 聊天过滤配置"); - pw.println("通知管理员: true"); - pw.println("全服通报: false"); - pw.println("禁言时长: 300"); - pw.println(); - pw.println("白名单:"); - pw.println("*.minecraft.net"); - pw.println("baidu.com"); - pw.println("github.com"); - pw.println(); - pw.println("处罚规则:"); - pw.println("1:warn:0"); - pw.println("2:warn:0"); - pw.println("3:mute:300"); - pw.println("5:mute:600"); - pw.println("8:kick:0"); - pw.println("10:ban:0"); - pw.println(); - pw.println("白名单玩家:"); - } catch (IOException ignored) { - } - } - - public void saveConfig() { - File f = new File( - plugin.getDataFolder(), "chat.txt"); - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8))) { - pw.println("# Sdf1_chat 聊天过滤配置"); - pw.println("通知管理员: " + notifyAdmin); - pw.println("全服通报: " + notifyAll); - pw.println("禁言时长: " + muteDuration); - pw.println(); - pw.println("白名单:"); - for (String u : whitelistUrls) - pw.println(u); - pw.println(); - pw.println("处罚规则:"); - for (Map.Entry entry - : punishmentRules.entrySet()) { - int dur = punishmentDurations - .getOrDefault(entry.getKey(), - muteDuration); - pw.println(entry.getKey() + ":" - + entry.getValue() - + ":" + dur); - } - pw.println(); - pw.println("白名单玩家:"); - for (String p : whitelistPlayers) - pw.println(p); - } catch (IOException e) { - plugin.getLogger().severe( - "[Sdf1_chat] 保存失败: " - + e.getMessage()); - } - } - - // ========== 消息 ========== - - public String msg(String key, - String... args) { - String template = messages.getOrDefault( - key, key); - for (int i = 0; i < args.length - 1; - i += 2) { - template = template.replace( - "{" + args[i] + "}", - args[i + 1]); - } - return template; - } - - // ========== URL检测 ========== - - private String normalizeMessage(String msg) { - msg = COLOR_CODES.matcher(msg) - .replaceAll(""); - msg = DOT_VARIANTS.matcher(msg) - .replaceAll("."); - return msg; - } - - public List extractUrls( - String message) { - String normalized = - normalizeMessage(message); - List urls = new ArrayList<>(); - Matcher m = URL_PATTERN - .matcher(normalized); - while (m.find()) { - String url = m.group().toLowerCase(); - url = url.replaceAll( - "[\\s.,;!?,。;!?、)》」』】" - + "\\]\\[\\((]+$", - ""); - if (!url.isEmpty()) urls.add(url); - } - return urls; - } - - public boolean isWhitelisted(String url) { - String clean = url - .replaceAll("^https?://", "") - .toLowerCase(); - int colon = clean.indexOf(':'); - int slash = clean.indexOf('/'); - if (colon > 0 && (slash < 0 - || colon < slash)) { - clean = clean.substring(0, colon); - } - slash = clean.indexOf('/'); - if (slash > 0) { - clean = clean.substring(0, slash); - } - for (String entry : whitelistUrls) { - String e = entry.toLowerCase().trim(); - if (e.startsWith("*.")) { - String suffix = e.substring(1); - if (clean.endsWith(suffix) - || clean.equals( - suffix.substring(1))) - return true; - } else { - if (clean.equals(e) - || clean.endsWith( - "." + e)) - return true; - } - } - return false; - } - - public boolean isLikelyDomain(String url) { - int lastDot = url.lastIndexOf('.'); - if (lastDot < 0 - || lastDot >= url.length() - 1) - return false; - String ext = url.substring(lastDot + 1) - .toLowerCase(); - return !FILE_EXT.contains(ext); - } - - // ========== 玩家操作 ========== - - public boolean isMuted(String name) { - Long expiry = mutedPlayers.get(name); - if (expiry == null) return false; - if (System.currentTimeMillis() - >= expiry) { - mutedPlayers.remove(name); - return false; - } - return true; - } - - public String fmtDuration(int seconds) { - if (seconds <= 0) return "0秒"; - int day = seconds / 86400; - int hr = (seconds % 86400) / 3600; - int min = (seconds % 3600) / 60; - int sec = seconds % 60; - StringBuilder sb = new StringBuilder(); - if (day > 0) sb.append(day).append("天"); - if (hr > 0) - sb.append(hr).append("小时"); - if (min > 0) - sb.append(min).append("分"); - if (sec > 0 || sb.length() == 0) - sb.append(sec).append("秒"); - return sb.toString(); - } - - public void applyPunishment(Player player, - int violation) { - String type = "warn"; - int duration = muteDuration; - int maxRule = 0; - for (Map.Entry entry - : punishmentRules.entrySet()) { - if (violation >= entry.getKey() - && entry.getKey() > maxRule) { - maxRule = entry.getKey(); - type = entry.getValue(); - duration = punishmentDurations - .getOrDefault( - entry.getKey(), - muteDuration); - } - } - if (type.equals("mute")) { - if (isMuted(player.getName())) - return; - mutedPlayers.put(player.getName(), - System.currentTimeMillis() - + (long) duration - * 1000L); - } - final String fType = type; - final int fDur = duration; - final int fV = violation; - final String fName = player.getName(); - Bukkit.getScheduler().runTask(plugin, - () -> { - Player p = Bukkit.getPlayer(fName); - if (p == null) return; - switch (fType) { - case "warn": - // 警告不处罚,只提示 - break; - case "mute": - p.sendMessage( - msg("chat_muted")); - break; - case "kick": - p.kickPlayer( - msg("chat_muted")); - break; - case "ban": - Bukkit.dispatchCommand( - Bukkit.getConsoleSender(), - "ban " + fName - + " §c多次发送违规链接"); - break; - case "banip": - InetSocketAddress addr = - p.getAddress(); - if (addr != null) { - String ip = addr - .getAddress() - .getHostAddress(); - Bukkit.dispatchCommand( - Bukkit.getConsoleSender(), - "ban-ip " + ip - + " §c发送违规链接"); - } - break; - } - }); - } - - public void unmutePlayer(String name) { - mutedPlayers.remove(name); - } - - public void resetPlayer(String name) { - mutedPlayers.remove(name); - violationCount.remove(name); - } - - public boolean isPlayerWhitelisted( - String name) { - return whitelistPlayers.contains(name); - } - - public boolean isNotifyAdmin() { - return notifyAdmin; - } - - public boolean isNotifyAll() { - return notifyAll; - } - - public int getViolationCount(String name) { - return violationCount - .getOrDefault(name, 0); - } - - public void incrementViolation( - String name) { - violationCount.put(name, - getViolationCount(name) + 1); - } - - public List getWhitelistUrls() { - return whitelistUrls; - } - - public Set getWhitelistPlayers() { - return whitelistPlayers; - } - - public Map - getPunishmentRules() { - return punishmentRules; - } - - public Map - getPunishmentDurations() { - return punishmentDurations; - } - - public Map - getMutedPlayers() { - return mutedPlayers; - } - - public void setNotifyAdmin(boolean v) { - notifyAdmin = v; - } - - public void setNotifyAll(boolean v) { - notifyAll = v; - } - - public void setMuteDuration(int v) { - muteDuration = v; - } - - public void addUrl(String url) { - whitelistUrls.add( - url.toLowerCase().trim()); - saveConfig(); - } - - public boolean removeUrl(String url) { - boolean r = whitelistUrls.remove( - url.toLowerCase().trim()); - saveConfig(); - return r; - } - - public void addWhitelistPlayer( - String name) { - whitelistPlayers.add(name); - saveConfig(); - } - - public boolean removeWhitelistPlayer( - String name) { - boolean r = - whitelistPlayers.remove(name); - saveConfig(); - return r; - } - - public void cleanExpired() { - long now = System.currentTimeMillis(); - mutedPlayers.entrySet().removeIf( - e -> e.getValue() <= now); - } - - private boolean parseBool(String v) { - String s = v.toLowerCase().trim(); - return s.equals("true") - || s.equals("on") - || s.equals("yes") - || s.equals("1") - || s.contains("开启") - || s.contains("启用") - || s.contains("是"); - } -} diff --git a/txt/ChatInputManager.txt b/txt/ChatInputManager.txt deleted file mode 100644 index e564b28..0000000 --- a/txt/ChatInputManager.txt +++ /dev/null @@ -1,823 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.util.HashMap; -import java.util.Map; - -public class ChatInputManager { - - public enum InputType { - NONE, - CHANGE_PWD_STEP1, - CHANGE_PWD_STEP2, - SET_EMAIL, - SET_EMAIL_VERIFY, - ADMIN_AUTH, - ADMIN_SET_POINTS, - ADMIN_SET_PWD, - ADMIN_DELETE_CONFIRM, - ADMIN_SET_AFK_TIME, - SMTP_CONFIG, - SMTP_VERIFY, - BACK_CHECKIN, - INVITE_INPUT_CODE, - TICKET_TITLE, - TICKET_DESC, - TICKET_SCORE, - TICKET_REJECT, - TICKET_ASSIGN, - TICKET_INVITE_PROVIDER, - TICKET_REMOVE_PROVIDER, - TICKET_COMPLETE, - TICKET_REPORT_ISSUE, - TICKET_REPLY, - TICKET_ADMIN_TYPE, - TICKET_ADMIN_TITLE, - TICKET_ADMIN_DESC, - TICKET_ADMIN_PROVIDER - } - - public static class PlayerState { - public InputType type = InputType.NONE; - public String targetPlayer = ""; - public String ticketType = ""; - public String ticketTitle = ""; - public String ticketDesc = ""; - public int ticketId = 0; - public int smtpStep = 0; - public int scoreValue = 0; - } - - private final Map states = - new HashMap<>(); - private Main plugin; - - public PlayerState getState(Player p) { - return states.computeIfAbsent( - p.getName(), - k -> new PlayerState()); - } - - public void reset(Player p) { - states.remove(p.getName()); - } - - public void handleInput(Player p, String msg, - Main mainPlugin) { - if (this.plugin == null) - this.plugin = mainPlugin; - PlayerState state = getState(p); - ConfigManager config = - mainPlugin.getConfig2(); - DatabaseManager db = mainPlugin.getDb(); - - switch (state.type) { - - case CHANGE_PWD_STEP1: { - String name = p.getName(); - String salt = (String) db.getField( - name, "password_salt"); - String hash = - PasswordUtils.hash(msg, salt); - boolean mainOk = - db.checkPassword(name, hash); - if (mainOk) { - state.type = - InputType.CHANGE_PWD_STEP2; - state.ticketTitle = "main"; - p.sendMessage( - "§e请输入新密码:"); - return; - } - boolean tempOk = - db.checkPasswordOrTemp( - name, hash); - if (tempOk) { - state.type = - InputType.CHANGE_PWD_STEP2; - state.ticketTitle = "temp"; - p.sendMessage( - "§e请输入新密码:"); - return; - } - p.sendMessage(config.msg( - "password_wrong")); - return; - } - - case CHANGE_PWD_STEP2: { - String name = p.getName(); - if (!PasswordUtils.validate(msg)) { - p.sendMessage(config.msg( - "password_format_error")); - reset(p); - return; - } - String oldHash = - (String) db.getField(name, - "password_hash"); - String oldSalt = - (String) db.getField(name, - "password_salt"); - String newSalt = - PasswordUtils.generateSalt(); - String newHash = - PasswordUtils.hash( - msg, newSalt); - if (newHash.equals(oldHash)) { - p.sendMessage(config.msg( - "password_same")); - reset(p); - return; - } - mainPlugin.recordPasswordChange( - name, oldHash, oldSalt); - db.setField(name, - "password_hash", newHash); - db.setField(name, - "password_salt", newSalt); - mainPlugin - .getNeedsPasswordChange() - .remove(name); - p.sendMessage(config.msg( - "password_changed")); - reset(p); - return; - } - - case SET_EMAIL: { - if (!msg.contains("@") - || !msg.contains(".")) { - p.sendMessage(config.msg( - "email_format_error")); - reset(p); - return; - } - String oldEmail = (String) - db.getField(p.getName(), - "email"); - String emailAddr = msg.trim(); - // 已绑定邮箱:验证登录密码 - if (oldEmail != null - && !oldEmail.isEmpty()) { - state.targetPlayer = emailAddr; - state.type = - InputType.SET_EMAIL_VERIFY; - state.ticketTitle = - "need_pwd"; - p.sendMessage( - "§e您已绑定邮箱: " - + oldEmail); - p.sendMessage( - "§e请验证登录密码以确认修改:"); - return; - } - // 未绑定邮箱:直接发验证码 - String code = String.valueOf( - (int) (Math.random() - * 900000 - + 100000)); - state.targetPlayer = emailAddr; - state.ticketTitle = code; - state.type = - InputType.SET_EMAIL_VERIFY; - final String to = emailAddr; - final String c = code; - final Player fp = p; - Bukkit.getScheduler() - .runTaskAsynchronously( - mainPlugin, () -> { - boolean sent = mainPlugin - .getEmail() - .sendVerifyCode( - to, c); - Bukkit.getScheduler() - .runTask(mainPlugin, - () -> { - if (sent) { - fp.sendMessage( - "§a验证码已发送到 " - + to); - fp.sendMessage( - "§e输入6位验证码(0取消):"); - } else { - fp.sendMessage( - "§c发送失败"); - reset(fp); - } - }); - }); - return; - } - - - case SET_EMAIL_VERIFY: { - if ("0".equals(msg.trim()) - || "取消".equals( - msg.trim())) { - p.sendMessage( - "§c邮箱设置已取消"); - reset(p); - return; - } - String expected = - state.ticketTitle; - if (msg.trim().equals(expected)) { - db.setField(p.getName(), - "email", - state.targetPlayer); - p.sendMessage( - "§a邮箱已设置: " - + state.targetPlayer); - reset(p); - } else { - p.sendMessage( - "§c验证码错误(0取消):"); - } - return; - } - - case ADMIN_AUTH: { - if (msg.equals( - config.adminPassword)) { - Bukkit.getScheduler().runTask( - mainPlugin, () -> - mainPlugin - .getGui() - .openAdmin(p)); - } else { - p.sendMessage( - "§c管理密码错误"); - } - reset(p); - return; - } - - case ADMIN_SET_POINTS: { - try { - int pts = Integer.parseInt( - msg.trim()); - db.setField( - state.targetPlayer, - "points", pts); - p.sendMessage("§a已将 " - + state.targetPlayer - + " 积分设为 " + pts); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c请输入有效数字"); - } - String tgt = - state.targetPlayer; - Bukkit.getScheduler().runTask( - mainPlugin, () -> - mainPlugin - .getGui() - .openUserDetail( - p, tgt)); - reset(p); - return; - } - - case ADMIN_SET_PWD: { - if (!PasswordUtils.validate(msg)) { - p.sendMessage(config.msg( - "password_format_error")); - reset(p); - return; - } - String tgt = state.targetPlayer; - String salt = - PasswordUtils - .generateSalt(); - String hash = - PasswordUtils.hash( - msg, salt); - db.setField(tgt, - "temp_password", hash); - db.setField(tgt, "temp_pw_expire", - System.currentTimeMillis() - + 300000L); - db.setField(tgt, - "temp_pw_used", 0); - p.sendMessage("§a已为 " + tgt - + " 设置临时密码"); - Player tp = - Bukkit.getPlayer(tgt); - if (tp != null && tp.isOnline()) - tp.kickPlayer( - "§c管理员已设置临时密码," - + "请使用临时密码登录"); - // 邮件通知 - String emailAddr = (String) - db.getField(tgt, "email"); - if (emailAddr != null - && !emailAddr.isEmpty()) { - String time = - new java.text.SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss") - .format(new java.util.Date()); - String body = "玩家 " + tgt - + ":\n您的密码已于 " - + time - + " 被管理员重置为临时密码。\n" - + "如果是本人操作请忽略。\n" - + "如非本人操作请立即联系管理员。"; - final String to = emailAddr; - final String fBody = body; - final String fName = tgt; - Bukkit.getScheduler() - .runTaskAsynchronously( - mainPlugin, () -> - mainPlugin.getEmail() - .sendTempPassword( - to, - fName, - fBody)); - } - reset(p); - return; - } - - case ADMIN_DELETE_CONFIRM: { - if (msg.equals( - state.targetPlayer)) { - db.deleteUser( - state.targetPlayer); - p.sendMessage("§a已删除 " - + state.targetPlayer); - Player tgt = - Bukkit.getPlayer( - state.targetPlayer); - if (tgt != null - && tgt.isOnline()) - tgt.kickPlayer( - "§c账号已被删除"); - } else { - p.sendMessage( - "§c输入不匹配,已取消"); - } - reset(p); - return; - } - - case ADMIN_SET_AFK_TIME: { - try { - int minutes = - Integer.parseInt( - msg.trim()); - config.afkTimeout = - minutes * 60; - config.saveSettings(); - p.sendMessage( - "§a挂机时长已设为 " - + minutes - + " 分钟"); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c请输入有效数字"); - } - reset(p); - return; - } - - case SMTP_CONFIG: { - String m = msg.trim(); - if ("000".equals(m) - || "取消取消取消" - .equals(m)) { - p.sendMessage( - "§cSMTP配置已取消"); - reset(p); - return; - } - if ("取消".equals(m)) { - p.sendMessage( - "§cSMTP配置已取消"); - reset(p); - return; - } - int step = state.smtpStep; - if (step <= 1) { - if (!"0".equals(m)) - config.setSmtp( - "smtp地址", m); - state.smtpStep = 2; - p.sendMessage("§e当前端口: " - + config.getSmtp( - "smtp端口")); - p.sendMessage( - "§e输入端口(0跳过):"); - } else if (step == 2) { - if (!"0".equals(m)) - config.setSmtp( - "smtp端口", m); - state.smtpStep = 3; - p.sendMessage( - "§e输入发件邮箱:"); - } else if (step == 3) { - if (!"0".equals(m)) - config.setSmtp( - "smtp账号", m); - state.smtpStep = 4; - p.sendMessage( - "§e输入SMTP密码(0跳过):"); - } else if (step == 4) { - if (!"0".equals(m)) - config.setSmtp( - "smtp密码", m); - state.smtpStep = 5; - p.sendMessage( - "§e输入接收邮箱(留空发到发件邮箱):"); - } else if (step == 5) { - String recv = m; - if (recv.isEmpty() - || "0".equals(recv)) - recv = config.getSmtp( - "smtp账号"); - config.setSmtp( - "验证码接收邮箱", recv); - if (!config - .canSendSmtpVerify()) { - p.sendMessage( - "§c发送间隔不足1分钟"); - reset(p); - return; - } - String code = config - .generateSmtpVerifyCode(); - String addr = config.getSmtp( - "smtp地址"); - String acct = config.getSmtp( - "smtp账号"); - if (acct.isEmpty() - || addr.isEmpty()) { - p.sendMessage( - "§c地址或账号为空"); - reset(p); - return; - } - config.recordSmtpSendTime(); - final String fRecv = recv; - final String fCode = code; - final Player fp = p; - Bukkit.getScheduler() - .runTaskAsynchronously( - mainPlugin, () -> { - Bukkit.getScheduler() - .runTask( - mainPlugin, - () -> fp.sendMessage( - "§e正在发送验证码...")); - boolean sent = - mainPlugin.getEmail() - .sendVerifyCode( - fRecv, - fCode); - final boolean fSent = sent; - Bukkit.getScheduler() - .runTask( - mainPlugin, - () -> { - if (fSent) { - state.type = - InputType - .SMTP_VERIFY; - state.smtpStep = 0; - fp.sendMessage( - "§a验证码已发送到 " - + fRecv); - fp.sendMessage( - "§e输入6位验证码(0取消):"); - } else { - fp.sendMessage( - "§c发送失败,查看控制台"); - reset(fp); - } - }); - }); - } - return; - } - - case SMTP_VERIFY: { - if ("0".equals(msg.trim()) - || "取消".equals( - msg.trim())) { - p.sendMessage( - "§cSMTP配置已取消"); - reset(p); - return; - } - if (config.checkSmtpVerifyCode( - msg.trim())) { - config.saveSmtp(); - config.clearSmtpVerifyCode(); - String addr = config.getSmtp( - "smtp地址"); - String port = config.getSmtp( - "smtp端口"); - String acct = config.getSmtp( - "smtp账号"); - String recv = config.getSmtp( - "验证码接收邮箱"); - boolean ssl = config.smtpSsl; - p.sendMessage("§a§l=== SMTP配置完成 ==="); - p.sendMessage("§7地址: §f" + addr); - p.sendMessage("§7端口: §f" + port); - p.sendMessage("§7账号: §f" + acct); - p.sendMessage("§7接收: §f" + recv); - p.sendMessage("§7加密: §f" + ssl); - p.sendMessage("§a§l===================="); - reset(p); - } else { - p.sendMessage( - "§c验证码错误(0取消):"); - } - return; - } - - case BACK_CHECKIN: { - int cost = 20; - if (!mainPlugin.getDb() - .deductPoints( - p.getName(), cost)) { - p.sendMessage( - "§c积分不足,补签需 " - + cost + " 积分"); - reset(p); - return; - } - p.sendMessage( - "§a补签成功!扣除 " - + cost + " 积分。" - + "日期: " + msg); - reset(p); - return; - } - - case INVITE_INPUT_CODE: { - String code = msg.trim(); - String myName = p.getName(); - String myCode = - (String) db.getField( - myName, - "invite_code"); - if (code.isEmpty()) { - p.sendMessage( - "§c邀请码不能为空"); - reset(p); - return; - } - if (code.equals(myCode)) { - p.sendMessage( - "§c不能用自己邀请码"); - reset(p); - return; - } - boolean found = false; - for (Map u : - db.getAllUsers()) { - String uc = (String) - u.getOrDefault( - "invite_code", ""); - if (code.equals(uc)) { - String inv = (String) - u.get("player_name"); - db.setField(myName, - "invited_by", inv); - db.addPoints(inv, 50); - p.sendMessage( - "§a邀请成功!邀请人: " - + inv); - Player ip = - Bukkit.getPlayer(inv); - if (ip != null - && ip.isOnline()) - ip.sendMessage("§a" - + myName - + " 使用了您的邀请码"); - found = true; - break; - } - } - if (!found) - p.sendMessage( - "§c邀请码不存在"); - reset(p); - return; - } - - case TICKET_TITLE: { - state.ticketTitle = msg; - state.type = - InputType.TICKET_DESC; - p.sendMessage( - "§e请输入工单详细描述:"); - return; - } - - case TICKET_DESC: { - String tt = state.ticketType; - String tl = state.ticketTitle; - reset(p); - mainPlugin.getTicket() - .submitTicket( - p, tt, tl, msg); - return; - } - - case TICKET_SCORE: { - int tid = state.ticketId; - try { - int sc = Integer.parseInt( - msg.trim()); - reset(p); - mainPlugin.getTicket() - .confirmAndScore( - p, tid, sc); - } catch ( - NumberFormatException e) { - reset(p); - p.sendMessage( - "§c请输入数字1-5"); - } - return; - } - - case TICKET_REJECT: { - int tid = state.ticketId; - reset(p); - mainPlugin.getTicket() - .rejectTicket( - p, tid, msg); - return; - } - - case TICKET_ASSIGN: { - int tid = state.ticketId; - String[] parts = msg.trim() - .split("\\s+"); - String prov = parts[0]; - int price = 0; - if (parts.length >= 2) { - try { - price = Integer.parseInt( - parts[1]); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c价格格式错误"); - } - } - reset(p); - mainPlugin.getTicket() - .assignToProvider( - p, tid, prov, - price); - final int fTid = tid; - Bukkit.getScheduler().runTask( - mainPlugin, () -> - mainPlugin - .getTicket() - .openDetail( - p, fTid)); - return; - } - - case TICKET_INVITE_PROVIDER: { - String tgt = msg.trim(); - reset(p); - if (!db.userExists(tgt)) { - p.sendMessage( - "§c玩家不存在"); - return; - } - db.addServiceProvider( - tgt, "waiter"); - p.sendMessage("§a已邀请 " + tgt - + " 成为服务商"); - return; - } - - case TICKET_REMOVE_PROVIDER: { - String tgt = msg.trim(); - reset(p); - if (!db.isServiceProvider(tgt)) { - p.sendMessage("§c" + tgt - + " 不是服务商"); - return; - } - db.removeServiceProvider(tgt); - p.sendMessage("§a已移除 " + tgt); - return; - } - - case TICKET_COMPLETE: { - try { - int tid = Integer.parseInt( - msg.trim()); - mainPlugin.getTicket() - .completeAsProvider( - p, tid); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c请输入有效工单号"); - } - reset(p); - return; - } - - case TICKET_REPORT_ISSUE: { - String report = msg.trim(); - reset(p); - p.sendMessage("§e异常已上报"); - String tag = config.adminTag; - for (Player op : - Bukkit.getOnlinePlayers()) { - if (op.getScoreboardTags() - .contains(tag)) { - op.sendMessage( - "§c§l[工单异常] §f" - + p.getName() - + ": " + report); - } - } - return; - } - - case TICKET_REPLY: { - int replyTid = state.ticketId; - reset(p); - mainPlugin.getTicket() - .replyTicket( - p, replyTid, msg); - return; - } - - case TICKET_ADMIN_TYPE: { - state.ticketType = msg.trim(); - state.type = - InputType - .TICKET_ADMIN_TITLE; - p.sendMessage( - "§e请输入工单标题:"); - return; - } - - case TICKET_ADMIN_TITLE: { - state.ticketTitle = msg; - state.type = - InputType - .TICKET_ADMIN_DESC; - p.sendMessage( - "§e请输入工单描述:"); - return; - } - - case TICKET_ADMIN_DESC: { - state.targetPlayer = msg; - state.type = - InputType - .TICKET_ADMIN_PROVIDER; - p.sendMessage( - "§e输入服务商名和价格:"); - p.sendMessage( - "§7例: playerA 20"); - return; - } - - case TICKET_ADMIN_PROVIDER: { - String[] parts2 = msg.trim() - .split("\\s+"); - String prov2 = parts2[0]; - int price2 = 0; - if (parts2.length >= 2) { - try { - price2 = Integer.parseInt( - parts2[1]); - } catch ( - NumberFormatException e) { - } - } - String t2 = state.ticketType; - String ti2 = state.ticketTitle; - String d2 = state.targetPlayer; - reset(p); - mainPlugin.getTicket() - .adminCreateAndAssign( - p, t2, ti2, - d2, prov2, price2); - return; - } - - default: - reset(p); - } - } -} diff --git a/txt/CheckInManager.txt b/txt/CheckInManager.txt deleted file mode 100644 index 79641c6..0000000 --- a/txt/CheckInManager.txt +++ /dev/null @@ -1,104 +0,0 @@ -package Sdf1_login; - -import org.bukkit.entity.Player; - -import java.text.SimpleDateFormat; -import java.util.Date; - -public class CheckInManager { - - private final Main plugin; - - public CheckInManager(Main plugin) { - this.plugin = plugin; - } - - public boolean isCheckedInToday(String name) { - String last = (String) plugin.getDb() - .getField(name, "last_checkin_date"); - String today = new SimpleDateFormat("yyyy-MM-dd") - .format(new Date()); - return today.equals(last); - } - - public String checkIn(Player p) { - String name = p.getName(); - if (isCheckedInToday(name)) { - return plugin.getConfig2().msg("checkin_already"); - } - - int streak = ((Number) plugin.getDb() - .getField(name, "checkin_streak")).intValue(); - 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()) { - try { - java.util.Date lastD = new SimpleDateFormat( - "yyyy-MM-dd").parse(lastDate); - java.util.Date todayD = new SimpleDateFormat( - "yyyy-MM-dd").parse(today); - long diff = todayD.getTime() - lastD.getTime(); - if (diff > 86400000L * 2) { - streak = 0; // 断签 - } else if (diff > 86400000L) { - streak++; // 连续 - } - } catch (Exception ignored) {} - } - - if (streak == 0) streak = 1; - else streak++; - - double multi = 1.0; - if (streak >= 21) multi = 1.5; - else if (streak >= 14) multi = 1.3; - else if (streak >= 7) multi = 1.1; - - int points = (int) (1 * multi); - plugin.getDb().addPoints(name, points); - plugin.getDb().setField( - name, "checkin_streak", streak); - plugin.getDb().setField( - name, "last_checkin_date", today); - int total = ((Number) plugin.getDb() - .getField(name, "total_checkin_days")) - .intValue(); - plugin.getDb().setField( - name, "total_checkin_days", total + 1); - - return plugin.getConfig2().msg("checkin_success", - "points", String.valueOf(points), - "streak", String.valueOf(streak), - "multi", String.valueOf(multi)); - } - - public String backCheckIn(Player p, String dateStr) { - String name = p.getName(); - if (!plugin.getDb().deductPoints(name, 10)) { - return plugin.getConfig2().msg("backcheckin_no_point"); - } - try { - java.util.Date target = new SimpleDateFormat( - "yyyy-MM-dd").parse(dateStr); - java.util.Date now = new SimpleDateFormat( - "yyyy-MM-dd").parse( - new SimpleDateFormat("yyyy-MM-dd") - .format(new Date())); - long diff = now.getTime() - target.getTime(); - if (diff < 0 || diff > 3 * 86400000L) { - plugin.getDb().addPoints(name, 10); // 退还 - return plugin.getConfig2().msg( - "backcheckin_expired"); - } - plugin.getDb().addPoints(name, 1); // 补签也给1分 - return plugin.getConfig2().msg("backcheckin_success"); - } catch (Exception e) { - plugin.getDb().addPoints(name, 10); - return "§c[Sdf1_login] §f日期格式错误,请用 yyyy-MM-dd"; - } - } -} diff --git a/txt/ConfigManager.txt b/txt/ConfigManager.txt deleted file mode 100644 index ca90c9e..0000000 --- a/txt/ConfigManager.txt +++ /dev/null @@ -1,451 +0,0 @@ -package Sdf1_login; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.*; - -import static java.lang.Double.parseDouble; - -public class ConfigManager { - - private final File dataFolder; - private final Map messages = new LinkedHashMap<>(); - private final Map smtpSettings = new LinkedHashMap<>(); - - public String adminTag = "admin"; - public String adminPassword = "qweasd"; - public int maxAccountsPerIP = 3; - public String approvalMode = "manual"; - public int autoApproveDelayMinutes = 30; - public int loginTimeout = 60; - public boolean afkEnabled = false; - public int afkTimeout = 300; - public boolean smtpSsl = true; - public String smtpVerifyCode = ""; - public long smtpVerifyExpire = 0; - public long smtpLastSendTime = 0; - public String smtpConfiguringPlayer = ""; - - - public ConfigManager(File dataFolder) { - this.dataFolder = dataFolder; - } - - // ==================== 消息 ==================== - - public void loadMessages() { - messages.clear(); - File file = new File(dataFolder, "消息.txt"); - if (!file.exists()) createDefaultMessages(file); - loadMap(file, messages); - } - - public String msg(String key) { - String val = messages.getOrDefault(key, key); - val = val.replace("", "&l"); - val = val.replace("", "&r"); - val = val.replace("
", "\n"); - val = val.replace("\\n", "\n"); - // &颜色码 转换为 §颜色码 - val = val.replaceAll( - "&([0-9a-fk-orA-FK-OR])", "§$1"); - return val; - } - - - private void createDefaultMessages(File f) { - List L = new ArrayList<>(); - L.add("# =========================================="); - L.add("# Sdf1_login 消息配置文件"); - L.add("# =========================================="); - L.add("# 颜色代码使用 § 符号"); - L.add("# §0黑色 §1深蓝 §2深绿 §3深青"); - L.add("# §4深红 §5深紫 §6金色 §7灰色"); - L.add("# §8深灰 §9蓝色 §a绿色 §b浅蓝"); - L.add("# §c红色 §d粉色 §e黄色 §f白色"); - L.add("# §l粗体 §n下划线 §o斜体 §r重置"); - L.add("#"); - L.add("# 占位符用 {xxx} 表示,运行时自动替换"); - L.add("# 支持 粗体 标签"); - L.add("# 支持 \\n 或
换行"); - L.add("# 修改后执行 /sdf1_login reload 重载"); - L.add("# =========================================="); - L.add(""); - - // --- 登录/注册 --- - L.add("# ---- 登录注册相关 ----"); - L.add("# login_timeout: 登录超时被踢出时的提示"); - L.add("login_timeout=§c登录超时,请重新登录"); - L.add("# not_registered: 未注册玩家收到的提示"); - L.add("not_registered=§e您尚未注册,请使用 /reg <密码>"); - L.add("# not_logged_in: 已注册但未登录的提示"); - L.add("not_logged_in=§e您尚未登录,请使用 /login <密码>"); - L.add("# already_registered: 已注册玩家再次注册时的提示"); - L.add("already_registered=§c您已注册过账号"); - L.add("# already_logged_in: 已登录玩家再次登录时的提示"); - L.add("already_logged_in=§c您已登录"); - L.add("# reg_success: 注册成功提示"); - L.add("reg_success=§a注册成功!欢迎来到服务器"); - L.add("# login_success: 登录成功提示"); - L.add("login_success=§a登录成功!"); - L.add("# login_failed: 密码错误提示"); - L.add("login_failed=§c密码错误,请重试"); - L.add(""); - - // --- 密码 --- - L.add("# ---- 密码相关 ----"); - L.add("# password_wrong: 旧密码验证失败"); - L.add("password_wrong=§c密码错误"); - L.add("# password_changed: 密码修改成功"); - L.add("password_changed=§a密码修改成功!"); - L.add("# password_format_error: 密码格式不符合要求"); - L.add("password_format_error=§c密码需6位以上,含大小写字母和数字"); - L.add("# password_same: 新密码与旧密码相同"); - L.add("password_same=§c新密码不能与旧密码相同"); - L.add("# reset_no_email: 找回密码时未绑定邮箱"); - L.add("reset_no_email=§c您未绑定邮箱,无法找回密码"); - L.add("# reset_sent: 临时密码已发送到邮箱"); - L.add("reset_sent=§a临时密码已发送到您的邮箱,请查收"); - L.add(""); - - // --- 签到积分 --- - L.add("# ---- 签到积分相关 ----"); - L.add("# checkin_already: 今日已签到"); - L.add("checkin_already=§c您今日已签到"); - L.add("# checkin_success: 签到成功,{points}=获得积分 {streak}=连续天数 {multi}=倍率"); - L.add("checkin_success=§a签到成功!获得 {points} 积分,连续 {streak} 天,倍率 x{multi}"); - L.add("# points_insufficient: 积分不足"); - L.add("points_insufficient=§c积分不足,当前: {points}"); - L.add("# points_purchase_success: 积分购买成功"); - L.add("points_purchase_success=§a购买成功!消耗 {count} 积分"); - L.add(""); - - // --- 挂机 --- - L.add("# ---- 挂机检测相关 ----"); - L.add("# afk_set_enabled: 挂机检测开启"); - L.add("afk_set_enabled=§a挂机检测已开启"); - L.add("# afk_set_disabled: 挂机检测关闭"); - L.add("afk_set_disabled=§c挂机检测已关闭"); - L.add("# afk_kick: 因挂机被踢出"); - L.add("afk_kick=§c因挂机过久被踢出"); - L.add(""); - - // --- 邮箱 --- - L.add("# ---- 邮箱相关 ----"); - L.add("# email_set: 邮箱设置成功"); - L.add("email_set=§a邮箱设置成功:{email}"); - L.add("# email_format_error: 邮箱格式错误"); - L.add("email_format_error=§c邮箱格式不正确"); - L.add(""); - - // --- 邀请 --- - L.add("# ---- 邀请相关 ----"); - L.add("# invite_code_generated: 邀请码生成"); - L.add("invite_code_generated=§a邀请码:{code}"); - L.add("# invite_used: 使用邀请码成功"); - L.add("invite_used=§a邀请成功,邀请人:{player}"); - L.add("# invite_self: 使用自己的邀请码"); - L.add("invite_self=§c不能使用自己的邀请码"); - L.add("# invite_not_found: 邀请码不存在"); - L.add("invite_not_found=§c邀请码不存在"); - L.add("# invite_referral_bonus: 被邀请人签到后邀请人获得奖励"); - L.add("invite_referral_bonus=§a邀请奖励!获得 {points} 积分"); - L.add(""); - - // --- 礼包 --- - L.add("# ---- 新人礼包相关 ----"); - L.add("# gift_not_ready: 礼包条件未满足"); - L.add("gift_not_ready=§c礼包条件未满足"); - L.add("# gift_claimed: 礼包领取成功"); - L.add("gift_claimed=§a礼包领取成功!"); - L.add(""); - - // --- 审批/IP --- - L.add("# ---- 审批与IP限制 ----"); - L.add("# need_approval: 注册需审批"); - L.add("need_approval=§e注册需审批 (工单#{id})"); - L.add("# ip_max_accounts: IP注册已达上限"); - L.add("ip_max_accounts=§cIP注册已达上限"); - L.add(""); - - // --- 管理员 --- - L.add("# ---- 管理员操作 ----"); - L.add("# admin_delete_confirm: 管理员删除账号确认提示"); - L.add("admin_delete_confirm=§c请在聊天中输入玩家名确认删除"); - L.add(""); - - // --- 聊天过滤 --- - L.add("# ---- 聊天过滤 ----"); - L.add("# chat_muted: 禁言玩家尝试聊天"); - L.add("chat_muted=§c§l[Sdf1_chat] §f你已被禁言,无法聊天"); - L.add("# chat_url_blocked: 发送违规链接被拦截"); - L.add("# {url}=检测到的链接地址"); - L.add("chat_url_blocked=§c§l[Sdf1_chat] §f检测到违规第三方链接: §e{url}"); - L.add("# chat_url_violation: 当前违规次数"); - L.add("# {count}=当前违规次数"); - L.add("chat_url_violation=§7当前违规次数: §e{count}"); - L.add("# chat_url_admin_notify: 通知管理员,{player}=玩家名 {url}=链接 {count}=次数"); - L.add("chat_url_admin_notify=§e§l[Sdf1_chat] §f{player} 发送违规链接: {url} (第{count}次)"); - L.add("# chat_url_broadcast: 全服通报,{player}=玩家名"); - L.add("chat_url_broadcast=§c§l[Sdf1_chat] §e{player} §f因发送违规链接被处罚"); - L.add(""); - - // --- 通用 --- - L.add("# ---- 通用 ----"); - L.add("# 未知参数: 未知命令或权限不足时的提示"); - L.add("未知参数=§c未知参数或权限不足"); - L.add("# only_player: 仅玩家可用的命令"); - L.add("# only_player=§c此命令仅玩家可用"); - - writeLines(f, L); - } - - - // ==================== SMTP ==================== - - public void loadSmtp() { - smtpSettings.clear(); - File file = new File(dataFolder, "SMTP设置.txt"); - if (!file.exists()) createDefaultSmtp(file); - loadMap(file, smtpSettings); - } - private double parseDouble(String s, double def) { - try { return Double.parseDouble(s.trim()); } - catch (Exception e) { return def; } - } - - public String getSmtp(String key) { - return smtpSettings.getOrDefault(key, ""); - } - /** 获取挂机白名单列表 */ - public List getAfkWhitelist() { - List list = new ArrayList<>(); - File file = new File(dataFolder, "挂机白名单.txt"); - if (!file.exists()) { - try { - file.createNewFile(); - try (BufferedWriter w = new BufferedWriter( - new OutputStreamWriter( - new FileOutputStream(file), - StandardCharsets.UTF_8))) { - w.write("# 每行一个玩家名,前面加 # 为注释"); - w.newLine(); - w.write("# 以下玩家不会被挂机踢出"); - w.newLine(); - } - } catch (IOException ignored) {} - } - try (BufferedReader r = new BufferedReader( - new InputStreamReader( - new FileInputStream(file), - StandardCharsets.UTF_8))) { - String line; - while ((line = r.readLine()) != null) { - line = line.trim(); - if (!line.isEmpty() && !line.startsWith("#")) { - list.add(line); - } - } - } catch (IOException ignored) {} - return list; - } - - /** 设置SMTP单项(聊天输入用) */ - public void setSmtp(String key, String value) { - smtpSettings.put(key, value); - } - - /** 将当前SMTP设置写回文件 */ - public void saveSmtp() { - File file = new File(dataFolder, "SMTP设置.txt"); - List L = new ArrayList<>(); - L.add("# ===== SMTP 邮件服务器配置 ====="); - for (Map.Entry e : smtpSettings.entrySet()) { - L.add(e.getKey() + "=" + e.getValue()); - } - writeLines(file, L); - } - - private void createDefaultSmtp(File f) { - List L = new ArrayList<>(); - L.add("# ===== SMTP 邮件服务器配置 ====="); - L.add("smtp地址=smtp.example.com"); - L.add("smtp端口=465"); - L.add("smtp账号=your_email@example.com"); - L.add("smtp密码="); - L.add("发件人名称=Sdf1_login"); - L.add("smtp加密=true"); - writeLines(f, L); - } - - // ==================== 插件设置 ==================== - - public void loadSettings() { - File file = new File(dataFolder, "插件设置.txt"); - if (!file.exists()) createDefaultSettings(file); - Map m = new LinkedHashMap<>(); - loadMap(file, m); - adminTag = m.getOrDefault("管理标签", adminTag); - adminPassword = m.getOrDefault("管理密码", adminPassword); - maxAccountsPerIP = parseInt( - m.getOrDefault("每IP最大账号数", "3"), 3); - approvalMode = m.getOrDefault("审批模式", approvalMode); - autoApproveDelayMinutes = parseInt( - m.getOrDefault("自动审批延迟分钟", "30"), 30); - loginTimeout = parseInt( - m.getOrDefault("登录超时秒数", "60"), 60); - afkEnabled = Boolean.parseBoolean( - m.getOrDefault("挂机踢出", "false")); - afkTimeout = parseInt( - m.getOrDefault("挂机超时秒数", "300"), 300); - smtpSsl = Boolean.parseBoolean( - m.getOrDefault("smtp加密", "true")); - providerPointsPerScore = parseDouble( - m.getOrDefault("服务商积分倍率", "2.0"), - 2.0); - baseEconomyReward = parseInt( - m.getOrDefault("基础经济奖励", "100"), - 100); - providerEconomyPerScore = parseDouble( - m.getOrDefault("经济奖励每评分", "50.0"), - 50.0); - requesterPointsReward = parseInt( - m.getOrDefault("报单人积分奖励", "10"), - 10); - - - } - - - public void saveSettings() { - File file = new File(dataFolder, "插件设置.txt"); - List L = new ArrayList<>(); - L.add("# ===== Sdf1_login 插件设置 ====="); - L.add("管理标签=" + adminTag); - L.add("管理密码=" + adminPassword); - L.add("每IP最大账号数=" + maxAccountsPerIP); - L.add("审批模式=" + approvalMode); - L.add("自动审批延迟分钟=" + autoApproveDelayMinutes); - L.add("登录超时秒数=" + loginTimeout); - L.add("挂机踢出=" + afkEnabled); - L.add("挂机超时秒数=" + afkTimeout); - // 垃圾站设置 - L.add("# ---- 垃圾站 ----"); - L.add("垃圾站_启用: " - + garbageEnabled); - L.add("垃圾站_清理间隔秒: " - + garbageInterval); - L.add("垃圾站_保留轮数: " - + garbageMaxRounds); - - writeLines(file, L); - } - public double providerPointsPerScore = 2.0; - public int baseEconomyReward = 100; - public double providerEconomyPerScore = 50.0; - public int requesterPointsReward = 10; - public boolean garbageEnabled = true; - public int garbageInterval = 300; - public int garbageMaxRounds = 1; - - - private void createDefaultSettings(File f) { - List L = new ArrayList<>(); - L.add("# ===== Sdf1_login 插件设置 ====="); - L.add("管理标签=admin"); - L.add("管理密码=qweasd"); - L.add("每IP最大账号数=3"); - L.add("审批模式=manual"); - L.add("自动审批延迟分钟=30"); - L.add("登录超时秒数=60"); - L.add("挂机踢出=false"); - L.add("挂机超时秒数=300"); - L.add("服务商积分倍率=" + providerPointsPerScore); - L.add("基础经济奖励=" + baseEconomyReward); - L.add("经济奖励每评分=" + providerEconomyPerScore); - L.add("报单人积分奖励=" + requesterPointsReward); - - writeLines(f, L); - } - - // ==================== 通用读写 ==================== - - private void loadMap(File file, Map map) { - try (BufferedReader r = new BufferedReader( - new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) { - String line; - while ((line = r.readLine()) != null) { - line = line.trim(); - if (line.isEmpty() || line.startsWith("#")) continue; - int eq = line.indexOf('='); - if (eq > 0) map.put(line.substring(0, eq).trim(), line.substring(eq + 1).trim()); - } - } catch (IOException e) { - System.err.println("[Sdf1_login] 读取失败: " + file.getName()); - } - } - - private void writeLines(File file, List lines) { - try (BufferedWriter w = new BufferedWriter( - new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) { - for (String l : lines) { w.write(l); w.newLine(); } - } catch (IOException e) { - System.err.println("[Sdf1_login] 写入失败: " + file.getName()); - } - } - - private int parseInt(String s, int def) { - try { return Integer.parseInt(s.trim()); } catch (Exception e) { return def; } - } - /** - * 生成6位验证码(字母+数字) - */ - public String generateSmtpVerifyCode() { - String chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - + "abcdefghijklmnopqrstuvwxyz" - + "0123456789"; - StringBuilder sb = new StringBuilder(); - java.util.Random r = new java.util.Random(); - for (int i = 0; i < 6; i++) { - sb.append(chars.charAt(r.nextInt( - chars.length()))); - } - smtpVerifyCode = sb.toString(); - smtpVerifyExpire = - System.currentTimeMillis() + 300000L; - return smtpVerifyCode; - } - - /** - * 检查验证码是否有效 - */ - public boolean checkSmtpVerifyCode(String code) { - if (System.currentTimeMillis() > smtpVerifyExpire) { - smtpVerifyCode = ""; - return false; - } - return smtpVerifyCode.equalsIgnoreCase(code); - } - - /** - * 清除验证码 - */ - public void clearSmtpVerifyCode() { - smtpVerifyCode = ""; - smtpVerifyExpire = 0; - } - - /** - * 检查发送间隔是否>=1分钟 - */ - public boolean canSendSmtpVerify() { - return System.currentTimeMillis() - - smtpLastSendTime >= 60000L; - } - - public void recordSmtpSendTime() { - smtpLastSendTime = System.currentTimeMillis(); - } - -} diff --git a/txt/DatabaseManager.txt b/txt/DatabaseManager.txt deleted file mode 100644 index 5fb6bf1..0000000 --- a/txt/DatabaseManager.txt +++ /dev/null @@ -1,1406 +0,0 @@ -package Sdf1_login; - -import java.io.File; -import java.sql.*; -import java.util.*; -import java.util.logging.Logger; - -public class DatabaseManager { - - private Connection db; - private final File dataFolder; - private final Logger logger; - - public DatabaseManager(File dataFolder) { - this.dataFolder = dataFolder; - this.logger = Logger.getLogger("Sdf1_login"); - } - - // ==================== 初始化 ==================== - - public void init() { - try { - Class.forName("org.sqlite.JDBC"); - File dbFile = new File(dataFolder, - "login.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 " - + "users (" - + "player_name TEXT PRIMARY KEY," - + "password_hash TEXT NOT NULL," - + "password_salt TEXT NOT NULL," - + "email TEXT DEFAULT ''," - + "register_time INTEGER DEFAULT 0," - + "last_login_time INTEGER DEFAULT 0," - + "total_online_time INTEGER DEFAULT 0," - + "last_online_check INTEGER DEFAULT 0," - + "is_logged_in INTEGER DEFAULT 0," - + "blocks_broken INTEGER DEFAULT 0," - + "blocks_placed INTEGER DEFAULT 0," - + "gift_stage INTEGER DEFAULT 0," - + "gift_claimed TEXT DEFAULT ''," - + "invite_code TEXT DEFAULT ''," - + "invited_by TEXT DEFAULT ''," - + "monthly_invite_count INTEGER DEFAULT 0," - + "invite_month TEXT DEFAULT ''," - + "points INTEGER DEFAULT 0," - + "checkin_streak INTEGER DEFAULT 0," - + "last_checkin_date TEXT DEFAULT ''," - + "total_checkin_days INTEGER DEFAULT 0," - + "temp_password TEXT DEFAULT ''," - + "temp_pw_expire INTEGER DEFAULT 0," - + "temp_pw_used INTEGER DEFAULT 0," - + "ip_address TEXT DEFAULT ''," - + "tasks_completed TEXT DEFAULT ''," - + "premium_uuid TEXT DEFAULT ''," - + "register_ip TEXT DEFAULT ''," - + "register_type TEXT DEFAULT 'manual'" - + ")"); - - // 安全添加缺失列 - safeAdd(st, "tasks_completed", - "TEXT DEFAULT ''"); - safeAdd(st, "temp_password", - "TEXT DEFAULT ''"); - safeAdd(st, "temp_pw_expire", - "INTEGER DEFAULT 0"); - safeAdd(st, "temp_pw_used", - "INTEGER DEFAULT 0"); - safeAdd(st, "ip_address", - "TEXT DEFAULT ''"); - safeAdd(st, "premium_uuid", - "TEXT DEFAULT ''"); - safeAdd(st, "register_ip", - "TEXT DEFAULT ''"); - safeAdd(st, "register_type", - "TEXT DEFAULT 'manual'"); - // 在 init 方法的 try 中添加 - // 如果表存在但没有save_time列,添加它 - try { - st.execute("ALTER TABLE " - + "inventory_backups " - + "ADD COLUMN save_time " - + "INTEGER DEFAULT 0"); - } catch (SQLException ignored) { - // 列已存在 - } - - // 背包备份表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "inventory_backups (" - + "player_name TEXT PRIMARY KEY," - + "inventory_data TEXT DEFAULT ''," - + "armor_data TEXT DEFAULT ''," - + "extra_data TEXT DEFAULT ''," - + "player_level INTEGER DEFAULT 0," - + "player_exp REAL DEFAULT 0.0)"); -// 确保 inventory_backups 表结构正确 - st.execute("CREATE TABLE IF NOT EXISTS " - + "inventory_backups (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "contents_b64 TEXT," - + "armor_b64 TEXT," - + "extra_b64 TEXT," - + "level INTEGER DEFAULT 0," - + "experience REAL DEFAULT 0," - + "save_time INTEGER DEFAULT 0" - + ")"); - // 安全报警表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "security_alerts (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "alert_ip TEXT NOT NULL," - + "alert_time INTEGER NOT NULL," - + "alert_date TEXT NOT NULL," - + "notified INTEGER DEFAULT 0," - + "notified_time INTEGER DEFAULT 0)"); - - // 工单表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "tickets (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "type TEXT NOT NULL," - + "status TEXT DEFAULT 'pending'," - + "priority TEXT DEFAULT 'normal'," - + "requester TEXT NOT NULL," - + "assigned_to TEXT DEFAULT ''," - + "title TEXT DEFAULT ''," - + "description TEXT DEFAULT ''," - + "reward_amount INTEGER DEFAULT 0," - + "score INTEGER DEFAULT 0," - + "target_player TEXT DEFAULT ''," - + "create_time INTEGER NOT NULL," - + "update_time INTEGER DEFAULT 0," - + "complete_time INTEGER DEFAULT 0," - + "admin_confirmed INTEGER DEFAULT 0," - + "reject_reason TEXT DEFAULT ''" - + ")"); - // 工单回复表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "ticket_replies (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "ticket_id INTEGER NOT NULL," - + "sender TEXT NOT NULL," - + "message TEXT NOT NULL," - + "create_time INTEGER NOT NULL" - + ")"); - - // 服务商表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "service_providers (" - + "player_name TEXT PRIMARY KEY," - + "role TEXT DEFAULT 'waiter'," - + "active INTEGER DEFAULT 1," - + "join_time INTEGER DEFAULT 0" - + ")"); - // 安全迁移 inventory_backups 表 - // 不删除旧数据,只补缺失列 - try { - st.execute("ALTER TABLE " - + "inventory_backups " - + "ADD COLUMN " - + "save_time INTEGER " - + "DEFAULT 0"); - } catch (SQLException ignored) { - } - try { - st.execute("ALTER TABLE " - + "inventory_backups " - + "ADD COLUMN " - + "level INTEGER " - + "DEFAULT 0"); - } catch (SQLException ignored) { - } - try { - st.execute("ALTER TABLE " - + "inventory_backups " - + "ADD COLUMN " - + "experience REAL " - + "DEFAULT 0"); - } catch (SQLException ignored) { - } - try { - st.execute("ALTER TABLE " - + "inventory_backups " - + "ADD COLUMN " - + "armor_b64 TEXT " - + "DEFAULT ''"); - } catch (SQLException ignored) { - } - try { - st.execute("ALTER TABLE " - + "inventory_backups " - + "ADD COLUMN " - + "extra_b64 TEXT " - + "DEFAULT ''"); - } catch (SQLException ignored) { - } - try { - st.execute("ALTER TABLE " - + "inventory_backups " - + "ADD COLUMN " - + "contents_b64 TEXT " - + "DEFAULT ''"); - } catch (SQLException ignored) { - } - - - st.close(); - logger.info("[Sdf1_login] 数据库初始化完成"); - } catch (Exception e) { - throw new RuntimeException( - "[Sdf1_login] DB初始化失败: " - + e.getMessage(), e); - } - } - - private void safeAdd(Statement st, - String col, String def) { - try { - st.executeUpdate("ALTER TABLE users " - + "ADD COLUMN " + col + " " + def); - } catch (SQLException ignored) { - } - } - - public void close() { - try { - if (db != null && !db.isClosed()) - db.close(); - } catch (Exception ignored) { - } - } - - // ==================== 用户操作 ==================== - - public boolean userExists(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT 1 FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - boolean r = ps.executeQuery().next(); - ps.close(); - return r; - } catch (SQLException e) { - return false; - } - } - - public void createUser(String name, String hash, - String salt) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO users " - + "(player_name, " - + "password_hash, " - + "password_salt, " - + "register_time) " - + "VALUES (?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, hash); - ps.setString(3, salt); - ps.setLong(4, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - // ========== 临时密码管理 ========== - - public void clearTempPassword(String name) { - setField(name, "temp_password", null); - setField(name, "temp_pw_expire", 0); - setField(name, "temp_pw_used", 1); - } - - public void clearAllTempPasswords() { - try { - Statement st = db.createStatement(); - st.executeUpdate( - "UPDATE users SET " - + "temp_password=NULL," - + "temp_pw_expire=0," - + "temp_pw_used=1 " - + "WHERE temp_password " - + "IS NOT NULL"); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public String getPlayerIPByName( - String name) { - Object ip = getField(name, "ip"); - return ip != null - ? String.valueOf(ip) : null; - } - - public boolean checkPasswordOrTemp( - String name, String hash) { - // 先检查主密码 - try { - PreparedStatement ps = db.prepareStatement( - "SELECT password_hash " - + "FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String mainHash = - rs.getString("password_hash"); - rs.close(); - ps.close(); - if (hash.equals(mainHash)) - return true; - } else { - rs.close(); - ps.close(); - } - } catch (SQLException e) { - e.printStackTrace(); - } - // 再检查临时密码 - try { - PreparedStatement ps2 = db.prepareStatement( - "SELECT temp_password, " - + "temp_pw_expire, " - + "temp_pw_used " - + "FROM users " - + "WHERE player_name=?"); - ps2.setString(1, name); - ResultSet rs2 = ps2.executeQuery(); - if (rs2.next()) { - String tempHash = - rs2.getString( - "temp_password"); - long expire = - rs2.getLong( - "temp_pw_expire"); - int used = - rs2.getInt( - "temp_pw_used"); - rs2.close(); - ps2.close(); - if (tempHash != null - && !tempHash.isEmpty() - && hash.equals(tempHash) - && System.currentTimeMillis() - < expire - && used != 1) { - return true; - } - } else { - rs2.close(); - ps2.close(); - } - } catch (SQLException e) { - e.printStackTrace(); - } - return false; - } - - - public Map getUser(String name) { - Map r = - new LinkedHashMap<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - r.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return r; - } - - public void setField(String name, String field, - Object val) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE users SET " + field - + "=? " - + "WHERE player_name=?"); - if (val instanceof Integer) - ps.setInt(1, (Integer) val); - else if (val instanceof Long) - ps.setLong(1, (Long) val); - else - ps.setString(1, - String.valueOf(val)); - ps.setString(2, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public Object getField(String name, - String field) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT " + field - + " FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - Object val = rs.next() - ? rs.getObject(1) : null; - rs.close(); - ps.close(); - return val; - } catch (SQLException e) { - return null; - } - } - - public boolean checkPassword(String name, - String hash) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT password_hash " - + "FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String stored = - rs.getString("password_hash"); - rs.close(); - ps.close(); - return hash.equals(stored); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return false; - } - - - - public boolean isUsingTempPassword(String name) { - String h = (String) getField(name, - "temp_password"); - return h != null && !h.isEmpty(); - } - - public void setLoggedIn(String name, - boolean val) { - setField(name, "is_logged_in", - val ? 1 : 0); - } - - public void addPoints(String name, int amount) { - Object cur = getField(name, "points"); - int curVal = cur instanceof Number - ? ((Number) cur).intValue() : 0; - setField(name, "points", curVal + amount); - } - - public boolean deductPoints(String name, - int amount) { - Object cur = getField(name, "points"); - int curVal = cur instanceof Number - ? ((Number) cur).intValue() : 0; - if (curVal < amount) return false; - setField(name, "points", - curVal - amount); - return true; - } - public List> - getAllUsers() { - List> list = - new ArrayList<>(); - try { - Statement st = db.createStatement(); - ResultSet rs = st.executeQuery( - "SELECT * FROM users"); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - public List> - getTicketsByProviderAll( - String provider) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE assigned_to=? " - + "ORDER BY create_time DESC"); - ps.setString(1, provider); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public void deleteUser(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void recordIP(String name, String ip) { - if (ip != null && !ip.isEmpty()) { - setField(name, "ip_address", ip); - } - } - - // ==================== IP查询 ==================== - - /** - * 统计指定IP注册的账号数量 - */ - public int countIPAccounts(String ip) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT COUNT(*) FROM users " - + "WHERE ip_address=?"); - ps.setString(1, ip); - ResultSet rs = ps.executeQuery(); - int count = 0; - if (rs.next()) count = rs.getInt(1); - rs.close(); - ps.close(); - return count; - } catch (SQLException e) { - return 0; - } - } - - /** - * 获取指定IP下所有账号详情 - */ - public List> - getAccountDetailsByIP(String ip) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT player_name, " - + "register_time " - + "FROM users " - + "WHERE ip_address=? " - + "ORDER BY register_time"); - ps.setString(1, ip); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("player_name", - rs.getString("player_name")); - row.put("register_time", - rs.getLong("register_time")); - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - // ==================== 背包备份 ==================== - - public void saveInventoryBackup(String name, - String contents, String armor, - String extra, int level, - double experience) { - try { - PreparedStatement del = - db.prepareStatement( - "DELETE FROM " - + "inventory_backups " - + "WHERE player_name=?"); - del.setString(1, name); - del.executeUpdate(); - del.close(); - PreparedStatement ps = - db.prepareStatement( - "INSERT INTO " - + "inventory_backups" - + " (player_name, " - + "contents_b64, " - + "armor_b64, " - + "extra_b64, " - + "level, " - + "experience, " - + "save_time) " - + "VALUES " - + "(?,?,?,?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, contents); - ps.setString(3, armor); - ps.setString(4, extra); - ps.setInt(5, level); - ps.setDouble(6, experience); - ps.setLong(7, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - - public String[] loadInventoryBackup( - String name) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT contents_b64, " - + "armor_b64, " - + "extra_b64, " - + "level, " - + "experience " - + "FROM " - + "inventory_backups " - + "WHERE player_name=? " - + "ORDER BY save_time " - + "DESC LIMIT 1"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String[] result = - new String[5]; - result[0] = rs.getString( - "contents_b64"); - result[1] = rs.getString( - "armor_b64"); - result[2] = rs.getString( - "extra_b64"); - result[3] = String.valueOf( - rs.getInt("level")); - result[4] = String.valueOf( - rs.getDouble("experience")); - rs.close(); - ps.close(); - return result; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - public void deleteInventoryBackup( - String name) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM inventory_backups " - + "WHERE player_name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public boolean hasInventoryBackup( - String name) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT id FROM " - + "inventory_backups " - + "WHERE player_name=? " - + "LIMIT 1"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - boolean has = rs.next(); - rs.close(); - ps.close(); - return has; - } catch (SQLException e) { - e.printStackTrace(); - return false; - } - } - - - // ==================== 安全报警 ==================== - - public void recordSecurityAlert( - String playerName, String ip) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO security_alerts " - + "(player_name, alert_ip, " - + "alert_time, alert_date) " - + "VALUES (?,?,?,?)"); - ps.setString(1, playerName); - ps.setString(2, ip); - long now = System.currentTimeMillis(); - ps.setLong(3, now); - ps.setString(4, - new java.text.SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss") - .format(new java.util.Date( - now))); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public List> - getUnnotifiedAlerts( - String playerName) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM security_alerts " - + "WHERE player_name=? " - + "AND notified=0 " - + "ORDER BY alert_time ASC"); - ps.setString(1, playerName); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public void markAlertNotified(int alertId) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE security_alerts " - + "SET notified=1, " - + "notified_time=? " - + "WHERE id=?"); - ps.setLong(1, - System.currentTimeMillis()); - ps.setInt(2, alertId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - - /** - * 获取玩家最近N条背包备份 - */ - public List> - getInventoryBackups(String name, - int limit) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT id, save_time " - + "FROM inventory_backups " - + "WHERE player_name=? " - + "ORDER BY save_time DESC " - + "LIMIT ?"); - ps.setString(1, name); - ps.setInt(2, limit); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("id", - rs.getInt("id")); - row.put("save_time", - rs.getLong("save_time")); - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - - /** - * 获取指定ID的备份数据 - */ - public String[] getInventoryBackupById( - int backupId) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT contents_b64, " - + "armor_b64, " - + "extra_b64, " - + "level, " - + "experience " - + "FROM inventory_backups " - + "WHERE id=?"); - ps.setInt(1, backupId); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String[] result = - new String[5]; - result[0] = rs.getString( - "contents_b64"); - result[1] = rs.getString( - "armor_b64"); - result[2] = rs.getString( - "extra_b64"); - result[3] = String.valueOf( - rs.getInt("level")); - result[4] = String.valueOf( - rs.getDouble("experience")); - rs.close(); - ps.close(); - return result; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - - // ==================== 工单操作 ==================== - - public int createTicket(String type, - String status, String priority, - String requester, String title, - String description, - int rewardAmount, - String targetPlayer) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO tickets " - + "(type, status, priority, " - + "requester, title, " - + "description, " - + "reward_amount, " - + "target_player, " - + "create_time) " - + "VALUES (?,?,?,?,?,?,?,?,?)"); - ps.setString(1, type); - ps.setString(2, status); - ps.setString(3, priority); - ps.setString(4, requester); - ps.setString(5, title); - ps.setString(6, description); - ps.setInt(7, rewardAmount); - ps.setString(8, targetPlayer); - ps.setLong(9, - System.currentTimeMillis()); - ps.executeUpdate(); - ResultSet rs = - ps.getGeneratedKeys(); - int id = -1; - if (rs.next()) id = rs.getInt(1); - rs.close(); - ps.close(); - return id; - } catch (SQLException e) { - e.printStackTrace(); - return -1; - } - } - - public void updateTicketStatus(int ticketId, - String status) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET status=?, " - + "update_time=? " - + "WHERE id=?"); - ps.setString(1, status); - ps.setLong(2, - System.currentTimeMillis()); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void assignTicket(int ticketId, - String provider) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " - + "assigned_to=?, " - + "status='processing', " - + "update_time=? " - + "WHERE id=?"); - ps.setString(1, provider); - ps.setLong(2, - System.currentTimeMillis()); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void completeTicket(int ticketId) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " - + "status='completed', " - + "complete_time=?, " - + "update_time=? " - + "WHERE id=?"); - long now = System.currentTimeMillis(); - ps.setLong(1, now); - ps.setLong(2, now); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void confirmTicket(int ticketId, - int score) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " - + "admin_confirmed=1, " - + "score=?, " - + "status='resolved', " - + "update_time=? " - + "WHERE id=?"); - ps.setInt(1, score); - ps.setLong(2, - System.currentTimeMillis()); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - - public void rejectTicketComplete(int ticketId, - String reason) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " - + "status='rejected', " - + "reject_reason=?, " - + "update_time=? " - + "WHERE id=?"); - ps.setString(1, reason); - ps.setLong(2, - System.currentTimeMillis()); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - /** - * 更新工单任意字段 - */ - public void updateTicketField(int ticketId, - String field, Object value) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " + field - + "=? WHERE id=?"); - if (value instanceof Integer) - ps.setInt(1, (Integer) value); - else if (value instanceof Long) - ps.setLong(1, (Long) value); - else - ps.setString(1, - String.valueOf(value)); - ps.setInt(2, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public Map getTicket( - int ticketId) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE id=?"); - ps.setInt(1, ticketId); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - rs.close(); - ps.close(); - return row; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - public List> - getTicketsByStatus(String status) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE status=? " - + "ORDER BY create_time DESC"); - ps.setString(1, status); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public void addTicketReply(int ticketId, - String sender, String message) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO ticket_replies " - + "(ticket_id, sender, " - + "message, create_time) " - + "VALUES (?,?,?,?)"); - ps.setInt(1, ticketId); - ps.setString(2, sender); - ps.setString(3, message); - ps.setLong(4, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - // 更新工单状态为已回复 - updateTicketStatus(ticketId, - "replied"); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public List> - getTicketReplies(int ticketId) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM ticket_replies " - + "WHERE ticket_id=? " - + "ORDER BY create_time ASC"); - ps.setInt(1, ticketId); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - /** - * 获取已回复但超时未互动的工单 - */ - public List> - getRepliedStaleTickets( - long timeoutMs) { - List> list = - new ArrayList<>(); - try { - long cutoff = - System.currentTimeMillis() - - timeoutMs; - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE status='replied' " - + "AND update_time < ?"); - ps.setLong(1, cutoff); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public List> - getTicketsByPlayer(String playerName) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE requester=? " - + "ORDER BY create_time DESC"); - ps.setString(1, playerName); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public List> - getTicketsByProvider(String provider) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE assigned_to=? " - + "AND (status='processing' " - + "OR status='replied') " - + "ORDER BY create_time DESC"); - ps.setString(1, provider); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public List> - getUnconfirmedCompleted( - long timeoutMs) { - List> list = - new ArrayList<>(); - try { - long cutoff = - System.currentTimeMillis() - - timeoutMs; - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE status='completed' " - + "AND admin_confirmed=0 " - + "AND complete_time < ?"); - ps.setLong(1, cutoff); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - // ==================== 服务商操作 ==================== - - public void addServiceProvider(String name, - String role) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT OR REPLACE INTO " - + "service_providers " - + "(player_name, role, " - + "active, join_time) " - + "VALUES (?,?,1,?)"); - ps.setString(1, name); - ps.setString(2, role); - ps.setLong(3, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void removeServiceProvider( - String name) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM service_providers " - + "WHERE player_name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public boolean isServiceProvider( - String name) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT 1 FROM service_providers " - + "WHERE player_name=? " - + "AND active=1"); - ps.setString(1, name); - boolean r = ps.executeQuery().next(); - ps.close(); - return r; - } catch (SQLException e) { - return false; - } - } - - public List> - getAllServiceProviders() { - List> list = - new ArrayList<>(); - try { - Statement st = db.createStatement(); - ResultSet rs = st.executeQuery( - "SELECT * FROM service_providers " - + "WHERE active=1"); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public Connection getDb() { - return db; - } -} diff --git a/txt/EmailManager.txt b/txt/EmailManager.txt deleted file mode 100644 index 3b37bb1..0000000 --- a/txt/EmailManager.txt +++ /dev/null @@ -1,210 +0,0 @@ -package Sdf1_login; - -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.net.Socket; -import java.util.Base64; -import java.util.logging.Logger; - -public class EmailManager { - - private final ConfigManager config; - private final Logger logger; - /** - * 发送SMTP验证码 - */ - public boolean sendVerifyCode(String to, String code) { - if (!isConfigured()) { - plugin.getLogger().severe( - "[Sdf1_login] SMTP未配置完整," - + "请检查SMTP设置.txt"); - return false; - } - String subject = - "[Sdf1_login] SMTP配置验证码"; - String body = "您的SMTP配置验证码是: " - + code + "\n" - + "验证码5分钟内有效。\n" - + "如非本人操作请忽略此邮件。"; - try { - return sendEmail(to, subject, body); - } catch (Exception e) { - plugin.getLogger().severe( - "[Sdf1_login] 邮件发送异常: " - + e.getClass().getSimpleName() - + ": " + e.getMessage()); - if (e.getCause() != null) { - plugin.getLogger().severe( - "[Sdf1_login] 根因: " - + e.getCause().getMessage()); - } - return false; - } - } - - - public EmailManager(ConfigManager config) { - this.config = config; - this.logger = Logger.getLogger("Sdf1_login"); - } - - /** - * 检查SMTP是否已配置 - */ - public boolean isConfigured() { - String host = config.getSmtp("smtp地址"); - String user = config.getSmtp("发件邮箱"); - String pass = config.getSmtp("授权码"); - return host != null && !host.isEmpty() - && user != null && !user.isEmpty() - && pass != null && !pass.isEmpty(); - } - - /** - * 发送验证码,返回是否成功发起 - */ - public boolean sendVerifyCode(String to, String player, String code) { - if (!isConfigured()) { - logger.warning("[Sdf1_login] SMTP未配置,无法发送验证码到 " + to); - return false; - } - logger.info("[Sdf1_login] 发送验证码到 " + to + " (玩家: " + player + ")"); - return sendEmailSync(to, "Sdf1_login 验证码", - "玩家 " + player + " 的验证码: " + code); - } - - /** - * 发送临时密码,返回是否成功发起 - */ - public boolean sendTempPassword(String to, String player, String tempPwd) { - if (!isConfigured()) { - logger.warning("[Sdf1_login] SMTP未配置,无法发送临时密码到 " + to); - return false; - } - logger.info("[Sdf1_login] 发送临时密码到 " + to + " (玩家: " + player + ")"); - return sendEmailSync(to, "Sdf1_login 临时密码", - "玩家 " + player + " 的临时密码: " + tempPwd + "\n请尽快登录并修改密码!"); - } - - private boolean sendEmailSync(String to, String subject, String body) { - final boolean[] result = {false}; - Thread t = new Thread(() -> { - try { - result[0] = sendEmail(to, subject, body); - } catch (Exception e) { - logger.severe("[Sdf1_login] 邮件线程异常: " + e.getMessage()); - } - }, "Sdf1_login-SMTP"); - t.setDaemon(true); - t.start(); - try { t.join(15000); } catch (InterruptedException ignored) {} - if (t.isAlive()) { - t.interrupt(); - logger.severe("[Sdf1_login] SMTP超时(15秒)"); - return false; - } - return result[0]; - } - - private boolean sendEmail(String to, String subject, String body) { - String host = config.getSmtp("smtp地址"); - String portStr = config.getSmtp("smtp端口"); - String user = config.getSmtp("发件邮箱"); - String pass = config.getSmtp("授权码"); - String fromName = config.getSmtp("发件人名"); - - int port; - try { port = Integer.parseInt(portStr); } catch (Exception e) { port = 465; } - - try { - String encSubject = "=?UTF-8?B?" + Base64.getEncoder().encodeToString(subject.getBytes("UTF-8")) + "?="; - String encBody = Base64.getEncoder().encodeToString(body.getBytes("UTF-8")); - - if (port == 465) { - SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault(); - SSLSocket socket = (SSLSocket) factory.createSocket(host, port); - socket.setSoTimeout(10000); - doSmtp(socket, host, user, pass, to, encSubject, encBody, fromName); - socket.close(); - } else { - Socket plainSocket = new Socket(host, port); - plainSocket.setSoTimeout(10000); - BufferedReader preReader = new BufferedReader(new InputStreamReader(plainSocket.getInputStream(), "UTF-8")); - PrintWriter preWriter = new PrintWriter(plainSocket.getOutputStream(), true); - checkResponse(preReader, "220"); - sendLine(preWriter, "EHLO " + host); - checkResponse(preReader, "250"); - sendLine(preWriter, "STARTTLS"); - checkResponse(preReader, "220"); - SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault(); - SSLSocket sslSocket = (SSLSocket) factory.createSocket(plainSocket, host, port, true); - sslSocket.startHandshake(); - doSmtp(sslSocket, host, user, pass, to, encSubject, encBody, fromName); - sslSocket.close(); - } - logger.info("[Sdf1_login] 邮件发送成功 -> " + to); - return true; - } catch (Exception e) { - logger.severe("[Sdf1_login] 邮件异常: " + e.getMessage()); - return false; - } - } - - private void doSmtp(SSLSocket socket, String host, String user, String pass, - String to, String subject, String body, String fromName) throws Exception { - BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8")); - OutputStream rawOut = socket.getOutputStream(); - PrintWriter writer = new PrintWriter(new java.io.BufferedOutputStream(rawOut), true); - - checkResponse(reader, "220"); - sendLine(writer, "EHLO " + host); - checkResponse(reader, "250"); - sendLine(writer, "AUTH LOGIN"); - checkResponse(reader, "334"); - sendLine(writer, Base64.getEncoder().encodeToString(user.getBytes("UTF-8"))); - checkResponse(reader, "334"); - sendLine(writer, Base64.getEncoder().encodeToString(pass.getBytes("UTF-8"))); - checkResponse(reader, "235"); - sendLine(writer, "MAIL FROM:<" + user + ">"); - checkResponse(reader, "250"); - sendLine(writer, "RCPT TO:<" + to + ">"); - checkResponse(reader, "250"); - sendLine(writer, "DATA"); - checkResponse(reader, "354"); - sendLine(writer, "From: " + fromName + " <" + user + ">"); - sendLine(writer, "To: <" + to + ">"); - sendLine(writer, "Reply-To: <" + user + ">"); - sendLine(writer, "Subject: " + subject); - sendLine(writer, "MIME-Version: 1.0"); - sendLine(writer, "Content-Type: text/plain; charset=UTF-8"); - sendLine(writer, "Content-Transfer-Encoding: base64"); - sendLine(writer, ""); - sendLine(writer, body); - sendLine(writer, "."); - checkResponse(reader, "250"); - sendLine(writer, "QUIT"); - } - - private void sendLine(PrintWriter writer, String line) { - writer.print(line + "\r\n"); - writer.flush(); - } - - private void checkResponse(BufferedReader reader, String expected) throws Exception { - StringBuilder sb = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line).append("\n"); - if (line.length() >= 4 && Character.isDigit(line.charAt(0)) && line.charAt(3) == ' ') break; - } - String resp = sb.toString().trim(); - if (!resp.startsWith(expected)) { - if (resp.startsWith("535")) logger.severe("[Sdf1_login] SMTP 535 认证失败"); - throw new Exception("SMTP响应异常: 期望 " + expected + ",实际: " + resp); - } - } -} diff --git a/txt/GUIManager.txt b/txt/GUIManager.txt deleted file mode 100644 index 6c60ac6..0000000 --- a/txt/GUIManager.txt +++ /dev/null @@ -1,861 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.*; -import org.bukkit.inventory.meta.ItemMeta; - -import java.util.*; - -public class GUIManager { - - private final Main plugin; - - public static final String T_MAIN = - "§6§l登录面板"; - public static final String T_MY_INFO = - "§a§l我的信息"; - public static final String T_ADMIN = - "§c§l管理员面板"; - public static final String T_USER_MGMT = - "§e§l用户管理"; - public static final String T_INVITE = - "§b§l邀请数据"; - public static final String T_TASK_CENTER = "§d§l任务中心"; - public static final String T_GIFT_STAGES = "§d§l新人任务"; - - - public GUIManager(Main plugin) { - this.plugin = plugin; - } - - public void openMain(Player p) { - Inventory g = Bukkit.createInventory( - null, 27, T_MAIN); - fillBg(g); - g.setItem(10, mkItem(Material.PLAYER_HEAD, - "§a§l我的信息")); - g.setItem(12, mkItem(Material.PAPER, - "§b§l邀请数据")); - g.setItem(14, mkItem(Material.EMERALD, - "§d§l积分商城")); - g.setItem(16, mkItem(Material.CHEST, - "§e§l任务中心")); - g.setItem(18, mkItem(Material.BOOK, - "§6工单系统", - "§7提交bug、求助、举报")); - if (p.isOp() || isAdmin(p)) { - g.setItem(20, mkItem(Material.CHEST, - "§6垃圾回收站", - "§7查看和取回清理的物品")); - } - if (isAdmin(p)) { - g.setItem(22, mkItem(Material.REDSTONE_BLOCK, - "§c§l管理员面板")); - } - p.openInventory(g); - } - - public void openMyInfo(Player p) { - Inventory g = Bukkit.createInventory( - null, 27, T_MY_INFO); - fillBg(g); - Map user = plugin.getDb() - .getUser(p.getName()); - boolean checkedIn = plugin.getCheckIn() - .isCheckedInToday(p.getName()); - Material checkMat = checkedIn - ? Material.EMERALD : Material.COAL; - String checkName = checkedIn - ? "§a已签到" : "§7点击签到"; - int streak = ((Number) user.getOrDefault( - "checkin_streak", 0)).intValue(); - int points = ((Number) user.getOrDefault( - "points", 0)).intValue(); - int totalDays = ((Number) user.getOrDefault( - "total_checkin_days", 0)).intValue(); - String email = (String) user.getOrDefault( - "email", ""); - g.setItem(4, mkItem(Material.NAME_TAG, - "§e" + p.getName())); - g.setItem(10, mkItem(checkMat, checkName, - "§7连续签到: " + streak + "天", - "§7累计签到: " + totalDays + "天", - "§7当前积分: " + points)); - g.setItem(11, mkItem(Material.PAPER, - "§e修改密码", "§7双击修改密码")); - g.setItem(12, mkItem(Material.CLOCK, - "§e补签", "§7消耗10积分补签3天内")); - g.setItem(13, mkItem(Material.ENDER_PEARL, - "§e邀请码", - "§7你的邀请码: " - + user.getOrDefault( - "invite_code", "无"), - "§7点击生成/查看")); - g.setItem(14, mkItem(Material.BOOK, - "§e邮箱: " - + (email.isEmpty() - ? "未绑定" : email), - "§7点击绑定邮箱")); - g.setItem(15, mkItem(Material.EMERALD_BLOCK, - "§a§l积分商城", - "§7当前: " + points + "积分")); - g.setItem(26, mkItem(Material.ARROW, "§7返回")); - p.openInventory(g); - } - - /** - * 打开账号删除选择面板 - * 展示该玩家IP下所有账号,供选择删除 - */ - public void openAccountDelete(Player p) { - String ip = plugin.getPlayerIP(p); - if (ip == null) { - p.sendMessage("§c无法获取你的IP地址"); - return; - } - - List> accounts = - plugin.getDb().getAccountDetailsByIP(ip); - - // 54格大箱子 - org.bukkit.inventory.Inventory g = - org.bukkit.Bukkit.createInventory( - null, 54, - "§c§l选择要删除的账号"); - - // 填充玻璃面板 - org.bukkit.inventory.ItemStack glass = - new org.bukkit.inventory.ItemStack( - org.bukkit.Material - .RED_STAINED_GLASS_PANE); - org.bukkit.inventory.meta.ItemMeta gm = - glass.getItemMeta(); - if (gm != null) { - gm.setDisplayName(" "); - glass.setItemMeta(gm); - } - for (int i = 0; i < 54; i++) { - g.setItem(i, glass); - } - - // 展示账号列表(从0号位开始放) - java.text.SimpleDateFormat sdf = - new java.text.SimpleDateFormat( - "yyyy-MM-dd HH:mm"); - int slot = 0; - for (Map acc : accounts) { - if (slot >= 45) break; - String accName = (String) acc - .get("player_name"); - long regTime = ((Number) acc - .get("register_time")).longValue(); - String dateStr = regTime > 0 - ? sdf.format( - new java.util.Date(regTime)) - : "未知"; - - // 当前登录的账号用不同颜色标记 - boolean isCurrent = - accName.equalsIgnoreCase( - p.getName()); - - org.bukkit.inventory.ItemStack item = - new org.bukkit.inventory.ItemStack( - isCurrent - ? org.bukkit.Material - .RED_WOOL - : org.bukkit.Material - .ORANGE_WOOL); - org.bukkit.inventory.meta.ItemMeta im = - item.getItemMeta(); - if (im != null) { - im.setDisplayName( - (isCurrent ? "§c" : "§e") - + accName); - im.setLore(java.util.Arrays.asList( - "§7注册时间: " + dateStr, - "§7IP: " + ip, - isCurrent - ? "§c§l(当前账号)" - : "§a点击选择删除此账号")); - item.setItemMeta(im); - } - g.setItem(slot, item); - slot++; - } - - // 49号位:返回 - org.bukkit.inventory.ItemStack back = - new org.bukkit.inventory.ItemStack( - org.bukkit.Material.ARROW); - org.bukkit.inventory.meta.ItemMeta backMeta = - back.getItemMeta(); - if (backMeta != null) { - backMeta.setDisplayName("§7返回"); - back.setItemMeta(backMeta); - } - g.setItem(49, back); - - p.openInventory(g); - } - - /** - * 打开删除确认面板 - */ - public void openDeleteConfirm(Player p, - String targetName) { - org.bukkit.inventory.Inventory g = - org.bukkit.Bukkit.createInventory( - null, 27, - "§c§l确认删除: " + targetName); - - org.bukkit.inventory.ItemStack glass = - new org.bukkit.inventory.ItemStack( - org.bukkit.Material - .GRAY_STAINED_GLASS_PANE); - org.bukkit.inventory.meta.ItemMeta gm = - glass.getItemMeta(); - if (gm != null) { - gm.setDisplayName(" "); - glass.setItemMeta(gm); - } - for (int i = 0; i < 27; i++) { - g.setItem(i, glass); - } - - // 11号位:确认删除 - org.bukkit.inventory.ItemStack confirm = - new org.bukkit.inventory.ItemStack( - org.bukkit.Material.LIME_WOOL); - org.bukkit.inventory.meta.ItemMeta cm = - confirm.getItemMeta(); - if (cm != null) { - cm.setDisplayName("§a§l确认删除"); - cm.setLore(java.util.Arrays.asList( - "§7删除账号: §c" + targetName, - "§c此操作不可撤销!")); - confirm.setItemMeta(cm); - } - g.setItem(11, confirm); - - // 15号位:取消 - org.bukkit.inventory.ItemStack cancel = - new org.bukkit.inventory.ItemStack( - org.bukkit.Material.RED_WOOL); - org.bukkit.inventory.meta.ItemMeta cml = - cancel.getItemMeta(); - if (cml != null) { - cml.setDisplayName("§c§l取消"); - cancel.setItemMeta(cml); - } - g.setItem(15, cancel); - - p.openInventory(g); - } - - public void openShopAdmin(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, "§c§l商城管理"); - ItemStack glass = new ItemStack( - Material.GRAY_STAINED_GLASS_PANE); - ItemMeta glm = glass.getItemMeta(); - if (glm != null) { - glm.setDisplayName(" "); - glass.setItemMeta(glm); - } - for (int i = 0; i < 54; i++) - g.setItem(i, glass); - g.setItem(49, mkItem(Material.ARROW, - "§7返回商城")); - g.setItem(50, mkItem(Material.EMERALD, - "§a添加商品", - "§7格式: /sdf1_login shopadd <名称> <价格> <命令>")); - g.setItem(51, mkItem(Material.BARRIER, - "§c删除商品", - "§7格式: /sdf1_login shopdel <序号>")); - p.openInventory(g); - } - - - public void openAdmin(Player p) { - Inventory g = Bukkit.createInventory( - null, 27, T_ADMIN); - fillBg(g); - g.setItem(4, mkItem(Material.NAME_TAG, - "§e§l管理员面板")); - g.setItem(10, mkItem(Material.REDSTONE, - "§6SMTP配置", - "§7配置邮件服务器")); - g.setItem(11, mkItem(Material.ARMOR_STAND, - "§a用户管理", - "§7管理玩家数据")); - g.setItem(12, mkItem(Material.PLAYER_HEAD, - "§a§l我的信息")); - g.setItem(14, mkItem(Material.EMERALD, - "§d§l积分商城")); - boolean afkOn = plugin.getConfig2().afkEnabled; - int afkMin = plugin.getConfig2().afkTimeout / 60; - g.setItem(16, mkItem( - afkOn ? Material.REDSTONE_TORCH - : Material.TORCH, - afkOn ? "§a§l挂机踢出: 开启" - : "§c§l挂机踢出: 关闭", - "§7左键: 切换开关", - "§7右键: 设置时长", - "§7当前: " + afkMin + "分钟")); - g.setItem(22, mkItem(Material.ARROW, "§7返回")); - p.openInventory(g); - } - - - public void openInvite(Player p) { - Inventory g = Bukkit.createInventory( - null, 27, T_INVITE); - fillBg(g); - String code = (String) plugin.getDb() - .getField(p.getName(), "invite_code"); - if (code == null || code.isEmpty()) { - code = plugin.getInvite().generateCode(p); - } - int monthly = ((Number) plugin.getDb() - .getField(p.getName(), - "monthly_invite_count")) - .intValue(); - g.setItem(10, mkItem(Material.PAPER, - "§b§l我的邀请码", - "§7" + code, - "", - "§e点击刷新邀请码")); - g.setItem(12, mkItem(Material.PLAYER_HEAD, - "§a本月邀请", - "§7已邀请 §e" + monthly + " §7人")); - g.setItem(16, mkItem(Material.BOOK, - "§e输入邀请码", - "§7绑定他人的邀请码", - "", - "§e点击输入")); - g.setItem(26, mkItem(Material.ARROW, "§7返回")); - p.openInventory(g); - } - - - private String getInviteRate(int count) { - if (count > 15) return "60%"; - if (count > 10) return "35%"; - if (count > 5) return "15%"; - if (count > 0) return "10%"; - return "0%"; - } - - public void openUserManagement(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, T_USER_MGMT); - fillBg(g); - List> users = plugin.getDb() - .getAllUsers(); - int slot = 0; - for (Map user : users) { - if (slot >= 45) break; - String name = (String) user.get("player_name"); - int pts = ((Number) user.getOrDefault( - "points", 0)).intValue(); - int stage = ((Number) user.getOrDefault( - "gift_stage", 0)).intValue(); - g.setItem(slot, mkItem(Material.PLAYER_HEAD, - "§e" + name, - "§7积分: " + pts - + " 礼包阶段: " + stage, - "§7双击管理")); - slot++; - } - - g.setItem(53, mkItem(Material.ARROW, "§7返回")); - p.openInventory(g); - } - - public void openTaskCenter(Player p) { - Inventory g = Bukkit.createInventory( - null, 27, T_TASK_CENTER); - fillBg(g); - - QuestTracker qt = - plugin.getQuestTracker(); - String player = p.getName(); - - // 新人任务进度 - List newQuests = - qt.getQuests("新人任务"); - int newTotal = newQuests.size(); - int newDone = - qt.getCompletedStageCount( - player, newQuests); - QuestTracker.QuestFile newCurrent = - qt.getCurrentStage( - player, newQuests); - - String newInfo = newDone + "/" + newTotal; - List newLore = new ArrayList<>(); - newLore.add("§7进度: §e" + newInfo); - if (newCurrent != null) { - newLore.add("§7当前: §f" - + newCurrent.displayName); - if (newCurrent.hasConditions()) { - List unmet = - qt.getUnmetConditions( - player, - newCurrent.conditions); - if (!unmet.isEmpty()) { - for (String c : unmet) { - newLore.add("§7 " - + qt.getProgress( - player, c)); - } - } else { - newLore.add("§a✓ 条件已满足"); - } - } - } else { - newLore.add("§a§l全部完成!"); - } - - g.setItem(10, mkItem(Material.BOOK, - "§e§l新人任务 §7[" - + newInfo + "]", - newLore.toArray(new String[0]))); - - // 主线任务 - List mainQuests = - qt.getQuests("主线任务"); - g.setItem(12, mkItem(Material.COMPASS, - "§b§l主线任务 §7[" - + qt.getCompletedStageCount( - player, mainQuests) - + "/" + mainQuests.size() - + "]", - "§7查看主线任务列表")); - - // 支线任务 - List sideQuests = - qt.getQuests("支线任务"); - g.setItem(14, mkItem(Material.PAPER, - "§a§l支线任务 §7[" - + qt.getCompletedStageCount( - player, sideQuests) - + "/" + sideQuests.size() - + "]", - "§7查看支线任务列表")); - - g.setItem(16, mkItem(Material.EMERALD, - "§d§l每日签到", - "§7签到获取积分")); - - // 打卡上报按钮(slot 21) - g.setItem(21, mkItem( - Material.EMERALD, - "§a§l打卡上报", - "§7验证当前阶段任务完成情况", - "§7满足条件将自动标记完成")); - - // 返回按钮(slot 26) - g.setItem(26, mkItem( - Material.ARROW, "§7返回")); - p.openInventory(g); - } - - - public void openGiftStages(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, T_GIFT_STAGES); - fillBg(g); - - QuestTracker qt = - plugin.getQuestTracker(); - List quests = - qt.getQuests("新人任务"); - int total = quests.size(); - int completed = - qt.getCompletedStageCount( - p.getName(), quests); - QuestTracker.QuestFile current = - qt.getCurrentStage( - p.getName(), quests); - - g.setItem(4, mkItem(Material.BOOK, - "§e§l新人任务进度", - "§7已完成: §e" + completed - + "/" + total, - current != null - ? "§7当前: §f" - + current.displayName - : "§a§l全部完成!")); - - for (int i = 0; - i < quests.size() && i < 44; i++) { - QuestTracker.QuestFile qf = - quests.get(i); - boolean done = - qt.isStageCompleted( - p.getName(), qf); - boolean isCurrent = - (current != null - && current == qf); - - Material mat; - List lore = new ArrayList<>(); - - if (done) { - mat = Material.LIME_WOOL; - } else if (isCurrent) { - mat = Material.YELLOW_WOOL; - } else { - mat = Material.RED_WOOL; - } - - if (!qf.details.isEmpty()) { - lore.add("§7---- 任务详情 ----"); - for (String d : qf.details) { - lore.add("§f" + d); - } - } - - if (qf.hasConditions() - && (done || isCurrent)) { - lore.add(""); - for (String c : qf.conditions) { - if (qt.isOptional(c)) - continue; - if (qt.isConditionMet( - p.getName(), c)) { - lore.add("§7 §a✓ " - + qt.getProgress( - p.getName(), c)); - } else { - lore.add("§7 §c✗ " - + qt.getProgress( - p.getName(), c)); - } - } - } - - if (!qf.rewards.isEmpty()) { - lore.add(""); - lore.add("§7---- 奖励 ----"); - for (String r : qf.rewards) { - lore.add("§a" + r); - } - } - - if (done - && !qf.rewards.isEmpty() - && !qt.hasClaimed( - p.getName(), qf)) { - lore.add(""); - lore.add("§e§l点击领取奖励"); - } - if (done && qt.hasClaimed( - p.getName(), qf)) { - lore.add(""); - lore.add("§7已领取奖励"); - } - - g.setItem(10 + i, mkItem(mat, - "§e" + qf.displayName, - lore.toArray(new String[0]))); - } - // 返回按钮 - g.setItem(53, mkItem( - Material.ARROW, "§7返回")); - p.openInventory(g); - } - - - - - public void openTaskList(Player p, - String folderName) { - QuestTracker qt = - plugin.getQuestTracker(); - List tasks = - qt.getQuests(folderName); - - int size = Math.min(54, - Math.max(54, - ((tasks.size() / 9) + 1) * 9)); - Inventory g = Bukkit.createInventory( - null, size, - "§d§l" + folderName); - fillBg(g); - - for (int i = 0; - i < tasks.size() - && i < 45; i++) { - QuestTracker.QuestFile qf = - tasks.get(i); - boolean done = - qt.isStageCompleted( - p.getName(), qf); - Material mat = done - ? Material.LIME_WOOL - : Material.BOOK; - - List lore = new ArrayList<>(); - - // 解锁条件 - if (qf.hasConditions()) { - lore.add("§7──── 解锁条件 ────"); - for (String c : qf.conditions) { - if (qt.isOptional(c)) - continue; - if (qt.isConditionMet( - p.getName(), c)) { - lore.add("§a ✓ " - + qt.getProgress( - p.getName(), c)); - } else { - lore.add("§c ✗ " - + qt.getProgress( - p.getName(), c)); - } - } - } - - // 任务详情 - if (!qf.details.isEmpty()) { - lore.add(""); - lore.add("§7──── 任务详情 ────"); - for (String d : qf.details) { - lore.add("§f" + d); - } - } - - // 任务奖励 - if (!qf.rewards.isEmpty()) { - lore.add(""); - lore.add("§7──── 任务奖励 ────"); - for (String r : qf.rewards) { - lore.add("§a" + r); - } - } - - g.setItem(i, mkItem(mat, - (done ? "§a✓ " : "§e") - + qf.displayName, - lore.toArray(new String[0]))); - } - - if (tasks.isEmpty()) { - g.setItem(22, mkItem( - Material.BARRIER, - "§7暂无任务")); - } - g.setItem(size - 1, mkItem( - Material.ARROW, "§7返回")); - p.openInventory(g); - } - - - public void openUserDetail(Player p, String target) { - Inventory g = Bukkit.createInventory( - null, 27, "§e§l管理: " + target); - fillBg(g); - Map user = plugin.getDb() - .getUser(target); - int pts = ((Number) user.getOrDefault( - "points", 0)).intValue(); - String email = (String) user.getOrDefault( - "email", ""); - g.setItem(4, mkItem(Material.NAME_TAG, - "§e" + target)); - g.setItem(10, mkItem(Material.EMERALD, - "§a积分: " + pts, - "§7点击设置积分")); - g.setItem(11, mkItem(Material.PAPER, - "§e邮箱: " - + (email.isEmpty() ? "无" : email))); - g.setItem(12, mkItem(Material.BOOK, - "§e设置密码")); - g.setItem(16, mkItem(Material.BARRIER, - "§c删除用户", "§7二次验证删除")); - // 雪球菜单检测(slot 14) - g.setItem(14, mkItem( - Material.SNOWBALL, - "§e§l雪球菜单", - "§7点击为该玩家发放菜单雪球")); - g.setItem(16, mkItem( - Material.ENDER_EYE, - "§b§l打卡记录", - "§7查看该玩家的群系打卡记录")); - - g.setItem(22, mkItem(Material.ARROW, "§7返回")); - p.openInventory(g); - } - - public void openCheckInRecords( - Player p, String target) { - Inventory g = Bukkit.createInventory( - null, 54, - "§b§l打卡记录: " + target); - fillBg(g); - - QuestTracker qt = - plugin.getQuestTracker(); - List records = - qt.getPlayerRecords(target); - - java.text.SimpleDateFormat sdf = - new java.text.SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss"); - - int slot = 0; - for (int i = records.size() - 1; - i >= 0 && slot < 45; i--) { - String r = records.get(i); - String[] parts = r.split("\\|"); - if (parts.length < 7) continue; - - String world = parts[1]; - String biome = parts[2]; - String category = parts[3]; - String x = parts[4]; - String y = parts[5]; - String z = parts[6]; - String timeStr = ""; - if (parts.length >= 8) { - try { - long ts = Long.parseLong( - parts[7]); - timeStr = sdf.format( - new java.util.Date( - ts)); - } catch (Exception e) { - timeStr = parts[7]; - } - } - - g.setItem(slot, mkItem( - Material.PAPER, - "§e" + category + " - " - + biome, - "§7时间: " + timeStr, - "§7世界: " + world, - "§7坐标: " + x - + ", " + y - + ", " + z)); - slot++; - } - - if (slot == 0) { - g.setItem(22, mkItem( - Material.BARRIER, - "§7暂无打卡记录")); - } - - g.setItem(53, mkItem( - Material.ARROW, "§7返回")); - p.openInventory(g); - } - - - /** - * 打开账号请求面板 - * 玩家通过 /oa 命令触发 - */ - public void openAccountRequest(Player p) { - org.bukkit.inventory.Inventory g = - org.bukkit.Bukkit.createInventory( - null, 27, - "§e§l账号请求"); - - // 填充玻璃面板 - org.bukkit.inventory.ItemStack glass = - new org.bukkit.inventory.ItemStack( - org.bukkit.Material - .YELLOW_STAINED_GLASS_PANE); - org.bukkit.inventory.meta.ItemMeta gm = - glass.getItemMeta(); - if (gm != null) { - gm.setDisplayName(" "); - glass.setItemMeta(gm); - } - for (int i = 0; i < 27; i++) { - g.setItem(i, glass); - } - - // 10号位:申请删除账号 - g.setItem(10, makeItem( - org.bukkit.Material.BARRIER, - "§c§l申请删除账号", - "§7点击后确认删除您的账号", - "§7此操作不可撤销")); - - // 13号位:找回密码 - g.setItem(13, makeItem( - org.bukkit.Material.TRIPWIRE_HOOK, - "§e§l找回密码", - "§7通过绑定邮箱获取临时密码", - "§7需要先绑定邮箱")); - - // 16号位:联系管理员 - g.setItem(16, makeItem( - org.bukkit.Material.EMERALD, - "§a§l联系管理员", - "§7遇到问题?向管理员提交工单")); - - // 22号位:返回主菜单 - g.setItem(22, makeItem( - org.bukkit.Material.ARROW, - "§7返回")); - - p.openInventory(g); - } - - /** - * 创建物品的工具方法 - */ - private org.bukkit.inventory.ItemStack makeItem( - org.bukkit.Material mat, - String displayName, - String... lore) { - org.bukkit.inventory.ItemStack item = - new org.bukkit.inventory.ItemStack(mat); - org.bukkit.inventory.meta.ItemMeta meta = - item.getItemMeta(); - if (meta != null) { - meta.setDisplayName(displayName); - if (lore.length > 0) { - meta.setLore( - java.util.Arrays.asList(lore)); - } - item.setItemMeta(meta); - } - return item; - } - - - private boolean isAdmin(Player p) { - return p.getScoreboardTags().contains( - plugin.getConfig2().adminTag); - } - - private void fillBg(Inventory inv) { - ItemStack gl = mkItem( - Material.GRAY_STAINED_GLASS_PANE, " "); - for (int i = 0; i < inv.getSize(); i++) - inv.setItem(i, gl); - } - - private ItemStack mkItem(Material mat, String name, - String... lore) { - ItemStack it = new ItemStack(mat); - ItemMeta im = it.getItemMeta(); - if (im != null) { - im.setDisplayName(name); - if (lore.length > 0) - im.setLore(Arrays.asList(lore)); - it.setItemMeta(im); - } - return it; - } -} diff --git a/txt/GarbageManager.txt b/txt/GarbageManager.txt deleted file mode 100644 index b4da32c..0000000 --- a/txt/GarbageManager.txt +++ /dev/null @@ -1,568 +0,0 @@ -package Sdf1_login; - -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Item; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.scheduler.BukkitRunnable; - -import java.io.*; -import java.lang.reflect.Type; -import java.nio.charset.StandardCharsets; -import java.sql.*; -import java.sql.Date; -import java.text.SimpleDateFormat; -import java.util.*; - -public class GarbageManager { - - private final Main plugin; - private Connection db; - private int cleanupInterval = 1800; - private int maxRounds = 1; - private boolean enabled = true; - private int currentRound = 0; - private final Map - recyclePages = new HashMap<>(); - - - public GarbageManager(Main plugin) { - this.plugin = plugin; - } - - public void init() { - try { - Class.forName("org.sqlite.JDBC"); - File dbFile = new File( - plugin.getDataFolder(), - "garbage.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 " - + "items (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "item_data TEXT NOT NULL," - + "amount INTEGER DEFAULT 1," - + "round INTEGER DEFAULT 0," - + "create_time INTEGER NOT NULL" - + ")"); - st.execute("CREATE TABLE IF NOT EXISTS " - + "config (" - + "key TEXT PRIMARY KEY," - + "value TEXT NOT NULL" - + ")"); - st.close(); - } catch (Exception e) { - plugin.getLogger().severe( - "[Garbage] DB失败: " - + e.getMessage()); - } - } - - public void loadConfig() { - // 从 ConfigManager 读取 - cleanupInterval = - plugin.getConfig2() - .garbageInterval; - enabled = - plugin.getConfig2() - .garbageEnabled; - maxRounds = - plugin.getConfig2() - .garbageMaxRounds; - - // 从数据库读取当前轮次 - try { - String val = getConfigValue("round"); - if (val != null) - currentRound = - Integer.parseInt(val); - } catch (Exception ignored) { - } - - plugin.getLogger().info( - "[Garbage] 间隔: " - + cleanupInterval + "秒, " - + "轮次保留: " - + maxRounds + "轮, " - + "启用: " + enabled); - } - - - private String getConfigValue(String key) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT value FROM config " - + "WHERE key=?"); - ps.setString(1, key); - ResultSet rs = ps.executeQuery(); - String val = rs.next() - ? rs.getString("value") : null; - rs.close(); - ps.close(); - return val; - } catch (Exception e) { - return null; - } - } - - private void setConfigValue(String key, - String value) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT OR REPLACE INTO config " - + "(key, value) " - + "VALUES (?, ?)"); - ps.setString(1, key); - ps.setString(2, value); - ps.executeUpdate(); - ps.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void startAutoCleanup() { - if (!enabled) return; - new BukkitRunnable() { - int countdown = 0; - - @Override - public void run() { - // 在线人数≤0时暂停 - if (Bukkit.getOnlinePlayers() - .isEmpty()) { - countdown = 0; - return; - } - - // 重新读取配置(支持热更) - cleanupInterval = - plugin.getConfig2() - .garbageInterval; - - int remain = - cleanupInterval - countdown; - if (remain == 300) - broadcast("§e§l[垃圾站] §f距离清理还有 §e5分钟"); - else if (remain == 180) - broadcast("§e§l[垃圾站] §f距离清理还有 §e3分钟"); - else if (remain == 60) - broadcast("§e§l[垃圾站] §f距离清理还有 §e1分钟"); - else if (remain == 30) - broadcast("§e§l[垃圾站] §f距离清理还有 §e30秒"); - else if (remain == 10) - broadcast("§e§l[垃圾站] §f距离清理还有 §e10秒"); - else if (remain <= 5 && remain > 0) - broadcast("§e§l[垃圾站] §c" + remain + "§f..."); - else if (remain == 0) { - doCleanup(); - countdown = 0; - return; - } - countdown++; - } - }.runTaskTimer(plugin, 20L, 20L); - } - - private void doCleanup() { - // 删除过期轮次的物品 - int deleteRound = - currentRound - maxRounds; - if (deleteRound >= 0) { - int deleted = deleteOldItems( - deleteRound); - if (deleted > 0) { - broadcast("§7[垃圾站] §f永久清理了 §e" - + deleted + " §f个过期物品"); - } - } - - // 递增轮次 - currentRound++; - setConfigValue("round", - String.valueOf(currentRound)); - - // 收集当前掉落物 - int count = collectItems(); - broadcast("§a§l[垃圾站] §f清理完成," - + "共清理 §e" + count - + " §f个掉落物,存入回收站"); - - // 打印下次清理时间 - long nextMs = System.currentTimeMillis() - + (long) cleanupInterval * 1000L; - String nextTime = - new SimpleDateFormat("HH:mm:ss") - .format(new Date(nextMs)); - broadcast("§e§l[垃圾站] §f下次清理时间: §a" - + nextTime); - } - - public int collectItems() { - int count = 0; - for (org.bukkit.World w : - Bukkit.getWorlds()) { - Collection items = - w.getEntitiesByClass(Item.class); - for (Item item : items) { - ItemStack stack = - item.getItemStack(); - if (stack == null - || stack.getType() - == Material.AIR) - continue; - saveItem(stack); - item.remove(); - count++; - } - } - return count; - } - - public void saveItem(ItemStack stack) { - { - try { - String data = - stackToBase64(stack); - if (data.isEmpty()) return; - PreparedStatement ps = db.prepareStatement( - "INSERT INTO items " - + "(item_data, amount, " - + "round, create_time) " - + "VALUES (?,?,?,?)"); - ps.setString(1, data); - ps.setInt(2, stack.getAmount()); - ps.setInt(3, currentRound); - ps.setLong(4, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (Exception e) { - e.printStackTrace(); - } - }} - - private int deleteOldItems(int beforeRound) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM items " - + "WHERE round <= ?"); - ps.setInt(1, beforeRound); - int rows = ps.executeUpdate(); - ps.close(); - return rows; - } catch (Exception e) { - return 0; - } - } - - // ========== 序列化 ========== - - private String stackToBase64(ItemStack stack) { - try { - Map map = - stack.serialize(); - String json = new Gson().toJson(map); - return Base64.getEncoder() - .encodeToString( - json.getBytes( - StandardCharsets.UTF_8)); - } catch (Exception e) { - return ""; - } - } - - private ItemStack base64ToStack(String base64) { - try { - byte[] bytes = Base64.getDecoder() - .decode(base64); - String json = new String(bytes, - StandardCharsets.UTF_8); - Type type = new TypeToken< - Map>() {} - .getType(); - Map map = - new Gson().fromJson(json, type); - map = fixNumberTypes(map); - return ItemStack.deserialize(map); - } catch (Exception e) { - return null; - } - } - - @SuppressWarnings("unchecked") - private Map fixNumberTypes( - Map map) { - Map result = - new LinkedHashMap<>(); - for (Map.Entry e - : map.entrySet()) { - Object v = e.getValue(); - if (v instanceof Double) { - double d = (Double) v; - if (d == (long) d) { - result.put(e.getKey(), (long) d); - } else { - result.put(e.getKey(), d); - } - } else if (v instanceof Map) { - result.put(e.getKey(), - fixNumberTypes( - (Map) v)); - } else if (v instanceof List) { - List list = - new ArrayList<>(); - for (Object item : (List) v) { - if (item instanceof Map) { - list.add(fixNumberTypes( - (Map) item)); - } else { - list.add(item); - } - } - result.put(e.getKey(), list); - } else { - result.put(e.getKey(), v); - } - } - return result; - } - - // ========== 回收站GUI ========== - public void openRecycle(Player p) { - openRecyclePage(p, 0); - } - - public void openRecyclePage(Player p, int page) { - - Inventory g = Bukkit.createInventory( - null, 54, "§6§l垃圾回收站"); - - // 获取全部存储物品 - List> allItems = - getStoredItems(999); - int total = allItems.size(); - int pageSize = 40; - int totalPages = Math.max(1, - (int) Math.ceil( - (double) total / pageSize)); - - // 修正页码 - if (page < 0) page = 0; - if (page >= totalPages) - page = totalPages - 1; - - recyclePages.put(p.getName(), page); - - // 填充物品(0-39 格 = 第1页,40-79 = 第2页...) - int start = page * pageSize; - int end = Math.min(start + pageSize, - total); - int slot = 0; - for (int i = start; i < end; i++) { - if (slot >= 40) break; - Map item = - allItems.get(i); - int id = ((Number) item.get("id")) - .intValue(); - String data = (String) item - .get("item_data"); - int amount = ((Number) item - .get("amount")).intValue(); - ItemStack stack = base64ToStack(data); - if (stack != null) { - stack.setAmount(amount); - ItemMeta im = - stack.getItemMeta(); - if (im != null) { - List lore = - new ArrayList<>(); - lore.add("§7ID: #" + id); - lore.add("§a点击取出"); - im.setLore(lore); - stack.setItemMeta(im); - } - g.setItem(slot, stack); - } - slot++; - } - - // 45-48 格:额外投放区(空位) - // 52-53 格:额外投放区(空位) - - // 49 格:翻页-上一页 - if (page > 0) { - g.setItem(49, mk(Material.ARROW, - "§e上一页 §7(" - + page + "/" - + totalPages + ")")); - } else { - g.setItem(49, mk(Material.ARROW, - "§e第一页 §7(" - + "1/" + totalPages + ")")); - } - - // 50 格:统计信息 - g.setItem(50, mk(Material.CHEST, - "§e回收站统计", - "§7总存量: §f" + total + " 个", - "§7当前页: §f" + (page + 1) - + "/" + totalPages)); - - // 51 格:翻页-下一页 - boolean canNext = total >= pageSize - && page < totalPages - 1; - if (canNext) { - g.setItem(51, mk(Material.ARROW, - "§e下一页 §7(" - + (page + 2) + "/" - + totalPages + ")")); - } else { - g.setItem(51, mk(Material.ARROW, - "§7已是最后一页")); - } - - p.openInventory(g); - } - public int getRecyclePage(String name) { - return recyclePages - .getOrDefault(name, 0); - } - - - public boolean handleClick(Player p, - String title, int slot) { - if (!title.equals("§6§l垃圾回收站")) - return false; - // 49:上一页 - if (slot == 49) { - int page = recyclePages - .getOrDefault(p.getName(), 0); - if (page > 0) - openRecyclePage(p, page - 1); - return true; - } - // 51:下一页 - if (slot == 51) { - int page = recyclePages - .getOrDefault(p.getName(), 0); - openRecyclePage(p, page + 1); - return true; - } - // 50/52/53:不处理 - if (slot >= 49 && slot <= 53) - return true; - return true; - } - - private List> - getStoredItems(int limit) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT id, item_data, " - + "amount " - + "FROM items " - + "ORDER BY " - + "create_time " - + "DESC LIMIT ?"); - ps.setInt(1, limit); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("id", - rs.getInt("id")); - row.put("item_data", - rs.getString("item_data")); - row.put("amount", - rs.getInt("amount")); - list.add(row); - } - rs.close(); - ps.close(); - } catch (Exception e) { - e.printStackTrace(); - } - return list; - } - - public boolean removeItem(int id) { - try { - PreparedStatement ps = - db.prepareStatement( - "DELETE FROM items " - + "WHERE id=?"); - ps.setInt(1, id); - int rows = ps.executeUpdate(); - ps.close(); - return rows > 0; - } catch (Exception e) { - return false; - } - } - - public int getItemCount() { - try { - Statement st = db.createStatement(); - ResultSet rs = st.executeQuery( - "SELECT COUNT(*) FROM items"); - int count = 0; - if (rs.next()) - count = rs.getInt(1); - rs.close(); - st.close(); - return count; - } catch (Exception e) { - return 0; - } - } - - private void broadcast(String msg) { - // 同步打印到控制台 - plugin.getLogger().info( - msg.replaceAll("§[0-9a-fk-orA-FK-OR]", "")); - // 同步打印到聊天栏 - Bukkit.broadcastMessage(msg); - } - - - private ItemStack mk(Material mat, - String name, String... lore) { - ItemStack it = new ItemStack(mat); - ItemMeta im = it.getItemMeta(); - if (im != null) { - im.setDisplayName(name); - if (lore.length > 0) { - List list = - new ArrayList<>(); - for (String l : lore) - if (!l.isEmpty()) list.add(l); - if (!list.isEmpty()) - im.setLore(list); - } - it.setItemMeta(im); - } - return it; - } - - public void close() { - try { - if (db != null && !db.isClosed()) - db.close(); - } catch (Exception ignored) { - } - } -} diff --git a/txt/GiftManager.txt b/txt/GiftManager.txt deleted file mode 100644 index 524ece8..0000000 --- a/txt/GiftManager.txt +++ /dev/null @@ -1,296 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.PrintWriter; - - -public class GiftManager { - - private final File giftFolder; - private final Main plugin; - private final List> stages = - new ArrayList<>(); - - public GiftManager(Main plugin) { - this.plugin = plugin; - this.giftFolder = new File( - plugin.getDataFolder(), "新人礼包"); - } - - public void loadStages() { - stages.clear(); - if (!giftFolder.exists()) { - giftFolder.mkdirs(); - writeDefaultStages(); - } - File[] files = giftFolder.listFiles( - (dir, name) -> name.endsWith(".md")); - if (files == null) return; - Arrays.sort(files); - for (File f : files) { - stages.add(parseMarkdown(f)); - } - } - - private Map parseMarkdown(File f) { - Map stage = new LinkedHashMap<>(); - stage.put("stage", stages.size() + 1); - stage.put("name", - f.getName().replace(".md", "")); - stage.put("rewards", new ArrayList()); - try { - BufferedReader r = new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets.UTF_8)); - String line; - List rewards = new ArrayList<>(); - while ((line = r.readLine()) != null) { - if (line.contains("礼包内容") - || line.contains("奖励") - || line.contains("任务内容")) { - int colon = line.indexOf(':'); - if (colon < 0) - colon = line.indexOf('\uff1a'); - if (colon >= 0) { - rewards.add( - line.substring(colon + 1) - .trim()); - } - } - } - stage.put("rewards", rewards); - r.close(); - } catch (Exception e) { - e.printStackTrace(); - } - return stage; - } - private void writeStage(String name, String content) { - File f = new File(giftFolder, name); - if (f.exists()) return; - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8))) { - pw.print(content); - } catch (IOException ignored) {} - } - - private void writeDefaultStages() { - writeStage("第一阶段.md", - "# 第一阶段\n\n## 领取条件\n\n- 无前级条件\n\n## 礼包内容\n\n- 皮革套装 × 1\n- 木制武器工具套装 × 1\n"); - writeStage("第二阶段.md", - "# 第二阶段\n\n## 领取条件\n\n- 在线时长>1小时\n\n## 礼包内容\n\n- 500 游戏币\n"); - writeStage("第三阶段.md", - "# 第三阶段\n\n## 领取条件\n\n- 破坏100个方块\n- 在线>1天\n\n## 礼包内容\n\n- 1500 游戏币\n- 铁质工具套装 × 1\n- 铁质防具 × 1\n"); - writeStage("第四阶段.md", - "# 第四阶段\n\n## 领取条件\n\n- 破坏/放置300方块\n- 在线≥7天\n\n## 礼包内容\n\n- 钻石套装 × 1\n- 2000 游戏币\n"); - } - - /** 从任意文件夹加载任务列表 */ - public List> loadTasksFromFolder( - File folder) { - List> tasks = - new ArrayList<>(); - if (!folder.exists() || !folder.isDirectory()) - return tasks; - File[] files = folder.listFiles( - (dir, name) -> name.endsWith(".md")); - if (files == null) return tasks; - Arrays.sort(files); - for (File f : files) { - Map task = - new LinkedHashMap<>(); - task.put("name", - f.getName().replace(".md", "")); - task.put("conditions", - new ArrayList()); - task.put("rewards", - new ArrayList()); - try { - BufferedReader r = new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets.UTF_8)); - String line; - List current = null; - while ((line = r.readLine()) != null) { - if (line.startsWith("# ")) { - task.put("name", - line.substring(2) - .trim()); - } else if (line.contains("条件") - || line.contains("领取条件") - || line.contains("完成条件")) { - current = (List) task - .get("conditions"); - } else if (line.contains("内容") - || line.contains("奖励") - || line.contains("任务内容") - || line.contains("礼包内容")) { - current = (List) task - .get("rewards"); - } else if (line.startsWith("- ") - && current != null) { - current.add(line.substring(2) - .trim()); - } - } - r.close(); - } catch (Exception e) { - e.printStackTrace(); - } - tasks.add(task); - } - return tasks; - } - - public boolean canClaim(Player p, int stage) { - Map user = plugin.getDb() - .getUser(p.getName()); - if (user.isEmpty()) return false; - int curStage = ((Number) user.getOrDefault( - "gift_stage", 0)).intValue(); - if (stage > curStage + 1) return false; - if (stage < 1 || stage > stages.size()) - return false; - String claimed = (String) user.getOrDefault( - "gift_claimed", ""); - if (claimed.contains("[" + stage + "]")) - return false; - - // debug 绕过:检查 tasks_completed - String completed = (String) user.getOrDefault( - "tasks_completed", ""); - if (completed.contains("[" + stage + "]")) - return true; - - // 正常条件检查 - long dbTime = ((Number) user.getOrDefault( - "total_online_time", 0L)).longValue(); - long sessionTime = 0; - Object lastObj = plugin.getDb().getField( - p.getName(), "last_online_check"); - if (lastObj != null && plugin.getLoggedIn() - .contains(p.getName())) { - long last = ((Number) lastObj).longValue(); - if (last > 0) { - sessionTime = - System.currentTimeMillis() - last; - } - } - long onlineTime = dbTime + sessionTime; - int broken = ((Number) user.getOrDefault( - "blocks_broken", 0)).intValue(); - int placed = ((Number) user.getOrDefault( - "blocks_placed", 0)).intValue(); - - switch (stage) { - case 1: return true; - case 2: return onlineTime >= 3600000; - case 3: return broken >= 100 - && onlineTime >= 86400000L; - case 4: return (broken + placed) >= 300 - && onlineTime >= 604800000L; - default: return false; - } - } - - public void claimReward(Player p, int stage) { - if (!canClaim(p, stage)) return; - Map user = plugin.getDb() - .getUser(p.getName()); - String claimed = (String) user.getOrDefault( - "gift_claimed", ""); - claimed += "[" + stage + "]"; - plugin.getDb().setField( - p.getName(), "gift_claimed", claimed); - plugin.getDb().setField( - p.getName(), "gift_stage", - Math.max(stage, - ((Number) user.getOrDefault( - "gift_stage", 0)) - .intValue())); - - if (stage == 1) { - giveItem(p, Material.LEATHER_HELMET); - giveItem(p, Material.LEATHER_CHESTPLATE); - giveItem(p, Material.LEATHER_LEGGINGS); - giveItem(p, Material.LEATHER_BOOTS); - giveItem(p, Material.WOODEN_SWORD); - giveItem(p, Material.WOODEN_PICKAXE); - giveItem(p, Material.WOODEN_AXE); - giveItem(p, Material.WOODEN_SHOVEL); - } else if (stage == 2) { - if (plugin.getEconomy() != null) { - double before = plugin.getEconomy() - .getBalance(p); - plugin.getEconomy() - .depositPlayer(p, 500); - double after = plugin.getEconomy() - .getBalance(p); - p.sendMessage("§a§l[礼包] §f游戏币: §e" - + (long) before - + "§f → §a" + (long) after); - } - } else if (stage == 3) { - if (plugin.getEconomy() != null) { - double before = plugin.getEconomy() - .getBalance(p); - plugin.getEconomy() - .depositPlayer(p, 1500); - double after = plugin.getEconomy() - .getBalance(p); - p.sendMessage("§a§l[礼包] §f游戏币: §e" - + (long) before - + "§f → §a" + (long) after); - } - giveItem(p, Material.IRON_PICKAXE); - giveItem(p, Material.IRON_AXE); - giveItem(p, Material.IRON_SWORD); - giveItem(p, Material.IRON_HELMET); - giveItem(p, Material.IRON_CHESTPLATE); - giveItem(p, Material.IRON_LEGGINGS); - giveItem(p, Material.IRON_BOOTS); - } else if (stage == 4) { - if (plugin.getEconomy() != null) { - double before = plugin.getEconomy() - .getBalance(p); - plugin.getEconomy() - .depositPlayer(p, 2000); - double after = plugin.getEconomy() - .getBalance(p); - p.sendMessage("§a§l[礼包] §f游戏币: §e" - + (long) before - + "§f → §a" + (long) after); - } - giveItem(p, Material.DIAMOND_HELMET); - giveItem(p, Material.DIAMOND_CHESTPLATE); - giveItem(p, Material.DIAMOND_LEGGINGS); - giveItem(p, Material.DIAMOND_BOOTS); - } - p.sendMessage(plugin.getConfig2().msg( - "gift_claimed")); - } - - private void giveItem(Player p, Material mat) { - p.getInventory().addItem(new ItemStack(mat)); - } - - public List> getStages() { - return stages; - } - - public int getMaxStage() { - return stages.size(); - } -} diff --git a/txt/IPGroupManager.txt b/txt/IPGroupManager.txt deleted file mode 100644 index 041e184..0000000 --- a/txt/IPGroupManager.txt +++ /dev/null @@ -1,36 +0,0 @@ -package Sdf1_login; - -import java.util.List; - -public class IPGroupManager { - - private final Main plugin; - private int maxAccounts; - - public IPGroupManager(Main plugin, int maxAccounts) { - this.plugin = plugin; - this.maxAccounts = maxAccounts; - } - - public int getMaxAccounts() { - return maxAccounts; - } - - public void setMaxAccounts(int max) { - this.maxAccounts = max; - } - - public List getAccountsByIP(String ip) { - return plugin.getDb().getAccountsByIP(ip); - } - - public int getAccountCount(String ip) { - if (ip == null) return 0; - return getAccountsByIP(ip).size(); - } - - public boolean canRegister(String ip) { - if (maxAccounts <= 0) return true; - return getAccountCount(ip) < maxAccounts; - } -} diff --git a/txt/InventorySerializer.txt b/txt/InventorySerializer.txt deleted file mode 100644 index 32bc632..0000000 --- a/txt/InventorySerializer.txt +++ /dev/null @@ -1,46 +0,0 @@ -package Sdf1_login; - -import org.bukkit.inventory.ItemStack; -import org.bukkit.util.io.BukkitObjectInputStream; -import org.bukkit.util.io.BukkitObjectOutputStream; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Base64; - -public class InventorySerializer { - - public static String toBase64(ItemStack[] items) throws IOException { - try { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream); - dataOutput.writeInt(items.length); - for (ItemStack item : items) { - dataOutput.writeObject(item); - } - dataOutput.close(); - return Base64.getEncoder().encodeToString(outputStream.toByteArray()); - } catch (Exception e) { - throw new IOException("Unable to serialize item stacks.", e); - } - } - - public static ItemStack[] fromBase64(String data) throws IOException { - if (data == null || data.isEmpty()) { - return new ItemStack[0]; - } - try { - ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(data)); - BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream); - ItemStack[] items = new ItemStack[dataInput.readInt()]; - for (int i = 0; i < items.length; i++) { - items[i] = (ItemStack) dataInput.readObject(); - } - dataInput.close(); - return items; - } catch (ClassNotFoundException e) { - throw new IOException("Unable to deserialize item stacks.", e); - } - } -} diff --git a/txt/InviteManager.txt b/txt/InviteManager.txt deleted file mode 100644 index a9ca6ea..0000000 --- a/txt/InviteManager.txt +++ /dev/null @@ -1,91 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.text.SimpleDateFormat; -import java.util.*; - -public class InviteManager { - - private final Main plugin; - - public InviteManager(Main plugin) { - this.plugin = plugin; - } - - public String generateCode(Player p) { - String code = PasswordUtils.generateInviteCode(); - plugin.getDb().setField( - p.getName(), "invite_code", code); - return code; - } - - public void onInviteeCheckIn(String inviteeName) { - String invitedBy = (String) plugin.getDb() - .getField(inviteeName, "invited_by"); - if (invitedBy == null || invitedBy.isEmpty()) return; - - String month = new SimpleDateFormat("yyyy-MM") - .format(new Date()); - String lastMonth = (String) plugin.getDb() - .getField(invitedBy, "invite_month"); - - if (!month.equals(lastMonth)) { - plugin.getDb().setField( - invitedBy, "invite_month", month); - plugin.getDb().setField( - invitedBy, "monthly_invite_count", 0); - } - - int count = ((Number) plugin.getDb() - .getField(invitedBy, - "monthly_invite_count")).intValue(); - plugin.getDb().setField( - invitedBy, "monthly_invite_count", count + 1); - - double rate = 0.10; - if (count + 1 > 15) { - rate = 0.60; - } else if (count + 1 > 10) { - rate = 0.35; - } else if (count + 1 > 5) { - rate = 0.15; - } - - int bonus = (int) (1 * rate); - if (bonus > 0) { - plugin.getDb().addPoints(invitedBy, bonus); - Player inviterPlayer = Bukkit - .getPlayerExact(invitedBy); - if (inviterPlayer != null - && inviterPlayer.isOnline()) { - inviterPlayer.sendMessage( - plugin.getConfig2().msg( - "invite_referral_bonus", - "points", - String.valueOf(bonus))); - } - } - } - - public List> getAllInviteData() { - List> result = new ArrayList<>(); - for (Map user - : plugin.getDb().getAllUsers()) { - String code = (String) user.getOrDefault( - "invite_code", ""); - if (!code.isEmpty()) { - Map row = new LinkedHashMap<>(); - row.put("player", - user.get("player_name")); - row.put("code", code); - row.put("monthly", - user.getOrDefault( - "monthly_invite_count", 0)); - result.add(row); - } - } - return result; - } -} diff --git a/txt/LoginManager.txt b/txt/LoginManager.txt deleted file mode 100644 index 4a50dc1..0000000 --- a/txt/LoginManager.txt +++ /dev/null @@ -1,301 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.Sound; -import org.bukkit.entity.Player; - -public class LoginManager { - - private final Main plugin; - - public LoginManager(Main plugin) { - this.plugin = plugin; - } - - public boolean handleRegister(Player p, String[] args) { - String name = p.getName(); - - if (plugin.getplugin.getDb()().userExists(name)) { - p.sendMessage(plugin.getConfig2().msg("already_registered")); - return true; - } - - // ===== IP注册限制检查 ===== - String ip = plugin.getPlayerIP(p); - if (ip != null && plugin.getConfig2().maxAccountsPerIP > 0) { - if (!plugin.getIPGroup().canRegister(ip)) { - int count = plugin.getIPGroup().getAccountCount(ip); - int max = plugin.getConfig2().maxAccountsPerIP; - p.sendMessage("§c[Sdf1_login] §f该IP注册已达上限(" + count + "/" + max + ")"); - return true; - } - } - - if (args.length < 1) { - p.sendMessage("§c用法: /reg <密码>"); - p.sendMessage("§7密码要求: 6位以上,包含大小写字母和数字"); - return true; - } - - String pwd = args[0]; - if (pwd.length() < 6) { - p.sendMessage("§c[Sdf1_login] §f密码长度不足6位"); - return true; - } - if (!pwd.matches(".*[a-z].*") || !pwd.matches(".*[A-Z].*") || !pwd.matches(".*[0-9].*")) { - p.sendMessage("§c[Sdf1_login] §f密码必须包含大写字母、小写字母和数字"); - return true; - } - - String salt = PasswordUtils.generateSalt(); - String hash = PasswordUtils.hash(pwd, salt); - plugin.getplugin.getDb()().createUser(name, hash, salt); - plugin.getLoggedIn().add(name); - plugin.getplugin.getDb()().setLoggedIn(name, true); - plugin.getplugin.getDb()().setField(name, "last_login_time", System.currentTimeMillis()); - plugin.getplugin.getDb()().setField(name, "last_online_check", System.currentTimeMillis()); - plugin.getplugin.getDb()().recordIP(name, ip); - plugin.getplugin.getDb()().setField(name, "register_time", System.currentTimeMillis()); - plugin.restoreInventory(p); - plugin.recordIPLogin(p); - - p.sendMessage(plugin.getConfig2().msg("reg_success")); - p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_CHIME, 1.0f, 0.79f); - p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1.0f, 0.79f); - p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 0.79f); - - try { - org.bukkit.plugin.Plugin cy = org.bukkit.Bukkit.getPluginManager().getPlugin("CY_beibao"); - if (cy != null && cy.isEnabled()) { - cy.getClass().getMethod("onSdf1Activation", String.class, int.class, int.class) - .invoke(cy, name, 9, 0); - } - } catch (Exception ignored) { - } - return true; - } - - public boolean handleLogin(Player p, - String[] args) { - String name = p.getName(); - if (plugin.getLoggedIn().contains(name)) { - p.sendMessage(plugin.getConfig2().msg( - "already_logged_in")); - return true; - } - if (!plugin.getplugin.getDb()().userExists(name)) { - p.sendMessage(plugin.getConfig2().msg( - "not_registered")); - return true; - } - if (args.length < 1) { - p.sendMessage("§c用法: /login <密码>"); - return true; - } - String pwd = args[0]; - - // 诊断:显示账号信息 - String salt = (String) plugin.getplugin.getDb()() - .getField(name, "password_salt"); - String regType = (String) plugin.getplugin.getDb()() - .getField(name, "register_type"); - String storedHash = (String) plugin.getplugin.getDb()() - .getField(name, "password_hash"); - plugin.getLogger().info( - "[Sdf1_login] [登录诊断] 玩家: " + name); - plugin.getLogger().info( - "[Sdf1_login] [登录诊断] 注册类型: " - + regType); - plugin.getLogger().info( - "[Sdf1_login] [登录诊断] 盐值: " + salt); - plugin.getLogger().info( - "[Sdf1_login] [登录诊断] 存储哈希: " - + (storedHash != null - ? storedHash.substring(0, - Math.min(16, - storedHash.length())) - + "..." - : "null")); - - if (salt == null) { - p.sendMessage( - "§c[Sdf1_login] §f数据异常,请联系管理员"); - plugin.getLogger().severe( - "[Sdf1_login] 玩家 " + name - + " 盐值为null"); - return true; - } - - String hash = PasswordUtils.hash(pwd, salt); - - plugin.getLogger().info( - "[Sdf1_login] [登录诊断] 输入密码哈希: " - + hash.substring(0, - Math.min(16, hash.length())) - + "..."); - - // 检查是否匹配主密码 - boolean matchMain = - plugin.getplugin.getDb()().checkPassword(name, hash); - plugin.getLogger().info( - "[Sdf1_login] [登录诊断] 主密码匹配: " - + matchMain); - - if (matchMain) { - // 主密码匹配 → 正常登录 - plugin.getLoggedIn().add(name); - plugin.getplugin.getDb()().setLoggedIn(name, true); - plugin.getplugin.getDb()().setField(name, - "last_login_time", - System.currentTimeMillis()); - plugin.getplugin.getDb()().setField(name, - "last_online_check", - System.currentTimeMillis()); - plugin.restoreInventory(p); - plugin.recordIPLogin(p); - p.sendMessage(plugin.getConfig2().msg("login_success")); - plugin.getLogger().info( - "[Sdf1_login] " + name - + " 主密码登录成功"); - try { - org.bukkit.plugin.Plugin cy = - org.bukkit.Bukkit - .getPluginManager() - .getPlugin("CY_beibao"); - if (cy != null && cy.isEnabled()) { - java.lang.reflect.Method m = - cy.getClass().getMethod( - "onSdf1Activation", - String.class, - int.class, int.class); - m.invoke(cy, name, 0, 0); - } - } catch (Exception ignored) { - } - return true; - } - - // 主密码不匹配 → 检查临时密码 - boolean matchTemp = - plugin.getplugin.getDb()().checkPasswordOrTemp( - name, hash); - plugin.getLogger().info( - "[Sdf1_login] [登录诊断] 临时密码匹配: " - + matchTemp); - - if (matchTemp) { - // 临时密码匹配 → 登录 + 强制改密 - plugin.getLoggedIn().add(name); - plugin.getplugin.getDb()().setLoggedIn(name, true); - plugin.getplugin.getDb()().setField(name, - "last_login_time", - System.currentTimeMillis()); - plugin.getplugin.getDb()().setField(name, - "last_online_check", - System.currentTimeMillis()); - plugin.restoreInventory(p); - plugin.recordIPLogin(p); - plugin.getNeedsPasswordChange().add(name); - p.sendMessage(plugin.getConfig2().msg("login_success")); - p.sendMessage( - "§c§l[警告] §f您使用的是临时密码," - + "请尽快修改密码!"); - p.sendMessage( - "§7用法: /sdf1_login pw"); - plugin.getLogger().info( - "[Sdf1_login] " + name - + " 临时密码登录成功," - + "强制要求改密"); - try { - org.bukkit.plugin.Plugin cy = - org.bukkit.Bukkit - .getPluginManager() - .getPlugin("CY_beibao"); - if (cy != null && cy.isEnabled()) { - java.lang.reflect.Method m = - cy.getClass().getMethod( - "onSdf1Activation", - String.class, - int.class, int.class); - m.invoke(cy, name, 0, 0); - } - } catch (Exception ignored) { - } - return true; - } - - // 都不匹配 → 登录失败 - plugin.getLogger().info( - "[Sdf1_login] " + name - + " 密码验证失败"); - p.sendMessage(plugin.getConfig2().msg( - "login_failed")); - return true; - } - - public void handleReset(Player p) { - String name = p.getName(); - String plugin.getEmail()Addr = (String) plugin.getDb().getField( - name, "plugin.getEmail()"); - if (plugin.getEmail()Addr == null - || plugin.getEmail()Addr.isEmpty()) { - p.sendMessage( - "§c未设置邮箱,无法重置密码"); - return; - } - String tempPwd = PasswordUtils - .generateTempPassword(); - String salt = (String) plugin.getDb().getField( - name, "password_salt"); - String hash = PasswordUtils.hash( - tempPwd, salt); - plugin.getDb().setField(name, - "temp_password", hash); - plugin.getDb().setField(name, - "temp_pw_expire", - System.currentTimeMillis() - + 3600000L); - plugin.getDb().setField(name, - "temp_pw_used", 0); - p.sendMessage("§e正在发送临时密码..."); - final String to = plugin.getEmail()Addr; - final String fName = name; - final String pwd = tempPwd; - Bukkit.getScheduler() - .Bukkit.getScheduler().runTask(plugin, ...)Asynchronously(this, - () -> { - final boolean[] sent = {false}; - Thread t = new Thread(() -> { - sent[0] = plugin.getEmail() - .sendTempPassword( - to, fName, pwd); - }, "Sdf1_login-reset"); - t.setDaemon(true); - t.start(); - try { - t.join(15000); - } catch ( - InterruptedException ignored) { - } - if (t.isAlive()) { - t.interrupt(); - Bukkit.getScheduler().Bukkit.getScheduler().runTask(plugin, ...)( - this, () -> - p.sendMessage( - "§c邮件发送超时")); - return; - } - Bukkit.getScheduler().Bukkit.getScheduler().runTask(plugin, ...)( - this, () -> { - if (sent[0]) { - p.sendMessage( - "§a临时密码已发送到 " - + to); - } else { - p.sendMessage( - "§c邮件发送失败"); - } - }); - }); - } -} \ No newline at end of file diff --git a/txt/Main.txt b/txt/Main.txt deleted file mode 100644 index 6c24d45..0000000 --- a/txt/Main.txt +++ /dev/null @@ -1,3712 +0,0 @@ -package Sdf1_login; - -import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import org.bukkit.event.player.PlayerInteractEvent; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.Location; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Villager; -import Sdf1_login.ChatInputManager.InputType; - - - - -import java.util.TreeSet; -import java.util.UUID; -import java.util.LinkedHashMap; -import java.util.concurrent.ConcurrentHashMap; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.entity.PlayerDeathEvent; -import org.bukkit.event.inventory.CraftItemEvent; -import org.bukkit.event.player.PlayerFishEvent; - - -import net.milkbowl.vault.economy.Economy; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryDragEvent; -import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.event.player.PlayerDropItemEvent; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerPickupItemEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.plugin.RegisteredServiceProvider; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.command.TabCompleter; -import org.bukkit.scheduler.BukkitRunnable; - -public class Main extends JavaPlugin - implements CommandExecutor, Listener, - TabCompleter { - - private DatabaseManager db; - private ConfigManager config; - private EmailManager email; - private LoginManager loginMgr; - private AFKManager afk; - private GiftManager gift; - private CheckInManager checkIn; - private InviteManager invite; - private PointsManager points; - private GUIManager gui; - private ChatInputManager chatInput; - private Economy economy; - private VerificationManager verification; - private IPGroupManager ipGroup; - private AccountRequestManager accountRequest; - private TicketManager ticket; - private ChatFilterManager chatFilter; - private GarbageManager garbage; - - private final Set loggedIn = - new TreeSet<>( - String.CASE_INSENSITIVE_ORDER); - private final Set needsPasswordChange = - new TreeSet<>( - String.CASE_INSENSITIVE_ORDER); - private final Set frozenFlightPlayers = - new TreeSet<>( - String.CASE_INSENSITIVE_ORDER); - private final Map joinTime = - new ConcurrentHashMap<>(); - private final Map joinLoc = - new HashMap<>(); - private final Map ipAutoLogin = - new HashMap<>(); - private static final long IP_SKIP_MS = 300000L; - - private final Map lastActivity = - new ConcurrentHashMap<>(); - // 登录后首次回收飞行(仅一次) - private final Set removeFlightOnce = - new TreeSet<>( - String.CASE_INSENSITIVE_ORDER); - private RadioManager radio; - public RadioManager getRadio() { - return radio; - } - - - private static class PwdRollback { - final String hash; - final String salt; - final long time; - PwdRollback(String h, String s, long t) { - hash = h; - salt = s; - time = t; - } - } - - private final Map - pwdRollback = - new ConcurrentHashMap<>(); - - public void recordPasswordChange(String name, - String oldHash, String oldSalt) { - pwdRollback.put(name, - new PwdRollback(oldHash, oldSalt, - System.currentTimeMillis())); - } - - // ===== Getters ===== - public DatabaseManager getDb() { - return db; - } - public ConfigManager getConfig2() { return config; } - public EmailManager getEmail() { return email; } - public Economy getEconomy() { return economy; } - public LoginManager getLoginMgr() { return loginMgr; } - public AFKManager getAfk() { return afk; } - public GiftManager getGift() { return gift; } - public CheckInManager getCheckIn() { return checkIn; } - public InviteManager getInvite() { return invite; } - public PointsManager getPoints() { return points; } - public GUIManager getGui() { return gui; } - public ChatInputManager getChatInput() { return chatInput; } - public VerificationManager getVerification() { return verification; } - public IPGroupManager getIPGroup() { return ipGroup; } - public AccountRequestManager getAccountRequest() { return accountRequest; } - public TicketManager getTicket() { return ticket; } - public ChatFilterManager getChatFilter() { return chatFilter; } - public GarbageManager getGarbage() { return garbage; } - public Set getLoggedIn() { return loggedIn; } - public Set getNeedsPasswordChange() { return needsPasswordChange; } - private WelcomeManager welcome; - private UpdateChecker updateChecker; - private QuestTracker questTracker; - private CommissionManager commission; - public CommissionManager getCommission() { - return commission; - } - - - - // ===== Enable/Disable ===== - @Override - public void onEnable() { - radio = new RadioManager(this); - getDataFolder().mkdirs(); - config = new ConfigManager(getDataFolder()); - config.loadMessages(); - config.loadSmtp(); - config.loadSettings(); - getCommand("oa").setExecutor(this); - - db = new DatabaseManager(getDataFolder()); - db.init(); - email = new EmailManager(config); - loginMgr = new LoginManager(this); - afk = new AFKManager(config, this); - gift = new GiftManager(this); - gift.loadStages(); - checkIn = new CheckInManager(this); - invite = new InviteManager(this); - points = new PointsManager(this); - gui = new GUIManager(this); - chatInput = new ChatInputManager(); - verification = new VerificationManager(this); - ipGroup = new IPGroupManager(this, - config.maxAccountsPerIP); - accountRequest = - new AccountRequestManager(this); - accountRequest.setMode(config.approvalMode); - accountRequest.setAutoDelayMinutes( - config.autoApproveDelayMinutes); - ticket = new TicketManager(this); - garbage = new GarbageManager(this); - garbage.init(); - garbage.loadConfig(); - garbage.startAutoCleanup(); - chatFilter = new ChatFilterManager(this); - chatFilter.loadConfig(); - welcome = new WelcomeManager(this); - updateChecker = new UpdateChecker(this); - questTracker = new QuestTracker(this); - commission = new CommissionManager(this); - - - setupEconomy(); - - if (getCommand("sdf1_login") != null) { - getCommand("sdf1_login").setExecutor(this); - getCommand("sdf1_login").setTabCompleter(this); - } - if (getCommand("reg") != null) - getCommand("reg").setExecutor(this); - if (getCommand("login") != null) - getCommand("login").setExecutor(this); - if (getCommand("l") != null) - getCommand("l").setExecutor(this); - if (getCommand("签到") != null) - getCommand("签到").setExecutor(this); - if (getCommand("recycle") != null) - getCommand("recycle").setExecutor(this); - if (getCommand("垃圾清理") != null) - getCommand("垃圾清理").setExecutor(this); - if (getCommand("绑定邮箱") != null) - getCommand("绑定邮箱").setExecutor(this); - if (getCommand("找回密码") != null) - getCommand("找回密码").setExecutor(this); - if (getCommand("玩家信息") != null) - getCommand("玩家信息").setExecutor(this); - if (getCommand("sdf1debug") != null) - getCommand("sdf1debug").setExecutor(this); - if (getCommand("oa") != null) - getCommand("oa").setExecutor(this); - if (getCommand("menu") != null) - getCommand("menu").setExecutor(this); - if (getCommand("radio") != null) - getCommand("radio").setExecutor(this); - getServer().getPluginManager() - .registerEvents(this, this); - - afk.startCheck(); - startLoginReminder(); - startTimeoutCheck(); - startPasswordReminder(); - startTicketAutoProcess(); - startPlayTimeTracker(); - // 启动后异步检查更新 - updateChecker.checkOnEnable(); - - getLogger().info( - "Sdf1_login v1.0 | 就绪"); - for (Player online - : Bukkit.getOnlinePlayers()) { - radio.sendResourcePack(online); - } - - - } - - @Override - public void onDisable() { - if (questTracker != null) { - questTracker.shutdown(); - } - if (radio != null) { - radio.stopAllLoginRadio(); - radio.stopMainRadio(); - } - - if (questTracker != null) { - } - } - -// 没替换,只是粗略看了下,有几个问题 - private void setupEconomy() { - if (getServer().getPluginManager() - .getPlugin("Vault") == null) return; - RegisteredServiceProvider rsp = - getServer().getServicesManager() - .getRegistration( - Economy.class); - if (rsp != null) - economy = rsp.getProvider(); - } - public WelcomeManager getWelcome() { - return welcome; - } - public UpdateChecker getUpdateChecker() { - return updateChecker; - } - public QuestTracker getQuestTracker() { - return questTracker; - } - - // ===== Timers ===== - private void startTimeoutCheck() { - new BukkitRunnable() { - public void run() { - long now = System.currentTimeMillis(); - Iterator> it = - joinTime.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry entry = - it.next(); - if (now - entry.getValue() - > config.loginTimeout - * 1000L) { - Player p = Bukkit.getPlayer( - entry.getKey()); - if (p != null && p.isOnline() - && !loggedIn.contains( - p.getName())) - p.kickPlayer(config.msg( - "login_timeout")); - it.remove(); - } - } - } - }.runTaskTimer(this, 80L, 80L); - } - - private void startLoginReminder() { - new BukkitRunnable() { - public void run() { - Iterator it = - joinTime.keySet().iterator(); - while (it.hasNext()) { - UUID uuid = it.next(); - Player p = Bukkit.getPlayer(uuid); - if (p == null || !p.isOnline()) { - it.remove(); - continue; - } - if (loggedIn.contains( - p.getName())) { - it.remove(); - continue; - } - // 已注册→提示登录,未注册→提示注册 - if (db.userExists(p.getName())) - p.sendMessage(config.msg( - "not_logged_in")); - else - p.sendMessage(config.msg( - "not_registered")); - } - } - }.runTaskTimer(this, 200L, 200L); - } - - private void startPasswordReminder() { - new BukkitRunnable() { - public void run() { - for (Player p : - Bukkit.getOnlinePlayers()) { - if (needsPasswordChange.contains( - p.getName())) { - p.sendTitle("§c§l请修改密码", - "§f使用 /sdf1_login pw 修改密码", - 10, 40, 10); - p.playSound(p.getLocation(), - Sound.BLOCK_NOTE_BLOCK_PLING, - 1.0f, 1.0f); - } - } - } - }.runTaskTimer(this, 200L, 200L); - } - - private void startTicketAutoProcess() { - new BukkitRunnable() { - public void run() { - if (ticket != null) - ticket.autoProcessCompleted(); - } - }.runTaskTimer(this, 6000L, 6000L); - } - - // ===== Utils ===== - public String getPlayerIP(Player p) { - InetSocketAddress addr = p.getAddress(); - if (addr == null) return null; - return addr.getAddress().getHostAddress(); - } - - - public void resetActivity(Player p) { - lastActivity.put(p.getUniqueId(), - System.currentTimeMillis()); - } - - public boolean canAutoLogin(Player p) { - String ip = getPlayerIP(p); - if (ip == null) return false; - Long last = ipAutoLogin.get(ip); - return last != null - && System.currentTimeMillis() - last - < IP_SKIP_MS; - } - - public void recordIPLogin(Player p) { - String ip = getPlayerIP(p); - if (ip != null) - ipAutoLogin.put(ip, - System.currentTimeMillis()); - } - - public boolean isFrozen(Player p) { - return !loggedIn.contains(p.getName()); - } - - private boolean isAdmin(CommandSender sender) { - if (sender instanceof Player) - return ((Player) sender) - .getScoreboardTags() - .contains(config.adminTag); - return true; - } - - private void clearPlayer(Player p) { - p.getInventory().clear(); - p.getInventory().setArmorContents( - new ItemStack[4]); - p.getInventory().setExtraContents( - new ItemStack[1]); - p.setLevel(0); - p.setExp(0f); - } - - public void restoreInventory(Player p) { - String name = p.getName(); - try { - String[] backup = - db.loadInventoryBackup(name); - if (backup == null - || !isValidBackup(backup)) { - return; - } - - debugPrintBase64("恢复前", name, - backup[0]); - - ItemStack[] contents = - InventorySerializer - .fromBase64(backup[0]); - if (contents != null) - p.getInventory() - .setContents(contents); - - if (backup[1] != null - && !backup[1].isEmpty()) { - ItemStack[] armor = - InventorySerializer - .fromBase64( - backup[1]); - if (armor != null) - p.getInventory() - .setArmorContents( - armor); - } - - if (backup[2] != null - && !backup[2].isEmpty()) { - ItemStack[] extra = - InventorySerializer - .fromBase64( - backup[2]); - if (extra != null) - p.getInventory() - .setExtraContents( - extra); - } - - int lv = Integer.parseInt( - backup[3]); - if (lv > 0) p.setLevel(lv); - double xp = Double.parseDouble( - backup[4]); - if (xp > 0) - p.setExp((float) xp); - - debugPrintBase64("恢复后", name, - InventorySerializer.toBase64( - p.getInventory() - .getContents())); - - getLogger().info( - "[Sdf1_login] " + name - + " 背包已恢复"); - } catch (Exception e) { - getLogger().severe( - "[Sdf1_login] 还原背包失败: " - + name); - e.printStackTrace(); - } - - } - - - @EventHandler - public void onBlockPlace_Quest( - BlockPlaceEvent e) { - if (questTracker == null) return; - Player p = e.getPlayer(); - if (isFrozen(p)) return; - questTracker.onBlockPlaced( - p.getName(), - e.getBlock().getType().name()); - } - - @EventHandler - public void onMoveBiome( - PlayerMoveEvent e) { - if (questTracker == null) return; - Player p = e.getPlayer(); - if (isFrozen(p)) return; - Location to = e.getTo(); - if (to == null) return; - String fromKey = e.getFrom().getBlock() - .getBiome().getKey().getKey(); - String toKey = to.getBlock() - .getBiome().getKey().getKey(); - if (fromKey.equals(toKey)) return; - questTracker.onPlayerMove( - p.getName(), toKey); - } - - - public void backupInventory(Player p) { - String name = p.getName(); - int cnt = countItems(p) - + countArmor(p) - + countExtra(p); - if (cnt == 0) { - getLogger().warning( - "[Sdf1_login] " + name - + " 背包为空,跳过备份"); - return; - } - try { - String invB64 = - InventorySerializer.toBase64( - p.getInventory() - .getContents()); - String armorB64 = - InventorySerializer.toBase64( - p.getInventory() - .getArmorContents()); - String extraB64 = - InventorySerializer.toBase64( - p.getInventory() - .getExtraContents()); - db.saveInventoryBackup(name, invB64, - armorB64, extraB64, - p.getLevel(), p.getExp()); - getLogger().info( - "[Sdf1_login] " + name - + " 背包已备份 (物品:" - + cnt + ")"); - } catch (Exception e) { - getLogger().severe( - "[Sdf1_login] 背包备份失败: " - + name); - e.printStackTrace(); - } - } - - private void hideInventory(Player p) { - String name = p.getName(); - try { - debugPrintBase64("清空前", name, - InventorySerializer.toBase64( - p.getInventory() - .getContents())); - - // 清空背包(备份已在上次退出时保存) - p.getInventory().clear(); - p.getInventory() - .setArmorContents(null); - p.getInventory() - .setExtraContents(null); - p.setLevel(0); - p.setExp(0); - - debugPrintBase64("清空后", name, - InventorySerializer.toBase64( - p.getInventory() - .getContents())); - } catch (Exception e) { - getLogger().severe( - "[Sdf1_login] hideInventory失败: " - + name); - e.printStackTrace(); - } - } - - private void hideInventoryDelayed(Player p, - int attempt) { - String name = p.getName(); - if (!p.isOnline()) return; - if (!isFrozen(p)) return; - - // 从数据库加载已有备份 - String[] existing = - db.loadInventoryBackup(name); - - // 调试打印 - debugPrintBase64("加入-加载DB", name, - existing != null - ? existing[0] : null); - - // 无有效备份 → 跳过清空背包 - if (existing == null - || !isValidBackup(existing)) { - getLogger().info( - "[Sdf1_login] " + name - + " 无有效备份," - + "跳过清空背包"); - return; - } - - // 有有效备份 → 清空当前背包 - hideInventory(p); - } - - - public void autoLogin(Player p, - String registerType) { - String name = p.getName(); - String ip = getPlayerIP(p); - String uuid = - p.getUniqueId().toString(); - String defaultPwd = "123456aA"; - - if (!db.userExists(name)) { - if (config.maxAccountsPerIP > 0 - && ip != null - && !ipGroup.canRegister(ip)) { - p.kickPlayer( - buildIPKickMessage(p, ip)); - return; - } - if (!"manual".equals( - config.approvalMode)) { - AccountRequestManager.Request req = - accountRequest.createRequest( - name, name, ip); - if (req != null) { - p.kickPlayer( - config.msg("need_approval") - .replace("{id}", - String.valueOf( - req.id))); - return; - } - radio.stopLoginRadio(p); - - } - String salt = - PasswordUtils.generateSalt(); - String hash = PasswordUtils.hash( - defaultPwd, salt); - db.createUser(name, hash, salt); - db.setField(name, - "premium_uuid", uuid); - db.setField(name, - "register_ip", ip); - db.setField(name, - "register_type", registerType); - db.recordIP(name, ip); - } - - loggedIn.add(name); - removeFlightOnce.add(name); - db.setLoggedIn(name, true); - db.setField(name, - "last_login_time", - System.currentTimeMillis()); - db.setField(name, - "last_online_check", - System.currentTimeMillis()); - db.recordIP(name, ip); - joinTime.remove(p.getUniqueId()); - recordIPLogin(p); - - // 标记注册登录条件完成 - if (questTracker != null) { - questTracker.markConditionComplete( - name, "完成注册登录"); - } - - // 欢迎信息 - this.getWelcome().onLogin(p); - - // 只在有有效备份时恢复 - restoreInventory(p); - - giveMenuSnowball(p); - p.sendMessage("§a[Sdf1_login] §f您已自动登录!"); - activateBeibao(p); - pushPendingAlerts(p); - } - - // ===== 任务追踪事件 ===== - - @EventHandler - public void onBlockMine(BlockBreakEvent event) { - if (questTracker == null) return; // ← 新增 - Player p = event.getPlayer(); - if (isFrozen(p)) return; - questTracker.onBlockMined( - p.getName(), - event.getBlock().getType().name()); - } - - @EventHandler - public void onMobDeath(EntityDeathEvent e) { - if (questTracker == null) return; // ← 新增 - if (e.getEntity().getKiller() == null) - return; - Player p = e.getEntity().getKiller(); - questTracker.onMobKill( - p.getName(), - e.getEntity().getType().name()); - } - - @EventHandler - public void onPlayerDeath( - PlayerDeathEvent e) { - if (questTracker == null) return; // ← 新增 - Player killer = - e.getEntity().getKiller(); - if (killer == null) return; - questTracker.onPlayerKill( - killer.getName()); - } - - @EventHandler - public void onCraft( - CraftItemEvent e) { - if (questTracker == null) return; // ← 新增 - if (!(e.getWhoClicked() - instanceof Player)) return; - Player p = (Player) e.getWhoClicked(); - if (isFrozen(p)) return; - questTracker.onItemCrafted( - p.getName()); - } - - @EventHandler - public void onFish( - PlayerFishEvent e) { - if (questTracker == null) return; // ← 新增 - if (e.getState() - != PlayerFishEvent - .State.CAUGHT_FISH) return; - if (!(e.getCaught() - instanceof - org.bukkit.entity.Item)) return; - Player p = e.getPlayer(); - questTracker.onFishCaught( - p.getName()); - } - - // 在线时长追踪(每分钟) - public void startPlayTimeTracker() { - Bukkit.getScheduler() - .runTaskTimer(this, - () -> { - if (questTracker == null) return; - for (Player p : - Bukkit.getOnlinePlayers()) { - if (isFrozen(p)) continue; - questTracker.onPlayTime( - p.getName(), - 60000L); - } - }, 1200L, 1200L); - } - - private void activateBeibao(Player p) { - try { - org.bukkit.plugin.Plugin cy = - Bukkit.getPluginManager() - .getPlugin("CY_beibao"); - if (cy != null && cy.isEnabled()) - cy.getClass().getMethod( - "onSdf1Activation", - String.class, - int.class, int.class) - .invoke(cy, p.getName(), - 0, 0); - } catch (Exception ignored) { - } - } - - public void triggerSecurityAlert(Player player, - String suspiciousIP) { - String name = player.getName(); - db.recordSecurityAlert(name, suspiciousIP); - Bukkit.broadcastMessage( - "§c§l[安全警告] §e" + name - + " §f账号疑似遭到 §c" - + suspiciousIP - + " §f的盗号尝试"); - String emailAddr = - (String) db.getField(name, "email"); - if (emailAddr != null - && !emailAddr.isEmpty()) { - String body = "玩家 " + name - + ":\n您的账号在 " - + new java.text.SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss") - .format(new java.util.Date()) - + " 疑似遭到 IP: " - + suspiciousIP - + " 的盗号尝试。\n" - + "建议立即修改密码。"; - final String to = emailAddr; - final String finalBody = body; - Bukkit.getScheduler() - .runTaskAsynchronously(this, - () -> email - .sendTempPassword( - to, name, - finalBody)); - } - } - - public void pushPendingAlerts(Player p) { - String name = p.getName(); - List> alerts = - db.getUnnotifiedAlerts(name); - if (alerts.isEmpty()) return; - p.sendMessage("§c§l========== 安全报警 =========="); - for (Map alert : alerts) { - p.sendMessage("§e时间: §f" - + alert.get("alert_date")); - p.sendMessage("§e可疑IP: §c" - + alert.get("alert_ip")); - db.markAlertNotified( - ((Number) alert.get("id")) - .intValue()); - } - p.sendMessage( - "§c建议使用 §e/sdf1_login pw §c修改密码"); - p.sendMessage( - "§c§l=================================="); - } - - public void handleReset(Player p) { - loginMgr.handleReset(p); - } - - public boolean dailySign(Player p) { - if (checkIn.isCheckedInToday(p.getName())) - return false; - p.sendMessage(checkIn.checkIn(p)); - invite.onInviteeCheckIn(p.getName()); - return true; - } - - - public void openAccountRequestPanel(Player p) { - Inventory g = Bukkit.createInventory( - null, 27, "§e§l账号请求"); - ItemStack gl = new ItemStack( - Material.GRAY_STAINED_GLASS_PANE); - ItemMeta glm = gl.getItemMeta(); - if (glm != null) { - glm.setDisplayName(" "); - gl.setItemMeta(glm); - } - for (int i = 0; i < 27; i++) - g.setItem(i, gl); - g.setItem(10, mkItem(Material.BARRIER, - "§c申请删除账号", - "§7申请删除自己名下的账号")); - g.setItem(13, mkItem(Material.PAPER, - "§6提交工单", - "§7提交bug反馈、求助、举报等")); - g.setItem(16, mkItem(Material.REDSTONE, - "§e找回密码", - "§7通过邮箱找回密码")); - g.setItem(22, mkItem(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - private void playSuccessSound(Player p) { - p.playSound(p.getLocation(), - Sound.BLOCK_NOTE_BLOCK_CHIME, - 1.0f, 0.79f); - p.playSound(p.getLocation(), - Sound.BLOCK_NOTE_BLOCK_BASS, - 1.0f, 0.79f); - p.playSound(p.getLocation(), - Sound.BLOCK_NOTE_BLOCK_PLING, - 1.0f, 0.79f); - } - - private int countItems(Player p) { - int c = 0; - for (ItemStack it : p.getInventory() - .getContents()) - if (it != null && it.getType() - != Material.AIR) c++; - return c; - } - - private int countArmor(Player p) { - int c = 0; - for (ItemStack it : p.getInventory() - .getArmorContents()) - if (it != null && it.getType() - != Material.AIR) c++; - return c; - } - - private int countExtra(Player p) { - int c = 0; - for (ItemStack it : p.getInventory() - .getExtraContents()) - if (it != null && it.getType() - != Material.AIR) c++; - return c; - } - - /** - * 检查备份是否有效(包含非雪球的真实物品) - */ - public boolean isValidBackup(String[] backup) { - if (backup == null - || backup[0] == null - || backup[0].isEmpty()) { - return false; - } - try { - ItemStack[] items = - InventorySerializer - .fromBase64(backup[0]); - if (items == null) return false; - for (ItemStack item : items) { - if (item == null - || item.getType() - == Material.AIR) - continue; - if (isMenuSnowball(item)) - continue; - return true; - } - } catch (Exception e) { - return false; - } - return false; - } - - /** - * 控制台打印 base64 前10位 - */ - private void debugPrintBase64(String label, - String name, String base64) { - if (base64 == null || base64.isEmpty()) { - getLogger().info( - "[BACKUP_DEBUG] " + label - + " " + name - + ": "); - return; - } - String preview = - base64.length() > 10 - ? base64.substring(0, 10) - : base64; - getLogger().info( - "[BACKUP_DEBUG] " + label + " " - + name + ": " + preview); - } - - private ItemStack mkItem(Material mat, - String name, String... lore) { - ItemStack it = new ItemStack(mat); - ItemMeta im = it.getItemMeta(); - if (im != null) { - im.setDisplayName(name); - if (lore.length > 0) { - List list = - new ArrayList<>(); - for (String l : lore) - if (!l.isEmpty()) list.add(l); - if (!list.isEmpty()) - im.setLore(list); - } - it.setItemMeta(im); - } - return it; - } - - private String buildIPKickMessage(Player p, - String ip) { - List> accounts = - db.getAccountDetailsByIP(ip); - StringBuilder sb = new StringBuilder(); - sb.append("§c§l======== IP注册已满 ========\n"); - sb.append("§e您的IP §f").append(ip) - .append(" §e下已注册 §f") - .append(accounts.size()) - .append(" §e个账号:\n"); - java.text.SimpleDateFormat sdf = - new java.text.SimpleDateFormat( - "yyyy-MM-dd HH:mm"); - int idx = 1; - for (Map acc : accounts) { - long regTime = ((Number) acc.get( - "register_time")).longValue(); - String dateStr = regTime > 0 - ? sdf.format(new java.util.Date( - regTime)) - : "未知"; - sb.append("§7").append(idx) - .append(". §f") - .append(acc.get("player_name")) - .append(" §7注册于: §e") - .append(dateStr).append("\n"); - idx++; - } - sb.append("§7-----------------------------\n"); - sb.append("§e请使用已有账号登录\n"); - sb.append("§e登录后输入 §f/oa §e提交工单\n"); - sb.append("§c§l============================"); - return sb.toString(); - } - /** - * 读取备份,返回物品名预览 - */ - private String getBackupItemPreview( - int backupId) { - try { - String[] backup = - db.getInventoryBackupById( - backupId); - if (backup == null - || backup[0] == null - || backup[0].isEmpty()) - return "§7空"; - ItemStack[] items = - InventorySerializer - .fromBase64( - backup[0]); - if (items == null) - return "§7空"; - Map counts = - new LinkedHashMap<>(); - for (ItemStack it : items) { - if (it == null - || it.getType() - == Material.AIR) - continue; - String name = translateMaterial( - it.getType().name()); - counts.merge(name, - it.getAmount(), - Integer::sum); - } - if (counts.isEmpty()) - return "§7空"; - StringBuilder sb = - new StringBuilder(); - int shown = 0; - for (Map.Entry en - : counts.entrySet()) { - if (shown >= 5) { - sb.append(" §7..."); - break; - } - if (shown > 0) - sb.append("§7, "); - sb.append("§f").append(en.getKey()) - .append("§7x") - .append(en.getValue()); - shown++; - } - return sb.toString(); - } catch (Exception e) { - return "§7解析失败"; - } - } - - private String translateMaterial( - String name) { - switch (name) { - case "GRASS_BLOCK": - return "草方块"; - case "OAK_LOG": - return "橡木原木"; - case "OAK_PLANKS": - return "橡木木板"; - case "STONE": - return "石头"; - case "COBBLESTONE": - return "圆石"; - case "DIRT": - return "泥土"; - case "SAND": - return "沙子"; - case "GRAVEL": - return "砂砾"; - case "DIAMOND": - return "钻石"; - case "DIAMOND_ORE": - return "钻石矿石"; - case "IRON_INGOT": - return "铁锭"; - case "GOLD_INGOT": - return "金锭"; - case "COAL": - return "煤炭"; - case "COAL_ORE": - return "煤矿石"; - case "RAW_IRON": - return "粗铁"; - case "RAW_GOLD": - return "粗金"; - case "OAK_LEAVES": - return "橡树树叶"; - case "OAK_SAPLING": - return "橡树树苗"; - case "STICK": - return "木棍"; - case "CRAFTING_TABLE": - return "工作台"; - case "FURNACE": - return "熔炉"; - case "CHEST": - return "箱子"; - case "TORCH": - return "火把"; - case "BEDROCK": - return "基岩"; - case "TNT": - return "TNT"; - case "REDSTONE": - return "红石"; - case "REDSTONE_ORE": - return "红石矿石"; - case "LAPIS_LAZULI": - return "青金石"; - case "EMERALD": - return "绿宝石"; - case "EMERALD_ORE": - return "绿宝石矿石"; - case "OBSIDIAN": - return "黑曜石"; - case "GLOWSTONE": - return "萤石"; - case "ARROW": - return "箭"; - case "BOW": - return "弓"; - case "IRON_SWORD": - return "铁剑"; - case "DIAMOND_SWORD": - return "钻石剑"; - case "IRON_PICKAXE": - return "铁镐"; - case "DIAMOND_PICKAXE": - return "钻石镐"; - case "IRON_AXE": - return "铁斧"; - case "DIAMOND_AXE": - return "钻石斧"; - case "IRON_HELMET": - return "铁头盔"; - case "IRON_CHESTPLATE": - return "铁胸甲"; - case "IRON_LEGGINGS": - return "铁护腿"; - case "IRON_BOOTS": - return "铁靴子"; - case "DIAMOND_HELMET": - return "钻石头盔"; - case "DIAMOND_CHESTPLATE": - return "钻石胸甲"; - case "DIAMOND_LEGGINGS": - return "钻石护腿"; - case "DIAMOND_BOOTS": - return "钻石靴子"; - case "BREAD": - return "面包"; - case "COOKED_BEEF": - return "牛排"; - case "COOKED_PORKCHOP": - return "熟猪排"; - case "COOKED_CHICKEN": - return "熟鸡肉"; - case "GOLDEN_APPLE": - return "金苹果"; - case "ENDER_PEARL": - return "末影珍珠"; - case "BLAZE_ROD": - return "烈焰棒"; - case "GHAST_TEAR": - return "恶魂之泪"; - case "NETHER_STAR": - return "下界之星"; - case "SHULKER_SHELL": - return "潜影壳"; - case "ELYTRA": - return "鞘翅"; - case "TOTEM_OF_UNDYING": - return "不死图腾"; - case "TRIDENT": - return "三叉戟"; - case "ENCHANTED_GOLDEN_APPLE": - return "附魔金苹果"; - default: - // 转中文名格式:SANDSTONE -> 沙石 - return name.toLowerCase() - .replace("_", " "); - } - } - - @EventHandler - public void onTeleportBiome( - PlayerTeleportEvent e) { - if (questTracker == null) return; - Player p = e.getPlayer(); - if (isFrozen(p)) return; - Location to = e.getTo(); - if (to == null) return; - String toKey = to.getBlock() - .getBiome().getKey().getKey(); - questTracker.onPlayerMove( - p.getName(), toKey); - } - - @EventHandler - public void onJoin(PlayerJoinEvent e) { - Player p = e.getPlayer(); - String name = p.getName(); - String ip = getPlayerIP(p); - // 重置聊天输入状态(防止上次退出时残留) - chatInput.reset(p); - if (needsPasswordChange.contains(name)) - needsPasswordChange.remove(name); - lastActivity.put(p.getUniqueId(), - System.currentTimeMillis()); - if (needsPasswordChange.contains(name)) - needsPasswordChange.remove(name); - - boolean isBedrock = - verification.isBedrockPlayer(p); - boolean isOnlineMode = - verification.isOnlineMode(); - - if (!db.userExists(name)) { - if (config.maxAccountsPerIP > 0 - && ip != null - && !ipGroup.canRegister(ip)) { - p.kickPlayer( - buildIPKickMessage(p, ip)); - return; - } - } - if (isFrozen(p)) { - p.setAllowFlight(true); - frozenFlightPlayers.add(p.getName()); - } - if (questTracker != null) { - questTracker.debugLogin(p.getName()); - } - - if (!"manual".equals(config.approvalMode) - && !db.userExists(name)) { - AccountRequestManager.Request req = - accountRequest.createRequest( - name, name, ip); - if (req != null) { - p.kickPlayer( - config.msg("need_approval") - .replace("{id}", - String.valueOf( - req.id))); - return; - } - } - - if (isOnlineMode) { - autoLogin(p, "premium"); - return; - } - if (isBedrock) { - autoLogin(p, "bedrock"); - return; - } - if (canAutoLogin(p)) { - autoLogin(p, "ip_reconnect"); - return; - } - afk.remove(p.getUniqueId()); - afk.recordAction(p.getUniqueId()); - hideInventoryDelayed(p, 0); - - - verification.verifyPremiumAsync(p, - isPremium -> { - if (isPremium) { - Bukkit.getScheduler() - .runTask(this, - () -> autoLogin(p, - "premium")); - } else { - Bukkit.getScheduler() - .runTask(this, () -> { - joinTime.put( - p.getUniqueId(), - System.currentTimeMillis()); - if (!db.userExists(name)) - p.sendMessage(config.msg( - "not_registered")); - else - p.sendMessage(config.msg( - "not_logged_in")); - }); - } - }); - } - - @EventHandler - public void onQuit(PlayerQuitEvent e) { - Player p = e.getPlayer(); - // 记录冻结状态的飞行 - if (!db.userExists(p.getName()) - || !loggedIn.contains(p.getName())) { - p.setAllowFlight(true); - frozenFlightPlayers.add(p.getName()); - } - - String name = p.getName(); - chatInput.reset(p); - - boolean wasLoggedIn = - loggedIn.contains(name); - - boolean hasRealItems = false; - for (ItemStack it : p.getInventory() - .getContents()) { - if (it != null - && it.getType() - != Material.AIR - && !isMenuSnowball(it)) { - hasRealItems = true; - break; - } - } - - if (wasLoggedIn || hasRealItems) { - try { - String contents = - InventorySerializer - .toBase64( - p.getInventory() - .getContents()); - String armor = - InventorySerializer - .toBase64( - p.getInventory() - .getArmorContents()); - String extra = - InventorySerializer - .toBase64( - p.getInventory() - .getExtraContents()); - int level = p.getLevel(); - double exp = p.getExp(); - - debugPrintBase64( - "退出保存", name, contents); - db.saveInventoryBackup(name, - contents, armor, extra, - level, exp); - } catch (Exception ex) { - getLogger().severe( - "[Sdf1_login] " - + "退出保存背包失败: " - + name); - } - } else { - getLogger().info( - "[BACKUP_DEBUG] 退出跳过 " - + name + ": 背包为空" - + "(插件已清空)"); - } - - if (wasLoggedIn) { - loggedIn.remove(name); - db.setLoggedIn(name, false); - recordIPLogin(p); - } - - frozenFlightPlayers.remove(name); // ← 新增 - lastActivity.remove(p.getUniqueId()); - joinTime.remove(p.getUniqueId()); - joinLoc.remove(p.getUniqueId()); - needsPasswordChange.remove(name); - } - - private static final String MENU_SNOWBALL_TAG = - "sdf1_menu"; - - // 雪球菜单 - public void giveMenuSnowball(Player p) { - // 检查开关 - Object val = db.getField( - p.getName(), "menu_snowball"); - if (val != null) { - int on = val instanceof Number - ? ((Number) val).intValue() - : 1; - if (on == 0) { - return; // 开关关了,不发放 - } - } - // 检查是否已有 - for (ItemStack it : p.getInventory() - .getContents()) { - if (isMenuSnowball(it)) { - return; - } - } - // 发放 - ItemStack snow = new ItemStack( - Material.SNOWBALL); - ItemMeta im = snow.getItemMeta(); - if (im != null) { - im.setDisplayName( - "§e§l[菜单] §f右键打开主菜单"); - List lore = - new ArrayList<>(); - lore.add("§7右键点击打开功能菜单"); - lore.add("§8" + MENU_SNOWBALL_TAG); - im.setLore(lore); - snow.setItemMeta(im); - } - int slot = p.getInventory() - .firstEmpty(); - if (slot >= 0) { - p.getInventory().setItem( - slot, snow); - } else { - p.getWorld().dropItemNaturally( - p.getLocation(), snow); - } - } - - /** - * 手动发放雪球菜单(绕过开关检查) - */ - public void forceGiveMenuSnowball( - Player p) { - // 只检查是否已有 - for (ItemStack it : p.getInventory() - .getContents()) { - if (isMenuSnowball(it)) { - return; - } - } - ItemStack snow = new ItemStack( - Material.SNOWBALL); - ItemMeta im = snow.getItemMeta(); - if (im != null) { - im.setDisplayName( - "§e§l[菜单] §f右键打开主菜单"); - List lore = - new ArrayList<>(); - lore.add("§7右键点击打开功能菜单"); - lore.add("§8" + MENU_SNOWBALL_TAG); - im.setLore(lore); - snow.setItemMeta(im); - } - int slot = p.getInventory() - .firstEmpty(); - if (slot >= 0) { - p.getInventory().setItem( - slot, snow); - } else { - p.getWorld().dropItemNaturally( - p.getLocation(), snow); - } - } - - private boolean isMenuSnowball(ItemStack item) { - if (item == null - || item.getType() - != Material.SNOWBALL) - return false; - if (!item.hasItemMeta()) - return false; - ItemMeta im = item.getItemMeta(); - if (im == null) return false; - if (!im.hasLore()) return false; - List lore = im.getLore(); - if (lore == null) return false; - for (String line : lore) { - if (line.contains(MENU_SNOWBALL_TAG)) - return true; - } - return false; - } - - @EventHandler - public void onMove(PlayerMoveEvent e) { - Player p = e.getPlayer(); - - // 已登录:一次性关闭飞行 - if (!isFrozen(p)) { - if (frozenFlightPlayers.remove( - p.getName())) { - p.setAllowFlight(false); - p.setFlying(false); - } - return; - } - - - // 未登录:检查是否能移动 - Location from = e.getFrom(); - Location to = e.getTo(); - if (to == null) return; - - // 允许转头(无位移) - if (from.getBlockX() == to.getBlockX() - && from.getBlockY() == to.getBlockY() - && from.getBlockZ() == to.getBlockZ()) { - return; - } - - // 有位移 → 取消移动 - e.setCancelled(true); - - // 确保允许飞行(防止被踢出) - if (!p.getAllowFlight()) { - p.setAllowFlight(true); - } - - // 确保已记录(首次加入时未触发移动的情况) - if (!frozenFlightPlayers.contains( - p.getName())) { - frozenFlightPlayers.add(p.getName()); - } - } - - @EventHandler - public void onInteract(PlayerInteractEvent e) { - Player p = e.getPlayer(); - ItemStack item = e.getItem(); - if (item == null - || item.getType() - != Material.SNOWBALL) - return; - if (!isMenuSnowball(item)) return; - if (isFrozen(p)) return; - if (!loggedIn.contains(p.getName())) return; - e.setCancelled(true); - gui.openMain(p); - } - - @EventHandler - public void onBlockBreak(BlockBreakEvent e) { - Player p = e.getPlayer(); - if (isFrozen(p)) { - e.setCancelled(true); - return; - } - afk.recordAction(p.getUniqueId()); - } - - @EventHandler - public void onBlockPlace(BlockPlaceEvent e) { - if (isFrozen(e.getPlayer())) - e.setCancelled(true); - } - - @EventHandler - public void onDrop(PlayerDropItemEvent e) { - if (isFrozen(e.getPlayer())) { - e.setCancelled(true); - return; - } - // 禁止丢弃菜单雪球 - ItemStack drop = - e.getItemDrop().getItemStack(); - if (isMenuSnowball(drop)) { - e.setCancelled(true); - } - } - - @EventHandler - public void onPickup(PlayerPickupItemEvent e) { - if (isFrozen(e.getPlayer())) - e.setCancelled(true); - } - - @EventHandler - public void onDamage(EntityDamageEvent e) { - if (e.getEntity() instanceof Player) { - if (isFrozen((Player) e.getEntity())) - e.setCancelled(true); - } - } - - @EventHandler - public void onEntityAttack( - EntityDamageByEntityEvent e) { - if (e.getDamager() instanceof Player - && isFrozen( - (Player) e.getDamager())) - e.setCancelled(true); - if (e.getEntity() instanceof Player - && isFrozen( - (Player) e.getEntity())) - e.setCancelled(true); - } - - @EventHandler(priority = EventPriority.LOWEST) - public void onChat(AsyncPlayerChatEvent e) { - Player p = e.getPlayer(); - String msg = e.getMessage(); - - if (chatInput.getState(p).type - != ChatInputManager.InputType.NONE) { - e.setCancelled(true); - ChatInputManager.InputType inputType = - chatInput.getState(p).type; - if (ticket != null - && ticket.handleChatInput( - p, msg, inputType)) { - return; - } - chatInput.handleInput(p, msg, this); - return; - } - - if (isFrozen(p)) { - e.setCancelled(true); - return; - } -// 在 onChat 方法中,isFrozen 检查之后 -// (在 chatFilter 检查之前) - if (!isFrozen(p) - && questTracker != null) { - final String pn = p.getName(); - Bukkit.getScheduler().runTask(this, - () -> questTracker - .onPlayerChat(pn)); - } - - if (chatFilter == null) return; - // 安全兜底:任何输入状态下跳过过滤 - if (chatInput.getState(p).type - != ChatInputManager.InputType.NONE) { - return; - } - - if (chatFilter.isPlayerWhitelisted( - p.getName())) return; - // 联控:绑定邮箱/配置SMTP时跳过URL过滤 - InputType inType = - chatInput.getState(p).type; - if (inType == InputType.SET_EMAIL - || inType == InputType.SET_EMAIL_VERIFY - || inType == InputType.SMTP_CONFIG - || inType == InputType.SMTP_VERIFY) { - return; - } - - if (chatFilter.isMuted(p.getName())) { - e.setCancelled(true); - p.sendMessage( - chatFilter.msg("chat_muted")); - return; - } - - List urls = - chatFilter.extractUrls(msg); - if (urls.isEmpty()) return; - - for (String u : urls) { - if (chatFilter.isPlayerWhitelisted(u)) - continue; - if (!chatFilter.isLikelyDomain(u)) - continue; - - e.setCancelled(true); - chatFilter.incrementViolation( - p.getName()); - int count = chatFilter - .getViolationCount(p.getName()); - - p.sendMessage(chatFilter - .msg("chat_url_blocked") - .replace("{url}", u)); - p.sendMessage(chatFilter - .msg("chat_url_violation") - .replace("{count}", - String.valueOf(count))); - - chatFilter.applyPunishment(p, count); - - if (chatFilter.isNotifyAdmin()) { - String tag = config.adminTag; - for (Player op : - Bukkit.getOnlinePlayers()) { - if (op.getScoreboardTags() - .contains(tag) - && !op.equals(p)) { - op.sendMessage(chatFilter - .msg("chat_url_admin_notify") - .replace("{player}", - p.getName()) - .replace("{url}", u) - .replace("{count}", - String.valueOf( - count))); - } - } - } - - if (chatFilter.isNotifyAll()) { - Bukkit.broadcastMessage(chatFilter - .msg("chat_url_broadcast") - .replace("{player}", - p.getName())); - } - } - } - - - - // ===== Inventory Click ===== - // ===== Inventory Click ===== - @EventHandler - public void onInvClick(InventoryClickEvent e) { - if (!(e.getWhoClicked() instanceof Player)) - return; - Player p = (Player) e.getWhoClicked(); - if (isFrozen(p)) { - e.setCancelled(true); - return; - } - String title = e.getView().getTitle(); - int slot = e.getRawSlot(); - - // Shift+点击背包物品 → 存入垃圾箱 - if (e.isShiftClick() - && e.getClickedInventory() != null - && e.getClickedInventory() - != e.getView().getTopInventory() - && e.getCurrentItem() != null - && e.getCurrentItem().getType() - != Material.AIR - && !isMenuSnowball( - e.getCurrentItem()) - && title.equals( - "§6§l垃圾回收站")) { - e.setCancelled(true); - - ItemStack clicked = - e.getCurrentItem().clone(); - - getLogger().info( - "[垃圾站] Shift存入: " - + clicked.getType() - + " x" + clicked.getAmount()); - - // 先存入数据库 - garbage.saveItem(clicked); - - // 关闭背包强制同步服务器与客户端状态 - // (解决Paper的shift-click取消后仍移动物品的问题) - p.closeInventory(); - - // 1 tick后:移除物品 + 重新打开垃圾站 - Bukkit.getScheduler() - .runTaskLater(this, - () -> { - // 此时物品状态已同步 - // 搜索并移除对应物品 - boolean found = false; - for (int i = 0; - i < p.getInventory() - .getSize(); - i++) { - ItemStack is = - p.getInventory() - .getItem(i); - if (is != null - && is.getType() - == clicked - .getType() - && is.getAmount() - == clicked - .getAmount()) { - p.getInventory() - .setItem( - i, - null); - found = true; - getLogger() - .info( - "[垃圾站] " - + "移除成功: slot=" - + i); - break; - } - } - if (!found) { - getLogger() - .info( - "[垃圾站] " - + "物品已被移走"); - } - - p.updateInventory(); - - // 重新打开垃圾站 - garbage - .openRecyclePage( - p, - garbage - .getRecyclePage( - p - .getName())); - }, 2L); - return; - } - - - if (needsPasswordChange - .contains(p.getName()) - && !title.equals( - "§6§l任务面板")) { - e.setCancelled(true); - p.sendMessage("§c请先修改密码!"); - return; - } - // 任务中心 - if (title.equals( - GUIManager.T_TASK_CENTER)) { - e.setCancelled(true); - if (slot == 26) gui.openMain(p); - else if (slot == 10) - gui.openGiftStages(p); - else if (slot == 12) - gui.openTaskList(p, "主线任务"); - else if (slot == 14) - gui.openTaskList(p, "支线任务"); - else if (slot == 16) { - if (dailySign(p)) { - gui.openTaskCenter(p); - playSuccessSound(p); - } else - p.sendMessage(config.msg( - "checkin_already")); - } - else if (slot == 21) { - handleCheckInReport(p); - } - return; - } - - // 工单GUI - if (title.equals( - TicketManager.T_TICKET_MAIN) - || title.equals( - TicketManager.T_TICKET_CREATE) - || title.equals( - TicketManager.T_TICKET_MY) - || title.equals( - TicketManager.T_TICKET_ADMIN) - || title.equals( - TicketManager.T_TICKET_DETAIL) - || title.equals( - "§d§l服务商面板") - || title.equals( - "§6§l抢单大厅") - || title.equals( - "§b§l我处理中的工单")) { - e.setCancelled(true); - ticket.handleClick( - p, title, slot); - return; - } - - // 垃圾回收站 - if (title.equals("§6§l垃圾回收站")) { - if (slot < 0 || slot >= 54) return; - - // 功能格 49-53 - if (slot >= 49 && slot <= 53) { - e.setCancelled(true); - garbage.handleClick( - p, title, slot); - return; - } - - ItemStack inSlot = - e.getInventory().getItem(slot); - ItemStack cursor = e.getCursor(); - - // ===== 取出 ===== - if (inSlot != null - && inSlot.hasItemMeta() - && inSlot.getItemMeta().hasLore()) { - List lore = - inSlot.getItemMeta().getLore(); - if (lore != null && !lore.isEmpty() - && lore.get(0) - .startsWith("§7ID: #")) { - e.setCancelled(true); - try { - String numStr = lore.get(0) - .replace("§7ID: #", "") - .trim(); - int dbId = - Integer.parseInt(numStr); - if (garbage.removeItem(dbId)) { - ItemStack give = - inSlot.clone(); - ItemMeta gm = - give.getItemMeta(); - gm.setLore(null); - give.setItemMeta(gm); - p.getInventory() - .addItem(give); - e.getInventory() - .setItem(slot, null); - } - } catch (Exception ex) { - p.sendMessage("§c取出失败"); - } - return; - } - } - - // ===== 光标存入 ===== - if (cursor != null - && cursor.getType() - != Material.AIR - && !isMenuSnowball(cursor)) { - ItemStack toSave = cursor.clone(); - getLogger().info( - "[垃圾站] 存入: " - + toSave.getType() - + " x" + toSave.getAmount()); - garbage.saveItem(toSave); - e.setCancelled(true); - Bukkit.getScheduler().runTaskLater( - this, () -> { - p.getOpenInventory() - .setCursor(null); - garbage.openRecyclePage(p, - garbage.getRecyclePage( - p.getName())); - }, 1L); - return; - } - - // 默认取消 - e.setCancelled(true); - return; - } - - // 主菜单 - if (title.equals(GUIManager.T_MAIN)) { - e.setCancelled(true); - 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.openTaskCenter(p); - else if (slot == 18) ticket.openMain(p); - else if (slot == 20) { - if (p.isOp() || p.getScoreboardTags() - .contains(config.adminTag)) { - garbage.openRecycle(p); - } - } - - else if (slot == 22 && isAdmin(p)) { - p.closeInventory(); - chatInput.getState(p).type = - ChatInputManager.InputType - .ADMIN_AUTH; - p.sendMessage("请输入管理密码:"); - } - return; - } - - - // 我的信息 - if (title.equals(GUIManager.T_MY_INFO)) { - e.setCancelled(true); - if (slot == 26) gui.openMain(p); - else if (slot == 10) { - if (checkIn.isCheckedInToday( - p.getName())) - p.sendMessage(config.msg( - "checkin_already")); - else { - p.sendMessage( - checkIn.checkIn(p)); - invite.onInviteeCheckIn( - p.getName()); - playSuccessSound(p); - gui.openMyInfo(p); - } - } else if (slot == 11) { - chatInput.getState(p).type = - ChatInputManager.InputType - .CHANGE_PWD_STEP1; - p.closeInventory(); - p.sendMessage("§e请输入当前密码:"); - } else if (slot == 12) { - chatInput.getState(p).type = - ChatInputManager.InputType - .BACK_CHECKIN; - p.closeInventory(); - p.sendMessage("§e请输入补签日期:"); - } else if (slot == 13) { - String code = (String) db.getField( - p.getName(), "invite_code"); - if (code == null || code.isEmpty()) - code = invite.generateCode(p); - p.sendMessage("§e邀请码: " + code); - } else if (slot == 14) { - chatInput.getState(p).type = - ChatInputManager.InputType - .SET_EMAIL; - p.closeInventory(); - p.sendMessage("§e请输入邮箱:"); - } else if (slot == 15) - p.openInventory( - points.createShopGUI(p)); - return; - } - - // 管理面板 - if (title.equals(GUIManager.T_ADMIN)) { - e.setCancelled(true); - if (slot == 22) gui.openMain(p); - else if (slot == 10) { - chatInput.getState(p).type = - ChatInputManager.InputType - .SMTP_CONFIG; - p.closeInventory(); - p.sendMessage("§e当前SMTP: " - + config.getSmtp("smtp地址")); - p.sendMessage("§e输入新地址(0跳过):"); - } else if (slot == 11) - gui.openUserManagement(p); - else if (slot == 12) gui.openMyInfo(p); - else if (slot == 14) - p.openInventory( - points.createShopGUI(p)); - else if (slot == 16) { - if (e.getClick().isLeftClick()) { - config.afkEnabled = - !config.afkEnabled; - p.sendMessage(config.msg( - config.afkEnabled - ? "afk_set_enabled" - : "afk_set_disabled")); - config.saveSettings(); - gui.openAdmin(p); - } else if (e.getClick() - .isRightClick()) { - p.closeInventory(); - chatInput.getState(p).type = - ChatInputManager.InputType - .ADMIN_SET_AFK_TIME; - p.sendMessage( - "§e输入挂机时长(分钟):"); - } - } - return; - } - - // 任务中心 - if (title.equals( - GUIManager.T_TASK_CENTER)) { - e.setCancelled(true); - if (slot == 22) gui.openMain(p); - else if (slot == 10) - gui.openGiftStages(p); - else if (slot == 12) - gui.openTaskList(p, "主线任务"); - else if (slot == 14) - gui.openTaskList(p, "支线任务"); - else if (slot == 16) { - if (dailySign(p)) { - gui.openTaskCenter(p); - playSuccessSound(p); - } else - p.sendMessage(config.msg( - "checkin_already")); - } - return; - } - - // 新人任务 - if (title.equals( - GUIManager.T_GIFT_STAGES)) { - e.setCancelled(true); - if (slot == 53) { - gui.openTaskCenter(p); - } else if (slot == 52) { - // 打卡上报 - handleCheckInReport(p); - } else if (slot == 4) { - // 进度标题 - } else if (slot >= 10 && slot < 53) { - int idx = slot - 10; - QuestTracker qt = questTracker; - List quests = - qt.getQuests("新人任务"); - if (idx < quests.size()) { - QuestTracker.QuestFile qf = - quests.get(idx); - boolean done = - qt.isStageCompleted( - p.getName(), qf); - if (done - && !qt.hasClaimed( - p.getName(), qf)) { - qt.markClaimed( - p.getName(), qf); - qt.claimRewards(p, qf); - gui.openGiftStages(p); - } else if (done - && qt.hasClaimed( - p.getName(), qf)) { - p.sendMessage( - "§c该阶段奖励已领取"); - } else { - p.sendMessage( - "§c该阶段尚未完成"); - } - } - } - return; - } - - - - // 邀请 - if (title.equals(GUIManager.T_INVITE)) { - e.setCancelled(true); - if (slot == 26) gui.openMain(p); - else if (slot == 10) { - String code = - invite.generateCode(p); - p.sendMessage("§e邀请码: " + code); - gui.openInvite(p); - } else if (slot == 16) { - chatInput.getState(p).type = - ChatInputManager.InputType - .INVITE_INPUT_CODE; - p.closeInventory(); - p.sendMessage("§e请输入邀请码:"); - } - - return; - } - - // 用户管理 - if (title.equals( - GUIManager.T_USER_MGMT)) { - e.setCancelled(true); - if (slot == 53) - gui.openAdmin(p); - else if (slot >= 0 && slot < 45) { - ItemStack item = - e.getInventory().getItem(slot); - if (item != null - && item.hasItemMeta() - && item.getItemMeta() - .hasDisplayName()) - gui.openUserDetail(p, - item.getItemMeta() - .getDisplayName() - .replace("§e", "") - .replace("§a", "")); - } - - return; - } - - // 用户详情 - if (title.startsWith("§e§l管理: ")) { - e.setCancelled(true); - String tgt = title.substring( - "§e§l管理: ".length()); - if (slot == 22) - gui.openUserManagement(p); - else if (slot == 10) { - chatInput.getState(p).type = - ChatInputManager.InputType - .ADMIN_SET_POINTS; - chatInput.getState(p) - .targetPlayer = tgt; - p.closeInventory(); - p.sendMessage("§e输入" + tgt - + "的新积分:"); - } else if (slot == 12) { - chatInput.getState(p).type = - ChatInputManager.InputType - .ADMIN_SET_PWD; - chatInput.getState(p) - .targetPlayer = tgt; - p.closeInventory(); - p.sendMessage("§e输入" + tgt - + "的临时密码:"); - } else if (slot == 13) { - gui.openCheckInRecords(p, tgt); - } else if (slot == 14) { - Player tp = Bukkit.getPlayer(tgt); - if (tp != null && tp.isOnline()) { - forceGiveMenuSnowball(tp); - p.sendMessage("§a已发放雪球菜单给 " - + tgt); - } else { - p.sendMessage("§c玩家不在线"); - } - } else if (slot == 16) { - - } else if (slot == 16) { - chatInput.getState(p).type = - ChatInputManager.InputType - .ADMIN_DELETE_CONFIRM; - chatInput.getState(p) - .targetPlayer = tgt; - p.closeInventory(); - p.sendMessage(config.msg( - "admin_delete_confirm")); - } - - return; - - } - - - // 积分商城 - if (title.startsWith("§d§l积分商城")) { - e.setCancelled(true); - if (slot == 49) gui.openMain(p); - else if (slot == 50 && isAdmin(p)) { - gui.openShopAdmin(p); - } else - points.handleClick(p, slot); - return; - } - - // 商城管理 - if (title.equals("§c§l商城管理")) { - e.setCancelled(true); - if (slot == 49) { - p.openInventory( - points.createShopGUI(p)); - return; - } - return; - } - - // CY背包商城 - if (title.startsWith("§d§lCY背包商城")) { - e.setCancelled(true); - int size = e.getInventory().getSize(); - if (slot == size - 1) - p.openInventory( - points.createShopGUI(p)); - else points.handleCYClick(p, slot); - return; - } - - // 账号请求面板 - if (title.equals("§e§l账号请求")) { - e.setCancelled(true); - if (slot == 22) gui.openMain(p); - else if (slot == 10) { - p.closeInventory(); - p.sendMessage("§c§l[确认] §f请输入:"); - p.sendMessage("§e/sdf1_login del " - + p.getName()); - } else if (slot == 13) { - ticket.openCreate(p); - } else if (slot == 16) { - p.closeInventory(); - loginMgr.handleReset(p); - } - return; - } - - // 主线/支线任务 - if (title.equals("§d§l主线任务") - || title.equals("§d§l支线任务")) { - e.setCancelled(true); - if (slot == e.getInventory().getSize() - 1) - gui.openTaskCenter(p); - return; - } - } - /** - * 打卡上报:验证当前阶段条件 - */ - - private void handleCheckInReport(Player p) { - QuestTracker qt = questTracker; - if (qt == null) { - p.sendMessage("§c任务系统未加载"); - return; - } - - p.sendMessage("§e§l[打卡] §f正在检测环境..."); - - // === 检测当前群系 === - Location loc = p.getLocation(); - String biomeKey = loc.getBlock() - .getBiome().getKey().getKey(); - String worldName = loc.getWorld().getName(); - - List biomeMatched = - qt.checkInBiome( - p.getName(), - biomeKey, worldName, - loc.getX(), loc.getY(), - loc.getZ()); - - qt.recordCheckIn( - p.getName(), biomeKey, - qt.mapBiomeCategory( - biomeKey, worldName), - worldName, - loc.getX(), loc.getY(), - loc.getZ()); - - p.sendMessage("§7 群系: §e" - + biomeKey); - p.sendMessage("§7 分类: §e" - + qt.mapBiomeCategory( - biomeKey, worldName)); - p.sendMessage("§7 世界: §e" - + worldName); - p.sendMessage("§7 坐标: §e" - + String.format("%.0f, %.0f, %.0f", - loc.getX(), loc.getY(), - loc.getZ())); - - if (!biomeMatched.isEmpty()) { - for (String m : biomeMatched) { - p.sendMessage("§7 §a✓ " + m); - } - } - - // === 检测附近村庄(50格内有村民) === - boolean nearVillage = false; - for (Entity entity - : p.getNearbyEntities(50, 50, 50)) { - if (entity instanceof Villager) { - nearVillage = true; - break; - } - } - if (nearVillage) { - boolean counted = - qt.onReportVillage( - p.getName(), - worldName, - loc.getX(), - loc.getY(), - loc.getZ()); - if (counted) { - p.sendMessage( - "§7 §a村庄上报计数成功!"); - } else { - p.sendMessage( - "§7 §e村庄32格内已上报过"); - } - } - - - // === 验证所有未完成的任务 === - p.sendMessage(""); - p.sendMessage("§e§l[打卡] §f正在验证" - + "未完成的任务..."); - - List categories = - Arrays.asList( - "新人任务", - "主线任务", - "支线任务"); - - int totalMet = 0; - int totalChecked = 0; - int finishedCount = 0; - - for (String cat : categories) { - List quests = - qt.getQuests(cat); - for (QuestTracker.QuestFile qf - : quests) { - if (qt.isStageCompleted( - p.getName(), qf)) { - finishedCount++; - continue; - } - if (!qf.hasConditions()) - continue; - - p.sendMessage("§e§l[" - + qf.displayName + "]"); - - for (String cond - : qf.conditions) { - if (qt.isOptional(cond)) - continue; - totalChecked++; - - boolean met = - qt.isConditionMet( - p.getName(), - cond); - - if (met) { - totalMet++; - p.sendMessage("§7 §a✓ " - + qt.getProgress( - p.getName(), - cond)); - } else { - p.sendMessage("§7 §c✗ " - + qt.getProgress( - p.getName(), - cond)); - } - } - } - } - - p.sendMessage(""); - if (finishedCount > 0) { - p.sendMessage("§7已跳过 " - + finishedCount - + " 个已完成的阶段"); - } - if (totalChecked == 0) { - p.sendMessage( - "§a§l[打卡] §f没有" - + "未完成的条件"); - } else if (totalMet >= totalChecked) { - p.sendMessage( - "§a§l[打卡] §f全部通过!(" - + totalMet + "/" - + totalChecked + ")"); - playSuccessSound(p); - } else { - p.sendMessage( - "§c§l[打卡] §f未通过 (" - + totalMet + "/" - + totalChecked - + ") §c请继续完成"); - } - - gui.openTaskCenter(p); - } - - - - @EventHandler - public void onInvDrag(InventoryDragEvent e) { - if (!(e.getWhoClicked() instanceof Player)) - return; - Player p = (Player) e.getWhoClicked(); - if (isFrozen(p)) { - frozenFlightPlayers.add(p.getName()); - e.setCancelled(true); - return; - } - String t = e.getView().getTitle(); - - // 回收站拖拽 - if (t.equals("§6§l垃圾回收站")) { - for (int rawSlot : - e.getRawSlots()) { - if (rawSlot >= 49) { - e.setCancelled(true); - return; - } - } - // 0-48允许拖入,不取消 - // 延迟2tick后扫描格子 - final Player fp = p; - Bukkit.getScheduler() - .runTaskLater(this, - () -> { - Inventory top = fp - .getOpenInventory() - .getTopInventory(); - for (int rawSlot : - e.getRawSlots()) { - if (rawSlot < 0 - || rawSlot >= 49) - continue; - ItemStack item = - top.getItem(rawSlot); - if (item != null - && item.getType() - != Material.AIR - && !isMenuSnowball( - item)) { - garbage.saveItem( - item.clone()); - top.setItem( - rawSlot, null); - } - } - }, 2L); - return; - } - - //服务商工单系统 - if (t.equals(TicketManager.T_TICKET_MAIN) - || t.equals( - TicketManager.T_TICKET_CREATE) - || t.equals( - TicketManager.T_TICKET_MY) - || t.equals( - TicketManager.T_TICKET_ADMIN) - || t.equals( - TicketManager.T_TICKET_DETAIL) - || t.equals("§d§l服务商面板") - || t.equals("§6§l抢单大厅") - || t.equals( - "§b§l我处理中的工单") - || t.equals("§6§l我的历史工单") - || t.equals("§c§l商城管理")) { - e.setCancelled(true); - } - } - - // ===== Commands ===== - @Override - public boolean onCommand(CommandSender sender, - Command cmd, String label, - String[] args) { - - String cmdName = cmd.getName().toLowerCase(); - - if (cmdName.equals("recycle")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player pr = (Player) sender; - if (!pr.isOp() - && !pr.getScoreboardTags() - .contains(config.adminTag)) { - pr.sendMessage("§c权限不足"); - return true; - } - garbage.openRecycle(pr); - return true; - } - - if (cmd.getName().equalsIgnoreCase("radio")) { - getLogger().info( - "[Radio] 收到命令: " - + Arrays.toString(args)); - - // 统一交给 RadioManager 处理 - radio.handleCommand(sender, args); - return true; - } - - - // /垃圾清理 - if (cmdName.equals("垃圾清理")) { - if (!isAdmin(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - int count = garbage.collectItems(); - sender.sendMessage("§a已清理 " - + count + " 个掉落物"); - return true; - } - // /menu 开关雪球菜单 - if (cmdName.equals("menu")) { - if (!(sender instanceof Player)) { - sender.sendMessage( - "§c仅玩家可用"); - return true; - } - Player p2 = (Player) sender; - if (args.length == 0) { - p2.sendMessage( - "§e用法: /menu <开/关>"); - return true; - } - String arg = args[0]; - if (arg.equals("开")) { - db.setField(p2.getName(), - "menu_snowball", 1); - p2.sendMessage( - "§a雪球菜单已开启," - + "下次进入将发放"); - } else if (arg.equals("关")) { - db.setField(p2.getName(), - "menu_snowball", 0); - p2.sendMessage( - "§c雪球菜单已关闭," - + "下次进入不再发放"); - } else { - p2.sendMessage( - "§e用法: /menu <开/关>"); - } - return true; - } -// /reg - if (cmdName.equals("reg") - || cmdName.equals("注册")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - chatInput.reset(p); - if (db.userExists(p.getName())) { - p.sendMessage( - "§c您已注册过,请使用 /login"); - return true; - } - try { - loginMgr.handleRegister(p, args); - } catch (Exception e) { - getLogger().severe( - "[Sdf1_login] reg命令异常: " - + e.getMessage()); - e.printStackTrace(); - p.sendMessage("§c注册出错,请联系管理员"); - } - // 注册/登录成功后统一处理 - if (loggedIn.contains(p.getName())) { - frozenFlightPlayers.remove(p.getName()); - p.setAllowFlight(false); - p.setFlying(false); - // 仅关闭冻结期间的飞行 - if (frozenFlightPlayers.remove( - p.getName())) { - p.setAllowFlight(false); - p.setFlying(false); - removeFlightOnce.add(p.getName()); - if (questTracker != null) { - questTracker.markConditionComplete( - p.getName(), - "完成注册登录"); - } - this.getWelcome().onLogin(p); - restoreInventory(p); - giveMenuSnowball(p); - } - return true; - } -} - - if (cmdName.equals("login") - || cmdName.equals("l") - || cmdName.equals("登录")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - chatInput.reset(p); - if (loggedIn.contains(p.getName())) { - frozenFlightPlayers.remove(p.getName()); - p.setAllowFlight(false); - p.setFlying(false); - removeFlightOnce.add(p.getName()); - p.sendMessage("§c您已登录"); - return true; - } - try { - loginMgr.handleLogin(p, args); - } catch (Exception e) { - getLogger().severe( - "[Sdf1_login] login命令异常: " - + e.getMessage()); - e.printStackTrace(); - p.sendMessage("§c登录出错,请联系管理员"); - } - // 登录成功后统一处理 - if (loggedIn.contains(p.getName())) { - if (questTracker != null) { - questTracker.markConditionComplete( - p.getName(), - "完成注册登录"); - } - this.getWelcome().onLogin(p); - restoreInventory(p); - giveMenuSnowball(p); - } - return true; - } - - // /找回密码 - if (cmdName.equals("找回密码")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - handleReset((Player) sender); - return true; - } - - // /签到 - if (cmdName.equals("签到")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - if (dailySign((Player) sender)) - playSuccessSound((Player) sender); - else - ((Player) sender).sendMessage( - config.msg("checkin_already")); - return true; - } - - // 绑定邮箱 - if (cmdName.equals("绑定邮箱")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p2 = (Player) sender; - if (args.length >= 1) { - String emailAddr = args[0]; - if (!emailAddr.contains("@") - || !emailAddr.contains(".")) { - p2.sendMessage("§c邮箱格式不正确"); - return true; - } - String oldEmail = (String) db.getField( - p2.getName(), "email"); - if (oldEmail != null - && !oldEmail.isEmpty()) { - chatInput.getState(p2).type = - ChatInputManager.InputType - .SET_EMAIL_VERIFY; - chatInput.getState(p2) - .targetPlayer = emailAddr; - chatInput.getState(p2) - .ticketTitle = "need_pwd"; - p2.sendMessage("§e您已绑定邮箱: " - + oldEmail); - p2.sendMessage( - "§e请输入登录密码以确认修改:"); - } else { - String code = String.valueOf( - (int) (Math.random() - * 900000 + 100000)); - chatInput.getState(p2).type = - ChatInputManager.InputType - .SET_EMAIL_VERIFY; - chatInput.getState(p2) - .targetPlayer = emailAddr; - chatInput.getState(p2) - .ticketTitle = code; - final String to = emailAddr; - final String c = code; - final Player fp = p2; - Bukkit.getScheduler() - .runTaskAsynchronously(this, - () -> { - boolean sent = email - .sendVerifyCode(to, c); - Bukkit.getScheduler().runTask( - this, () -> { - if (sent) { - fp.sendMessage( - "§a验证码已发送到 " - + to); - fp.sendMessage( - "§e输入6位验证码(0取消):"); - } else { - fp.sendMessage( - "§c发送失败"); - } - }); - }); - } - } else { - chatInput.getState(p2).type = - ChatInputManager.InputType - .SET_EMAIL; - p2.sendMessage("§e请输入邮箱:"); - } - return true; - } - - - // /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)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - openAccountRequestPanel( - (Player) sender); - return true; - } - - // /玩家信息 - if (cmdName.equals("玩家信息")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - gui.openMain((Player) sender); - return true; - } - - // ===== /sdf1_login ===== - if (!cmdName.equals("sdf1_login")) - return false; - - if (args.length == 0) { - if (!(sender instanceof Player)) { - sender.sendMessage( - "§e用法: /sdf1_login <子命令>"); - return true; - } - gui.openMain((Player) sender); - return true; - } - - String sub = args[0].toLowerCase(); - // update - 检查更新(控制台+玩家均可) - if (sub.equals("update")) { - if (!isAdmin(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - sender.sendMessage("§e[更新] 正在检测..."); - Bukkit.getScheduler() - .runTaskAsynchronously(this, - () -> updateChecker - .checkUpdate(sender)); - return true; - } - - // stop - if (sub.equals("stop")) { - if (!(sender instanceof Player)) { - // 控制台 - if (args.length >= 2) { - db.clearTempPassword(args[1]); - sender.sendMessage("§a已报废 " - + args[1] + " 的临时密码"); - } else { - db.clearAllTempPasswords(); - sender.sendMessage( - "§a已报废所有临时密码"); - } - return true; - } - Player p2 = (Player) sender; - if (args.length >= 2) { - String tgt = args[1]; - String myIP = - getPlayerIP(p2); - String targetIP = - db.getPlayerIPByName(tgt); - if (targetIP == null) { - Player tp = - Bukkit.getPlayer(tgt); - if (tp != null && tp.isOnline()) - targetIP = - getPlayerIP(tp); - } - if (targetIP == null - || !targetIP.equals(myIP)) { - p2.sendMessage( - "§c只能操作同IP下的玩家"); - return true; - } - db.clearTempPassword(tgt); - p2.sendMessage("§a已报废 " + tgt - + " 的临时密码"); - } else { - db.clearTempPassword(p2.getName()); - p2.sendMessage( - "§a已报废您的临时密码"); - } - return true; - } - - // reload - if (sub.equals("reload")) { - if (!isAdmin(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - config.loadMessages(); - config.loadSmtp(); - config.loadSettings(); - chatFilter.loadConfig(); - sender.sendMessage("§a配置已重载!"); - return true; - } - // back - 恢复背包备份 - if (sub.equals("back")) { - if (!isAdmin(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - if (args.length < 2) { - sender.sendMessage( - "§e用法: /sdf1_login back <玩家> [#编号]"); - return true; - } - String backTarget = args[1]; - if (args.length >= 3 - && args[2].startsWith("#")) { - // /sdf1_login back Aplayer #1 - int backupId; - try { - backupId = Integer.parseInt( - args[2].replace("#", "")); - } catch (NumberFormatException ex) { - sender.sendMessage( - "§c无效编号"); - return true; - } - String[] backup = - db.getInventoryBackupById( - backupId); - if (backup == null) { - sender.sendMessage( - "§c备份 #" + backupId - + " 不存在"); - return true; - } - // 如果玩家在线,直接恢复 - Player tp = Bukkit.getPlayer( - backTarget); - if (tp != null && tp.isOnline()) { - try { - tp.getInventory() - .setContents( - InventorySerializer - .fromBase64( - backup[0])); - if (backup[1] != null - && !backup[1] - .isEmpty()) - tp.getInventory() - .setArmorContents( - InventorySerializer - .fromBase64( - backup[1])); - if (backup[2] != null - && !backup[2] - .isEmpty()) - tp.getInventory() - .setExtraContents( - InventorySerializer - .fromBase64( - backup[2])); - int lv = Integer.parseInt( - backup[3]); - if (lv > 0) - tp.setLevel(lv); - tp.sendMessage( - "§a管理员已恢复您的背包"); - sender.sendMessage( - "§a已恢复 " + backTarget - + " 的背包 (备份#" - + backupId + ")"); - } catch (Exception e) { - sender.sendMessage( - "§c恢复失败: " - + e.getMessage()); - } - } else { - // 玩家离线:保存到数据库供下次登录恢复 - db.saveInventoryBackup( - backTarget, - backup[0], - backup[1], - backup[2], - Integer.parseInt( - backup[3]), - Double.parseDouble( - backup[4])); - sender.sendMessage( - "§a" + backTarget - + " 离线,备份已写入数据库," - + "下次登录时恢复"); - } - } else { - // /sdf1_login back Aplayer - // 罗列最近5条备份 - List> backups = - db.getInventoryBackups( - backTarget, 5); - if (backups.isEmpty()) { - sender.sendMessage( - "§c" + backTarget - + " 没有背包备份"); - return true; - } - sender.sendMessage( - "§e§l=== " + backTarget - + " 背包备份 ==="); - java.text.SimpleDateFormat sdf = - new java.text.SimpleDateFormat( - "MM-dd HH:mm:ss"); - for (Map b : - backups) { - int bid = ((Number) - b.get("id")) - .intValue(); - Object stObj = - b.get("save_time"); - long st = stObj != null - ? ((Number) stObj) - .longValue() : 0; - String time = st > 0 - ? sdf.format( - new java.util.Date(st)) - : "未知时间"; - String itemNames = - getBackupItemPreview( - bid); - sender.sendMessage( - "§7#" + bid - + " §f" + time - + " §7: " - + itemNames); - } - sender.sendMessage( - "§7使用 §e/sdf1_login back " - + backTarget - + " #编号 §7恢复"); - sender.sendMessage( - "§e§l================================"); - } - } - - // pw / password - if (sub.equals("pw") - || sub.equals("password")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p2 = (Player) sender; - - if (args.length >= 3) { - // /sdf1_login pw 旧密码 新密码 - String oldPwd = args[1]; - String newPwd = args[2]; - if (!PasswordUtils.validate( - newPwd)) { - p2.sendMessage(config.msg( - "password_format_error")); - return true; - } - String salt = (String) db.getField( - p2.getName(), - "password_salt"); - String hash = - PasswordUtils.hash( - oldPwd, salt); - boolean mainOk = - db.checkPassword( - p2.getName(), hash); - boolean tempOk = - db.checkPasswordOrTemp( - p2.getName(), hash); - if (!mainOk && !tempOk) { - p2.sendMessage(config.msg( - "password_wrong")); - return true; - } - String oldHash = - (String) db.getField( - p2.getName(), - "password_hash"); - String newSalt = - PasswordUtils.generateSalt(); - String newHash = - PasswordUtils.hash( - newPwd, newSalt); - if (newHash.equals(oldHash)) { - p2.sendMessage(config.msg( - "password_same")); - return true; - } - recordPasswordChange( - p2.getName(), - oldHash, salt); - db.setField(p2.getName(), - "password_hash", newHash); - db.setField(p2.getName(), - "password_salt", newSalt); - if (!mainOk && tempOk) { - db.clearTempPassword( - p2.getName()); - } - frozenFlightPlayers.remove(p2.getName()); - p2.setAllowFlight(false); - p2.setFlying(false); - needsPasswordChange.remove( - p2.getName()); - p2.sendMessage(config.msg( - "password_changed")); - return true; - } - - if (args.length == 2) { - // /sdf1_login pw 旧密码 - // 验证旧密码,然后聊天输入新密码 - String oldPwd = args[1]; - String salt = (String) db.getField( - p2.getName(), - "password_salt"); - String hash = - PasswordUtils.hash( - oldPwd, salt); - boolean mainOk = - db.checkPassword( - p2.getName(), hash); - boolean tempOk = - db.checkPasswordOrTemp( - p2.getName(), hash); - if (!mainOk && !tempOk) { - p2.sendMessage(config.msg( - "password_wrong")); - return true; - } - chatInput.getState(p2).type = - ChatInputManager.InputType - .CHANGE_PWD_STEP2; - chatInput.getState(p2) - .ticketTitle = - mainOk ? "main" : "temp"; - p2.sendMessage( - "§e请输入新密码:"); - return true; - } - - // /sdf1_login pw (无参数) - chatInput.getState(p2).type = - ChatInputManager.InputType - .CHANGE_PWD_STEP1; - p2.sendMessage( - "§e请输入当前密码:"); - return true; - } - - // email - if (sub.equals("email")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p2 = (Player) sender; - if (args.length >= 2) { - String emailAddr = args[1]; - if (!emailAddr.contains("@") - || !emailAddr.contains(".")) { - p2.sendMessage("§c邮箱格式不正确"); - return true; - } - String oldEmail = (String) db.getField( - p2.getName(), "email"); - if (oldEmail != null - && !oldEmail.isEmpty()) { - // 已绑定:先验证密码 - chatInput.getState(p2).type = - ChatInputManager.InputType - .SET_EMAIL_VERIFY; - chatInput.getState(p2) - .targetPlayer = emailAddr; - chatInput.getState(p2) - .ticketTitle = "need_pwd"; - p2.sendMessage("§e您已绑定邮箱: " - + oldEmail); - p2.sendMessage( - "§e请输入登录密码以确认修改:"); - } else { - // 未绑定:直接发验证码 - String code = String.valueOf( - (int) (Math.random() - * 900000 + 100000)); - chatInput.getState(p2).type = - ChatInputManager.InputType - .SET_EMAIL_VERIFY; - chatInput.getState(p2) - .targetPlayer = emailAddr; - chatInput.getState(p2) - .ticketTitle = code; - final String to = emailAddr; - final String c = code; - final Player fp = p2; - Bukkit.getScheduler() - .runTaskAsynchronously(this, - () -> { - boolean sent = email - .sendVerifyCode(to, c); - Bukkit.getScheduler().runTask( - this, () -> { - if (sent) { - fp.sendMessage( - "§a验证码已发送到 " - + to); - fp.sendMessage( - "§e输入6位验证码(0取消):"); - } else { - fp.sendMessage( - "§c发送失败"); - } - }); - }); - } - } else { - chatInput.getState(p2).type = - ChatInputManager.InputType - .SET_EMAIL; - p2.sendMessage("§e请输入邮箱:"); - } - return true; - } - - - // sign - if (sub.equals("sign")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - if (dailySign((Player) sender)) - playSuccessSound((Player) sender); - else - ((Player) sender).sendMessage( - config.msg("checkin_already")); - return true; - } - - // reset - if (sub.equals("reset")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - loginMgr.handleReset( - (Player) sender); - return true; - } - - // undo - if (sub.equals("undo")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p2 = (Player) sender; - PwdRollback rb = - pwdRollback.get(p2.getName()); - if (rb == null) { - p2.sendMessage("§c无可撤销记录"); - return true; - } - if (System.currentTimeMillis() - rb.time - > 600000L) { - pwdRollback.remove(p2.getName()); - p2.sendMessage("§c撤销已过期"); - return true; - } - db.setField(p2.getName(), - "password_hash", rb.hash); - db.setField(p2.getName(), - "password_salt", rb.salt); - pwdRollback.remove(p2.getName()); - p2.sendMessage("§a密码已撤销!"); - return true; - } - - // take - if (sub.equals("take")) { - if (!isAdmin(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - if (args.length < 3) { - sender.sendMessage( - "§e用法: /sdf1_login take <玩家> <积分>"); - return true; - } - int amount; - try { - amount = Integer.parseInt(args[2]); - } catch (NumberFormatException ex) { - sender.sendMessage("§c无效数字"); - return true; - } - if (!db.deductPoints(args[1], amount)) { - sender.sendMessage("§c积分不足"); - return true; - } - sender.sendMessage("§a已扣除 " + args[1] - + " " + amount + " 积分"); - return true; - } - - // set - if (sub.equals("set")) { - if (!isAdmin(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - if (args.length < 3) { - sender.sendMessage( - "§e用法: /sdf1_login set <玩家> <临时密码>"); - return true; - } - String tgt = args[1]; - String tempPwd = args[2]; - if (!PasswordUtils.validate(tempPwd)) { - sender.sendMessage("§c密码格式不符合要求"); - return true; - } - if (!db.userExists(tgt)) { - sender.sendMessage("§c玩家不存在"); - return true; - } - String salt = (String) db.getField( - tgt, "password_salt"); - String hash = PasswordUtils.hash( - tempPwd, salt); - db.setField(tgt, "temp_password", hash); - db.setField(tgt, "temp_pw_expire", - System.currentTimeMillis() - + 300000L); - - db.setField(tgt, "temp_pw_used", 0); - sender.sendMessage("§a已为 " + tgt - + " 设置临时密码"); - Player tp = Bukkit.getPlayer(tgt); - if (tp != null && tp.isOnline()) - tp.kickPlayer( - "§c管理员已设置临时密码"); - // 邮件通知 - String emailAddr = (String) db.getField( - tgt, "email"); - if (emailAddr != null - && !emailAddr.isEmpty()) { - String time = - new java.text.SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss") - .format(new java.util.Date()); - String body = "玩家 " + tgt - + ":\n您的密码已于 " + time - + " 被管理员重置为临时密码。\n" - + "如果是本人操作请忽略。\n" - + "如非本人操作请立即联系管理员。"; - final String to = emailAddr; - final String fName = tgt; - final String bodyF = body; - Bukkit.getScheduler() - .runTaskAsynchronously(this, - () -> email - .sendTempPassword( - to, fName, - bodyF)); - } - return true; - } - // give - 手动发放雪球菜单 - if (sub.equals("give")) { - if (args.length < 2) { - sender.sendMessage( - "§e用法: /sdf1_login give <玩家名>"); - return true; - } - Player tp = Bukkit.getPlayer( - args[1]); - if (tp == null || !tp.isOnline()) { - sender.sendMessage( - "§c玩家不在线: " - + args[1]); - return true; - } - forceGiveMenuSnowball(tp); - tp.sendMessage( - "§a管理员已为您发放菜单雪球"); - sender.sendMessage( - "§a已发放雪球菜单给 " - + args[1]); - return true; - } - - // shopadd - if (sub.equals("shopadd")) { - if (!isAdmin(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - if (args.length < 4) { - sender.sendMessage( - "§e用法: /sdf1_login shopadd <名称> <价格> <命令>"); - return true; - } - String shopName = args[1]; - int price; - try { - price = Integer.parseInt(args[2]); - } catch (NumberFormatException ex) { - sender.sendMessage("§c无效数字"); - return true; - } - String shopCmd = args[3]; - points.addItem(shopName, price, shopCmd); - sender.sendMessage("§a已添加商品: " - + shopName + " (" + price - + "积分)"); - return true; - } - - // shopdel - if (sub.equals("shopdel")) { - if (!isAdmin(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - if (args.length < 2) { - sender.sendMessage( - "§e用法: /sdf1_login shopdel <序号>"); - return true; - } - try { - int idx = Integer.parseInt(args[1]); - points.removeItem(idx); - sender.sendMessage("§a已删除商品 #" + idx); - } catch (NumberFormatException ex) { - sender.sendMessage("§c无效数字"); - } - return true; - } - - // get - if (sub.equals("get")) { - if (!isAdmin(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - if (args.length < 2) { - sender.sendMessage( - "§e用法: /sdf1_login get <玩家>"); - return true; - } - Map user = - db.getUser(args[1]); - if (user.isEmpty()) { - sender.sendMessage("§c玩家不存在"); - return true; - } - sender.sendMessage( - "§e=== " + args[1] + " ==="); - sender.sendMessage("§7积分: §e" - + user.getOrDefault("points", 0)); - sender.sendMessage("§7邮箱: §e" - + user.getOrDefault("email", "无")); - sender.sendMessage("§7签到天数: §e" - + user.getOrDefault( - "total_checkin_days", 0)); - sender.sendMessage("§7邀请码: §e" - + user.getOrDefault( - "invite_code", "无")); - return true; - } - - // del - if (sub.equals("del")) { - if (!isAdmin(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - if (args.length < 2) { - sender.sendMessage( - "§e用法: /sdf1_login del <玩家>"); - return true; - } - db.deleteUser(args[1]); - sender.sendMessage("§a已删除 " + args[1]); - Player tp = Bukkit.getPlayer(args[1]); - if (tp != null && tp.isOnline()) - tp.kickPlayer("§c账号已被管理员删除"); - return true; - } - - // kick (挂机) - if (sub.equals("kick")) { - if (!isAdmin(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - if (args.length < 2) { - sender.sendMessage( - "§e用法: /sdf1_login kick <开关> [分钟]"); - return true; - } - String mode = args[1].toLowerCase(); - if (mode.equals("on") - || mode.equals("开") - || mode.equals("启用")) { - config.afkEnabled = true; - if (args.length >= 3) { - try { - config.afkTimeout = - Integer.parseInt( - args[2]) * 60; - } catch (NumberFormatException ignored) { - } - } - config.saveSettings(); - int minutes = - config.afkTimeout / 60; - sender.sendMessage( - "§a挂机踢出已开启,超时: " - + minutes + " 分钟"); - } else if (mode.equals("off") - || mode.equals("关") - || mode.equals("停用")) { - config.afkEnabled = false; - config.saveSettings(); - sender.sendMessage( - "§c挂机踢出已关闭"); - } else { - sender.sendMessage( - "§c无效参数 on/off"); - } - return true; - } - - // add - if (sub.equals("add")) { - if (!isAdmin(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - if (args.length < 3) { - sender.sendMessage( - "§e用法: /sdf1_login add <玩家> <积分>"); - return true; - } - int amount; - try { - amount = Integer.parseInt(args[2]); - } catch (NumberFormatException ex) { - sender.sendMessage("§c无效数字"); - return true; - } - db.addPoints(args[1], amount); - sender.sendMessage("§a已为 " + args[1] - + " 增加 " + amount + " 积分"); - return true; - } - - // ticket - if (sub.equals("ticket") - || sub.equals("工单")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - ticket.openMain((Player) sender); - return true; - } - - sender.sendMessage("§c未知参数"); - return true; - } - /** - * 是否是垃圾站展示物品(带ID lore) - */ - private boolean isTrashDisplayItem( - ItemStack item) { - if (item == null - || item.getType() - == Material.AIR) - return false; - if (!item.hasItemMeta()) - return false; - ItemMeta im = item.getItemMeta(); - if (im == null || !im.hasLore()) - return false; - List lore = im.getLore(); - if (lore == null || lore.isEmpty()) - return false; - return lore.get(0) - .startsWith("§7ID: #"); - } - - private void sendChatHelp(Player p) { - p.sendMessage( - "§e§l======== 聊天过滤帮助 =========="); - p.sendMessage("§e/chat reload §7- 重载配置"); - p.sendMessage("§e/chat add §7- 添加白名单URL"); - p.sendMessage("§e/chat remove §7- 移除白名单URL"); - p.sendMessage("§e/chat addplayer <玩家> §7- 添加白名单玩家"); - p.sendMessage("§e/chat takeplayer <玩家> §7- 移除白名单玩家"); - p.sendMessage("§e/chat unmute <玩家> §7- 解禁玩家"); - p.sendMessage("§e/chat reset <玩家> §7- 重置违规记录"); - p.sendMessage( - "§e§l=================================="); - } - private int findEmptySlot(Inventory inv) { - for (int i = 0; i < 49; i++) { - ItemStack item = inv.getItem(i); - if (item == null - || item.getType() - == Material.AIR) - return i; - } - return -1; - } - - // ===== TabComplete ===== - @Override - public List onTabComplete( - CommandSender sender, Command cmd, - String label, String[] args) { - List list = new ArrayList<>(); - if (cmd.getName().equalsIgnoreCase( - "sdf1_login") - && args.length == 1) { - if (isAdmin(sender)) { - list.addAll(Arrays.asList( - "reload", "pw", "email", - "sign", "reset", "undo", - "get", "del", "set", - "take", "add", "kick", - "ticket", "oa", "stop", - "shopadd", "shopdel","back","update")); - } else { - list.addAll(Arrays.asList( - "pw", "email", "sign", - "reset", "undo", - "ticket", "oa", "stop")); - } - } - if (cmd.getName().equalsIgnoreCase("chat") - && args.length == 1 - && isAdmin(sender)) { - list.addAll(Arrays.asList( - "reload", "add", "remove", - "addplayer", "takeplayer", - "unmute", "reset")); - } - return list; - } - -} \ No newline at end of file diff --git a/txt/PasswordUtils.txt b/txt/PasswordUtils.txt deleted file mode 100644 index 4029ed4..0000000 --- a/txt/PasswordUtils.txt +++ /dev/null @@ -1,72 +0,0 @@ -package Sdf1_login; - -import java.security.MessageDigest; -import java.security.SecureRandom; -import java.util.Base64; - -public class PasswordUtils { - - private static final SecureRandom RANDOM = - new SecureRandom(); - - public static String generateSalt() { - byte[] salt = new byte[16]; - RANDOM.nextBytes(salt); - return Base64.getEncoder().encodeToString(salt); - } - - public static String hash(String password, String salt) { - try { - MessageDigest md = - MessageDigest.getInstance("SHA-256"); - md.update(Base64.getDecoder().decode(salt)); - byte[] hash = - md.digest(password.getBytes("UTF-8")); - return Base64.getEncoder().encodeToString(hash); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static boolean validate(String password) { - if (password == null) return false; - if (password.length() < 6 || password.length() > 20) - return false; - boolean hasUpper = false; - boolean hasLower = false; - boolean hasDigit = false; - for (char c : password.toCharArray()) { - if (Character.isUpperCase(c)) hasUpper = true; - if (Character.isLowerCase(c)) hasLower = true; - if (Character.isDigit(c)) hasDigit = true; - } - return hasUpper && hasLower && hasDigit; - } - - public static String generateTempPassword() { - String chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - StringBuilder sb = new StringBuilder(8); - for (int i = 0; i < 8; i++) { - sb.append(chars.charAt( - RANDOM.nextInt(chars.length()))); - } - return sb.toString(); - } - - public static String generateInviteCode() { - String chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - StringBuilder sb = new StringBuilder(6); - for (int i = 0; i < 6; i++) { - sb.append(chars.charAt( - RANDOM.nextInt(chars.length()))); - } - return sb.toString(); - } - - public static String generateVerifyCode() { - int code = 100000 + RANDOM.nextInt(900000); - return String.valueOf(code); - } -} diff --git a/txt/PointsManager.txt b/txt/PointsManager.txt deleted file mode 100644 index 07e865e..0000000 --- a/txt/PointsManager.txt +++ /dev/null @@ -1,328 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; - -import java.lang.reflect.Method; -import java.util.*; - -public class PointsManager { - - private final Main plugin; - private Object cyInstance = null; - private Method cyActivationMethod = null; - private Method cyGetShopItemsMethod = null; - - private final List> cyItems = - new ArrayList<>(); - - public PointsManager(Main plugin) { - this.plugin = plugin; - discoverCY(); - } - - private void discoverCY() { - try { - org.bukkit.plugin.Plugin p = Bukkit - .getPluginManager() - .getPlugin("CY_beibao"); - if (p != null && p.isEnabled()) { - cyInstance = p; - cyGetShopItemsMethod = p.getClass() - .getMethod("onSdf1GetShopItems"); - cyActivationMethod = p.getClass() - .getMethod("onSdf1Activation", - String.class, - int.class, int.class); - plugin.getLogger().info( - "[Sdf1_login] CY_beibao 联控已连接"); - } - } catch (Exception ignored) {} - } - - // ===== CY 商品拉取 ===== - - private void refreshCYItems() { - cyItems.clear(); - if (cyInstance == null - || cyGetShopItemsMethod == null) return; - try { - Object result = - cyGetShopItemsMethod.invoke(cyInstance); - if (result == null) return; - String data = result.toString(); - if (data.isEmpty()) return; - String[] items = data.split(";"); - for (String itemStr : items) { - String[] p = itemStr.split("\\|"); - if (p.length < 8) continue; - Map item = - new LinkedHashMap<>(); - item.put("id", p[0]); - item.put("name", p[1]); - item.put("days", - Integer.parseInt(p[2])); - item.put("slots", - Integer.parseInt(p[3])); - item.put("price", - Integer.parseInt(p[4])); - item.put("stock", - Integer.parseInt(p[5])); - item.put("icon", p[6]); - item.put("lifetime", - "1".equals(p[7])); - cyItems.add(item); - } - } catch (Exception e) { - plugin.getLogger().warning( - "[Sdf1_login] 拉取CY商品失败: " - + e.getMessage()); - } - } - - private void activateCY(String name, - int slots, int days) { - try { - if (cyInstance == null - || cyActivationMethod == null) return; - cyActivationMethod.invoke( - cyInstance, name, slots, days); - } catch (Exception e) { - plugin.getLogger().warning( - "[Sdf1_login] CY激活失败: " - + e.getMessage()); - } - } - - // ===== 积分查询 ===== - - public int getPoints(String name) { - return ((Number) plugin.getDb() - .getField(name, "points")).intValue(); - } - - // ===== 主面板 ===== - - public Inventory createShopGUI(Player p) { - int points = getPoints(p.getName()); - Inventory inv = Bukkit.createInventory( - null, 54, - "§d§l积分商城 | 当前积分: " + points); - fillBg(inv); - inv.setItem(10, mkItem(Material.EMERALD, - "§a游戏币充值", - "§7消耗积分兑换游戏币", - "§7100积分 = $1000", - "", - "§e点击兑换")); - inv.setItem(12, mkItem(Material.ENDER_CHEST, - "§b背包空间", - "§7消耗积分兑换背包格子", - "§750积分 = 9格永久空间", - "", - "§e点击兑换")); - inv.setItem(14, mkItem(Material.EXPERIENCE_BOTTLE, - "§e经验瓶", - "§7消耗积分兑换经验", - "§720积分 = 10级经验", - "", - "§e点击兑换")); - if (cyInstance != null) { - inv.setItem(31, mkItem(Material.CHEST, - "§6背包商城", - "§7来自CY_beibao的商品", - "", - "§e点击查看")); - } - inv.setItem(49, mkItem(Material.ARROW, "§7返回")); - return inv; - } - - // ===== CY 商品面板 ===== - - public Inventory createCYShopGUI(Player p) { - refreshCYItems(); - int points = getPoints(p.getName()); - int size = Math.max(54, - ((cyItems.size() / 9) + 1) * 9); - if (size > 54) size = 54; - Inventory inv = Bukkit.createInventory( - null, size, - "§d§lCY背包商城 | 当前积分: " + points); - fillBg(inv); - for (int i = 0; i < cyItems.size() - && i < 45; i++) { - Map item = cyItems.get(i); - String name = (String) item.get("name"); - int price = ((Number) item.get("price")) - .intValue(); - int stock = ((Number) item.get("stock")) - .intValue(); - int days = ((Number) item.get("days")) - .intValue(); - int slots = ((Number) item.get("slots")) - .intValue(); - boolean lifetime = (boolean) item - .get("lifetime"); - - List lore = new ArrayList<>(); - lore.add("§7价格: §e" + price + " 积分"); - if (lifetime) { - lore.add("§7格子: §e终身 +" + slots); - } else { - lore.add("§7格子: §e+" + slots); - lore.add("§7时间: §e" + days + "天"); - } - if (stock == 0) { - lore.add("§7库存: §a无限"); - } else if (stock == -1) { - lore.add("§7库存: §c已下架"); - } else { - lore.add("§7库存: §e" + stock); - } - lore.add(""); - if (points >= price && stock != -1) { - lore.add("§e点击购买"); - } else if (stock == -1) { - lore.add("§c已下架"); - } else { - lore.add("§c积分不足"); - } - inv.setItem(i, mkItem(Material.CHEST, - "§a" + name, - lore.toArray(new String[0]))); - } - inv.setItem(size - 1, - mkItem(Material.ARROW, "§7返回")); - return inv; - } - - // ===== 主面板点击 ===== - - public void handleClick(Player p, int slot) { - int points = getPoints(p.getName()); - switch (slot) { - case 10: - if (points >= 100) { - plugin.getDb().deductPoints( - p.getName(), 100); - if (plugin.getEconomy() != null) { - plugin.getEconomy() - .depositPlayer(p, 1000); - } - p.sendMessage(plugin.getConfig2().msg( - "points_purchase_success", - "count", "100")); - } else { - p.sendMessage(plugin.getConfig2().msg( - "points_insufficient", - "points", - String.valueOf(points))); - } - break; - case 12: - if (points >= 50) { - plugin.getDb().deductPoints( - p.getName(), 50); - activateCY(p.getName(), 9, 0); - p.sendMessage(plugin.getConfig2().msg( - "points_purchase_success", - "count", "50")); - } else { - p.sendMessage(plugin.getConfig2().msg( - "points_insufficient", - "points", - String.valueOf(points))); - } - break; - case 14: - if (points >= 20) { - plugin.getDb().deductPoints( - p.getName(), 20); - p.setLevel(p.getLevel() + 10); - p.sendMessage(plugin.getConfig2().msg( - "points_purchase_success", - "count", "20")); - } else { - p.sendMessage(plugin.getConfig2().msg( - "points_insufficient", - "points", - String.valueOf(points))); - } - break; - case 31: - if (cyInstance != null) { - p.openInventory( - createCYShopGUI(p)); - } - break; - } - } - - // ===== CY 商品面板点击 ===== - - public void handleCYClick(Player p, int slot) { - if (slot < 0 || slot >= cyItems.size() - || slot >= 45) return; - Map item = cyItems.get(slot); - int price = ((Number) item.get("price")) - .intValue(); - int stock = ((Number) item.get("stock")) - .intValue(); - int days = ((Number) item.get("days")) - .intValue(); - int slots = ((Number) item.get("slots")) - .intValue(); - boolean lifetime = (boolean) item - .get("lifetime"); - - if (stock == -1) { - p.sendMessage( - "§c[Sdf1_login] §f该商品已下架"); - return; - } - int points = getPoints(p.getName()); - if (points < price) { - p.sendMessage(plugin.getConfig2().msg( - "points_insufficient", - "points", - String.valueOf(points))); - return; - } - - plugin.getDb().deductPoints(p.getName(), price); - int actDays = lifetime ? 0 : days; - activateCY(p.getName(), slots, actDays); - - String tl = lifetime - ? "终身" : (days + "天"); - p.sendMessage("§a[Sdf1_login] §f购买成功!获得 " - + tl + " +" + slots + "格 背包空间"); - } - - // ===== 工具 ===== - - private void fillBg(Inventory inv) { - ItemStack gl = mkItem( - Material.GRAY_STAINED_GLASS_PANE, " "); - for (int i = 0; i < inv.getSize(); i++) - inv.setItem(i, gl); - } - - private ItemStack mkItem(Material mat, String name, - String... lore) { - ItemStack it = new ItemStack(mat); - ItemMeta im = it.getItemMeta(); - if (im != null) { - im.setDisplayName(name); - if (lore.length > 0) - im.setLore(Arrays.asList(lore)); - it.setItemMeta(im); - } - return it; - } -} diff --git a/txt/QuestTracker.txt b/txt/QuestTracker.txt deleted file mode 100644 index b6c9147..0000000 --- a/txt/QuestTracker.txt +++ /dev/null @@ -1,2493 +0,0 @@ -package Sdf1_login; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.bukkit.plugin.Plugin; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - - - -public class QuestTracker { - - private final Plugin plugin; - private final PlayerStorage storage; - private final Map - playerBiome = new HashMap<>(); - private final Map> - questCache = new HashMap<>(); - private final List - checkInRecords = new ArrayList<>(); - private final Map - lastCheckInPos = new HashMap<>(); - - public void recordCheckIn( - String player, - String biomeKey, - String category, - String worldName, - double x, double y, double z) { - String record = player - + "|" + worldName - + "|" + biomeKey - + "|" + category - + "|" + String.format( - "%.1f", x) - + "|" + String.format( - "%.1f", y) - + "|" + String.format( - "%.1f", z) - + "|" + System.currentTimeMillis(); - checkInRecords.add(record); - plugin.getLogger().info( - "[QuestTracker] 打卡记录: " - + record); - } - - public List getCheckInRecords() { - return new ArrayList<>(checkInRecords); - } - - public List getPlayerRecords( - String player) { - List list = new ArrayList<>(); - for (String r : checkInRecords) { - if (r.startsWith( - player.toLowerCase() - + "|")) { - list.add(r); - } - } - return list; - } - - - public QuestTracker(Plugin plugin) { - this.plugin = plugin; - this.storage = new PlayerStorage( - plugin.getDataFolder(), - plugin.getLogger()); - loadAllQuests(); - } - - public void debugPlayer(String player) { - plugin.getLogger().info( - "[QuestTracker] === " - + player + " 任务数据 ==="); - } - public void shutdown() { - if (storage != null) { - storage.close(); - } - } - - /** - * 玩家移动时记录当前群系 - */ - public void onPlayerMove( - String player, - String biomeName) { - playerBiome.put(player, biomeName); - } - /** - * 标记玩家已打卡该群系 - */ - // ========== 群系分类映射表 ========== - private static final Map - BIOME_CATEGORY = new HashMap<>(); - static { - // 海洋群系 - BIOME_CATEGORY.put("minecraft:ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:warm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_warm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:lukewarm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_lukewarm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:cold_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_cold_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:frozen_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_frozen_ocean", "海洋"); - // 平原群系 - BIOME_CATEGORY.put("minecraft:plains", "平原"); - BIOME_CATEGORY.put("minecraft:sunflower_plains", "平原"); - BIOME_CATEGORY.put("minecraft:meadow", "平原"); - // 森林群系 - BIOME_CATEGORY.put("minecraft:forest", "森林"); - BIOME_CATEGORY.put("minecraft:flower_forest", "森林"); - BIOME_CATEGORY.put("minecraft:birch_forest", "森林"); - BIOME_CATEGORY.put("minecraft:old_growth_birch_forest", "森林"); - BIOME_CATEGORY.put("minecraft:dark_forest", "森林"); - BIOME_CATEGORY.put("minecraft:taiga", "森林"); - BIOME_CATEGORY.put("minecraft:snowy_taiga", "森林"); - BIOME_CATEGORY.put("minecraft:old_growth_pine_taiga", "森林"); - BIOME_CATEGORY.put("minecraft:old_growth_spruce_taiga", "森林"); - BIOME_CATEGORY.put("minecraft:cherry_grove", "森林"); - BIOME_CATEGORY.put("minecraft:jungle", "森林"); - BIOME_CATEGORY.put("minecraft:sparse_jungle", "森林"); - BIOME_CATEGORY.put("minecraft:bamboo_jungle", "森林"); - // 沙漠群系 - BIOME_CATEGORY.put("minecraft:desert", "沙漠"); - BIOME_CATEGORY.put("minecraft:savanna", "沙漠"); - BIOME_CATEGORY.put("minecraft:savanna_plateau", "沙漠"); - BIOME_CATEGORY.put("minecraft:beach", "沙漠"); - BIOME_CATEGORY.put("minecraft:snowy_beach", "沙漠"); - BIOME_CATEGORY.put("minecraft:windswept_savanna", "沙漠"); - // 恶地群系 - BIOME_CATEGORY.put("minecraft:badlands", "恶地"); - BIOME_CATEGORY.put("minecraft:wooded_badlands", "恶地"); - BIOME_CATEGORY.put("minecraft:eroded_badlands", "恶地"); - // 雪原群系 - BIOME_CATEGORY.put("minecraft:snowy_plains", "雪原"); - BIOME_CATEGORY.put("minecraft:grove", "雪原"); - BIOME_CATEGORY.put("minecraft:ice_spikes", "雪原"); - BIOME_CATEGORY.put("minecraft:jagged_peaks", "雪原"); - BIOME_CATEGORY.put("minecraft:frozen_river", "雪原"); - BIOME_CATEGORY.put("minecraft:frozen_peaks", "雪原"); - BIOME_CATEGORY.put("minecraft:stony_peaks", "雪原"); - BIOME_CATEGORY.put("minecraft:snowy_slopes", "雪原"); - // 沙砾群系 - BIOME_CATEGORY.put("minecraft:windswept_hills", "沙砾"); - BIOME_CATEGORY.put("minecraft:windswept_forest", "沙砾"); - BIOME_CATEGORY.put("minecraft:windswept_gravelly_hills", "沙砾"); - // 沼泽群系 - BIOME_CATEGORY.put("minecraft:swamp", "沼泽"); - BIOME_CATEGORY.put("minecraft:mangrove_swamp", "沼泽"); - // 河流 - BIOME_CATEGORY.put("minecraft:river", "河流"); - // 蘑菇岛 - BIOME_CATEGORY.put("minecraft:mushroom_fields", "蘑菇岛"); - // 地下群系 - BIOME_CATEGORY.put("minecraft:dripstone_caves", "地下"); - BIOME_CATEGORY.put("minecraft:lush_caves", "地下"); - BIOME_CATEGORY.put("minecraft:deep_dark", "地下"); - // 下界群系 - BIOME_CATEGORY.put("minecraft:nether_wastes", "下界"); - BIOME_CATEGORY.put("minecraft:crimson_forest", "下界"); - BIOME_CATEGORY.put("minecraft:warped_forest", "下界"); - BIOME_CATEGORY.put("minecraft:soul_sand_valley", "下界"); - BIOME_CATEGORY.put("minecraft:basalt_deltas", "下界"); - // 末地群系 - BIOME_CATEGORY.put("minecraft:the_end", "末地"); - BIOME_CATEGORY.put("minecraft:the_void", "末地"); - BIOME_CATEGORY.put("minecraft:end_highlands", "末地"); - BIOME_CATEGORY.put("minecraft:end_midlands", "末地"); - BIOME_CATEGORY.put("minecraft:end_barrens", "末地"); - } - - // 所有群系大类列表 - private static final String[] ALL_CATEGORIES = - {"海洋", "平原", "森林", "沙漠", - "恶地", "雪原", "沙砾", "沼泽", - "河流", "蘑菇岛", "地下", - "下界", "末地"}; - - // 所有群系分类的条件关键词映射 - private static final Map - CATEGORY_KEYWORDS = new HashMap<>(); - static { - CATEGORY_KEYWORDS.put("海洋", "海洋"); - CATEGORY_KEYWORDS.put("平原", "平原"); - CATEGORY_KEYWORDS.put("森林", "森林"); - CATEGORY_KEYWORDS.put("沙漠", "沙漠"); - CATEGORY_KEYWORDS.put("恶地", "恶地"); - CATEGORY_KEYWORDS.put("雪原", "雪原"); - CATEGORY_KEYWORDS.put("沙砾", "沙砾"); - CATEGORY_KEYWORDS.put("沼泽", "沼泽"); - CATEGORY_KEYWORDS.put("河流", "河流"); - CATEGORY_KEYWORDS.put("蘑菇岛", "蘑菇岛"); - CATEGORY_KEYWORDS.put("地下", "地下"); - CATEGORY_KEYWORDS.put("下界", "下界"); - CATEGORY_KEYWORDS.put("末地", "末地"); - // 条件文本中的别名映射 - CATEGORY_KEYWORDS.put("地狱", "下界"); - CATEGORY_KEYWORDS.put("下届", "下界"); - CATEGORY_KEYWORDS.put("主岛", "末地"); - } - - /** - * 用世界维度名分类(最可靠) - * 世界名含 the_nether → 下界 - * 世界名含 the_end → 末地 - * 其余 → 主世界 - */ - public String mapBiomeCategory( - String rawKey, String worldName) { - // 下界维度优先用世界名 - if (worldName != null) { - String w = worldName.toLowerCase(); - if (w.contains("nether")) - return "下界"; - if (w.contains("end")) - return "末地"; - } - // 用群系映射表 - if (rawKey != null) { - String cat = - BIOME_CATEGORY.get( - rawKey.toLowerCase()); - if (cat != null) return cat; - } - return "主世界"; - } - - -// ========== 解析为区块 ========== - - private List
parseIntoSections( - List lines) { - boolean hasHash = false; - boolean hasDash = false; - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("#")) hasHash = true; - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) - hasDash = true; - } - - if (hasHash) { - return parseHashSections(lines); - } else if (hasDash) { - List converted = - new ArrayList<>(); - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) { - String title = t - .replaceAll( - "^[-=*]+\\s*", - "") - .replaceAll( - "\\s*[-=*]+$", - "") - .trim(); - if (title.isEmpty()) - continue; - converted.add("# " + title); - } else { - converted.add(l); - } - } - return parseHashSections(converted); - } - return new ArrayList<>(); - } - - private List
parseHashSections( - List lines) { - List
sections = - new ArrayList<>(); - String curTitle = null; - List curLines = - new ArrayList<>(); - - for (String raw : lines) { - String t = raw.trim(); - if (t.startsWith("#")) { - if (curTitle != null) { - sections.add(new Section( - curTitle, - new ArrayList<>( - curLines))); - } - curTitle = t.replaceFirst( - "^#+\\s*", "").trim(); - curLines = new ArrayList<>(); - } else if (t.isEmpty()) { - // skip - } else { - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty() - && curTitle != null) { - curLines.add(cond); - } - } - } - if (curTitle != null) { - sections.add(new Section( - curTitle, curLines)); - } - return sections; - } - - private List
buildFlatSections( - List lines, - String category) { - List conditions = - new ArrayList<>(); - for (String raw : lines) { - String t = raw.trim(); - if (t.isEmpty()) continue; - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty()) { - conditions.add(cond); - } - } - if (conditions.isEmpty()) - return new ArrayList<>(); - List
list = - new ArrayList<>(); - list.add(new Section( - "第一阶段", conditions)); - return list; - } - - // ========== 文件加载入口 ========== - - private void loadAllQuests() { - loadCategory("新人任务"); - loadCategory("主线任务"); - loadCategory("支线任务"); - } - - private void loadCategory(String name) { - plugin.getLogger().info( - "[QuestTracker] " + name - + ": 扫描中..."); - File folder = new File( - plugin.getDataFolder(), - name); - String path = "plugins/Sdf1_login/" - + name + "/"; - - if (!folder.exists()) { - folder.mkdirs(); - plugin.getLogger().warning( - "[QuestTracker] 已创建目录: " - + path); - questCache.put(name, - new ArrayList<>()); - return; - } - - File[] mdFiles = folder.listFiles( - (d, n) -> n.endsWith(".md")); - - if (mdFiles == null - || mdFiles.length == 0) { - plugin.getLogger().warning( - "[QuestTracker] " + path - + " 下无 .md 文件"); - questCache.put(name, - new ArrayList<>()); - return; - } - Arrays.sort(mdFiles); - - plugin.getLogger().info( - "[QuestTracker] " + name - + ": 发现 " + mdFiles.length - + " 个文件,开始扫描..."); - - // 合并所有文件的阶段 - TreeMap merged = - new TreeMap<>(); - int fileCount = 0; - - for (int fi = 0; - fi < mdFiles.length; fi++) { - File file = mdFiles[fi]; - List lines = - readFileLines(file); - - if (lines.isEmpty()) { - plugin.getLogger().warning( - "[QuestTracker] " - + file.getName() - + ": 空文件,跳过"); - continue; - } - - List
sections = - parseIntoSections(lines); - - if (sections.isEmpty()) { - sections = buildFlatSections( - lines, name); - } - - if (sections.isEmpty()) { - plugin.getLogger().warning( - "[QuestTracker] " - + file.getName() - + ": 无法识别,跳过"); - continue; - } - - // 每个文件独立处理阶段号 - int defaultNum = fi + 1; - buildSingleFileStages( - sections, name, - merged, defaultNum); - - fileCount++; - } - - if (merged.isEmpty()) { - plugin.getLogger().warning( - "[QuestTracker] " + name - + ": 无有效任务数据"); - questCache.put(name, - new ArrayList<>()); - return; - } - - // 日志 - for (Map.Entry en - : merged.entrySet()) { - int num = en.getKey() >= 1000 - ? en.getKey() - 999 - : en.getKey(); - plugin.getLogger().info( - "[QuestTracker] " + name - + " 阶段" + num - + " \"" - + en.getValue().displayName - + "\" → 条件" - + en.getValue() - .conditions.size() - + "项, 详情" - + en.getValue() - .details.size() - + "项, 奖励" - + en.getValue() - .rewards.size() - + "项"); - } - - List list = - new ArrayList<>( - merged.values()); - questCache.put(name, list); - - plugin.getLogger().info( - "[QuestTracker] " + name - + ": 解析完成 → " - + list.size() + " 个阶段" - + " (来自 " + fileCount - + " 个文件)"); - } - - private Integer extractStageNumber( - String title) { - if (title == null) return null; - String t = title.toLowerCase().trim(); - - // 含阶段/stage/关/chapter 关键词 - boolean isStage = - t.contains("阶段") - || t.contains("stage") - || t.contains("关卡") - || t.contains("章节") - || t.contains("chapter"); - - if (isStage) { - String digits = title - .replaceAll("[^0-9]", ""); - if (!digits.isEmpty()) { - try { - return Integer.parseInt(digits); - } catch (NumberFormatException e) { - // fall through - } - } - return parseChineseNumber(title); - } - - // 无关键词但以数字开头(如 "1. 挖掘") - String trimmed = t - .replaceAll("^[.、\\s]+", "") - .trim(); - if (trimmed.length() > 0 - && Character.isDigit( - trimmed.charAt(0))) { - String num = trimmed - .replaceAll("[^0-9]", ""); - if (!num.isEmpty()) { - try { - return Integer.parseInt(num); - } catch (NumberFormatException e) { - // fall through - } - } - } - - return null; - } -// ========== 领取记录 ========== - - /** - * 标记已领取某阶段奖励 - */ - public void markClaimed( - String player, QuestFile qf) { - if (player == null || qf == null) - return; - // 用阶段编号代替显示名,保证稳定 - int num = getStageNumber(qf); - String key = "claimed_" - + qf.fileName + "_" + num; - storage.setCondition(player, key); - } - - public boolean hasClaimed( - String player, QuestFile qf) { - if (player == null || qf == null) - return false; - int num = getStageNumber(qf); - String key = "claimed_" - + qf.fileName + "_" + num; - return toBool( - storage.getCondition( - player, key)); - } - - private int parseChineseNumber( - String title) { - String[] cn = {"一", "二", "三", - "四", "五", "六", "七", - "八", "九", "十"}; - if (title.contains("十")) { - for (int j = 0; j < 9; j++) { - if (title.contains(cn[j])) { - return 10 + j + 1; - } - } - return 10; - } - for (int i = 0; i < 9; i++) { - if (title.contains(cn[i])) { - return i + 1; - } - } - return 0; - } - - // ========== md 解析核心 ========== - - /** - * 读取文件所有行 - */ - private List readFileLines( - File file) { - List lines = - new ArrayList<>(); - try { - BufferedReader br = - new BufferedReader( - new FileReader(file)); - String line; - while ((line = br.readLine()) - != null) { - lines.add(line); - } - br.close(); - } catch (Exception e) { - plugin.getLogger().warning( - "[QuestTracker] 读取 " - + file.getName() - + " 失败: " - + e.getMessage()); - } - return lines; - } - - /** - * 自动检测格式并解析单个文件 - */ - private List parseMdFile( - File file, String category) { - List lines = - readFileLines(file); - if (lines.isEmpty()) - return new ArrayList<>(); - - // 检测分隔符 - boolean hasHash = false; - boolean hasDash = false; - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("#")) - hasHash = true; - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) - hasDash = true; - } - - if (hasHash) { - // # 分隔格式 - return parseSections( - lines, category); - } else if (hasDash) { - // ---- 分隔格式:转换为 # 后复用 - List converted = - new ArrayList<>(); - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) { - String title = t - .replaceAll( - "^[-=*]+\\s*", - "") - .replaceAll( - "\\s*[-=*]+$", - "") - .trim(); - if (title.isEmpty()) - title = "separator"; - converted.add("# " + title); - } else { - converted.add(l); - } - } - return parseSections( - converted, category); - } else { - // 无分隔符:整文件一个阶段 - return parseFlat(lines, category); - } - } - - /** - * # 分隔格式 → 区块 → 阶段 - */ - private List parseSections( - List lines, - String category) { - // 拆分为区块 - List
sections = - new ArrayList<>(); - String curTitle = null; - List curLines = - new ArrayList<>(); - - for (String raw : lines) { - String t = raw.trim(); - - // # 开头 → 新区块 - if (t.startsWith("#")) { - if (curTitle != null) { - sections.add( - new Section( - curTitle, - new ArrayList<>( - curLines))); - } - curTitle = t.replaceFirst( - "^#+\\s*", "").trim(); - curLines = new ArrayList<>(); - } - // 空行/分隔线 → 跳过 - else if (t.isEmpty() - || t.startsWith("----") - || t.startsWith("====") - || t.startsWith("***")) { - // skip - } - // 普通行 → 区块内容 - else { - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty()) { - curLines.add(cond); - } - } - } - // 保存最后一个区块 - if (curTitle != null) { - sections.add( - new Section( - curTitle, curLines)); - } - - // 直接转为 QuestFile 列表 - List result = - new ArrayList<>(); - int num = 1; - for (Section sec : sections) { - QuestFile qf = new QuestFile(); - qf.displayName = - extractDisplayName( - sec.title); - qf.fileName = category; - qf.conditions = - new ArrayList<>(sec.lines); - qf.details = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - result.add(qf); - num++; - } - return result; - } - - /** - * 无分隔符:整文件 = 一个阶段 - */ - private List parseFlat( - List lines, - String category) { - List list = - new ArrayList<>(); - QuestFile qf = makeDefaultStage( - category); - for (String raw : lines) { - String t = raw.trim(); - if (t.isEmpty()) continue; - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty()) { - qf.conditions.add(cond); - } - } - if (!qf.conditions.isEmpty()) { - list.add(qf); - } - return list; - } -// ========== Vault 经济奖励发放 ========== - - /** - * 发放阶段奖励 - * 含经济内容的走 Vault API - */ - public void claimRewards( - Player player, - QuestFile qf) { - if (qf == null - || qf.rewards == null - || qf.rewards.isEmpty()) { - player.sendMessage( - "§c无可领取的奖励"); - return; - } - - net.milkbowl.vault.economy.Economy econ = - ((Main) plugin).getEconomy(); - - int sentCount = 0; - - for (String reward : qf.rewards) { - String rt = reward.trim(); - if (rt.isEmpty()) continue; - - // 检查可选奖励标记 - if (hasOptionalMarker(rt)) { - if (!areAllConditionsMet(player.getName(), qf)) - player.sendMessage( - "§7[可选奖励] " - + "需完成所有可选条件: " - + rt); - continue; - } - - - // 按 + 分割子项 - String[] parts = - rt.split("[+,]"); - for (String part : parts) { - part = part.trim(); - if (part.isEmpty()) continue; - - // 尝试发经济 - double amt = - extractEconomyAmount(part); - if (amt > 0 && econ != null) { - double before = - econ.getBalance(player); - econ.depositPlayer( - player, amt); - player.sendMessage( - "§a§l[任务奖励] §f" - + part - + " §7(余额: §e" - + String.format( - "%.2f", before) - + " §7→ §a" - + String.format( - "%.2f", - econ.getBalance( - player)) - + "§7)"); - // 通知提成系统 - if (plugin instanceof Main) { - ((Main) plugin) - .getCommission() - .payCommission( - player - .getName(), - amt); - } - - sentCount++; - continue; - } - - // 尝试发物品 - java.util.List items = - parseRewardItems(part); - if (!items.isEmpty()) { - for (ItemStack is : items) { - player.getInventory() - .addItem(is); - } - player.sendMessage( - "§a§l[任务奖励] §f" - + part); - sentCount++; - continue; - } - - // 非经济非物品 → 仅消息 - player.sendMessage( - "§a§l[任务奖励] §f" - + part); - sentCount++; - } - } - - plugin.getLogger().info( - "[QuestTracker] " - + player.getName() - + " 领取了 \"" - + qf.displayName - + "\" 共 " + sentCount - + " 项奖励"); - } - /** - * 检查奖励文本是否含可选标记 - */ - private boolean hasOptionalMarker( - String text) { - return text.contains("(可选)") - || text.contains("(进阶)") - || text.contains("【可选】") - || text.contains("【进阶】"); - } - - /** - * 检查阶段所有条件(含可选)是否全部满足 - * 用于决定可选奖励是否发放 - */ - private boolean areAllConditionsMet( - String player, QuestFile qf) { - if (qf.conditions == null - || qf.conditions.isEmpty()) - return true; - for (String cond : qf.conditions) { - if (!isConditionMet(player, cond)) - return false; - } - return true; - } - - /** - * 从奖励文本中提取经济金额 - * 支持: "500游戏币" "500" "金币500" - * "$500" "1000-5000"(随机) "1000~5000" - */ - private double extractEconomyAmount( - String text) { - String t = text.replaceAll(" ", ""); - - // 必须包含经济关键词才算经济奖励 - boolean hasEcon = - t.contains("游戏币") - || t.contains("金币") - || t.contains("积分") - || t.contains("块钱") - || t.contains("元") - || t.contains("$") - || t.contains("经济") - || t.contains("余额") - || t.contains("发钱"); - - if (!hasEcon) return 0; - - // 范围: "1000-5000" 或 "1000~5000" - Matcher rm = Pattern - .compile("(\\d+)[-~](\\d+)") - .matcher(t); - if (rm.find()) { - double min = Double.parseDouble( - rm.group(1)); - double max = Double.parseDouble( - rm.group(2)); - return min + Math.random() - * (max - min); - } - - // 纯数字(含 $ 前缀) - Matcher nm = Pattern - .compile("\\$?(\\d+(\\.\\d+)?)") - .matcher(t); - if (nm.find()) { - return Double.parseDouble( - nm.group(1)); - } - - return 0; - } - /** - * 解析奖励物品文本 - * "皮革套装*1" → 4件皮革装备 - * "木剑*1" → 1把木剑 - * "钻石*64" → 64颗钻石 - */ - private List parseRewardItems( - String text) { - List result = - new ArrayList<>(); - - // 去掉条件前缀 - // 如 "完成注册登录:皮革套装*1" - if (text.contains(":")) { - text = text.substring( - text.indexOf(":") + 1); - } - if (text.contains(":")) { - text = text.substring( - text.indexOf(":") + 1); - } - - // 按 + 分割多个物品 - String[] parts = - text.split("[+,]"); - for (String part : parts) { - part = part.trim(); - if (part.isEmpty()) continue; - - // 跳过经济文本 - if (extractEconomyAmount(part) > 0) - continue; - - int amount = 1; - String itemName = part; - - // 解析 *N 数量 - if (part.contains("*")) { - String[] sp = - part.split("\\*"); - itemName = sp[0].trim(); - try { - amount = Integer.parseInt( - sp[1].trim()); - } catch (Exception e) { - amount = 1; - } - } - - List mats = - matchMaterials(itemName); - for (Material m : mats) { - int give = m.name() - .contains("HELMET") - || m.name() - .contains("CHESTPLATE") - || m.name() - .contains("LEGGINGS") - || m.name() - .contains("BOOTS") - ? 1 : amount; - result.add( - new ItemStack(m, give)); - } - } - return result; - } - - /** - * 中文物品名 → Material 列表 - * 套装返回多个部件 - */ - private List matchMaterials( - String name) { - List list = - new ArrayList<>(); - String n = name.toLowerCase().trim(); - - // 套装 - if (n.contains("皮革套装") - || n.contains("皮套装")) { - list.add(Material.LEATHER_HELMET); - list.add(Material.LEATHER_CHESTPLATE); - list.add(Material.LEATHER_LEGGINGS); - list.add(Material.LEATHER_BOOTS); - return list; - } - if (n.contains("铁套装") - || n.contains("铁甲")) { - list.add(Material.IRON_HELMET); - list.add(Material.IRON_CHESTPLATE); - list.add(Material.IRON_LEGGINGS); - list.add(Material.IRON_BOOTS); - return list; - } - if (n.contains("钻石套装") - || n.contains("钻甲")) { - list.add(Material.DIAMOND_HELMET); - list.add(Material.DIAMOND_CHESTPLATE); - list.add(Material.DIAMOND_LEGGINGS); - list.add(Material.DIAMOND_BOOTS); - return list; - } - // 木制工具套装 - if (n.contains("木制工具") - || n.contains("木工具")) { - list.add(Material.WOODEN_SWORD); - list.add(Material.WOODEN_PICKAXE); - list.add(Material.WOODEN_AXE); - list.add(Material.WOODEN_SHOVEL); - return list; - } - if (n.contains("石制工具") - || n.contains("石工具")) { - list.add(Material.STONE_SWORD); - list.add(Material.STONE_PICKAXE); - list.add(Material.STONE_AXE); - list.add(Material.STONE_SHOVEL); - return list; - } - if (n.contains("铁工具")) { - list.add(Material.IRON_SWORD); - list.add(Material.IRON_PICKAXE); - list.add(Material.IRON_AXE); - list.add(Material.IRON_SHOVEL); - return list; - } - - // 单个物品 - if (n.contains("木剑")) - list.add(Material.WOODEN_SWORD); - else if (n.contains("木镐")) - list.add(Material.WOODEN_PICKAXE); - else if (n.contains("铁剑")) - list.add(Material.IRON_SWORD); - else if (n.contains("铁镐")) - list.add(Material.IRON_PICKAXE); - else if (n.contains("钻石剑")) - list.add(Material.DIAMOND_SWORD); - else if (n.contains("钻石镐")) - list.add(Material.DIAMOND_PICKAXE); - else if (n.contains("钻石")) - list.add(Material.DIAMOND); - else if (n.contains("铁锭")) - list.add(Material.IRON_INGOT); - else if (n.contains("金锭") - || n.contains("金块")) - list.add(Material.GOLD_INGOT); - else if (n.contains("煤炭")) - list.add(Material.COAL); - else if (n.contains("木棍")) - list.add(Material.STICK); - else if (n.contains("弓")) - list.add(Material.BOW); - else if (n.contains("箭")) - list.add(Material.ARROW); - else if (n.contains("床")) - list.add(Material.RED_BED); - else { - // 尝试直接匹配 Material 名 - try { - list.add(Material.valueOf( - n.toUpperCase() - .replaceAll("[^A-Z_]", - ""))); - } catch (Exception e) { - // 未知物品 - } - } - return list; - } - - private QuestFile getOrCreateStage( - TreeMap map, - int key, - String category) { - if (!map.containsKey(key)) { - QuestFile qf = new QuestFile(); - qf.displayName = "阶段 " + key; - qf.fileName = category; - qf.conditions = new ArrayList<>(); - qf.details = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - map.put(key, qf); - } - return map.get(key); - } - - /** - * 单个文件的区块 → 阶段 - * defaultStageNum: 文件无阶段标题时的默认编号 - */ - private void buildSingleFileStages( - List
sections, - String category, - TreeMap stageMap, - int defaultStageNum) { - Integer curKey = null; - - for (Section sec : sections) { - Integer stageNum = - extractStageNumber(sec.title); - - if (stageNum != null) { - curKey = stageNum; - } else if (curKey == null) { - curKey = defaultStageNum; - } - - QuestFile qf = getOrCreateStage( - stageMap, curKey, category); - - String displayName = - extractDisplayName(sec.title); - - // 设置显示名 - if (!displayName.isEmpty() - && qf.displayName - .startsWith("阶段 ")) { - qf.displayName = displayName; - } - - // 路由 - if (stageNum != null - && sec.lines.isEmpty()) { - if (!displayName.isEmpty()) { - qf.displayName = displayName; - } - } - else if (isRewardTitle(sec.title)) { - // 奖励 → 只进 rewards - qf.rewards.addAll(sec.lines); - } - else if (isDetailsTitle(sec.title)) { - // 详情 → 进 details + conditions - qf.details.addAll(sec.lines); - for (String line : sec.lines) { - if (!qf.conditions - .contains(line)) { - qf.conditions.add(line); - } - } - } - else if (isConditionsTitle( - sec.title)) { - // 条件 → 只进 conditions - qf.conditions.addAll(sec.lines); - } - else { - // 其他 → 条件 - qf.conditions.addAll(sec.lines); - } - } - } - - /** - * "主线任务-斗志昂扬" → "斗志昂扬" - * "支线-钓鱼" → "钓鱼" - * "阶段 1" → "阶段 1" - * "第一阶段" → "第一阶段" - */ - private String extractDisplayName( - String title) { - if (title == null) return ""; - // 按 - 或 — 或 – 分割 - String[] parts = title.split( - "\\s*[-—–]\\s*", 2); - if (parts.length >= 2) { - String right = parts[1].trim(); - if (!right.isEmpty()) { - return right; - } - } - return title; - } - private DatabaseManager getDb() { - return ((Main) plugin).getDb(); - } - - private boolean isDetailsTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("任务详情") - || t.contains("详情") - || t.contains("任务说明") - || t.contains("任务目标") - || t.contains("description") - || t.contains("任务要求"); - } - - private boolean isConditionsTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("解锁条件") - || t.contains("条件") - || t.contains("前置条件") - || t.contains("前置要求") - || t.contains("解锁") - || t.contains("condition"); - } - - - private void ensureStage( - TreeMap map, - String category, - QuestFile current, - int counter) { - if (current != null) return; - // 用大数字确保排在有编号阶段后面 - int key = 1000 + counter; - QuestFile qf = new QuestFile(); - qf.displayName = "阶段 " + (counter + 1); - qf.fileName = category; - qf.conditions = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - map.put(key, qf); - // 更新调用方的引用(通过返回值) - // 这里用 map 操作,current 无法直接更新 - // 所以改用返回值方式 - } - /** - * 自动将条件中的奖励内容移到 rewards - */ - private void separateRewards(QuestFile qf) { - List remaining = - new ArrayList<>(); - for (String line : qf.conditions) { - if (isRewardContent(line)) { - if (!qf.rewards.contains(line)) { - qf.rewards.add(line); - } - } else { - remaining.add(line); - } - } - qf.conditions = remaining; - } - private boolean checkStageDependency( - String player, - String condition) { - Matcher m = Pattern.compile( - "第([一二三四五六七八九十\\d]+)" - + "阶段") - .matcher(condition); - - while (m.find()) { - String numStr = m.group(1); - int stageNum = - parseChineseNumber(numStr); - if (stageNum == 0) { - try { - stageNum = - Integer.parseInt(numStr); - } catch (Exception e) { - return false; - } - } - - boolean stageDone = false; - for (List quests - : questCache.values()) { - for (QuestFile qf : quests) { - Integer qfNum = - extractStageNumber( - qf.displayName); - if (qfNum != null - && qfNum == stageNum) { - if (isStageComplete(player, - qf.conditions)) { - stageDone = true; - } - break; - } - } - if (stageDone) break; - } - - if (!stageDone) return false; - } - return true; - } - - /** - * 判断一行内容是否为奖励 - */ - private boolean isRewardContent( - String line) { - String l = line.toLowerCase(); - // 含货币关键词 - if (l.contains("游戏币") - || l.contains("金币") - || l.contains("积分") - || l.contains("钻石") - || l.contains("发钱") - || l.contains("发放") - || l.contains("经验") - || l.contains("等级") - || l.contains("经济") - || l.contains("余额") - || l.contains("块钱")) - return true; - // 纯数字+单位(如 "500游戏币") - if (l.matches( - ".*\\d+\\s*(游戏币|金币|元|积分|" - + "钻石|块钱|经验|级|经济|余额).*")) - return true; - // $前缀(如 "$500") - if (l.matches(".*\\$\\d+.*")) - return true; - // 范围格式(如 "1000-5000" 或 "1000~5000") - if (l.matches(".*\\d+[-~]\\d+.*") - && !l.contains("阶段") - && !l.contains("stage")) - return true; - return false; - } - - // ========== 标题分类 ========== - - private boolean isStageTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("阶段") - || t.contains("stage") - || t.contains("第一") - || t.contains("第二") - || t.contains("第三") - || t.contains("第四") - || t.contains("第五"); - } - - private boolean isRewardTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("奖励") - || t.contains("奖品") - || t.contains("报酬") - || t.contains("reward") - || t.contains("奖赏") - || t.contains("礼包内容") - || t.contains("礼包") - || t.contains("物品奖励"); - } - - - private QuestFile makeDefaultStage( - String category) { - QuestFile qf = new QuestFile(); - qf.displayName = "第一阶段"; - qf.fileName = category; - qf.conditions = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - return qf; - } - - private static class Section { - final String title; - final List lines; - Section(String title, - List lines) { - this.title = title; - this.lines = lines; - } - } - - // ========== 公开查询 ========== - - public List getQuests( - String category) { - List list = - questCache.get(category); - return list != null ? list - : new ArrayList<>(); - } - - public int getCompletedStageCount( - String player, - List quests) { - int count = 0; - if (quests == null) return 0; - for (QuestFile qf : quests) { - if (isStageComplete(player, - qf.conditions)) { - count++; - } - } - return count; - } - - public QuestFile getCurrentStage( - String player, - List quests) { - if (quests == null) return null; - for (QuestFile qf : quests) { - if (!isStageComplete( - player, qf.conditions)) { - return qf; - } - } - return null; - } - - public int getStageNumber( - List quests, - QuestFile qf) { - if (quests == null || qf == null) - return 0; - return quests.indexOf(qf) + 1; - } - - public int getStageNumber( - QuestFile qf) { - if (qf == null) return 0; - List quests = - getQuests(qf.fileName); - return getStageNumber(quests, qf); - } - - public boolean isStageCompleted( - String player, - QuestFile qf) { - if (qf == null) return false; - return isStageComplete( - player, qf.conditions); - } - - public List getUnmetConditions( - String player, - List conditions) { - List unmet = - new ArrayList<>(); - if (conditions == null) return unmet; - for (String cond : conditions) { - if (isOptional(cond)) continue; - if (!isConditionMet( - player, cond)) { - unmet.add( - getProgress( - player, cond)); - } - } - return unmet; - } - - public String getProgress( - String player, - String rawCond) { - if (player == null - || rawCond == null) - return "§7未知"; - - String key = stripOptional(rawCond); - // 打卡群系进度 - if (key.contains("打卡") - && key.contains("群系")) { - String biome = - extractBiome(key); - - if ("ALL".equals(biome)) { - int total = getCounter( - player, "biome_ALL"); - return "§7群系打卡: §e" - + total + "/1"; - } - - if (!biome.isEmpty()) { - int catCount = getCounter( - player, - "biomecat_" + biome); - return "§7打卡 " + biome - + "群系: §e" - + catCount + "/1"; - } - - int total = getCounter( - player, "biome_ALL"); - return "§7群系打卡: §e" - + total + "/1"; - } - - - // 阶段依赖 - if (key.contains("完成") - && key.contains("阶段")) { - boolean done = checkStageDependency( - player, key); - return done - ? "§a✓ " + key - : "§c✗ " + key; - } - - - // 注册登录(检查主数据库是否已注册) - // 注册登录(检查主数据库是否已注册) - if ("完成注册登录".equals(key)) { - boolean done = getDb() != null - && getDb().userExists( - player); - return done - ? "§a✓ 完成注册登录" - : "§c✗ 完成注册登录"; - } - - // 绑定邮箱 - if (key.contains("绑定Email") - || key.contains("绑定邮箱")) { - Object val = - getDb().getField( - player, "email"); - boolean done = val != null - && !val.toString().isEmpty(); - return done - ? "§a✓ 绑定Email" - : "§c✗ 绑定Email"; - } - - // 在线时间 - if (key.contains("在线") - && key.contains("小时")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - long cur = getCounterLong( - player, "PLAYTIME"); - return "§7在线: §e" - + formatMs(cur) + "/" - + needed + "小时"; - } - - // 打招呼/发消息/聊天 - if (key.contains("打招呼") - || key.contains("打个招呼") - || key.contains("发消息") - || key.contains("聊天")) { - int cur = getCounter( - player, "CHAT"); - return "§7聊天: §e" + cur + "/1"; - } - - // 破坏/放置方块 - if ((key.contains("破坏") - || key.contains("放置")) - && key.contains("方块")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int total = getCounter( - player, "MINE:ALL") - + getCounter( - player, "PLACE:ALL"); - return "§7方块操作: §e" - + total + "/" + needed; - } - - // 连续登录 - if (key.contains("连续") - && key.contains("登录")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - Object val = - getDb().getField( - player, - "checkin_streak"); - int streak = val instanceof Number - ? ((Number) val).intValue() - : 0; - return "§7连续登录: §e" - + streak + "/" + needed + "天"; - } - - // 挖矿 - if (key.startsWith("MINE:")) { - String[] parts = key.split(":"); - if (parts.length >= 3) { - int needed = - parseInt(parts[2]); - int cur = getCounter( - player, - "MINE:" + parts[1]); - return "§7挖 " + parts[1] - + ": §e" + cur - + "/" + needed; - } - } - - // 击杀生物 - if (key.startsWith("KILL:")) { - String[] parts = key.split(":"); - if (parts.length >= 3) { - int needed = - parseInt(parts[2]); - int cur = getCounter( - player, - "KILL:" + parts[1]); - return "§7杀 " + parts[1] - + ": §e" + cur - + "/" + needed; - } - } - - // 击杀玩家 - if (key.startsWith("KILL_PLAYER")) { - String[] parts = key.split(":"); - int needed = parts.length >= 2 - ? parseInt(parts[1]) : 1; - int cur = getCounter( - player, "KILL_PLAYER"); - return "§7击杀玩家: §e" - + cur + "/" + needed; - } - - // 合成 - if (key.startsWith("CRAFT")) { - String[] parts = key.split(":"); - int needed = parts.length >= 2 - ? parseInt(parts[1]) : 1; - int cur = getCounter( - player, "CRAFT"); - return "§7合成: §e" - + cur + "/" + needed; - } - - // 钓鱼 - if (key.startsWith("FISH")) { - String[] parts = key.split(":"); - int needed = parts.length >= 2 - ? parseInt(parts[1]) : 1; - int cur = getCounter( - player, "FISH"); - return "§7钓鱼: §e" - + cur + "/" + needed; - } - - // 在线时长(毫秒) - if (key.startsWith("PLAYTIME")) { - String[] parts = key.split(":"); - long needed = parts.length >= 2 - ? Long.parseLong(parts[1]) - : 0; - long cur = getCounterLong( - player, "PLAYTIME"); - return "§7在线: §e" - + formatMs(cur) + "/" - + formatMs(needed); - } - // 上报村庄 - if (key.contains("上报") - && key.contains("村庄")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter( - player, "REPORT_VILLAGE"); - return "§7上报村庄: §e" - + cur + "/" + needed; - } - - // 探索村庄 - if (key.contains("探索") - && key.contains("村庄")) { - int cur = getCounter( - player, "REPORT_VILLAGE"); - return "§7探索村庄: §e" - + cur + "/1"; - } - // 近7天登录 - if (key.contains("7天") - || key.contains("七天")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int streak = getIntField( - player, - "checkin_streak"); - if (streak == 0) { - streak = getIntField( - player, "streak"); - } - if (streak == 0) { - streak = getIntField( - player, - "total_checkin_days"); - } - return "§7近7天登录: §e" - + streak + "/" - + needed + "天"; - } - - - // 完成新手任务全部 - if (key.contains("新手任务") - && key.contains("全部")) { - List quests = - getQuests("新人任务"); - int done = 0; - for (QuestFile qf : quests) { - if (isStageComplete( - player, - qf.conditions)) - done++; - } - return "§7新手任务: §e" - + done + "/" - + quests.size(); - } - - // 完成主线任务全部 - if (key.contains("主线任务") - && key.contains("全部")) { - List quests = - getQuests("主线任务"); - int done = 0; - for (QuestFile qf : quests) { - if (isStageComplete( - player, - qf.conditions)) - done++; - } - return "§7主线任务: §e" - + done + "/" - + quests.size(); - } - - return "§7" + key; - } - private int getIntField( - String player, String field) { - try { - Object val = getDb().getField( - player, field); - if (val instanceof Number) { - return ((Number) val).intValue(); - } - if (val != null) { - return Integer.parseInt( - val.toString().trim()); - } - } catch (Exception e) { - // 忽略 - } - return 0; - } - - public List checkInBiome( - String player, - String rawBiomeKey, - String worldName, - double x, double y, double z) { - List matched = - new ArrayList<>(); - - // 32格去重 - String posKey = - player + "_lastPos"; - String curPos = worldName - + "|" + String.format( - "%.0f", x / 32) - + "|" + String.format( - "%.0f", y / 32) - + "|" + String.format( - "%.0f", z / 32); - String lastPos = - lastCheckInPos.get(posKey); - if (curPos.equals(lastPos)) { - plugin.getLogger().info( - "[QuestTracker] 打卡忽略: " - + player - + " 32格内重复"); - return matched; - } - lastCheckInPos.put(posKey, curPos); - - String category = - mapBiomeCategory( - rawBiomeKey, worldName); - - // 记录总打卡 - int allCur = getCounter( - player, "biome_ALL"); - setCounter(player, "biome_ALL", - allCur + 1); - - // 记录分类打卡 - if (!category.isEmpty() - && !"主世界" - .equals(category)) { - String catKey = - "biomecat_" + category; - int catCur = getCounter( - player, catKey); - setCounter(player, catKey, - catCur + 1); - } - - // 记录具体群系 - String bioKey = - "biome_" + rawBiomeKey; - int bioCur = getCounter( - player, bioKey); - setCounter(player, bioKey, - bioCur + 1); - - // 检查所有任务条件是否匹配 - List categories = - Arrays.asList( - "新人任务", - "主线任务", - "支线任务"); - - for (String cat : categories) { - List quests = - getQuests(cat); - for (QuestFile qf : quests) { - if (isStageCompleted( - player, qf)) - continue; - for (String cond - : qf.conditions) { - if (isOptional(cond)) - continue; - String condKey = - stripOptional(cond); - if (!condKey - .contains("打卡") - || !condKey - .contains("群系")) - continue; - String target = - extractBiome(condKey); - boolean hit = false; - if ("ALL".equals(target) - || "风景点" - .equals(target)) { - hit = true; - } else if (!target.isEmpty() - && target.equals( - category)) { - hit = true; - } - if (hit && !matched - .contains(cond)) { - matched.add(cond); - } - } - } - } - - return matched; - } - - - /** - * 从条件文本中提取目标群系分类 - * "打卡上报地狱任意群系" → "下界" - * "打卡任意海洋群系" → "海洋" - * "打卡任意群系" → "ALL" - * "打卡任意风景点" → "" - */ - private String extractBiome(String text) { - if (text == null) return ""; - // 逐个匹配分类关键词 - for (Map.Entry en - : CATEGORY_KEYWORDS.entrySet()) { - if (text.contains(en.getKey())) { - return en.getValue(); - } - } - if (text.contains("风景点") - || text.contains("风景")) { - return "ALL"; - } - - // 有"群系"但无具体分类 → ALL - if (text.contains("群系")) { - return "ALL"; - } - return ""; - } - - - // ========== 标记条件 ========== - - public void markConditionComplete( - String player, - String conditionName) { - if (player == null - || conditionName == null) - return; - storage.setCondition( - player, conditionName); - } - - // ========== 事件回调 ========== - - public void onBlockMined( - String player, String material) { - String key = "MINE:" + material; - setCounter(player, key, - getCounter(player, key) + 1); - // 总计数 - setCounter(player, "MINE:ALL", - getCounter(player, "MINE:ALL") - + 1); - } - public boolean onReportVillage( - String player, - String worldName, - double x, double y, double z) { - String dbKey = "village_lastPos"; - String curPos = worldName - + "|" + String.format( - "%.0f", x / 32) - + "|" + String.format( - "%.0f", y / 32) - + "|" + String.format( - "%.0f", z / 32); - Object lastObj = - storage.getCondition( - player, dbKey); - String lastPos = lastObj != null - ? lastObj.toString() : ""; - if (curPos.equals(lastPos)) { - return false; - } - storage.setConditionRaw( - player, dbKey, curPos); - - String key = "REPORT_VILLAGE"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - return true; - } - - public void onMobKill( - String player, String mobType) { - String key = "KILL:" + mobType; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onPlayerKill( - String player) { - String key = "KILL_PLAYER"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onItemCrafted( - String player) { - String key = "CRAFT"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onFishCaught( - String player) { - String key = "FISH"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onPlayTime( - String player, long ms) { - String key = "PLAYTIME"; - long cur = getCounterLong( - player, key); - setCounter(player, key, cur + ms); - } - - // ========== 内部:条件判定 ========== - - private boolean isStageComplete( - String player, - List conditions) { - if (conditions == null - || conditions.isEmpty()) - return true; - for (String cond : conditions) { - if (isOptional(cond)) continue; - if (!isConditionMet( - player, cond)) - return false; - } - return true; - } - - public boolean isConditionMet( - String player, - String rawCond) { - if (player == null - || rawCond == null) - return false; - - String key = stripOptional(rawCond); - // 打卡群系 - if (key.contains("打卡") - && (key.contains("群系") - || key.contains("风景点"))) { - - String biome = - extractBiome(key); - - if ("ALL".equals(biome)) { - // 打卡任意群系 - int total = getCounter( - player, "biome_ALL"); - return total >= 1; - } - - if (!biome.isEmpty()) { - // 打卡特定分类群系 - int catCount = getCounter( - player, - "biomecat_" + biome); - } - - // 无特定要求 - return getCounter( - player, "biome_ALL") >= 1; - } - - - // 阶段依赖 - if (key.contains("完成") - && key.contains("阶段")) { - return checkStageDependency( - player, key); - } - - // 注册登录 - - if ("完成注册登录".equals(key)) { - return getDb() != null - && getDb().userExists( - player); - } - - - // 绑定邮箱 - if (key.contains("绑定Email") - || key.contains("绑定邮箱")) { - Object val = - getDb().getField( - player, "email"); - return val != null - && !val.toString().isEmpty(); - } - - // 在线时间 - if (key.contains("在线") - && key.contains("小时")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - long cur = getCounterLong( - player, "PLAYTIME"); - return cur >= needed * 3600000L; - } - - // 打招呼/发消息/聊天 - if (key.contains("打招呼") - || key.contains("打个招呼") - || key.contains("发消息") - || key.contains("聊天")) { - return getCounter( - player, "CHAT") >= 1; - } - - // 破坏/放置方块 - if ((key.contains("破坏") - || key.contains("放置")) - && key.contains("方块")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int total = getCounter( - player, "MINE:ALL") - + getCounter( - player, "PLACE:ALL"); - return total >= needed; - } - - // 连续登录 - if (key.contains("连续") - && key.contains("登录")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - Object val = - getDb().getField( - player, - "checkin_streak"); - int streak = val instanceof Number - ? ((Number) val).intValue() - : 0; - return streak >= needed; - } - - // 挖矿 - if (key.startsWith("MINE:")) { - String[] p = key.split(":"); - if (p.length >= 3) { - return getCounter(player, - "MINE:" + p[1]) - >= parseInt(p[2]); - } - } - - // 击杀生物 - if (key.startsWith("KILL:")) { - String[] p = key.split(":"); - if (p.length >= 3) { - return getCounter(player, - "KILL:" + p[1]) - >= parseInt(p[2]); - } - } - - // 击杀玩家 - if (key.startsWith("KILL_PLAYER")) { - String[] p = key.split(":"); - int n = p.length >= 2 - ? parseInt(p[1]) : 1; - return getCounter(player, - "KILL_PLAYER") >= n; - } - - // 合成 - if (key.startsWith("CRAFT")) { - String[] p = key.split(":"); - int n = p.length >= 2 - ? parseInt(p[1]) : 1; - return getCounter( - player, "CRAFT") >= n; - } - - // 钓鱼 - if (key.startsWith("FISH")) { - String[] p = key.split(":"); - int n = p.length >= 2 - ? parseInt(p[1]) : 1; - return getCounter( - player, "FISH") >= n; - } - - // 在线时长 - if (key.startsWith("PLAYTIME")) { - String[] p = key.split(":"); - long n = p.length >= 2 - ? Long.parseLong(p[1]) : 0; - return getCounterLong(player, - "PLAYTIME") >= n; - } - // 上报村庄 - if (key.contains("上报") - && key.contains("村庄")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter( - player, "REPORT_VILLAGE"); - return cur >= needed; - } - - // 探索村庄 - if (key.contains("探索") - && key.contains("村庄")) { - return getCounter( - player, "REPORT_VILLAGE") - >= 1; - } - // 近7天登录记录 - if (key.contains("7天") - || key.contains("七天")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - - // 诊断:读取数据库所有字段 - try { - var user = getDb().getUser( - player); - if (user != null) { - plugin.getLogger().info( - "[QuestTracker] " - + "登录诊断 " - + player + ": " - + user.toString()); - } else { - plugin.getLogger().info( - "[QuestTracker] " - + "登录诊断: " - + player - + " getUser返回null"); - } - } catch (Exception e) { - plugin.getLogger().info( - "[QuestTracker] " - + "登录诊断异常: " - + e.getMessage()); - } - - int streak = getIntField( - player, - "checkin_streak"); - if (streak == 0) { - streak = getIntField( - player, "streak"); - } - if (streak == 0) { - streak = getIntField( - player, - "total_checkin_days"); - } - - plugin.getLogger().info( - "[QuestTracker] 登录检测: " - + player - + " 需要=" + needed - + " 实际=" + streak); - - return streak >= needed; - } - - // 完成新手任务全部内容 - if (key.contains("新手任务") - && key.contains("全部")) { - List quests = - getQuests("新人任务"); - for (QuestFile qf : quests) { - if (!isStageComplete( - player, - qf.conditions)) - return false; - } - return true; - } - - // 完成主线任务全部内容 - if (key.contains("主线任务") - && key.contains("全部")) { - List quests = - getQuests("主线任务"); - for (QuestFile qf : quests) { - if (!isStageComplete( - player, - qf.conditions)) - return false; - } - return true; - } - - return false; - } - - public void debugLogin(String player) { - try { - var user = getDb().getUser(player); - if (user != null) { - plugin.getLogger().info( - "[QuestTracker] " - + "=== " + player - + " 数据库全部字段 ==="); - for (var entry - : user.entrySet()) { - plugin.getLogger().info( - "[QuestTracker] " - + entry.getKey() - + " = " - + entry.getValue() - + " (" - + (entry.getValue() - != null - ? entry.getValue() - .getClass() - .getSimpleName() - : "null") - + ")"); - } - } else { - plugin.getLogger().info( - "[QuestTracker] " - + "getUser返回null: " - + player); - } - } catch (Exception e) { - plugin.getLogger().info( - "[QuestTracker] " - + "debug异常: " - + e.getMessage()); - } - } - - // ========== 内部:计数器 ========== - - private int getCounter( - String player, String key) { - Object val = storage.getCondition( - player, "cnt_" + key); - if (val instanceof Number) { - return ((Number) val).intValue(); - } - return 0; - } - - private long getCounterLong( - String player, String key) { - Object val = storage.getCondition( - player, "cnt_" + key); - if (val instanceof Number) { - return ((Number) val).longValue(); - } - return 0; - } - - private void setCounter( - String player, String key, - int value) { - storage.setConditionRaw( - player, "cnt_" + key, - String.valueOf(value)); - } - - private void setCounter( - String player, String key, - long value) { - storage.setConditionRaw( - player, "cnt_" + key, - String.valueOf(value)); - } - - // ========== 工具 ========== - - private boolean toBool(Object val) { - if (val == null) return false; - if (val instanceof Boolean) - return (Boolean) val; - if (val instanceof Number) - return ((Number) val) - .intValue() != 0; - String s = String.valueOf(val); - return "1".equals(s) - || "true" - .equalsIgnoreCase(s); - } - - public boolean isOptional(String cond) { - if (cond == null) return false; - String c = cond; - return c.contains("(进阶)") - || c.contains("(可选)") - || c.contains(",可选)") - || c.contains(",可选)") - || c.contains("【可选】") - || c.contains("【进阶】"); - } - - - private String stripOptional(String cond) { - if (cond == null) return ""; - return cond.replace("(进阶)", "") - .replace("(可选)", "") - .replace(",可选)", "") - .replace(",可选)", "") - .replace("【可选】", "") - .replace("【进阶】", "") - .trim(); - } - - private int parseInt(String s) { - - try { - return Integer.parseInt(s.trim()); - } catch (Exception e) { - return 0; - } - } - private Object getDbField( - String player, String field) { - Main main = (Main) plugin; - return main.getDb().getField( - player, field); - } - - /** - * 从文本中提取第一个数字 - * "在线时间≥1小时" → 1 - * "破坏/放置方块100个" → 100 - */ - private int parseNumberFromText( - String text) { - java.util.regex.Matcher m = - java.util.regex.Pattern - .compile("(\\d+)") - .matcher(text); - if (m.find()) { - try { - return Integer.parseInt( - m.group(1)); - } catch (Exception e) { - return 0; - } - } - return 0; - } - public void onBlockPlaced( - String player, String material) { - String key = "PLACE:" + material; - setCounter(player, key, - getCounter(player, key) + 1); - setCounter(player, "PLACE:ALL", - getCounter(player, "PLACE:ALL") - + 1); - } - - public void onPlayerChat( - String player) { - String key = "CHAT"; - setCounter(player, key, - getCounter(player, key) + 1); - } - - private String formatMs(long ms) { - long sec = ms / 1000; - long min = sec / 60; - long hr = min / 60; - if (hr > 0) - return hr + "小时" - + (min % 60) + "分"; - if (min > 0) - return min + "分" - + (sec % 60) + "秒"; - return sec + "秒"; - } - - // ========== 数据结构 ========== - - public static class QuestFile { - public String displayName = ""; - public String fileName = ""; - public List details = - new ArrayList<>(); - public List rewards = - new ArrayList<>(); - public List conditions = - new ArrayList<>(); - - public boolean hasConditions() { - return conditions != null - && !conditions.isEmpty(); - } - } -} diff --git a/txt/RadioConverter.txt b/txt/RadioConverter.txt deleted file mode 100644 index d072efb..0000000 --- a/txt/RadioConverter.txt +++ /dev/null @@ -1,113 +0,0 @@ -package Sdf1_login; - -import java.io.*; -import java.nio.file.*; -import java.util.logging.Logger; - -public class RadioConverter { - - private final Logger logger; - private final boolean ffmpegAvailable; - - public RadioConverter(Logger logger) { - this.logger = logger; - this.ffmpegAvailable = checkFfmpeg(); - } - - /** - * 转换音频文件为 ogg 格式 - * 支持: mp3, wav, flac, ogg - */ - public boolean convert(File input, - File output) { - String name = - input.getName().toLowerCase(); - - // ogg 文件直接复制 - if (name.endsWith(".ogg")) { - return copyFile(input, output); - } - - // 有 ffmpeg 则转换 - if (ffmpegAvailable) { - return convertFfmpeg(input, output); - } - - // 无 ffmpeg 无法转换 - logger.warning( - "[Radio] 无法转换: " - + input.getName() - + " (需要安装ffmpeg)"); - return false; - } - - private boolean copyFile(File src, - File dest) { - try { - dest.getParentFile().mkdirs(); - Files.copy(src.toPath(), - dest.toPath(), - StandardCopyOption - .REPLACE_EXISTING); - return true; - } catch (Exception e) { - return false; - } - } - - private boolean convertFfmpeg( - File input, File output) { - try { - output.getParentFile().mkdirs(); - ProcessBuilder pb = - new ProcessBuilder( - "ffmpeg", "-y", - "-i", - input - .getAbsolutePath(), - "-c:a", - "libvorbis", - "-q:a", "5", - output - .getAbsolutePath()); - pb.redirectErrorStream(true); - Process p = pb.start(); - p.waitFor(); - return output.exists(); - } catch (Exception e) { - logger.warning( - "[Radio] ffmpeg转换失败: " - + input.getName()); - return false; - } - } - - private boolean checkFfmpeg() { - try { - Process p = - new ProcessBuilder( - "ffmpeg", - "-version") - .start(); - boolean ok = p.waitFor() == 0; - if (ok) { - logger.info( - "[Radio] ffmpeg可用"); - } else { - logger.info( - "[Radio] ffmpeg不可用" - + " (仅支持ogg文件)"); - } - return ok; - } catch (Exception e) { - logger.info( - "[Radio] ffmpeg未安装" - + " (仅支持ogg文件)"); - return false; - } - } - - public boolean isFfmpegAvailable() { - return ffmpegAvailable; - } -} diff --git a/txt/RadioManager.txt b/txt/RadioManager.txt deleted file mode 100644 index 4756823..0000000 --- a/txt/RadioManager.txt +++ /dev/null @@ -1,563 +0,0 @@ -package Sdf1_login; - -import org.bukkit.*; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; - -import java.io.*; -import java.nio.file.*; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Logger; -import java.util.zip.*; - -public class RadioManager { - - private final Main plugin; - private File radioDir; - private File oggDir; - private File packDir; - private BukkitTask mainRadioTask; - private BukkitTask loginRadioTask; - private final Map - loginPlayers = - new ConcurrentHashMap<>(); - private int radioIndex = 0; - private RadioConverter converter; - - private final List mainFiles = - new ArrayList<>(); - private final List loginFiles = - new ArrayList<>(); - - public RadioManager(Main plugin) { - this.plugin = plugin; - this.radioDir = new File( - plugin.getDataFolder(), - "radio"); - this.oggDir = new File( - plugin.getDataFolder(), - "radio/ogg"); - this.packDir = new File( - plugin.getDataFolder(), - "radiopack"); - radioDir.mkdirs(); - oggDir.mkdirs(); - packDir.mkdirs(); - } - - // ========== 初始化 ========== - - public void init() { - converter = new RadioConverter( - plugin.getLogger()); - - convertAll(); - buildResourcePack(); - loadFiles(); - - plugin.getLogger().info( - "[Radio] 初始化完成: 全服=" - + mainFiles.size() - + " 登录=" + loginFiles.size()); - } - - // ========== 格式转换 ========== - - private void convertAll() { - // 根目录 - convertDir(radioDir, - new File(oggDir, "radio")); - // login 目录 - File loginSrc = new File( - radioDir, "login"); - if (loginSrc.exists()) { - convertDir(loginSrc, - new File(oggDir, - "radio/login")); - } - // reg 目录 - File regSrc = new File( - radioDir, "reg"); - if (regSrc.exists()) { - convertDir(regSrc, - new File(oggDir, - "radio/reg")); - } - } - - private void convertDir(File src, - File dest) { - dest.mkdirs(); - File[] files = src.listFiles( - (d, n) -> { - String l = - n.toLowerCase(); - return l.endsWith(".mp3") - || l.endsWith(".wav") - || l.endsWith(".flac") - || l.endsWith(".ogg"); - }); - if (files == null) return; - - for (File f : files) { - String oggName = f.getName() - .replaceAll( - "\\.(mp3|wav|flac" - + "|ogg)$", - ".ogg"); - File oggFile = - new File(dest, oggName); - if (oggFile.exists() - && oggFile.lastModified() - >= f.lastModified()) { - continue; - } - boolean ok = converter.convert( - f, oggFile); - if (ok) { - plugin.getLogger().info( - "[Radio] 转换成功: " - + f.getName()); - } else { - plugin.getLogger().warning( - "[Radio] 转换失败: " - + f.getName()); - } - } - } - - // ========== 资源包 ========== - private void buildResourcePack() { - // 清理旧文件 - deleteDir(packDir); - packDir.mkdirs(); - - List allOggs = new ArrayList<>(); - collectOggs(oggDir, allOggs); - - if (allOggs.isEmpty()) { - plugin.getLogger().warning( - "[Radio] 无 ogg 文件,跳过资源包"); - return; - } - - // assets/sdf1radio/sounds/ 下面直接放文件 - File soundsDir = new File( - packDir, - "assets/sdf1radio/sounds"); - soundsDir.mkdirs(); - - // 构建 sounds.json - StringBuilder json = - new StringBuilder("{\n"); - - for (int i = 0; - i < allOggs.size(); i++) { - File ogg = allOggs.get(i); - // 简化路径:只取文件名(去掉所有目录前缀) - String simpleName = - ogg.getName() - .replace(".ogg", ""); - // 检查是否有同名文件,加前缀区分 - String soundKey = simpleName; - - // 复制 ogg 到资源包 - File dest = - new File(soundsDir, - simpleName + ".ogg"); - try { - Files.copy(ogg.toPath(), - dest.toPath(), - StandardCopyOption - .REPLACE_EXISTING); - } catch (Exception e) { - // 忽略 - } - - // 写 JSON - json.append(" \"sdf1radio:") - .append(soundKey) - .append("\": {\n"); - json.append(" \"sounds\": [{\n"); - json.append(" \"name\": \"") - .append("sdf1radio/") - .append(soundKey) - .append("\",\n"); - json.append( - " \"stream\": true\n"); - json.append(" }]\n"); - json.append(" }"); - if (i < allOggs.size() - 1) - json.append(","); - json.append("\n"); - } - json.append("}"); - - // 写 sounds.json - try { - Files.write( - new File(packDir, - "assets/sdf1radio/" - + "sounds.json") - .toPath(), - json.toString() - .getBytes("UTF-8")); - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] sounds.json写入失败"); - } - - // 写 pack.mcmeta - try { - Files.write( - new File(packDir, - "pack.mcmeta") - .toPath(), - ("{\n" - + " \"pack\": {\n" - + " \"pack_format\": 34,\n" - + " \"description\": " - + "\"Sdf1 Radio\"\n" - + " }\n" - + "}") - .getBytes("UTF-8")); - } catch (Exception e) { - // 忽略 - } - - // 打包 zip - try { - File zipFile = new File( - plugin.getDataFolder(), - "radiopack.zip"); - packZip(packDir, zipFile); - plugin.getLogger().info( - "[Radio] 资源包生成: " - + allOggs.size() - + " 个音频"); - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] zip打包失败: " - + e.getMessage()); - } - } - - private void deleteDir(File dir) { - if (!dir.exists()) return; - File[] files = dir.listFiles(); - if (files != null) { - for (File f : files) { - if (f.isDirectory()) { - deleteDir(f); - } else { - f.delete(); - } - } - } - dir.delete(); - } - - private void collectOggs(File dir, - List list) { - File[] files = dir.listFiles(); - if (files == null) return; - for (File f : files) { - if (f.isDirectory()) { - collectOggs(f, list); - } else if (f.getName() - .endsWith(".ogg")) { - list.add(f); - } - } - } - - private String getRelativePath( - File base, File file) { - return base.toURI() - .relativize(file.toURI()) - .getPath(); - } - - private void packZip(File root, - File zipFile) throws Exception { - try (ZipOutputStream zos = - new ZipOutputStream( - new FileOutputStream( - zipFile))) { - zipDir(root, root, zos); - } - } - - private void zipDir(File root, - File current, - ZipOutputStream zos) - throws Exception { - File[] files = current.listFiles(); - if (files == null) return; - for (File f : files) { - if (f.isDirectory()) { - zipDir(root, f, zos); - } else { - String path = - root.toURI() - .relativize(f.toURI()) - .getPath(); - zos.putNextEntry( - new ZipEntry(path)); - zos.write(Files.readAllBytes( - f.toPath())); - zos.closeEntry(); - } - } - } - - // ========== 加载文件列表 ========== - - private void loadFiles() { - mainFiles.clear(); - loginFiles.clear(); - - File radioOgg = new File( - oggDir, "radio"); - loadOggs(radioOgg, mainFiles); - - File loginOgg = new File( - oggDir, "radio/login"); - loadOggs(loginOgg, loginFiles); - - File regOgg = new File( - oggDir, "radio/reg"); - loadOggs(regOgg, loginFiles); - } - - private void loadOggs(File dir, - List list) { - File[] files = dir.listFiles( - (d, n) -> n.toLowerCase() - .endsWith(".ogg")); - if (files != null) { - Arrays.sort(files); - for (File f : files) - list.add(f); - } - } - - // ========== 资源包发送 ========== - public void sendResourcePack(Player p) { - File zip = new File( - plugin.getDataFolder(), - "radiopack.zip"); - if (!zip.exists()) return; - try { - byte[] data = - Files.readAllBytes( - zip.toPath()); - java.security.MessageDigest md = - java.security.MessageDigest - .getInstance("SHA-1"); - md.update(data); - byte[] hashBytes = md.digest(); - StringBuilder sb = - new StringBuilder(); - for (byte b : hashBytes) - sb.append(String.format( - "%02x", b)); - String hash = sb.toString(); - UUID uuid = UUID.randomUUID(); - - // 用反射兼容不同版本 - try { - java.lang.reflect.Method m = - p.getClass().getMethod( - "setResourcePack", - UUID.class, - byte[].class, - String.class, - String.class, - boolean.class); - m.invoke(p, uuid, data, - hash, - "Sdf1 Radio", - false); - } catch (NoSuchMethodException ex) { - try { - java.lang.reflect.Method m2 = - p.getClass().getMethod( - "setResourcePack", - UUID.class, - byte[].class, - String.class); - m2.invoke(p, uuid, - data, hash); - } catch (Exception ex2) { - plugin.getLogger().warning( - "[Radio] 服务端不支持" - + "发送资源包"); - } - } - } catch (Exception e) { - } - } - - // ========== 全服广播 ========== - - public void startMainRadio() { - stopMainRadio(); - if (mainFiles.isEmpty()) { - plugin.getLogger().warning( - "[Radio] radio/ 目录" - + "无 .ogg 文件"); - return; - } - plugin.getLogger().info( - "[Radio] 启动全服广播" - + " (" + mainFiles.size() - + " 个文件)"); - - mainRadioTask = new BukkitRunnable() { - @Override - public void run() { - playNext(); - } - }.runTaskTimer( - plugin, 100L, 2400L); - } - private void playNext() { - if (mainFiles.isEmpty()) return; - File file = mainFiles.get( - radioIndex % mainFiles.size()); - String name = file.getName() - .replace(".ogg", ""); - String key = "sdf1radio:" + name; - - for (Player p - : Bukkit.getOnlinePlayers()) { - p.playSound( - p.getLocation(), - key, - SoundCategory.RECORDS, - 2.0f, 1.0f); - } - - Bukkit.broadcastMessage( - "§6§l[广播] §f正在播放: §e" - + name); - radioIndex++; - } - - - public void stopMainRadio() { - if (mainRadioTask != null) { - mainRadioTask.cancel(); - mainRadioTask = null; - } - } - - // ========== 登录阶段广播 ========== - - public void startLoginRadio( - Player p) { - loginPlayers.put(p.getName(), true); - if (loginRadioTask != null) return; - if (loginFiles.isEmpty()) return; - - loginRadioTask = new BukkitRunnable() { - @Override - public void run() { - if (loginPlayers.isEmpty()) { - cancel(); - loginRadioTask = null; - return; - } - int idx = (int) - (System.currentTimeMillis() - / 2400L - % loginFiles.size()); - File file = - loginFiles.get(idx); - String rel = - getRelativePath( - oggDir, file) - .replace("\\", "/"); - String key = - "sdf1radio:" - + rel.replace(".ogg", ""); - - for (String name - : loginPlayers.keySet()) { - Player lp = Bukkit - .getPlayer(name); - if (lp != null - && lp.isOnline()) { - lp.playSound( - lp.getLocation(), - key, - SoundCategory - .RECORDS, - 1.5f, 1.0f); - } - } - } - }.runTaskTimer( - plugin, 0L, 24L); - } - - public void stopLoginRadio( - Player p) { - loginPlayers.remove(p.getName()); - if (loginPlayers.isEmpty() - && loginRadioTask != null) { - loginRadioTask.cancel(); - loginRadioTask = null; - } - } - - public void stopAllLoginRadio() { - loginPlayers.clear(); - if (loginRadioTask != null) { - loginRadioTask.cancel(); - loginRadioTask = null; - } - } - - // ========== 手动播放 ========== - public boolean playSpecific( - Player sender, String name) { - List all = new ArrayList<>(); - collectOggs(oggDir, all); - for (File f : all) { - String fn = f.getName() - .replace(".ogg", ""); - if (fn.equalsIgnoreCase(name)) { - String key = "sdf1radio:" + name; - for (Player p - : Bukkit.getOnlinePlayers()) { - p.playSound( - p.getLocation(), - key, - SoundCategory.RECORDS, - 2.0f, 1.0f); - } - Bukkit.broadcastMessage( - "§6§l[广播] §f正在播放: §e" - + name); - return true; - } - } - sender.sendMessage("§c未找到: " + name); - return false; - } - - // ========== 重载 ========== - - public void reload() { - stopMainRadio(); - stopAllLoginRadio(); - convertAll(); - buildResourcePack(); - loadFiles(); - // 不自动启动,由管理员手动启动 - } } - diff --git a/txt/RiskControlManager.txt b/txt/RiskControlManager.txt deleted file mode 100644 index 5671a0d..0000000 --- a/txt/RiskControlManager.txt +++ /dev/null @@ -1,276 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - -public class RiskControlManager { - - private final Main plugin; - - // 玩家当前是否处于风控状态 - private final Map riskActive = new ConcurrentHashMap<>(); - // 密码验证连续错误次数 - private final Map passwordAttempts = new ConcurrentHashMap<>(); - // 邮箱验证码连续错误次数 - private final Map emailAttempts = new ConcurrentHashMap<>(); - // 风控触发时间(5分钟过期) - private final Map riskTriggerTime = new ConcurrentHashMap<>(); - // 临时封禁到期时间 - private final Map tempBanExpiry = new ConcurrentHashMap<>(); - - private static final long RISK_TIMEOUT = 5 * 60 * 1000L; // 5分钟 - private static final int MAX_ATTEMPTS = 3; - private static final long TEMP_BAN_DURATION = 10 * 60 * 1000L; // 10分钟 - - public RiskControlManager(Main plugin) { - this.plugin = plugin; - } - - /** - * 检查玩家是否被临时封禁 - */ - public boolean isTempBanned(String name) { - Long expiry = tempBanExpiry.get(name); - if (expiry == null) return false; - if (System.currentTimeMillis() >= expiry) { - tempBanExpiry.remove(name); - return false; - } - return true; - } - - /** - * 获取临时封禁剩余秒数 - */ - public int getTempBanRemaining(String name) { - Long expiry = tempBanExpiry.get(name); - if (expiry == null) return 0; - return (int) ((expiry - System.currentTimeMillis()) / 1000); - } - - /** - * 检查玩家是否需要风控验证 - * 条件:IP变化 + 有背包数据(老玩家) - */ - public boolean shouldTrigger(String name, String currentIP) { - if (isTempBanned(name)) return false; - if (riskActive.getOrDefault(name, false)) return false; - - // 获取上次记录的IP - String lastIP = (String) plugin.getDb().getField(name, "last_ip"); - if (lastIP == null || lastIP.isEmpty()) return false; // 新玩家不触发 - - // IP相同不触发 - if (lastIP.equals(currentIP)) return false; - - // 有背包数据的老玩家 + IP变化 → 触发风控 - String invData = (String) plugin.getDb().getField(name, "inventory_data"); - if (invData != null && !invData.isEmpty()) { - return true; - } - return false; - } - - /** - * 触发风控:冻结玩家,保存并隐藏背包 - */ - public void trigger(Player p, String currentIP) { - String name = p.getName(); - riskActive.put(name, true); - riskTriggerTime.put(name, System.currentTimeMillis()); - passwordAttempts.put(name, 0); - emailAttempts.put(name, 0); - - // 保存背包并冻结 - plugin.hideInventory(p); - - p.sendMessage("§c§l[风控系统] §f检测到异常登录!"); - p.sendMessage("§e当前IP: §f" + currentIP); - p.sendMessage("§e请选择验证方式:"); - p.sendMessage("§a1. §f密码验证 §7(输入: 密码 <你的密码>)"); - p.sendMessage("§a2. §f邮箱验证 §7(输入: 邮箱)"); - p.sendMessage("§7风控有效期: 5分钟"); - } - - /** - * 处理密码验证 - */ - public boolean handlePassword(Player p, String input) { - String name = p.getName(); - if (!riskActive.getOrDefault(name, false)) return false; - - // 检查过期 - if (isExpired(name)) { expireRisk(name); return false; } - - int attempts = passwordAttempts.getOrDefault(name, 0) + 1; - passwordAttempts.put(name, attempts); - - String salt = (String) plugin.getDb().getField(name, "password_salt"); - if (salt == null) return false; - String hash = PasswordUtils.hash(input, salt); - - if (plugin.getDb().checkPassword(name, hash)) { - passRisk(p); - return true; - } - - if (attempts >= MAX_ATTEMPTS) { - failRisk(p); - } else { - p.sendMessage("§c§l[风控系统] §f密码错误!剩余 " + (MAX_ATTEMPTS - attempts) + " 次机会"); - } - return true; - } - - /** - * 发送邮箱验证码 - */ - public void sendEmailCode(Player p) { - String name = p.getName(); - if (!riskActive.getOrDefault(name, false)) return; - if (isExpired(name)) { expireRisk(name); return; } - - String emailAddr = (String) plugin.getDb().getField(name, "email"); - if (emailAddr == null || emailAddr.isEmpty()) { - p.sendMessage("§c§l[风控系统] §f你未绑定邮箱,无法使用邮箱验证"); - return; - } - - if (!plugin.getEmail().isConfigured()) { - p.sendMessage("§c§l[风控系统] §f邮件服务未配置,请使用密码验证"); - return; - } - - String code = PasswordUtils.generateVerifyCode(); - plugin.getChatInput().setVerifyCode(p, code, "risk_control"); - boolean sent = plugin.getEmail().sendVerifyCode(emailAddr, name, code); - if (sent) { - p.sendMessage("§a§l[风控系统] §f验证码已发送到 " + emailAddr); - p.sendMessage("§e请输入: 验证 <验证码>"); - } else { - p.sendMessage("§c§l[风控系统] §f验证码发送失败,请使用密码验证"); - } - } - - /** - * 处理邮箱验证码 - */ - public boolean handleEmailCode(Player p, String code) { - String name = p.getName(); - if (!riskActive.getOrDefault(name, false)) return false; - if (isExpired(name)) { expireRisk(name); return false; } - - int attempts = emailAttempts.getOrDefault(name, 0) + 1; - emailAttempts.put(name, attempts(p, code, "risk_control")) { - passRisk(p); - return true; - } - - if (attempts >= MAX_ATTEMPTS) { - failRisk(p); - } else { - p.sendMessage("§c§l[风控系统] §f验证码); - - if (plugin.getChatInput().checkVerifyCode错误!剩余 " + (MAX_ATTEMPTS - attempts) + " 次机会"); - } - return true; - } - - /** - * 验证通过:解冻,记录新IP - */ - private void passRisk(Player p) { - String name = p.getName(); - riskActive.remove(name); - passwordAttempts.remove(name); - emailAttempts.remove(name); - riskTriggerTime.remove(name); - - // 记录新IP - String ip = plugin.getPlayerIP(p); - if (ip != null) plugin.getDb().setField(name, "last_ip", ip); - plugin.recordIPLogin(p); - - // 还原背包 - plugin.restoreInventory(p); - plugin.getLoggedIn().add(name); - plugin.getDb().setLoggedIn(name, true); - - p.sendMessage("§a§l[风控系统] §f验证通过!已解除风控限制"); - p.playSound(p.getLocation(), org.bukkit.Sound.BLOCK_NOTE_BLOCK_CHIME, 1.0f, 0.79f); - } - - /** - * 验证失败3次:临时封禁 + 邮件报警 - */ - private void failRisk(Player p) { - String name = p.getName(); - riskActive.remove(name); - passwordAttempts.remove(name); - emailAttempts.remove(name); - riskTriggerTime.remove(name); - - // 临时封禁10分钟 - tempBanExpiry.put(name, System.currentTimeMillis() + TEMP_BAN_DURATION); - - p.sendMessage("§c§l[风控系统] §f验证失败次数过多,临时封禁10分钟"); - - // 邮件报警 - String emailAddr = (String) plugin.getDb().getField(name, "email"); - if (emailAddr != null && !emailAddr.isEmpty() && plugin.getEmail().isConfigured()) { - String ip = plugin.getPlayerIP(p); - plugin.getEmail().sendVerifyCode(emailAddr, name, - "[Sdf1_login] 风控报警: 玩家 " + name + " IP(" + ip + ")验证失败3次,已临时封禁10分钟"); - } - - // 踢出 - p.kickPlayer("§c§l[风控系统] §f验证失败,临时封禁10分钟,请稍后再试"); - } - - /** - * 检查风控是否过期 - */ - private boolean isExpired(String name) { - Long trigger = riskTriggerTime.get(name); - if (trigger == null) return true; - return System.currentTimeMillis() - trigger > RISK_TIMEOUT; - } - - /** - * 风控过期处理 - */ - private void expireRisk(String name) { - riskActive.remove(name); - passwordAttempts.remove(name); - emailAttempts.remove(name); - riskTriggerTime.remove(name); - } - - /** - * 玩家是否处于风控状态 - */ - public boolean isRiskActive(String name) { - return riskActive.getOrDefault(name, false); - } - - /** - * 定时清理过期数据 - */ - public void cleanup() { - long now = System.currentTimeMillis(); - riskTriggerTime.entrySet().removeIf(e -> { - if (now - e.getValue() > RISK_TIMEOUT) { - riskActive.remove(e.getKey()); - passwordAttempts.remove(e.getKey()); - emailAttempts.remove(e.getKey()); - return true; - } - return false; - }); - tempBanExpiry.entrySet().removeIf(e -> now >= e.getValue()); - } -} diff --git a/txt/TicketManager.txt b/txt/TicketManager.txt deleted file mode 100644 index 18f3392..0000000 --- a/txt/TicketManager.txt +++ /dev/null @@ -1,1650 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.OfflinePlayer; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public class TicketManager { - - private final Main plugin; - - public static final String T_TICKET_MAIN = - "§6§l工单中心"; - public static final String T_TICKET_CREATE = - "§e§l创建工单"; - public static final String T_TICKET_MY = - "§b§l我的工单"; - public static final String T_TICKET_ADMIN = - "§c§l工单管理"; - public static final String T_TICKET_DETAIL = - "§a§l工单详情"; - - public static final String TYPE_BUG = - "bug反馈"; - public static final String TYPE_HELP = - "求助"; - public static final String TYPE_REPORT = - "举报"; - public static final String TYPE_APPLY = - "申请"; - public static final String TYPE_OTHER = - "其他"; - public static final String TYPE_DELETE = - "删号"; - public static final String TYPE_PASSWORD = - "修改密码"; - - - - private static final Map - PRICES = new LinkedHashMap<>(); - static { - PRICES.put(TYPE_BUG, 10); - PRICES.put(TYPE_HELP, 5); - PRICES.put(TYPE_REPORT, 8); - PRICES.put(TYPE_APPLY, 15); - PRICES.put(TYPE_OTHER, 5); - PRICES.put(TYPE_DELETE, 0); - PRICES.put(TYPE_PASSWORD, 0); - } - - - public TicketManager(Main plugin) { - this.plugin = plugin; - } - - // ========== GUI ========== - - public void openMain(Player p) { - Inventory g = Bukkit.createInventory( - null, 27, T_TICKET_MAIN); - fillBg(g); - g.setItem(10, mk(Material.PAPER, - "§e创建工单", - "§7提交bug、求助、举报等")); - g.setItem(12, mk(Material.BOOK, - "§b我的工单", - "§7查看已提交的工单")); - g.setItem(14, mk(Material.CHEST, - "§6工单记录", - "§7查看历史工单")); - if (adm(p)) { - g.setItem(16, mk(Material.REDSTONE, - "§c工单管理", - "§7管理员专用")); - } - if (plugin.getDb() - .isServiceProvider( - p.getName())) { - g.setItem(18, mk(Material.EMERALD, - "§a服务商面板", - "§7接单、处理工单")); - } - g.setItem(22, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - public void openCreate(Player p) { - Inventory g = Bukkit.createInventory( - null, 36, T_TICKET_CREATE); - fillBg(g); - g.setItem(10, mk(Material.BOOK, - "§cbug反馈")); - g.setItem(11, mk(Material.EMERALD, - "§a求助")); - g.setItem(12, mk(Material.BLAZE_POWDER, - "§6举报")); - g.setItem(13, mk(Material.NAME_TAG, - "§e申请")); - g.setItem(14, mk(Material.PAPER, - "§7其他")); - g.setItem(20, mk(Material.BARRIER, - "§c申请删号", - "§7申请删除账号")); - g.setItem(21, mk(Material.NETHER_WART, - "§d申请改密", - "§7请求管理员协助改密")); - g.setItem(31, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - public void openMyTickets(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, T_TICKET_MY); - fillBg(g); - List> list = - plugin.getDb() - .getTicketsByPlayer( - p.getName()); - fillList(g, list); - g.setItem(49, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - public void openDetail(Player p, int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - Inventory g = Bukkit.createInventory( - null, 27, T_TICKET_DETAIL); - fillBg(g); - - String type = sv(t, "type"); - String status = sv(t, "status"); - String requester = sv(t, "requester"); - String title = sv(t, "title"); - String desc = sv(t, "description"); - String assigned = sv(t, "assigned_to"); - int reward = nv(t, "reward_amount"); - int score = nv(t, "score"); - int confirmed = nv(t, "admin_confirmed"); - - g.setItem(10, mk(Material.PAPER, - "§e§l" + (title.isEmpty() - ? type : title), - "§7类型: §f" + type, - "§7状态: " + stxt(status), - "§7提交者: §f" + requester, - "§7奖励: §e" + reward + " 积分")); - - g.setItem(12, mk(Material.BOOK, - "§7详细描述", - desc.isEmpty() ? "§7无描述" - : "§7" + (desc.length() > 40 - ? desc.substring(0, 40) - + "..." : desc))); - - if (!assigned.isEmpty()) { - g.setItem(14, mk(Material.PLAYER_HEAD, - "§7处理人: §f" + assigned, - confirmed == 1 - ? "§7评分: §e" + score - : "§7处理中")); - } else { - g.setItem(14, mk(Material.BARRIER, - "§7未分配")); - } - - // 驳回原因 - if ("closed".equals(status)) { - String reason = sv(t, "reject_reason"); - if (!reason.isEmpty()) { - g.setItem(13, mk(Material.BARRIER, - "§c驳回原因", - "§7" + reason)); - } - } - - // 回复按钮(非关闭/已完结状态) - if (!"closed".equals(status) - && !"withdrawn".equals(status) - && !"resolved".equals(status)) { - g.setItem(15, mk( - Material.WRITABLE_BOOK, - "§b回复工单", - "§7向对方发送消息")); - g.setItem(16, mk(Material.BOOK, - "§7查看回复记录")); - } - - // ===== 管理员按钮 ===== - if (adm(p)) { - if ("pending".equals(status)) { - // 待分配:可分配给服务商 - g.setItem(19, mk(Material.EMERALD, - "§a分配工单", - "§7指定服务商处理")); - // 也可自己处理 - g.setItem(20, mk(Material.LIME_DYE, - "§a我来处理", - "§7管理员直接处理此工单")); - } - if ("processing".equals(status) - && !assigned.equals("admin") - && !assigned.isEmpty()) { - // 服务商在处理:可取消指派收回 - g.setItem(19, mk(Material.BARRIER, - "§c取消指派", - "§7取消服务商指派,收回工单")); - // 也可自己接手处理 - g.setItem(20, mk(Material.LIME_DYE, - "§a我来处理", - "§7管理员直接处理此工单")); - } - if ("processing".equals(status) - && assigned.equals("admin")) { - // 管理员自己在处理:可标记完结 - g.setItem(19, mk(Material.LIME_DYE, - "§a标记完结", - "§7确认工单处理完成")); - } - if ("replied".equals(status) - && assigned.equals("admin")) { - g.setItem(19, mk(Material.LIME_DYE, - "§a标记完结", - "§7确认工单处理完成")); - } - // 驳回(非已关闭/已完结状态) - if (!"closed".equals(status) - && !"resolved".equals(status) - && !"withdrawn".equals(status)) { - g.setItem(21, mk(Material.RED_DYE, - "§c驳回工单", - "§7驳回并关闭此工单")); - } - } - - // ===== 服务商按钮 ===== - if (plugin.getDb() - .isServiceProvider(p.getName()) - && !TYPE_DELETE.equals(type) - && !TYPE_PASSWORD.equals(type)) { - if ("pending".equals(status) - && assigned.isEmpty()) { - // 待分配无人:抢单 - g.setItem(19, mk( - Material.GOLD_INGOT, - "§6抢单", - "§7接取此工单")); - } - if (assigned.equals(p.getName()) - && ("processing".equals(status) - || "replied".equals(status))) { - // 我在处理:标记完结/上报异常 - g.setItem(19, mk( - Material.LIME_DYE, - "§a标记完结", - "§7上报处理完成")); - g.setItem(20, mk( - Material.REDSTONE, - "§c上报异常", - "§7报告处理中的问题")); - } - } - - g.setItem(22, mk(Material.ARROW, - "§7返回")); - - ItemStack info = g.getItem(10); - if (info != null) { - ItemMeta im = info.getItemMeta(); - if (im != null) { - im.setCustomModelData(tid); - info.setItemMeta(im); - } - } - p.openInventory(g); - } - - - public void openAdminList(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, T_TICKET_ADMIN); - fillBg(g); - g.setItem(45, mk(Material.EMERALD, - "§a邀请服务商")); - g.setItem(46, mk(Material.BARRIER, - "§c移除服务商")); - g.setItem(47, mk(Material.CLOCK, - "§e服务商列表")); - g.setItem(48, mk(Material.NETHER_STAR, - "§e直接派单", - "§7管理员直接创建并派单")); - List> all = - new ArrayList<>(); - all.addAll(plugin.getDb() - .getTicketsByStatus("pending")); - all.addAll(plugin.getDb() - .getTicketsByStatus("processing")); - all.addAll(plugin.getDb() - .getTicketsByStatus("replied")); - all.addAll(plugin.getDb() - .getTicketsByStatus("completed")); - all.addAll(plugin.getDb() - .getTicketsByStatus("resolved")); - all.addAll(plugin.getDb() - .getTicketsByStatus("closed")); - fillList(g, all); - g.setItem(49, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - - public void openProviderPanel(Player p) { - Inventory g = Bukkit.createInventory( - null, 27, "§d§l服务商面板"); - fillBg(g); - g.setItem(10, mk(Material.CHEST, - "§e抢单大厅", - "§7查看可接工单")); - g.setItem(12, mk(Material.CLOCK, - "§b我的工单", - "§7我正在处理的工单")); - g.setItem(14, mk(Material.PAPER, - "§6历史工单", - "§7已完成的工单")); - g.setItem(22, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - public void openGrabList(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, "§6§l抢单大厅"); - fillBg(g); - List> pending = - plugin.getDb() - .getTicketsByStatus( - "pending"); - List> list = - new ArrayList<>(); - for (Map t : pending) { - if (sv(t, "assigned_to").isEmpty()) { - list.add(t); - } - } - fillList(g, list); - g.setItem(49, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - public void openMyAssigned(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, - "§b§l我处理中的工单"); - fillBg(g); - List> list = - plugin.getDb() - .getTicketsByProvider( - p.getName()); - fillList(g, list); - g.setItem(49, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - // ========== 业务 ========== - - public int submitTicket(Player p, - String type, String title, - String desc) { - int price = PRICES - .getOrDefault(type, 5); - int id = plugin.getDb().createTicket( - type, "pending", "normal", - p.getName(), title, desc, - price, ""); - if (id > 0) { - if (TYPE_DELETE.equals(type) - || TYPE_PASSWORD.equals(type)) { - plugin.getDb().updateTicketField( - id, "assigned_to", "admin"); - // 播放派单音效 - if (plugin.getTicketSound() != null) { - plugin.getTicketSound() - .playSound(p, "assign"); - } - - plugin.getDb().updateTicketStatus( - id, "processing"); - p.sendMessage( - "§a§l[工单] §f工单 #" - + id + " 已提交," - + "等待管理员处理"); - } else { - p.sendMessage( - "§a§l[工单] §f创建成功!" - + " #§e" + id - + " 奖励: §e" - + price + "积分"); - } - notifyAdmins(p.getName(), id); - } else { - p.sendMessage("§c创建失败"); - } - return id; - } - - - public void adminCreateAndAssign( - Player p, String type, - String title, String desc, - String provider, int price) { - if (!plugin.getDb() - .isServiceProvider(provider)) { - p.sendMessage("§c" + provider - + " 不是服务商"); - return; - } - int id = plugin.getDb().createTicket( - type, "processing", "normal", - p.getName(), title, desc, - price, ""); - if (id <= 0) { - p.sendMessage("§c创建失败"); - return; - } - plugin.getDb().assignTicket( - id, provider); - if (price > 0) { - plugin.getDb().updateTicketField( - id, "reward_amount", price); - } - p.sendMessage("§a工单 #" + id - + " 已创建并分配给 " + provider); - Player tp = Bukkit.getPlayer(provider); - if (tp != null && tp.isOnline()) { - tp.sendMessage( - "§e§l[工单] §f管理员指派工单 #" - + id + " 给您"); - playAssignedSound(tp); - } - } - - public void grabTicket(Player p, int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - if (!"pending".equals( - sv(t, "status"))) { - p.sendMessage("§c该工单已被接单"); - return; - } - if (!sv(t, "assigned_to").isEmpty()) { - p.sendMessage("§c该工单已被分配"); - return; - } - plugin.getDb().assignTicket( - tid, p.getName()); - p.sendMessage("§a抢单成功!工单 #" + tid); - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§e§l[工单] §f您的工单 #" - + tid + " 已被 " - + p.getName() - + " 接单"); - } - } - - public void assignToProvider(Player p, - int tid, String provider, - int price) { - if (!plugin.getDb() - .isServiceProvider(provider)) { - p.sendMessage("§c" + provider - + " 不是服务商"); - return; - } - plugin.getDb().assignTicket( - tid, provider); - if (price > 0) { - plugin.getDb().updateTicketStatus( - tid, "processing"); - plugin.getDb().updateTicketField( - tid, "reward_amount", price); - } - p.sendMessage("§a工单 #" + tid - + " 已分配给 " + provider); - Player tp = Bukkit.getPlayer(provider); - if (tp != null && tp.isOnline()) { - tp.sendMessage( - "§e§l[工单] §f您有新工单: #" - + tid); - playAssignedSound(tp); - } - } - - public void confirmAndScore(Player p, - int tid, int score) { - if (score < 1 || score > 5) { - p.sendMessage("§c评分1-5"); - return; - } - plugin.getDb().confirmTicket( - tid, score); - p.sendMessage("§a工单 #" + tid - + " 已确认,评分: " + score); - Map t = - plugin.getDb().getTicket(tid); - if (t != null) { - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§a§l[工单] §f工单 #" - + tid - + " 已确认完结"); - } - } - settleTicket(tid); - } - - public void completeAsProvider(Player p, - int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - if (!sv(t, "assigned_to") - .equals(p.getName())) { - p.sendMessage("§c这不是你的工单"); - return; - } - plugin.getDb().completeTicket(tid); - p.sendMessage("§a工单 #" + tid - + " 已标记完结,等待确认"); - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§a§l[工单] §f工单 #" - + tid - + " 已处理完成"); - } - } - - public void rejectTicket(Player p, - int tid, String reason) { - plugin.getDb().updateTicketStatus( - tid, "closed"); - plugin.getDb().updateTicketField( - tid, "reject_reason", reason); - p.sendMessage("§c工单 #" + tid - + " 已驳回并关闭"); - Map t = - plugin.getDb().getTicket(tid); - if (t != null) { - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§c§l[工单] §f工单 #" - + tid - + " 被驳回: " - + reason); - } - } - } - - public void replyTicket(Player p, - int tid, String message) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - String status = sv(t, "status"); - if ("closed".equals(status) - || "withdrawn".equals(status) - || "resolved".equals(status)) { - p.sendMessage("§c该工单已关闭"); - return; - } - plugin.getDb().addTicketReply( - tid, p.getName(), message); - p.sendMessage("§a回复成功"); - String requester = - sv(t, "requester"); - String assigned = - sv(t, "assigned_to"); - String sender = p.getName(); - if (!requester.equals(sender)) { - Player rp = Bukkit.getPlayer( - requester); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§e§l[工单] §f工单 #" - + tid + " 新回复"); - } - } - if (!assigned.isEmpty() - && !assigned.equals(sender)) { - Player ap = Bukkit.getPlayer( - assigned); - if (ap != null && ap.isOnline()) { - ap.sendMessage( - "§e§l[工单] §f工单 #" - + tid + " 新回复"); - } - } - } - - public void showReplies(Player p, - int tid) { - List> replies = - plugin.getDb() - .getTicketReplies(tid); - if (replies.isEmpty()) { - p.sendMessage("§7暂无回复"); - return; - } - p.sendMessage("§e§l=== 回复记录 ==="); - SimpleDateFormat sdf = - new SimpleDateFormat( - "MM-dd HH:mm"); - for (Map r : replies) { - String sender = sv(r, "sender"); - String msg2 = sv(r, "message"); - long time = ((Number) r.get( - "create_time")).longValue(); - p.sendMessage("§7[" + sdf.format( - new Date(time)) + "] §f" - + sender + "§7: " + msg2); - } - p.sendMessage( - "§e§l================="); - } - - public void withdrawTicket(Player p, - int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - if (!sv(t, "requester") - .equals(p.getName()) - && !adm(p)) { - p.sendMessage("§c无权操作"); - return; - } - plugin.getDb().updateTicketStatus( - tid, "withdrawn"); - p.sendMessage("§a工单 #" + tid - + " 已撤回"); - } - - public void showProviderList(Player p) { - List> list = - plugin.getDb() - .getAllServiceProviders(); - if (list.isEmpty()) { - p.sendMessage("§7暂无服务商"); - return; - } - p.sendMessage( - "§e§l=== 服务商列表 ==="); - for (Map pro : list) { - p.sendMessage("§7- §f" - + pro.get("player_name") - + " §7角色: §e" - + pro.get("role")); - } - p.sendMessage( - "§e§l==================="); - } - - public void autoProcessCompleted() { - long timeoutMs = 10 * 60 * 1000L; - - // 自动处理删号工单(5分钟自动通过) - List> deleteTickets = - plugin.getDb() - .getTicketsByStatus( - "processing"); - for (Map t : deleteTickets) { - String dtype = sv(t, "type"); - if (!TYPE_DELETE.equals(dtype)) continue; - int did = nv(t, "id"); - int dconf = nv(t, "admin_confirmed"); - // 已经处理过了跳过 - if (dconf != 0) continue; - String dasg = sv(t, "assigned_to"); - // 只处理分配给admin的 - if (!"admin".equals(dasg)) continue; - long createTime = ((Number) t.get( - "create_time")).longValue(); - if (System.currentTimeMillis() - - createTime >= 5 * 60 * 1000L) { - processDeleteAccount(did, t); - } - } - - - // 已完结未确认→10分钟自动完结 - List> list = - plugin.getDb() - .getUnconfirmedCompleted( - timeoutMs); - for (Map t : list) { - int id = nv(t, "id"); - plugin.getDb().confirmTicket(id, 3); - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) - rp.sendMessage( - "§e§l[工单] §f工单 #" - + id + " 已自动完结"); - String assigned = - sv(t, "assigned_to"); - if (!assigned.isEmpty()) { - Player pp = - Bukkit.getPlayer(assigned); - if (pp != null && pp.isOnline()) - pp.sendMessage( - "§e§l[工单] §f工单 #" - + id - + " 已自动完结"); - } - settleTicket(id); - } - - // 已回复但10分钟无互动→自动完结 - List> stale = - plugin.getDb() - .getRepliedStaleTickets( - timeoutMs); - for (Map t : stale) { - int id = nv(t, "id"); - plugin.getDb().confirmTicket(id, 3); - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) - rp.sendMessage( - "§e§l[工单] §f工单 #" - + id + " 超时自动完结"); - settleTicket(id); - } - } - - - - // ========== GUI点击 ========== - public boolean handleClick(Player p, - String title, int slot) { - - if (title.equals(T_TICKET_MAIN)) { - if (slot == 10) openCreate(p); - else if (slot == 12) - openMyTickets(p); - else if (slot == 14) - openMyTickets(p); - else if (slot == 16) { - if (adm(p)) openAdminList(p); - else p.sendMessage("§c权限不足"); - } - else if (slot == 18) { - if (plugin.getDb() - .isServiceProvider(p.getName())) { - openProviderPanel(p); - } else { - p.sendMessage("§c权限不足"); - } - } - else if (slot == 22) - plugin.getGui().openMain(p); - return true; - } - - if (title.equals(T_TICKET_CREATE)) { - if (slot == 31) { - openMain(p); - return true; - } - String type = null; - if (slot == 10) type = TYPE_BUG; - else if (slot == 11) type = TYPE_HELP; - else if (slot == 12) type = TYPE_REPORT; - else if (slot == 13) type = TYPE_APPLY; - else if (slot == 14) type = TYPE_OTHER; - else if (slot == 20) type = TYPE_DELETE; - else if (slot == 21) type = TYPE_PASSWORD; - if (type != null) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_TITLE; - plugin.getChatInput() - .getState(p) - .ticketType = type; - if (TYPE_DELETE.equals(type)) { - p.sendMessage("§e请描述删号原因:"); - } else if (TYPE_PASSWORD.equals(type)) { - p.sendMessage("§e请描述需要协助的内容:"); - } else { - p.sendMessage("§e请输入工单标题:"); - } - } - return true; - } - - - if (title.equals(T_TICKET_MY)) { - if (slot == 49) { - openMain(p); - return true; - } - if (slot >= 0 && slot < 45) { - ItemStack it = p.getOpenInventory() - .getTopInventory().getItem(slot); - int id = gt(it); - if (id > 0) openDetail(p, id); - } - return true; - } - - if (title.equals(T_TICKET_DETAIL)) { - return handleClickDetail(p, slot); - } - - if (title.equals(T_TICKET_ADMIN)) { - if (slot == 49) { - openMain(p); - return true; - } - if (slot == 45) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_INVITE_PROVIDER; - p.sendMessage("§e输入服务商玩家名:"); - return true; - } - if (slot == 46) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_REMOVE_PROVIDER; - p.sendMessage("§e输入要移除的服务商:"); - return true; - } - if (slot == 47) { - showProviderList(p); - return true; - } - if (slot == 48) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_ADMIN_TYPE; - p.sendMessage("§e选择工单类型:"); - p.sendMessage("§7bug/求助/举报/申请/其他"); - return true; - } - if (slot >= 0 && slot < 45) { - ItemStack it = p.getOpenInventory() - .getTopInventory().getItem(slot); - int id = gt(it); - if (id > 0) openDetail(p, id); - } - return true; - } - - if (title.equals("§d§l服务商面板")) { - if (slot == 22) { - plugin.getGui().openMain(p); - return true; - } - if (slot == 10) openGrabList(p); - else if (slot == 12) openMyAssigned(p); - else if (slot == 14) openMyTickets(p); - return true; - } - - if (title.equals("§6§l抢单大厅")) { - if (slot == 49) { - openProviderPanel(p); - return true; - } - if (slot >= 0 && slot < 45) { - ItemStack it = p.getOpenInventory() - .getTopInventory().getItem(slot); - int id = gt(it); - if (id > 0) openDetail(p, id); - } - return true; - } - - if (title.equals("§b§l我处理中的工单")) { - if (slot == 49) { - openProviderPanel(p); - return true; - } - if (slot >= 0 && slot < 45) { - ItemStack it = p.getOpenInventory() - .getTopInventory().getItem(slot); - int id = gt(it); - if (id > 0) openDetail(p, id); - } - return true; - } - - return false; - } - - private boolean handleClickDetail(Player p, - int slot) { - if (slot == 22) { - openMyTickets(p); - return true; - } - int id = gtFromDetail(p); - if (id <= 0) return true; - Map t = - plugin.getDb().getTicket(id); - if (t == null) return true; - - String st = sv(t, "status"); - int conf = nv(t, "admin_confirmed"); - String asg = sv(t, "assigned_to"); - - // 回复按钮 - if (slot == 15) { - if (!"closed".equals(st) - && !"withdrawn".equals(st) - && !"resolved".equals(st)) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_REPLY; - plugin.getChatInput() - .getState(p).ticketId = id; - p.sendMessage("§e输入回复内容:"); - } - return true; - } - - // 查看回复记录 - if (slot == 16) { - showReplies(p, id); - return true; - } - - // slot 19 - if (slot == 19) { - // 管理员:分配工单 - if (adm(p) && "pending".equals(st) - && asg.isEmpty()) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_ASSIGN; - plugin.getChatInput() - .getState(p).ticketId = id; - p.sendMessage( - "§e输入: 服务商名 价格"); - return true; - } - // 管理员:取消指派(服务商在处理中) - if (adm(p) && "processing".equals(st) - && !asg.equals("admin") - && !asg.isEmpty()) { - cancelTicket(p, id); - openDetail(p, id); - return true; - } - // 管理员:自己处理(pending) - if (adm(p) && "pending".equals(st) - && asg.isEmpty()) { - plugin.getDb().assignTicket( - id, "admin"); - plugin.getDb().updateTicketStatus( - id, "processing"); - p.sendMessage("§a工单 #" + id - + " 已由您接手处理"); - openDetail(p, id); - return true; - } - // 管理员:标记完结(已回复状态) - if (adm(p) && "replied".equals(st) - && asg.equals("admin")) { - - // 管理员:标记完结(已回复状态) - if (adm(p) && "replied".equals(st) - && asg.equals("admin")) { - Map tk = - plugin.getDb().getTicket(id); - String tkType = sv(tk, "type"); - if (TYPE_DELETE.equals(tkType)) { - approveDeleteTicket(p, id); - return true; - } - plugin.getDb().completeTicket(id); - p.sendMessage("§a工单 #" + id - + " 已标记完结"); - openDetail(p, id); - return true; - } - } - // 服务商:抢单 - if (plugin.getDb() - .isServiceProvider(p.getName()) - && "pending".equals(st) - && asg.isEmpty()) { - grabTicket(p, id); - openDetail(p, id); - return true; - } - // 服务商:标记完结 - if (plugin.getDb() - .isServiceProvider(p.getName()) - && asg.equals(p.getName()) - && ("processing".equals(st) - || "replied".equals(st))) { - completeAsProvider(p, id); - openDetail(p, id); - return true; - } - return true; - } - - // slot 20 - if (slot == 20) { - // 管理员:自己处理(pending) - if (adm(p) && "pending".equals(st) - && asg.isEmpty()) { - plugin.getDb().assignTicket( - id, "admin"); - plugin.getDb().updateTicketStatus( - id, "processing"); - p.sendMessage("§a工单 #" + id - + " 已由您接手处理"); - openDetail(p, id); - return true; - } - // 管理员:自己接手(服务商在处理中) - if (adm(p) && "processing".equals(st) - && !asg.equals("admin") - && !asg.isEmpty()) { - plugin.getDb().assignTicket( - id, "admin"); - p.sendMessage("§a工单 #" + id - + " 已由您接手处理"); - openDetail(p, id); - return true; - } - // 服务商:上报异常 - if (plugin.getDb() - .isServiceProvider(p.getName()) - && asg.equals(p.getName()) - && ("processing".equals(st) - || "replied".equals(st))) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_REPORT_ISSUE; - plugin.getChatInput() - .getState(p).ticketId = id; - p.sendMessage("§e输入异常描述:"); - return true; - } - return true; - } - - // slot 21:驳回 - if (slot == 21) { - if (adm(p) && !"closed".equals(st) - && !"resolved".equals(st) - && !"withdrawn".equals(st)) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_REJECT; - plugin.getChatInput() - .getState(p).ticketId = id; - p.sendMessage("§e输入驳回原因:"); - } - return true; - } - - return true; - } - - // ========== 聊天输入 ========== - - public boolean handleChatInput(Player p, - String msg, - ChatInputManager.InputType type) { - switch (type) { - case TICKET_TITLE: { - plugin.getChatInput() - .getState(p) - .ticketTitle = msg; - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_DESC; - p.sendMessage( - "§e请输入工单详细描述:"); - return true; - } - case TICKET_DESC: { - String tt = plugin.getChatInput() - .getState(p).ticketType; - String tl = plugin.getChatInput() - .getState(p).ticketTitle; - plugin.getChatInput().reset(p); - submitTicket(p, tt, tl, msg); - return true; - } - case TICKET_SCORE: { - int tid2 = plugin.getChatInput() - .getState(p).ticketId; - try { - int sc = Integer.parseInt( - msg.trim()); - Map tk = - plugin.getDb() - .getTicket(tid2); - String st = tk != null - ? sv(tk, "status") - : ""; - if ("completed".equals(st)) { - plugin.getChatInput() - .reset(p); - confirmAndScore( - p, tid2, sc); - } else { - plugin.getChatInput() - .reset(p); - p.sendMessage( - "§c工单尚未处理完成"); - } - } catch ( - NumberFormatException e) { - plugin.getChatInput() - .reset(p); - p.sendMessage( - "§c请输入数字1-5"); - } - return true; - } - case TICKET_REJECT: { - int tid2 = plugin.getChatInput() - .getState(p).ticketId; - plugin.getChatInput().reset(p); - rejectTicket(p, tid2, msg); - return true; - } - case TICKET_ASSIGN: { - int tid2 = plugin.getChatInput() - .getState(p).ticketId; - String[] parts = msg.trim() - .split("\\s+"); - String prov = parts[0]; - int price = 0; - if (parts.length >= 2) { - try { - price = Integer.parseInt( - parts[1]); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c价格格式错误"); - } - } - plugin.getChatInput().reset(p); - assignToProvider(p, tid2, - prov, price); - runSync(() -> - openDetail(p, tid2)); - return true; - } - case TICKET_INVITE_PROVIDER: { - String tgt = msg.trim(); - plugin.getChatInput().reset(p); - if (!plugin.getDb() - .userExists(tgt)) { - p.sendMessage( - "§c玩家不存在"); - return true; - } - plugin.getDb() - .addServiceProvider( - tgt, "waiter"); - p.sendMessage("§a已邀请 " + tgt - + " 成为服务商"); - return true; - } - case TICKET_REMOVE_PROVIDER: { - String tgt = msg.trim(); - plugin.getChatInput().reset(p); - if (!plugin.getDb() - .isServiceProvider( - tgt)) { - p.sendMessage("§c" + tgt - + " 不是服务商"); - return true; - } - plugin.getDb() - .removeServiceProvider( - tgt); - p.sendMessage( - "§a已移除 " + tgt); - return true; - } - default: - return false; - } - } - - // ========== 私有 ========== - - private void settleTicket(int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) return; - String requester = - sv(t, "requester"); - String assigned = - sv(t, "assigned_to"); - int reward = nv(t, "reward_amount"); - int score = nv(t, "score"); - ConfigManager cfg = - plugin.getConfig2(); - if (reward > 0) { - plugin.getDb().addPoints( - requester, reward); - Player rp = Bukkit.getPlayer( - requester); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§e§l[工单] §f获得 " - + reward - + " 积分奖励"); - } - } - if (!assigned.isEmpty()) { - int pts = (int) (score - * cfg.providerPointsPerScore); - if (pts > 0) { - plugin.getDb().addPoints( - assigned, pts); - } - double money = - cfg.baseEconomyReward - + score - * cfg.providerEconomyPerScore; - boolean ok = false; - try { - if (plugin.getEconomy() != null) { - OfflinePlayer op = - Bukkit.getOfflinePlayer( - assigned); - plugin.getEconomy() - .depositPlayer( - op, money); - ok = true; - } - } catch (Exception e) { - plugin.getLogger().warning( - "[Sdf1_login] 经济失败: " - + e.getMessage()); - } - Player pp = - Bukkit.getPlayer(assigned); - if (pp != null && pp.isOnline()) { - pp.sendMessage( - "§a§l[工单] §f工单 #" - + tid - + " 确认完结"); - if (pts > 0) - pp.sendMessage("§7积分: §e" - + pts); - if (ok) - pp.sendMessage("§7经济: §a$" - + String.format( - "%.0f", money)); - } - } - } - - private void notifyAdmins(String creator, - int tid) { - String tag = - plugin.getConfig2().adminTag; - for (Player op : - Bukkit.getOnlinePlayers()) { - if (op.getScoreboardTags() - .contains(tag)) { - op.sendMessage( - "§e§l[工单] §f" + creator - + " 提交了新工单 #" - + tid); - } - } - } - - private void playAssignedSound(Player p) { - for (int i = 0; i < 3; i++) { - final int d = i * 15; - Bukkit.getScheduler() - .runTaskLater(plugin, () -> { - if (p.isOnline()) { - p.playSound(p.getLocation(), - Sound - .BLOCK_NOTE_BLOCK_CHIME, - 1.0f, 0.79f); - p.playSound(p.getLocation(), - Sound - .BLOCK_NOTE_BLOCK_BASS, - 1.0f, 0.79f); - p.playSound(p.getLocation(), - Sound - .BLOCK_NOTE_BLOCK_PLING, - 1.0f, 0.79f); - } - }, d); - } - } - - private void runSync(Runnable task) { - Bukkit.getScheduler() - .runTask(plugin, task); - } - - private boolean adm(Player p) { - return p.getScoreboardTags().contains( - plugin.getConfig2().adminTag); - } - - private void fillBg(Inventory g) { - ItemStack glass = mk( - Material.GRAY_STAINED_GLASS_PANE, - " "); - for (int i = 0; i < g.getSize(); i++) { - if (g.getItem(i) == null - || g.getItem(i).getType() - == Material.AIR) { - g.setItem(i, glass); - } - } - } - - private void fillList(Inventory g, - List> list) { - if (list.isEmpty()) { - g.setItem(22, mk(Material.BARRIER, - "§7暂无工单")); - return; - } - int slot = 0; - for (Map t : list) { - if (slot >= 45) break; - g.setItem(slot, makeItem(t)); - slot++; - } - } - /** - * 执行删号流程 - */ - /** - * 执行删号流程 - */ - public void processDeleteAccount(int tid, - Map t) { - String requester = sv(t, "requester"); - plugin.getDb().updateTicketStatus( - tid, "resolved"); - plugin.getDb().updateTicketField(tid, - "admin_confirmed", 1); - Player target = - Bukkit.getPlayer(requester); - if (target != null && target.isOnline()) { - plugin.backupInventory(target); - target.sendMessage( - "§a§l[工单] §f您的删号请求已通过"); - target.sendMessage( - "§7物品已备份,下次登录可取回"); - target.kickPlayer( - "§a删号成功,物品已备份。\n" - + "请等待5分钟后重新注册取回物品。"); - } else { - // 离线:仅标记,不操作数据库 - plugin.getLogger().info( - "[Sdf1_login] " + requester - + " 删号工单已通过,等待上线执行"); - } - // 延迟执行删号 - final String name = requester; - Bukkit.getScheduler().runTaskLater( - plugin, () -> { - plugin.getDb().deleteUser(name); - plugin.getLogger().info( - "[Sdf1_login] " + name - + " 删号工单已执行"); - }, 100L); - } - - /** - * 管理员手动批准删号 - */ - public void approveDeleteTicket(Player p, - int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - if (!TYPE_DELETE.equals(sv(t, "type"))) { - p.sendMessage("§c这不是删号工单"); - return; - } - p.sendMessage("§a工单 #" + tid - + " 已批准,正在执行删号..."); - processDeleteAccount(tid, t); - } - - /** - * 管理员取消工单(服务商异常后) - */ - /** - * 管理员取消工单(解除服务商绑定,管理员可自行处理) - */ - public void cancelTicket(Player p, int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - // 解除服务商绑定,状态回退到pending - plugin.getDb().updateTicketField( - tid, "assigned_to", ""); - plugin.getDb().updateTicketStatus( - tid, "pending"); - p.sendMessage("§a工单 #" + tid - + " 已取消指派,回到待分配状态"); - // 通知原服务商 - String assigned = sv(t, "assigned_to"); - if (!assigned.isEmpty()) { - Player ap = Bukkit.getPlayer(assigned); - if (ap != null && ap.isOnline()) { - ap.sendMessage( - "§c§l[工单] §f工单 #" - + tid - + " 已被管理员取消指派"); - } - } - } - - - /** - * 服务商查看历史工单 - */ - public void openProviderHistory(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, - "§6§l我的历史工单"); - fillBg(g); - List> all = - plugin.getDb() - .getTicketsByProviderAll( - p.getName()); - fillList(g, all); - g.setItem(49, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - private ItemStack makeItem( - Map t) { - int id = nv(t, "id"); - String type = sv(t, "type"); - String status = sv(t, "status"); - String requester = - sv(t, "requester"); - String title = sv(t, "title"); - int reward = nv(t, "reward_amount"); - Material mat; - switch (status) { - case "processing": - mat = Material.YELLOW_DYE; - break; - case "replied": - mat = Material.CYAN_DYE; - break; - case "completed": - mat = Material.LIME_DYE; - break; - case "resolved": - mat = Material.LIME_DYE; - break; - case "closed": - mat = Material.GRAY_DYE; - break; - case "rejected": - mat = Material.RED_DYE; - break; - case "withdrawn": - mat = Material.GRAY_DYE; - break; - default: - mat = Material.ORANGE_DYE; - break; - } - ItemStack item = new ItemStack(mat); - ItemMeta im = item.getItemMeta(); - if (im != null) { - im.setDisplayName("§e#" + id - + " " + (title.isEmpty() - ? type : title)); - List lore = - new ArrayList<>(); - lore.add("§7类型: §f" + type); - lore.add("§7状态: " - + stxt(status)); - lore.add("§7提交者: §f" - + requester); - lore.add("§7奖励: §e" + reward - + " 积分"); - lore.add(""); - lore.add("§7点击查看详情"); - im.setLore(lore); - im.setCustomModelData(id); - item.setItemMeta(im); - } - return item; - } - - private int gt(ItemStack item) { - if (item == null) return -1; - ItemMeta im = item.getItemMeta(); - if (im == null) return -1; - if (im.hasCustomModelData()) - return im.getCustomModelData(); - return -1; - } - - private int gtFromDetail(Player p) { - ItemStack it = p.getOpenInventory() - .getTopInventory().getItem(10); - return gt(it); - } - - private String stxt(String status) { - switch (status) { - case "pending": - return "§e待处理"; - case "processing": - return "§b处理中"; - case "replied": - return "§9已回复"; - case "completed": - return "§a待确认"; - case "resolved": - return "§a已完结"; - case "closed": - return "§7已关闭"; - case "rejected": - return "§c已驳回"; - case "withdrawn": - return "§7已撤回"; - default: - return "§7" + status; - } - } - - private String sv(Map m, - String key) { - Object v = m.get(key); - return v != null - ? String.valueOf(v) : ""; - } - - private int nv(Map m, - String key) { - Object v = m.get(key); - return v instanceof Number - ? ((Number) v).intValue() : 0; - } - - private ItemStack mk(Material mat, - String name, String... lore) { - ItemStack it = new ItemStack(mat); - ItemMeta im = it.getItemMeta(); - if (im != null) { - im.setDisplayName(name); - if (lore.length > 0) { - List list = - new ArrayList<>(); - for (String l : lore) { - if (!l.isEmpty()) - list.add(l); - } - if (!list.isEmpty()) - im.setLore(list); - } - it.setItemMeta(im); - } - return it; - } -} diff --git a/txt/VerificationManager.txt b/txt/VerificationManager.txt deleted file mode 100644 index 6b42bdc..0000000 --- a/txt/VerificationManager.txt +++ /dev/null @@ -1,137 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.java.JavaPlugin; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.UUID; -import java.util.function.Consumer; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class VerificationManager { - - private final JavaPlugin plugin; - private final Logger logger; - - public VerificationManager(JavaPlugin plugin) { - this.plugin = plugin; - this.logger = plugin.getLogger(); - } - - public boolean isBedrockPlayer(Player player) { - UUID uuid = player.getUniqueId(); - if (uuid.toString().startsWith("00000000-0000-0000-0009-")) { - logger.info("[Sdf1_login] Geyser: UUID前缀匹配 → 基岩版"); - return true; - } - if (player.hasMetadata("isgeyserplayer")) { - logger.info("[Sdf1_login] Geyser: metadata匹配 → 基岩版"); - return true; - } - if (player.hasPermission("geyser.player")) { - logger.info("[Sdf1_login] Geyser: permission匹配 → 基岩版"); - return true; - } - try { - Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate"); - if (floodgate == null) floodgate = Bukkit.getPluginManager().getPlugin("Floodgate"); - if (floodgate != null && floodgate.isEnabled()) { - Class apiClass = Class.forName("org.geysermc.floodgate.api.FloodgateApi"); - Object api = apiClass.getMethod("getInstance").invoke(null); - boolean isBedrock = (boolean) apiClass.getMethod("isFloodgatePlayer", UUID.class).invoke(api, uuid); - if (isBedrock) { - logger.info("[Sdf1_login] Geyser: Floodgate API确认 → 基岩版"); - return true; - } - } - } catch (Exception ignored) {} - logger.info("[Sdf1_login] Geyser: 非基岩版"); - return false; - } - - public boolean isOnlineMode() { - return Bukkit.getOnlineMode(); - } - - public void verifyPremiumAsync(Player player, Consumer callback) { - String name = player.getName(); - UUID playerUUID = player.getUniqueId(); - logger.info("[Sdf1_login] Mojang验证: 查询 " + name + " ..."); - logger.info("[Sdf1_login] Mojang验证: 玩家UUID = " + playerUUID); - - Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { - UUID premiumUUID = queryMojangUUID(name); - - if (premiumUUID == null) { - logger.info("[Sdf1_login] Mojang验证: " + name + " 无记录 → 非正版"); - Bukkit.getScheduler().runTask(plugin, () -> callback.accept(false)); - return; - } - - logger.info("[Sdf1_login] Mojang验证: 正版UUID = " + premiumUUID); - boolean match = playerUUID.equals(premiumUUID); - - if (match) { - logger.info("[Sdf1_login] Mojang验证: ✓ UUID匹配 → 正版"); - } else { - logger.info("[Sdf1_login] Mojang验证: ✗ UUID不匹配 → 非正版"); - logger.info("[Sdf1_login] Mojang验证: 玩家: " + playerUUID); - logger.info("[Sdf1_login] Mojang验证: 正版: " + premiumUUID); - } - - final boolean finalResult = match; - Bukkit.getScheduler().runTask(plugin, () -> callback.accept(finalResult)); - }); - } - - private UUID queryMojangUUID(String name) { - try { - URL url = new URL("https://api.mojang.com/users/profiles/minecraft/" + name); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("GET"); - conn.setRequestProperty("User-Agent", "Sdf1_login"); - conn.setConnectTimeout(5000); - conn.setReadTimeout(5000); - - int code = conn.getResponseCode(); - if (code != 200) { - conn.disconnect(); - return null; - } - - BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8")); - StringBuilder sb = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) sb.append(line); - reader.close(); - conn.disconnect(); - - String json = sb.toString(); - logger.info("[Sdf1_login] Mojang响应: " + json); - - int idIndex = json.indexOf("\"id\""); - if (idIndex < 0) return null; - - int colonIndex = json.indexOf(":", idIndex); - int quoteStart = json.indexOf("\"", colonIndex + 1); - int quoteEnd = json.indexOf("\"", quoteStart + 1); - - String rawUUID = json.substring(quoteStart + 1, quoteEnd); - String formatted = rawUUID.replaceFirst( - "(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", - "$1-$2-$3-$4-$5"); - - return UUID.fromString(formatted); - - } catch (Exception e) { - logger.log(Level.WARNING, "[Sdf1_login] Mojang查询异常: " + name, e); - return null; - } - } -} diff --git a/工作簿1.xlsx b/工作簿1.xlsx deleted file mode 100644 index 953a03e..0000000 Binary files a/工作簿1.xlsx and /dev/null differ diff --git a/新建 文本文档.txt b/新建 文本文档.txt deleted file mode 100644 index d6089c3..0000000 --- a/新建 文本文档.txt +++ /dev/null @@ -1 +0,0 @@ -playSpecific无结果,位置:Main.java