commit 8454df7e753f8eb7336da4be8f175bc0d3fc2a10 Author: ypsdf1 Date: Wed May 6 17:28:50 2026 +0800 初始化Sdf1_login项目 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..480bdf5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ +.kotlin + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..f6906f2 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# 已忽略包含查询文件的默认文件夹 +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..fdc35ea --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..2eac67e --- /dev/null +++ b/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + sdf1 + Sdf1_login + 1.0 + jar + + + 21 + UTF-8 + + + + + papermc + https://repo.papermc.io/repository/maven-public/ + + + jitpack.io + https://jitpack.io + + + + + + io.papermc.paper + paper-api + 1.21.4-R0.1-SNAPSHOT + provided + + + com.github.MilkBowl + VaultAPI + 1.7.1 + provided + + + org.xerial + sqlite-jdbc + 3.46.0.0 + provided + + + + + + Sdf1_login + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 21 + 21 + + + + + diff --git a/src/main/java/Sdf1_login/AFKManager.java b/src/main/java/Sdf1_login/AFKManager.java new file mode 100644 index 0000000..152d9ad --- /dev/null +++ b/src/main/java/Sdf1_login/AFKManager.java @@ -0,0 +1,186 @@ +package Sdf1_login; + +import org.bukkit.Bukkit; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +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() { + Bukkit.getScheduler().runTaskTimer(plugin, + () -> { + if (!config.afkEnabled) return; + long now = System.currentTimeMillis(); + for (Player p : + Bukkit.getOnlinePlayers()) { + if (!isLoggedIn(p.getName())) + continue; + if (config.afkWhitelist + .contains(p.getName())) + continue; + UUID uuid = p.getUniqueId(); + Long last = lastAction + .getOrDefault(uuid, now); + long idle = (now - last) / 1000; + int timeout = config.afkTimeout; + long remain = timeout - idle; + + if (remain <= 0) { + p.kickPlayer(config.msg( + "afk_kicked")); + lastAction.remove(uuid); + lastStage.remove(uuid); + continue; + } + + int stage = + lastStage.getOrDefault(uuid, 0); + + if (remain <= 10) { + // 第三阶段:最后10秒 + if (stage < 3) { + lastStage.put(uuid, 3); + } + // 聊天刷屏 + p.sendMessage(config.msg( + "afk_warning", + "duration", fmtSec(idle), + "remain", + fmtSec(remain))); + // 红色大标题循环 + p.sendTitle( + "§c§l⚠ 即将踢出 ⚠", + "§c" + fmtSec(remain) + + "后被踢出", + 5, 25, 5); + // 音效循环 + p.playSound(p.getLocation(), + Sound.BLOCK_NOTE_BLOCK_PLING, + 1.0f, 0.79f); + p.playSound(p.getLocation(), + Sound.BLOCK_NOTE_BLOCK_BASS, + 1.0f, 0.79f); + + } else if (remain <= 60) { + // 第二阶段:剩余1分钟 + if (stage < 2) { + lastStage.put(uuid, 2); + // 仅一次聊天提醒 + p.sendMessage(config.msg( + "afk_warning", + "duration", fmtSec(idle), + "remain", + fmtSec(remain))); + } + // 持续大标题循环 + p.sendTitle( + "§e§l⚠ 挂机警告 ⚠", + "§f" + fmtSec(remain) + + "后被踢出", + 10, 40, 10); + // 音效循环 + p.playSound(p.getLocation(), + Sound.BLOCK_NOTE_BLOCK_PLING, + 1.0f, 0.79f); + p.playSound(p.getLocation(), + Sound.BLOCK_NOTE_BLOCK_BASS, + 1.0f, 0.79f); + + } else if (remain <= 180) { + // 第一阶段:剩余3分钟 + if (stage < 1) { + lastStage.put(uuid, 1); + // 一次聊天提醒 + p.sendMessage(config.msg( + "afk_warning", + "duration", fmtSec(idle), + "remain", + fmtSec(remain))); + // 一次大标题 + p.sendTitle( + "§e§l⚠ 挂机警告 ⚠", + "§f" + fmtSec(remain) + + "后被踢出", + 10, 60, 20); + // 3次音效 + p.playSound(p.getLocation(), + Sound.BLOCK_NOTE_BLOCK_PLING, + 1.0f, 0.79f); + p.playSound(p.getLocation(), + Sound.BLOCK_NOTE_BLOCK_BASS, + 1.0f, 0.79f); + Bukkit.getScheduler() + .runTaskLater(plugin, + () -> { + if (p.isOnline()) { + p.playSound( + p.getLocation(), + Sound.BLOCK_NOTE_BLOCK_PLING, + 1.0f, 0.79f); + p.playSound( + p.getLocation(), + Sound.BLOCK_NOTE_BLOCK_BASS, + 1.0f, 0.79f); + } + }, 10L); + Bukkit.getScheduler() + .runTaskLater(plugin, + () -> { + if (p.isOnline()) { + p.playSound( + p.getLocation(), + Sound.BLOCK_NOTE_BLOCK_PLING, + 1.0f, 0.79f); + p.playSound( + p.getLocation(), + Sound.BLOCK_NOTE_BLOCK_BASS, + 1.0f, 0.79f); + } + }, 20L); + } + } else { + lastStage.remove(uuid); + } + } + }, 20L, 20L); + } + + public void recordAction(UUID uuid) { + lastAction.put(uuid, + System.currentTimeMillis()); + lastStage.remove(uuid); + } + + 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/src/main/java/Sdf1_login/ChatInputManager.java b/src/main/java/Sdf1_login/ChatInputManager.java new file mode 100644 index 0000000..b1a372b --- /dev/null +++ b/src/main/java/Sdf1_login/ChatInputManager.java @@ -0,0 +1,583 @@ +package Sdf1_login; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class ChatInputManager { + + public enum InputType { + NONE, + CHANGE_PWD_STEP1, + CHANGE_PWD_STEP2, + CHANGE_PWD_STEP3, + SET_EMAIL, + EMAIL_VERIFY, + SMTP_STEP1, + SMTP_STEP2, + SMTP_STEP3, + SMTP_STEP4, + SMTP_STEP5, + SMTP_VERIFY, + ADMIN_AUTH, + ADMIN_SET_AFK_TIME, + ADMIN_SET_PWD, + ADMIN_SET_POINTS, + ADMIN_DELETE_CONFIRM, + INVITE_INPUT_CODE, + BACK_CHECKIN + } + + public static class InputState { + public InputType type = InputType.NONE; + public String tmpStr = ""; + public int tmpInt = 0; + public String targetPlayer = ""; + public Map tmpMap = + new LinkedHashMap<>(); + } + + private final Map states = + new ConcurrentHashMap<>(); + private final Map verifyCodes = + new ConcurrentHashMap<>(); + private final Map verifyExpiry = + new ConcurrentHashMap<>(); + + public InputState getState(Player p) { + return states.computeIfAbsent( + p.getUniqueId(), k -> new InputState()); + } + + public void clear(Player p) { + InputState s = states.get(p.getUniqueId()); + if (s != null) { + s.type = InputType.NONE; + s.tmpMap.clear(); + } + } + + public void setVerifyCode(Player p, String code, + String purpose) { + String key = p.getUniqueId() + ":" + purpose; + verifyCodes.put(key, code); + verifyExpiry.put(key, + System.currentTimeMillis() + 300000L); + } + + public boolean checkVerifyCode(Player p, String code, + String purpose) { + String key = p.getUniqueId() + ":" + purpose; + String stored = verifyCodes.get(key); + Long expiry = verifyExpiry.get(key); + if (stored == null || expiry == null) + return false; + if (System.currentTimeMillis() > expiry) { + verifyCodes.remove(key); + verifyExpiry.remove(key); + return false; + } + if (stored.equals(code)) { + verifyCodes.remove(key); + verifyExpiry.remove(key); + return true; + } + return false; + } + + public boolean handleInput(Player p, String msg, + Main main) { + InputState s = getState(p); + if (s.type == InputType.NONE) return false; + + if (msg.equalsIgnoreCase("00")) { + clear(p); + p.sendMessage( + "§e[Sdf1_login] §f操作已取消"); + return true; + } + + switch (s.type) { + + case CHANGE_PWD_STEP1: { + String salt = (String) main.getDb() + .getField(p.getName(), + "password_salt"); + if (salt == null) { + clear(p); + return true; + } + String hash = + PasswordUtils.hash(msg, salt); + if (main.getDb().checkPassword( + p.getName(), hash)) { + s.type = + InputType.CHANGE_PWD_STEP2; + p.sendMessage( + "§e[Sdf1_login] §f请输入新密码(6-20位,含大写+小写+数字)"); + } else { + p.sendMessage( + "§c[Sdf1_login] §f旧密码错误!"); + clear(p); + } + return true; + } + + case CHANGE_PWD_STEP2: { + if (!PasswordUtils.validate(msg)) { + p.sendMessage(main.getConfig2() + .msg("password_invalid")); + return true; + } + s.tmpStr = msg; + s.type = + InputType.CHANGE_PWD_STEP3; + p.sendMessage( + "§e[Sdf1_login] §f请再次输入新密码确认"); + return true; + } + + case CHANGE_PWD_STEP3: { + if (!msg.equals(s.tmpStr)) { + p.sendMessage( + "§c[Sdf1_login] §f两次密码不一致"); + s.type = + InputType.CHANGE_PWD_STEP2; + return true; + } + String salt = + PasswordUtils.generateSalt(); + String hash = + PasswordUtils.hash(msg, salt); + main.getDb().setField(p.getName(), + "password_hash", hash); + main.getDb().setField(p.getName(), + "password_salt", salt); + main.getDb().setField(p.getName(), + "temp_password", ""); + main.getDb().setField(p.getName(), + "temp_pw_used", 0); + main.getNeedsPasswordChange() + .remove(p.getName()); + p.sendMessage( + "§a[Sdf1_login] §f密码修改成功!"); + clear(p); + return true; + } + + case SET_EMAIL: { + if (!msg.contains("@") + || !msg.contains(".")) { + p.sendMessage(main.getConfig2() + .msg("email_invalid")); + return true; + } + String code = + PasswordUtils + .generateVerifyCode(); + setVerifyCode(p, code, "email"); + main.getEmail().sendVerifyCode( + msg, p.getName(), code); + s.tmpStr = msg; + s.type = + InputType.EMAIL_VERIFY; + p.sendMessage(main.getConfig2() + .msg("email_verify_sent", + "email", msg)); + return true; + } + + case EMAIL_VERIFY: { + if (checkVerifyCode(p, msg, "email")) { + main.getDb().setField( + p.getName(), + "email", s.tmpStr); + p.sendMessage(main.getConfig2() + .msg( + "email_verify_success")); + clear(p); + } else { + p.sendMessage(main.getConfig2() + .msg( + "email_verify_failed")); + } + return true; + } + + case SMTP_STEP1: { + s.tmpMap.put("smtp地址", msg); + s.type = InputType.SMTP_STEP2; + p.sendMessage( + "§e[Sdf1_login] §f当前端口: " + + main.getConfig2() + .getSmtp("smtp端口")); + p.sendMessage( + "§e[Sdf1_login] §f输入新端口(输入0跳过):"); + return true; + } + + case SMTP_STEP2: { + if (!msg.equals("0")) + s.tmpMap.put("smtp端口", msg); + s.type = InputType.SMTP_STEP3; + p.sendMessage( + "§e[Sdf1_login] §f输入发件邮箱(输入0跳过):"); + return true; + } + + case SMTP_STEP3: { + if (!msg.equals("0")) + s.tmpMap.put("发件邮箱", msg); + s.type = InputType.SMTP_STEP4; + p.sendMessage( + "§e[Sdf1_login] §f输入授权码(输入0跳过):"); + return true; + } + + case SMTP_STEP4: { + if (!msg.equals("0")) + s.tmpMap.put("授权码", msg); + s.type = InputType.SMTP_STEP5; + p.sendMessage( + "§e[Sdf1_login] §f输入测试接收邮箱(输入0跳过):"); + return true; + } + + case SMTP_STEP5: { + if (!msg.equals("0")) + s.tmpMap.put("测试邮箱", msg); + String testEmail = + s.tmpMap.getOrDefault( + "测试邮箱", ""); + String senderEmail = + s.tmpMap.getOrDefault( + "发件邮箱", ""); + String recipient = + !testEmail.isEmpty() + ? testEmail : senderEmail; + if (recipient == null + || recipient.isEmpty()) { + p.sendMessage( + "§c[Sdf1_login] §f请至少填写发件邮箱"); + clear(p); + return true; + } + String code = + PasswordUtils + .generateVerifyCode(); + setVerifyCode(p, code, "smtp"); + main.getEmail().sendVerifyCode( + recipient, + p.getName(), code); + s.type = + InputType.SMTP_VERIFY; + p.sendMessage(main.getConfig2() + .msg("smtp_verify_sent", + "email", recipient)); + return true; + } + + case SMTP_VERIFY: { + if (checkVerifyCode(p, msg, "smtp")) { + String addr = + s.tmpMap.getOrDefault( + "smtp地址", + main.getConfig2() + .getSmtp("smtp地址")); + String port = + s.tmpMap.getOrDefault( + "smtp端口", + main.getConfig2() + .getSmtp("smtp端口")); + String sender = + s.tmpMap.getOrDefault( + "发件邮箱", + main.getConfig2() + .getSmtp("发件邮箱")); + String auth = + s.tmpMap.getOrDefault( + "授权码", + main.getConfig2() + .getSmtp("授权码")); + String test = + s.tmpMap.getOrDefault( + "测试邮箱", + main.getConfig2() + .getSmtp("测试邮箱")); + main.getConfig2().setSmtp( + "smtp地址", addr); + main.getConfig2().setSmtp( + "smtp端口", port); + main.getConfig2().setSmtp( + "发件邮箱", sender); + main.getConfig2().setSmtp( + "授权码", auth); + main.getConfig2().setSmtp( + "测试邮箱", test); + p.sendMessage(main.getConfig2() + .msg( + "smtp_verify_success")); + clear(p); + } else { + p.sendMessage(main.getConfig2() + .msg( + "smtp_verify_failed")); + clear(p); + } + return true; + } + + case ADMIN_AUTH: { + String pass = + main.getConfig2().adminPassword; + if (msg.equals(pass)) { + if ("qweasd".equals(pass)) { + p.sendMessage( + "§c§l[警告] §f当前使用默认管理密码,请尽快修改!"); + } + Bukkit.getScheduler() + .runTask(main, () -> { + main.getGui() + .openAdmin(p); + }); + } else { + p.sendMessage( + "§c[Sdf1_login] §f管理密码错误!"); + } + clear(p); + return true; + } + + case ADMIN_SET_AFK_TIME: { + int seconds = + parseAfkTime(msg); + if (seconds > 0) { + main.getConfig2() + .afkTimeout = seconds; + int min = seconds / 60; + p.sendMessage(main.getConfig2() + .msg("afk_set_time", + "duration", + min + "分钟")); + } else { + p.sendMessage( + "§c[Sdf1_login] §f时间格式无效"); + } + clear(p); + return true; + } + + case ADMIN_SET_PWD: { + if (msg.equals("0")) { + p.sendMessage( + "§e[Sdf1_login] §f操作已取消"); + clear(p); + return true; + } + String target = s.targetPlayer; + String salt = + PasswordUtils.generateSalt(); + String hash = + PasswordUtils.hash(msg, salt); + main.getDb().setField(target, + "password_hash", hash); + main.getDb().setField(target, + "password_salt", salt); + p.sendMessage(main.getConfig2() + .msg("admin_set_password", + "user", target)); + clear(p); + return true; + } + + + case ADMIN_SET_POINTS: { + try { + int pts = + Integer.parseInt(msg); + main.getDb().setField( + s.targetPlayer, + "points", pts); + p.sendMessage(main.getConfig2() + .msg("admin_points_set", + "user", + s.targetPlayer, + "points", + String.valueOf(pts))); + } catch (NumberFormatException e) { + p.sendMessage( + "§c[Sdf1_login] §f请输入数字"); + } + clear(p); + return true; + } + + case ADMIN_DELETE_CONFIRM: { + String[] parts = + msg.split("\\s+", 2); + if (parts.length < 2) { + p.sendMessage(main.getConfig2() + .msg("admin_delete_confirm")); + p.sendMessage( + "§c格式: <玩家名> <你的密码>"); + return true; + } + String targetName = parts[0]; + String pwd = parts[1]; + String salt = (String) main.getDb() + .getField(p.getName(), + "password_salt"); + if (salt == null) { + clear(p); + return true; + } + String hash = + PasswordUtils.hash(pwd, salt); + if (main.getDb().checkPassword( + p.getName(), hash) + && targetName + .equalsIgnoreCase( + s.targetPlayer)) { + main.getDb() + .deleteUser(targetName); + p.sendMessage(main.getConfig2() + .msg("admin_delete_success", + "user", + targetName)); + } else { + p.sendMessage(main.getConfig2() + .msg( + "admin_delete_failed")); + } + clear(p); + return true; + } + + case BACK_CHECKIN: { + String result = + main.getCheckIn() + .backCheckIn(p, msg); + p.sendMessage(result); + clear(p); + return true; + } + + case INVITE_INPUT_CODE: { + String code = + msg.trim().toUpperCase(); + String inviter = + findInvokerByCode(main, code); + if (inviter != null + && !inviter.equals( + p.getName())) { + main.getDb().setField( + p.getName(), + "invited_by", inviter); + // 更新邀请人数据 + String month = + new SimpleDateFormat("yyyy-MM") + .format(new Date()); + String lastMonth = + (String) main.getDb() + .getField(inviter, + "invite_month"); + if (!month.equals(lastMonth)) { + main.getDb().setField( + inviter, + "invite_month", month); + main.getDb().setField( + inviter, + "monthly_invite_count", + 0); + } + int cnt = ((Number) main.getDb() + .getField(inviter, + "monthly_invite_count")) + .intValue(); + main.getDb().setField( + inviter, + "monthly_invite_count", + cnt + 1); + // 通知邀请人 + Player inviterPlayer = + Bukkit.getPlayerExact( + inviter); + if (inviterPlayer != null + && inviterPlayer + .isOnline()) { + inviterPlayer.sendMessage( + main.getConfig2().msg( + "invite_referral_bonus", + "points", + String.valueOf( + cnt + 1))); + } + p.sendMessage(main.getConfig2() + .msg("invite_bound", + "user", inviter)); + } else { + p.sendMessage( + "§c[Sdf1_login] §f邀请码无效"); + } + clear(p); + return true; + } + + default: + return false; + } + } + + private int parseAfkTime(String input) { + String s = input.toLowerCase().trim(); + if (s.contains("小时") || s.contains("时")) { + String num = + s.replaceAll("[^0-9]", ""); + if (!num.isEmpty()) + return Integer.parseInt(num) * 3600; + } + if (s.contains("分钟") || s.contains("分")) { + String num = + s.replaceAll("[^0-9]", ""); + if (!num.isEmpty()) + return Integer.parseInt(num) * 60; + } + if (s.endsWith("h")) { + String num = + s.replaceAll("[^0-9]", ""); + if (!num.isEmpty()) + return Integer.parseInt(num) * 3600; + } + if (s.endsWith("m")) { + String num = + s.replaceAll("[^0-9]", ""); + if (!num.isEmpty()) + return Integer.parseInt(num) * 60; + } + String num = + s.replaceAll("[^0-9]", ""); + if (!num.isEmpty()) + return Integer.parseInt(num) * 60; + return -1; + } + + private String findInvokerByCode(Main main, + String code) { + for (Map user + : main.getDb().getAllUsers()) { + String ic = (String) user.getOrDefault( + "invite_code", ""); + if (code.equals(ic)) { + return (String) user + .get("player_name"); + } + } + return null; + } +} diff --git a/src/main/java/Sdf1_login/CheckInManager.java b/src/main/java/Sdf1_login/CheckInManager.java new file mode 100644 index 0000000..36ea962 --- /dev/null +++ b/src/main/java/Sdf1_login/CheckInManager.java @@ -0,0 +1,104 @@ +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/src/main/java/Sdf1_login/ConfigManager.java b/src/main/java/Sdf1_login/ConfigManager.java new file mode 100644 index 0000000..4259e6b --- /dev/null +++ b/src/main/java/Sdf1_login/ConfigManager.java @@ -0,0 +1,433 @@ +package Sdf1_login; +// 配置文件 +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.regex.*; + +public class ConfigManager { + + private final Map messages = + new LinkedHashMap<>(); + private final Map smtp = new LinkedHashMap<>(); + private final File dataFolder; + + // 配置项 + public int afkTimeout = 300; + public int loginTimeout = 120; + public boolean afkEnabled = true; + public Set afkWhitelist = + new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + public String adminTag = "admin"; + public String adminPassword = "qweasd"; + + + public ConfigManager(File dataFolder) { + this.dataFolder = dataFolder; + } + + // ===== 消息配置 ===== + + public void loadMessages() { + messages.clear(); + File f = new File(dataFolder, "设置.txt"); + if (!f.exists()) writeDefaultMessages(); + try { + BufferedReader r = new BufferedReader( + new InputStreamReader( + new FileInputStream(f), + StandardCharsets.UTF_8)); + 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; + int eq = t.indexOf('='); + if (eq < 0) continue; + messages.put(t.substring(0, eq).trim(), + t.substring(eq + 1).trim()); + } + r.close(); + } catch (Exception e) { + e.printStackTrace(); + } + ensureMessageDefaults(); + } + + + private void ensureMessageDefaults() { + putDefault("not_registered", + "§c§l[Sdf1_login] §f你还没有注册,请使用 /reg <密码> 注册"); + putDefault("not_logged_in", + "§c§l[Sdf1_login] §f请先使用 /login <密码> 登录"); + putDefault("login_success", + "§a§l[Sdf1_login] §f登录成功!欢迎回来 {user}"); + putDefault("register_success", + "§a§l[Sdf1_login] §f注册成功!请使用 /login <密码> 登录"); + putDefault("login_failed", + "§c§l[Sdf1_login] §f密码错误!"); + putDefault("already_registered", + "§c§l[Sdf1_login] §f你已经注册过了"); + putDefault("already_logged_in", + "§a§l[Sdf1_login] §f你已经登录了"); + putDefault("password_invalid", + "§c§l[Sdf1_login] §f密码不符合要求!需要6-20位,包含大写字母+小写字母+数字"); + putDefault("password_format_hint", + "§7密码要求: 6-20位,至少1个大写字母+1个小写字母+1个数字"); + putDefault("auto_login_ip", + "§a§l[Sdf1_login] §f检测到同IP近期登录,已自动登录!欢迎回来 {user}"); + putDefault("email_verify_sent", + "§a§l[Sdf1_login] §f验证码已发送到 {email},请输入6位验证码:"); + putDefault("email_verify_success", + "§a§l[Sdf1_login] §f邮箱验证成功,已绑定!"); + putDefault("email_verify_failed", + "§c§l[Sdf1_login] §f验证码错误,请重试"); + putDefault("smtp_verify_sent", + "§a§l[Sdf1_login] §f验证码已发送到 {email},请输入6位验证码:"); + putDefault("smtp_verify_success", + "§a§l[Sdf1_login] §fSMTP配置验证成功!"); + putDefault("smtp_verify_failed", + "§c§l[Sdf1_login] §f验证码错误,请重试"); + putDefault("login_timeout", + "§c§l[Sdf1_login] §f登录超时,已被踢出"); + putDefault("login_required", + "§c§l[Sdf1_login] §f你还没有登录,请先 /login <密码>"); + putDefault("reset_no_email", + "§c§l[Sdf1_login] §f你还没有绑定邮箱,无法通过邮件重置密码"); + putDefault("reset_sent", + "§a§l[Sdf1_login] §f临时密码已发送到你的邮箱,请查收"); + putDefault("reset_temp_used", + "§a§l[Sdf1_login] §f临时密码已生效,旧密码已失效"); + putDefault("reset_failed", + "§c§l[Sdf1_login] §f重置失败,请联系管理员"); + putDefault("email_set", + "§a§l[Sdf1_login] §f邮箱已设置为: {email}"); + putDefault("email_invalid", + "§c§l[Sdf1_login] §f邮箱格式不正确"); + putDefault("checkin_success", + "§a§l[Sdf1_login] §f签到成功!获得 {points} 积分(连续{streak}天,倍率x{multi})"); + putDefault("checkin_already", + "§c§l[Sdf1_login] §f今天已经签到过了"); + putDefault("backcheckin_success", + "§a§l[Sdf1_login] §f补签成功!消耗10积分"); + putDefault("backcheckin_no_point", + "§c§l[Sdf1_login] §f积分不足,补签需要10积分"); + putDefault("backcheckin_expired", + "§c§l[Sdf1_login] §f只能补签3天内的签到"); + putDefault("points_insufficient", + "§c§l[Sdf1_login] §f积分不足!当前: {points}"); + putDefault("points_purchase_success", + "§a§l[Sdf1_login] §f兑换成功!消耗 {count} 积分"); + putDefault("invite_code_generated", + "§a§l[Sdf1_login] §f你的邀请码: {code}"); + putDefault("invite_bound", + "§a§l[Sdf1_login] §f你已被 {user} 邀请!"); + putDefault("invite_referral_bonus", + "§a§l[Sdf1_login] §f邀请返点: +{points} 积分"); + putDefault("gift_claimed", + "§a§l[Sdf1_login] §f礼包领取成功!"); + putDefault("gift_not_ready", + "§c§l[Sdf1_login] §f礼包条件未满足"); + putDefault("gift_all_claimed", + "§a§l[Sdf1_login] §f所有礼包已领取完毕!"); + putDefault("admin_no_permission", + "§c§l[Sdf1_login] §f无权限"); + putDefault("admin_set_password", + "§a§l[Sdf1_login] §f已为 {user} 设置新密码"); + putDefault("admin_delete_confirm", + "§c§l[Sdf1_login] §f请输入要删除的玩家名和你的密码进行确认"); + putDefault("admin_delete_success", + "§a§l[Sdf1_login] §f玩家 {user} 已删除"); + putDefault("admin_delete_failed", + "§c§l[Sdf1_login] §f删除失败:验证不通过"); + putDefault("admin_points_set", + "§a§l[Sdf1_login] §f已设置 {user} 积分为 {points}"); + putDefault("afk_warning", + "§e§l[Sdf1_login] §f你已挂机 {duration},将在 {remain} 后被踢出"); + putDefault("afk_kicked", + "§c§l[Sdf1_login] §f因挂机过久被踢出"); + putDefault("afk_set_time", + "§a§l[Sdf1_login] §f挂机踢出时长已设为 {duration}"); + putDefault("afk_set_enabled", + "§a§l[Sdf1_login] §f挂机踢出已开启"); + putDefault("afk_set_disabled", + "§a§l[Sdf1_login] §f挂机踢出已关闭"); + putDefault("afk_whitelist_added", + "§a§l[Sdf1_login] §f已将 {user} 加入防踢白名单"); + putDefault("afk_whitelist_removed", + "§a§l[Sdf1_login] §f已将 {user} 移出防踢白名单"); + } + + private void putDefault(String key, String value) { + messages.putIfAbsent(key, value); + } + + + public String msg(String key, Object... kvPairs) { + String template = messages.getOrDefault(key, key); + for (int i = 0; i < kvPairs.length - 1; i += 2) { + String varName = kvPairs[i].toString(); + String value = kvPairs[i + 1] != null + ? kvPairs[i + 1].toString() : ""; + String[] open = {"{", "[", "(", "【", "(", "《", "<"}; + String[] close = {"}", "]", ")", "】", ")", "》", ">"}; + for (int j = 0; j < open.length; j++) { + template = template.replace( + open[j] + varName + close[j], value); + } + } + template = template.replace("\\n", "\n"); + template = template.replace("
", "\n"); + return template; + } + + private void writeDefaultMessages() { + File f = new File(dataFolder, "设置.txt"); + try (PrintWriter pw = new PrintWriter( + new OutputStreamWriter( + new FileOutputStream(f), + StandardCharsets.UTF_8))) { + pw.println("# =========================================="); + pw.println("# Sdf1_login 消息提示配置"); + pw.println("# =========================================="); + pw.println("# 变量支持: {变量} [变量] (变量) 【变量】"); + pw.println("# 可用变量: [user] [duration] [count]"); + pw.println("# [code] [points] [stage]"); + pw.println("# [email] [url] [reason]"); + pw.println(); + pw.println("# ===== 登录系统 ====="); + pw.println("not_registered=§c§l[Sdf1_login] §f你还没有注册,请使用 /reg <密码> 注册"); + pw.println("not_logged_in=§c§l[Sdf1_login] §f请先使用 /login <密码> 登录"); + pw.println("login_success=§a§l[Sdf1_login] §f登录成功!欢迎回来 [user]"); + pw.println("register_success=§a§l[Sdf1_login] §f注册成功!请使用 /login <密码> 登录"); + pw.println("login_failed=§c§l[Sdf1_login] §f密码错误!"); + pw.println("already_registered=§c§l[Sdf1_login] §f你已经注册过了"); + pw.println("already_logged_in=§a§l[Sdf1_login] §f你已经登录了"); + pw.println("password_invalid=§c§l[Sdf1_login] §f密码不符合要求!需要6-20位,包含大写字母+小写字母+数字"); + pw.println("password_format_hint=§7密码要求: 6-20位,至少1个大写字母+1个小写字母+1个数字"); + pw.println(); + pw.println("# ===== IP自动登录 ====="); + pw.println("auto_login_ip=§a§l[Sdf1_login] §f检测到同IP近期登录,已自动登录!欢迎回来 [user]"); + pw.println(); + pw.println("# ===== 邮箱验证 ====="); + pw.println("email_verify_sent=§a§l[Sdf1_login] §f验证码已发送到 [email],请输入6位验证码:"); + pw.println("email_verify_success=§a§l[Sdf1_login] §f邮箱验证成功,已绑定!"); + pw.println("email_verify_failed=§c§l[Sdf1_login] §f验证码错误,请重试"); + pw.println(); + pw.println("# ===== SMTP验证 ====="); + pw.println("smtp_verify_sent=§a§l[Sdf1_login] §f验证码已发送到 [email],请输入6位验证码:"); + pw.println("smtp_verify_success=§a§l[Sdf1_login] §fSMTP配置验证成功!"); + pw.println("smtp_verify_failed=§c§l[Sdf1_login] §f验证码错误,请重试"); + pw.println("login_timeout=§c§l[Sdf1_login] §f登录超时,已被踢出"); + pw.println("login_required=§c§l[Sdf1_login] §f你还没有登录,请先 /login <密码>"); + pw.println(); + pw.println("# ===== 密码重置 ====="); + pw.println("reset_no_email=§c§l[Sdf1_login] §f你还没有绑定邮箱,无法通过邮件重置密码"); + pw.println("reset_sent=§a§l[Sdf1_login] §f临时密码已发送到你的邮箱,请查收"); + pw.println("reset_temp_used=§a§l[Sdf1_login] §f临时密码已生效,旧密码已失效"); + pw.println("reset_failed=§c§l[Sdf1_login] §f重置失败,请联系管理员"); + pw.println(); + pw.println("# ===== 邮箱 ====="); + pw.println("email_set=§a§l[Sdf1_login] §f邮箱已设置为: [email]"); + pw.println("email_invalid=§c§l[Sdf1_login] §f邮箱格式不正确"); + pw.println(); + pw.println("# ===== 签到 ====="); + pw.println("checkin_success=§a§l[Sdf1_login] §f签到成功!获得 [points] 积分(连续[streak]天,倍率x[multi])"); + pw.println("checkin_already=§c§l[Sdf1_login] §f今天已经签到过了"); + pw.println("backcheckin_success=§a§l[Sdf1_login] §f补签成功!消耗10积分"); + pw.println("backcheckin_no_point=§c§l[Sdf1_login] §f积分不足,补签需要10积分"); + pw.println("backcheckin_expired=§c§l[Sdf1_login] §f只能补签3天内的签到"); + pw.println(); + pw.println("# ===== 积分 ====="); + pw.println("points_insufficient=§c§l[Sdf1_login] §f积分不足!当前: [points]"); + pw.println("points_purchase_success=§a§l[Sdf1_login] §f兑换成功!消耗 [count] 积分"); + pw.println(); + pw.println("# ===== 邀请 ====="); + pw.println("invite_code_generated=§a§l[Sdf1_login] §f你的邀请码: [code]"); + pw.println("invite_bound=§a§l[Sdf1_login] §f你已被 [user] 邀请!"); + pw.println("invite_referral_bonus=§a§l[Sdf1_login] §f邀请返点: +[points] 积分"); + pw.println(); + pw.println("# ===== 新人礼包 ====="); + pw.println("gift_claimed=§a§l[Sdf1_login] §f礼包领取成功!"); + pw.println("gift_not_ready=§c§l[Sdf1_login] §f礼包条件未满足"); + pw.println("gift_all_claimed=§a§l[Sdf1_login] §f所有礼包已领取完毕!"); + pw.println(); + pw.println("# ===== 管理员 ====="); + pw.println("admin_no_permission=§c§l[Sdf1_login] §f无权限"); + pw.println("admin_set_password=§a§l[Sdf1_login] §f已为 [user] 设置新密码"); + pw.println("admin_delete_confirm=§c§l[Sdf1_login] §f请输入要删除的玩家名和你的密码进行确认"); + pw.println("admin_delete_success=§a§l[Sdf1_login] §f玩家 [user] 已删除"); + pw.println("admin_delete_failed=§c§l[Sdf1_login] §f删除失败:验证不通过"); + pw.println("admin_points_set=§a§l[Sdf1_login] §f已设置 [user] 积分为 [points]"); + pw.println("管理密码=qweasd"); + pw.println(); + pw.println("# ===== 挂机踢出 ====="); + pw.println("afk_warning=§e§l[Sdf1_login] §f你已挂机 [duration],将在 [remain] 后被踢出"); + pw.println("afk_kicked=§c§l[Sdf1_login] §f因挂机过久被踢出"); + pw.println("afk_set_time=§a§l[Sdf1_login] §f挂机踢出时长已设为 [duration]"); + pw.println("afk_set_enabled=§a§l[Sdf1_login] §f挂机踢出已开启"); + pw.println("afk_set_disabled=§a§l[Sdf1_login] §f挂机踢出已关闭"); + pw.println("afk_whitelist_added=§a§l[Sdf1_login] §f已将 [user] 加入防踢白名单"); + pw.println("afk_whitelist_removed=§a§l[Sdf1_login] §f已将 [user] 移出防踢白名单"); + pw.println(); + pw.println("# ===== 帮助 ====="); + pw.println("help_header=§e[Sdf1_login] §f命令列表:"); + pw.println("help_1=§a/sdf1_login §7- 打开面板"); + pw.println("help_2=§a/reg <密码> §7- 注册"); + pw.println("help_3=§a/login <密码> §7- 登录"); + pw.println("help_4=§a/sdf1_login reset §7- 重置密码"); + pw.println("help_5=§a/sdf1_login email <邮箱> §7- 绑定邮箱"); + pw.println("help_6=§a/sdf1_login set <玩家> <密码> §7- [管理员] 设置密码"); + pw.println("help_7=§a/sdf1_login del <玩家> §7- [管理员] 删除用户"); + pw.println("help_8=§a/sdf1_login kick on/off [分钟] §7- [管理员] 挂机设置"); + pw.println("help_9=§a/sdf1_login add/remove <玩家> §7- [管理员] 防踢白名单"); + pw.println("help_10=§a/sdf1_login get <玩家> §7- [管理员] 查看礼包进度"); + pw.println("help_11=§a/sdf1_login reload §7- [管理员] 重载配置"); + } catch (IOException ignored) {} + } + + // ===== SMTP配置 ===== + + public void loadSmtp() { + smtp.clear(); + File f = new File(dataFolder, "smtp.txt"); + if (!f.exists()) writeDefaultSmtp(); + try { + BufferedReader r = new BufferedReader( + new InputStreamReader( + new FileInputStream(f), + StandardCharsets.UTF_8)); + 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; + int eq = t.indexOf('='); + if (eq < 0) continue; + smtp.put(t.substring(0, eq).trim(), + t.substring(eq + 1).trim()); + } + r.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void writeDefaultSmtp() { + File f = new File(dataFolder, "smtp.txt"); + try (PrintWriter pw = new PrintWriter( + new OutputStreamWriter( + new FileOutputStream(f), + StandardCharsets.UTF_8))) { + pw.println("# =========================================="); + pw.println("# Sdf1_login SMTP邮件配置"); + pw.println("# =========================================="); + pw.println(); + pw.println("# SMTP服务器地址"); + pw.println("smtp地址=smtp.qq.com"); + pw.println(); + pw.println("# 端口号(SSL默认465)"); + pw.println("smtp端口=465"); + pw.println(); + pw.println("# 发件人邮箱地址"); + pw.println("发件邮箱=your@email.com"); + pw.println(); + pw.println("# 邮箱授权码(非登录密码)"); + pw.println("授权码="); + pw.println(); + pw.println("# 测试接收邮箱(留空则发给发件人)"); + pw.println("测试邮箱="); + } catch (IOException ignored) {} + } + + public String getSmtp(String key) { + return smtp.getOrDefault(key, ""); + } + + public void setSmtp(String key, String value) { + smtp.put(key, value); + saveSmtp(); + } + + public Map getSmtpAll() { + return new LinkedHashMap<>(smtp); + } + public void saveSettings() { + File f = new File(dataFolder, "url.txt"); + try (PrintWriter pw = new PrintWriter( + new OutputStreamWriter( + new FileOutputStream(f), + StandardCharsets.UTF_8))) { + pw.println("管理标签: " + adminTag); + pw.println("管理密码: " + adminPassword); + pw.println("挂机踢出: " + + (afkEnabled ? "true" : "false")); + pw.println("挂机时长: " + (afkTimeout / 60)); + pw.println("登录超时: " + loginTimeout); + } catch (IOException ignored) {} + } + + private void saveSmtp() { + File f = new File(dataFolder, "smtp.txt"); + try (PrintWriter pw = new PrintWriter( + new OutputStreamWriter( + new FileOutputStream(f), + StandardCharsets.UTF_8))) { + pw.println("# =========================================="); + pw.println("# Sdf1_login SMTP邮件配置"); + pw.println("# =========================================="); + pw.println(); + pw.println("smtp地址=" + smtp.getOrDefault("smtp地址", "smtp.qq.com")); + pw.println("smtp端口=" + smtp.getOrDefault("smtp端口", "465")); + pw.println("发件邮箱=" + smtp.getOrDefault("发件邮箱", "")); + pw.println("授权码=" + smtp.getOrDefault("授权码", "")); + pw.println("测试邮箱=" + smtp.getOrDefault("测试邮箱", "")); + } catch (IOException ignored) {} + } + + // ===== 其他配置 ===== + + public void loadSettings() { + File f = new File(dataFolder, "url.txt"); + if (!f.exists()) return; + try { + BufferedReader r = new BufferedReader( + new InputStreamReader( + new FileInputStream(f), + StandardCharsets.UTF_8)); + String line; + while ((line = r.readLine()) != null) { + String t = line.trim(); + if (t.isEmpty() || t.startsWith("#")) continue; + String[] kv = t.split(":", 2); + if (kv.length < 2) continue; + String k = kv[0].trim(); + String v = kv[1].trim(); + switch (k) { + case "管理标签": + adminTag = v; break; + case "管理密码": + adminPassword = v; break; + case "挂机踢出": + afkEnabled = v.equalsIgnoreCase("true") + || v.equalsIgnoreCase("开启"); + break; + case "挂机时长": + try { + afkTimeout = Integer.parseInt(v) * 60; + } catch (Exception ignored) {} + break; + case "登录超时": + try { + loginTimeout = Integer.parseInt(v); + } catch (Exception ignored) {} + break; + } + + } + r.close(); + } catch (Exception ignored) {} + } +} diff --git a/src/main/java/Sdf1_login/DatabaseManager.java b/src/main/java/Sdf1_login/DatabaseManager.java new file mode 100644 index 0000000..e5e2c0f --- /dev/null +++ b/src/main/java/Sdf1_login/DatabaseManager.java @@ -0,0 +1,259 @@ +package Sdf1_login; + +import java.io.File; +import java.sql.*; +import java.util.*; + +//全部数据库操作 +public class DatabaseManager { + + private Connection db; + private final File dataFolder; + + public DatabaseManager(File dataFolder) { + this.dataFolder = dataFolder; + } + + 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)"); + addColumnIfMissing("tasks_completed", "TEXT DEFAULT ''"); + addColumnIfMissing("temp_password", "TEXT DEFAULT ''"); + addColumnIfMissing("temp_pw_expire", "INTEGER DEFAULT 0"); + addColumnIfMissing("temp_pw_used", "INTEGER DEFAULT 0"); + st.close(); + } catch (Exception e) { + throw new RuntimeException( + "[Sdf1_login] DB初始化失败: " + e.getMessage(), e); + } + } + + public void close() { + try { + if (db != null && !db.isClosed()) db.close(); + } catch (Exception ignored) {} + } + + public Connection getDb() { return db; } + private void addColumnIfMissing(String col, String def) { + try { + db.createStatement().executeUpdate( + "ALTER TABLE users ADD COLUMN " + + col + " " + def); + } catch (SQLException 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 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 stored.equals(hash); + } + rs.close(); + ps.close(); + return false; + } catch (SQLException e) { + return false; + } + } + + public boolean checkPasswordOrTemp( + String name, String hash) { + if (checkPassword(name, hash)) return true; + Map user = getUser(name); + if (user.isEmpty()) return false; + String tempHash = (String) user.getOrDefault( + "temp_password", ""); + long expire = ((Number) user.getOrDefault( + "temp_pw_expire", 0L)).longValue(); + if (tempHash == null || tempHash.isEmpty()) + return false; + if (System.currentTimeMillis() > expire) + return false; + return tempHash.equals(hash); + } + + 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) { + int cur = ((Number) getField(name, "points")) + .intValue(); + setField(name, "points", cur + amount); + } + + public boolean deductPoints(String name, int amount) { + int cur = ((Number) getField(name, "points")) + .intValue(); + if (cur < amount) return false; + setField(name, "points", cur - 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 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(); + } + } +} diff --git a/src/main/java/Sdf1_login/EmailManager.java b/src/main/java/Sdf1_login/EmailManager.java new file mode 100644 index 0000000..9b4700b --- /dev/null +++ b/src/main/java/Sdf1_login/EmailManager.java @@ -0,0 +1,139 @@ +package Sdf1_login; + +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +public class EmailManager { + + private final ConfigManager config; + + public EmailManager(ConfigManager config) { + this.config = config; + } + + public void sendTempPassword(String toEmail, + String playerName, + String tempPassword) { + String subject = "[Sdf1_login] 密码重置 - " + + playerName; + String body = "玩家: " + playerName + + "\r\n临时密码: " + tempPassword + + "\r\n有效期: 5分钟" + + "\r\n使用后自动失效" + + "\r\n请尽快登录并修改密码"; + sendEmail(toEmail, subject, body); + } + + public void sendVerifyCode(String toEmail, + String playerName, + String code) { + String subject = "[Sdf1_login] 验证码 - " + + playerName; + String body = "玩家: " + playerName + + "\r\n验证码: " + code + + "\r\n有效期: 5分钟"; + sendEmail(toEmail, subject, body); + } + + private void sendEmail(String toEmail, + String subject, String body) { + new Thread(() -> { + try { + String host = config.getSmtp("smtp地址"); + int port = 465; + try { + port = Integer.parseInt( + config.getSmtp("smtp端口")); + } catch (Exception ignored) {} + String from = config.getSmtp("发件邮箱"); + String pass = config.getSmtp("授权码"); + + if (host.isEmpty() || from.isEmpty() + || pass.isEmpty()) return; + if (toEmail == null || toEmail.isEmpty()) + return; + + SSLSocketFactory factory = + (SSLSocketFactory) SSLSocketFactory + .getDefault(); + SSLSocket socket = (SSLSocket) factory + .createSocket(host, port); + + BufferedReader reader = new BufferedReader( + new InputStreamReader( + socket.getInputStream(), + StandardCharsets.UTF_8)); + BufferedWriter writer = new BufferedWriter( + new OutputStreamWriter( + socket.getOutputStream(), + StandardCharsets.UTF_8)); + + readResponse(reader); + sendCmd(writer, "EHLO localhost"); + readResponse(reader); + sendCmd(writer, "AUTH LOGIN"); + readResponse(reader); + sendCmd(writer, Base64.getEncoder() + .encodeToString(from.getBytes( + StandardCharsets.UTF_8))); + readResponse(reader); + sendCmd(writer, Base64.getEncoder() + .encodeToString(pass.getBytes( + StandardCharsets.UTF_8))); + readResponse(reader); + sendCmd(writer, + "MAIL FROM:<" + from + ">"); + readResponse(reader); + sendCmd(writer, + "RCPT TO:<" + toEmail + ">"); + readResponse(reader); + sendCmd(writer, "DATA"); + readResponse(reader); + + String encodedSubject = Base64.getEncoder() + .encodeToString(subject.getBytes( + StandardCharsets.UTF_8)); + + sendCmd(writer, "From: " + from); + sendCmd(writer, "To: " + toEmail); + sendCmd(writer, "Subject: =?UTF-8?B?" + + encodedSubject + "?="); + sendCmd(writer, "MIME-Version: 1.0"); + sendCmd(writer, + "Content-Type: text/plain;" + + " charset=UTF-8"); + sendCmd(writer, ""); + sendCmd(writer, body); + sendCmd(writer, "."); + readResponse(reader); + sendCmd(writer, "QUIT"); + readResponse(reader); + + socket.close(); + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + } + + private void sendCmd(BufferedWriter w, String cmd) + throws IOException { + w.write(cmd + "\r\n"); + w.flush(); + } + + private String readResponse(BufferedReader r) + throws IOException { + StringBuilder sb = new StringBuilder(); + String line; + while ((line = r.readLine()) != null) { + sb.append(line).append("\n"); + if (line.length() < 4) break; + if (line.charAt(3) == ' ') break; + } + return sb.toString(); + } +} diff --git a/src/main/java/Sdf1_login/GUIManager.java b/src/main/java/Sdf1_login/GUIManager.java new file mode 100644 index 0000000..8e8b869 --- /dev/null +++ b/src/main/java/Sdf1_login/GUIManager.java @@ -0,0 +1,338 @@ +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.io.File; + +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新人礼包")); + 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); + } + + 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); + g.setItem(10, mkItem(Material.BOOK, + "§e§l新人任务", + "§7完成新手阶段获取奖励")); + g.setItem(12, mkItem(Material.COMPASS, + "§b§l主线任务", + "§7查看主线任务列表")); + g.setItem(14, mkItem(Material.PAPER, + "§a§l支线任务", + "§7查看支线任务列表")); + g.setItem(16, mkItem(Material.EMERALD, + "§d§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); + int max = plugin.getGift().getMaxStage(); + for (int i = 0; i < max && i < 9; i++) { + int stage = i + 1; + boolean claimed = plugin.getDb() + .getField(p.getName(), + "gift_claimed") != null + && ((String) plugin.getDb() + .getField(p.getName(), + "gift_claimed")) + .contains("[" + stage + "]"); + boolean can = plugin.getGift() + .canClaim(p, stage); + Material mat = claimed ? Material.LIME_WOOL + : (can ? Material.YELLOW_WOOL + : Material.RED_WOOL); + String status = claimed ? "§a已领取" + : (can ? "§e可领取" : "§c未达标"); + g.setItem(10 + i, mkItem(mat, + "§e第" + stage + "阶段", + "§7" + status)); + } + g.setItem(26, mkItem(Material.ARROW, "§7返回")); + p.openInventory(g); + } + + public void openTaskList(Player p, String folderName) { + File folder = new File( + plugin.getDataFolder(), folderName); + List> tasks = + plugin.getGift() + .loadTasksFromFolder(folder); + int size = Math.max(54, + ((tasks.size() / 9) + 1) * 9); + if (size > 54) size = 54; + Inventory g = Bukkit.createInventory(null, size, + "§d§l" + folderName); + fillBg(g); + for (int i = 0; i < tasks.size() + && i < 45; i++) { + Map task = tasks.get(i); + String name = (String) task.get("name"); + List conds = + (List) task.get("conditions"); + List rewards = + (List) task.get("rewards"); + List lore = new ArrayList<>(); + lore.add("§7───── 条件 ─────"); + if (conds != null) + for (String c : conds) + lore.add("§e" + c); + lore.add(""); + lore.add("§7───── 奖励 ─────"); + if (rewards != null) + for (String r2 : rewards) + lore.add("§a" + r2); + g.setItem(i, mkItem(Material.BOOK, + "§e" + name, + 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二次验证删除")); + g.setItem(22, mkItem(Material.ARROW, "§7返回")); + p.openInventory(g); + } + + 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/src/main/java/Sdf1_login/GiftManager.java b/src/main/java/Sdf1_login/GiftManager.java new file mode 100644 index 0000000..ddda2b3 --- /dev/null +++ b/src/main/java/Sdf1_login/GiftManager.java @@ -0,0 +1,296 @@ +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/src/main/java/Sdf1_login/InviteManager.java b/src/main/java/Sdf1_login/InviteManager.java new file mode 100644 index 0000000..bdd880f --- /dev/null +++ b/src/main/java/Sdf1_login/InviteManager.java @@ -0,0 +1,91 @@ +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/src/main/java/Sdf1_login/LoginManager.java b/src/main/java/Sdf1_login/LoginManager.java new file mode 100644 index 0000000..e5cc86c --- /dev/null +++ b/src/main/java/Sdf1_login/LoginManager.java @@ -0,0 +1,167 @@ +package Sdf1_login; + +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.getDb().userExists(name)) { + p.sendMessage(plugin.getConfig2().msg( + "already_registered")); + return true; + } + if (args.length < 1) { + p.sendMessage("§c用法: /reg <密码>"); + p.sendMessage(plugin.getConfig2().msg( + "password_format_hint")); + return true; + } + String pwd = args[0]; + if (!PasswordUtils.validate(pwd)) { + p.sendMessage(plugin.getConfig2().msg( + "password_invalid")); + p.sendMessage(plugin.getConfig2().msg( + "password_format_hint")); + return true; + } + String salt = PasswordUtils.generateSalt(); + String hash = PasswordUtils.hash(pwd, salt); + plugin.getDb().createUser(name, hash, salt); + plugin.getLoggedIn().add(name); + plugin.getDb().setLoggedIn(name, true); + plugin.getDb().setField(name, + "last_login_time", + System.currentTimeMillis()); + plugin.getDb().setField(name, + "last_online_check", + System.currentTimeMillis()); + plugin.restoreInventory(p); + plugin.recordIPLogin(p); + p.sendMessage(plugin.getConfig2().msg( + "register_success", "user", 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, 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.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.getDb() + .getField(name, "password_salt"); + if (salt == null) { + p.sendMessage( + "§c[Sdf1_login] §f数据异常,请联系管理员"); + return true; + } + String hash = PasswordUtils.hash(pwd, salt); + if (plugin.getDb().checkPasswordOrTemp( + name, hash)) { + plugin.getLoggedIn().add(name); + plugin.getDb().setLoggedIn(name, true); + plugin.getDb().setField(name, + "last_login_time", + System.currentTimeMillis()); + plugin.getDb().setField(name, + "last_online_check", + System.currentTimeMillis()); + plugin.restoreInventory(p); + plugin.recordIPLogin(p); + p.sendMessage(plugin.getConfig2().msg( + "login_success", "user", name)); + if (!plugin.getDb().checkPassword( + name, hash)) { + plugin.getNeedsPasswordChange() + .add(name); + p.sendMessage( + "§c§l[警告] §f您使用的是临时密码,请尽快修改密码!"); + p.sendMessage( + "§7用法: /sdf1_login pw <旧密码> <新密码> <确认新密码>"); + } + 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) {} + } else { + p.sendMessage(plugin.getConfig2().msg( + "login_failed")); + } + return true; + } + + public boolean handleReset(Player p) { + String name = p.getName(); + String email = (String) plugin.getDb() + .getField(name, "email"); + if (email == null || email.isEmpty()) { + p.sendMessage(plugin.getConfig2().msg( + "reset_no_email")); + return true; + } + String tempPwd = + PasswordUtils.generateTempPassword(); + String mainSalt = (String) plugin.getDb() + .getField(name, "password_salt"); + String hash = + PasswordUtils.hash(tempPwd, mainSalt); + plugin.getDb().setField(name, + "temp_password", hash); + plugin.getDb().setField(name, + "temp_pw_expire", + System.currentTimeMillis() + + 300000L); + plugin.getEmail().sendTempPassword( + email, name, tempPwd); + p.sendMessage(plugin.getConfig2().msg( + "reset_sent")); + return true; + } +} diff --git a/src/main/java/Sdf1_login/Main.java b/src/main/java/Sdf1_login/Main.java new file mode 100644 index 0000000..9b40e4d --- /dev/null +++ b/src/main/java/Sdf1_login/Main.java @@ -0,0 +1,1091 @@ +package Sdf1_login; + +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import net.milkbowl.vault.economy.Economy; +import org.bukkit.Bukkit; +import org.bukkit.Location; +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.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerMoveEvent; +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; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +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 final Set loggedIn = + new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + private final Set needsPasswordChange = + new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + private final Map joinTime = + new ConcurrentHashMap<>(); + private final Map joinLoc = + new HashMap<>(); + private final Map savedInventory = + new HashMap<>(); + private final Map savedArmor = + new HashMap<>(); + private final Map savedExtra = + new HashMap<>(); + private final Map savedLevel = + new HashMap<>(); + private final Map savedExp = + new HashMap<>(); + private final Map ipAutoLogin = + new HashMap<>(); + private static final long IP_SKIP_MS = 300000L; + + 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())); + } + + 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 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 Set getLoggedIn() { return loggedIn; } + public Set getNeedsPasswordChange() { return needsPasswordChange; } + + /* ==================== 生命周期 ==================== */ + + @Override + public void onEnable() { + getDataFolder().mkdirs(); + config = new ConfigManager(getDataFolder()); + config.loadMessages(); + config.loadSmtp(); + config.loadSettings(); + 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(); + setupEconomy(); + getCommand("sdf1_login").setExecutor(this); + getCommand("sdf1_login").setTabCompleter(this); + getCommand("reg").setExecutor(this); + getCommand("reg").setTabCompleter(this); + getCommand("login").setExecutor(this); + getCommand("login").setTabCompleter(this); + getCommand("l").setExecutor((s, c, la, a) -> true); + getCommand("注册").setExecutor(this); + getCommand("登录").setExecutor(this); + getCommand("玩家信息").setExecutor(this); + getCommand("改密码").setExecutor(this); + getCommand("设置邮箱").setExecutor(this); + getCommand("撤销").setExecutor(this); + getCommand("删除").setExecutor(this); + getCommand("找回密码").setExecutor(this); + getCommand("签到").setExecutor(this); + getCommand("debug").setExecutor(this); + getServer().getPluginManager().registerEvents(this, this); + afk.startCheck(); + startLoginReminder(); + startTimeoutCheck(); + startPasswordReminder(); + getLogger().info("Sdf1_login v1.0 | 就绪"); + } + + @Override + public void onDisable() { + if (db != null) db.close(); + } + + private void setupEconomy() { + if (getServer().getPluginManager().getPlugin("Vault") == null) return; + RegisteredServiceProvider rsp = + getServer().getServicesManager().getRegistration(Economy.class); + if (rsp != null) economy = rsp.getProvider(); + } + + 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_registered")); + } else { + p.sendMessage(config.msg("not_logged_in")); + } + } + } + }.runTaskTimer(this, 60L, 60L); + } + + 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 String getPlayerIP(Player p) { + InetSocketAddress addr = p.getAddress(); + if (addr == null) return null; + return addr.getAddress().getHostAddress(); + } + + private boolean canAutoLogin(Player p) { + String ip = getPlayerIP(p); + if (ip == null) return false; + Long last = ipAutoLogin.get(ip); + if (last == null) return false; + return System.currentTimeMillis() - last < IP_SKIP_MS; + } + + public void recordIPLogin(Player p) { + String ip = getPlayerIP(p); + if (ip != null) ipAutoLogin.put(ip, System.currentTimeMillis()); + } + + public void hideInventory(Player p) { + savedInventory.put(p.getUniqueId(), p.getInventory().getContents()); + savedArmor.put(p.getUniqueId(), p.getInventory().getArmorContents()); + savedExtra.put(p.getUniqueId(), p.getInventory().getExtraContents()); + savedLevel.put(p.getUniqueId(), p.getLevel()); + savedExp.put(p.getUniqueId(), p.getExp()); + 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) { + ItemStack[] inv = savedInventory.remove(p.getUniqueId()); + ItemStack[] armor = savedArmor.remove(p.getUniqueId()); + ItemStack[] extra = savedExtra.remove(p.getUniqueId()); + Integer level = savedLevel.remove(p.getUniqueId()); + Float exp = savedExp.remove(p.getUniqueId()); + if (inv != null) p.getInventory().setContents(inv); + if (armor != null) p.getInventory().setArmorContents(armor); + if (extra != null) p.getInventory().setExtraContents(extra); + if (level != null) p.setLevel(level); + if (exp != null) p.setExp(exp); + } + + 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; + } + + 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 openTaskPanel(Player p) { + Inventory g = Bukkit.createInventory(null, 27, "§6§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); + 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今日签到获取积分")); + g.setItem(22, mkItem(Material.ARROW, "§7返回")); + p.openInventory(g); + } + + 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; + } + + /* ==================== 指令 ==================== */ + + @Override + public boolean onCommand(CommandSender sender, Command cmd, + String label, String[] args) { + String cn = cmd.getName(); + + if (cn.equalsIgnoreCase("debug")) { + if (!isAdmin(sender)) { sender.sendMessage(config.msg("admin_no_permission")); return true; } + if (args.length < 2) { sender.sendMessage("§c用法: /debug <玩家> take.<阶段>"); return true; } + String target = args[0]; + String action = args[1].toLowerCase(); + if (action.startsWith("take.")) { + try { + int stg = Integer.parseInt(action.substring(5)); + db.setField(target, "gift_stage", stg); + StringBuilder sb = new StringBuilder(); + for (int i = 1; i <= stg; i++) sb.append("[").append(i).append("]"); + db.setField(target, "gift_claimed", sb.toString()); + db.setField(target, "tasks_completed", sb.toString()); + sender.sendMessage("§a[Debug] §f已将 " + target + " 设为礼包阶段 " + stg); + } catch (NumberFormatException e) { + sender.sendMessage("§c[Debug] §f阶段号无效"); + } + } else { + sender.sendMessage("§c[Debug] §f未知操作: " + action); + } + return true; + } + + if (cn.equals("注册")) { + if (!(sender instanceof Player)) return true; + return loginMgr.handleRegister((Player) sender, args); + } + if (cn.equals("登录")) { + if (!(sender instanceof Player)) return true; + return loginMgr.handleLogin((Player) sender, args); + } + if (cn.equals("玩家信息")) { + if (!(sender instanceof Player)) return true; + gui.openMain((Player) sender); + return true; + } + if (cn.equals("改密码")) { + if (!(sender instanceof Player)) return true; + String[] na = new String[args.length + 1]; + na[0] = "pw"; + System.arraycopy(args, 0, na, 1, args.length); + return handleSubCommand((Player) sender, na); + } + if (cn.equals("设置邮箱")) { + if (!(sender instanceof Player)) return true; + String[] na = new String[args.length + 1]; + na[0] = "email"; + System.arraycopy(args, 0, na, 1, args.length); + return handleSubCommand((Player) sender, na); + } + if (cn.equals("撤销")) { + if (!(sender instanceof Player)) return true; + String[] na = new String[args.length + 1]; + na[0] = "undo"; + System.arraycopy(args, 0, na, 1, args.length); + return handleSubCommand((Player) sender, na); + } + if (cn.equals("删除")) { + if (!(sender instanceof Player)) { + if (args.length < 2) { sender.sendMessage("§c用法: /删除 <玩家> <管理密码>"); return true; } + if (args[1].equals(config.adminPassword)) { + db.deleteUser(args[0]); + sender.sendMessage("§a[Sdf1_login] §f玩家 " + args[0] + " 已删除"); + } else { + sender.sendMessage("§c[Sdf1_login] §f管理密码错误!"); + } + return true; + } + String[] na = new String[args.length + 1]; + na[0] = "del"; + System.arraycopy(args, 0, na, 1, args.length); + return handleSubCommand((Player) sender, na); + } + if (cn.equals("找回密码")) { + if (!(sender instanceof Player)) return true; + return loginMgr.handleReset((Player) sender); + } + if (cn.equals("签到")) { + if (!(sender instanceof Player)) return true; + Player pl = (Player) sender; + if (dailySign(pl)) gui.openTaskCenter(pl); + else pl.sendMessage(config.msg("checkin_already")); + return true; + } + if (cn.equalsIgnoreCase("reg")) { + if (!(sender instanceof Player)) return true; + return loginMgr.handleRegister((Player) sender, args); + } + if (cn.equalsIgnoreCase("login")) { + if (!(sender instanceof Player)) return true; + return loginMgr.handleLogin((Player) sender, args); + } + if (cn.equalsIgnoreCase("sdf1_login")) { + if (args.length == 0) { + if (!(sender instanceof Player)) return true; + gui.openMain((Player) sender); + return true; + } + if (args[0].equalsIgnoreCase("kick") && !(sender instanceof Player)) { + return handleAfkCommand(sender, args); + } + if (args[0].equalsIgnoreCase("del") && !(sender instanceof Player)) { + if (args.length < 3) { sender.sendMessage("§c用法: /sdf1_login del <玩家> <管理密码>"); return true; } + if (args[2].equals(config.adminPassword)) { + db.deleteUser(args[1]); + sender.sendMessage("§a[Sdf1_login] §f玩家 " + args[1] + " 已删除"); + } else { + sender.sendMessage("§c[Sdf1_login] §f管理密码错误!"); + } + return true; + } + if (!(sender instanceof Player)) return true; + return handleSubCommand((Player) sender, args); + } + return false; + } + + /* ==================== 子命令 ==================== */ + + private boolean handleSubCommand(Player p, String[] args) { + String sub = args[0].toLowerCase(); + switch (sub) { + case "reset": + return loginMgr.handleReset(p); + case "email": + if (args.length < 2) { + chatInput.getState(p).type = ChatInputManager.InputType.SET_EMAIL; + p.sendMessage("§e[Sdf1_login] §f请输入邮箱地址:"); + return true; + } + if (!args[1].contains("@")) { p.sendMessage(config.msg("email_invalid")); return true; } + { + String code = PasswordUtils.generateVerifyCode(); + chatInput.setVerifyCode(p, code, "email"); + email.sendVerifyCode(args[1], p.getName(), code); + chatInput.getState(p).tmpStr = args[1]; + chatInput.getState(p).type = ChatInputManager.InputType.EMAIL_VERIFY; + p.sendMessage(config.msg("email_verify_sent", "email", args[1])); + } + return true; + case "password": + case "pw": + if (!loggedIn.contains(p.getName())) { p.sendMessage(config.msg("not_logged_in")); return true; } + if (args.length < 4) { p.sendMessage("§c用法: /sdf1_login pw <旧密码> <新密码> <确认新密码>"); return true; } + { + String newPwd = args[2]; + String cfmPwd = args[3]; + if (!newPwd.equals(cfmPwd)) { p.sendMessage("§c[Sdf1_login] §f两次新密码不一致"); return true; } + if (!PasswordUtils.validate(newPwd)) { p.sendMessage(config.msg("password_invalid")); return true; } + String pwdSalt = (String) db.getField(p.getName(), "password_salt"); + if (pwdSalt == null) return true; + String oldHash = PasswordUtils.hash(args[1], pwdSalt); + if (db.checkPasswordOrTemp(p.getName(), oldHash)) { + String oldH = (String) db.getField(p.getName(), "password_hash"); + String oldS = (String) db.getField(p.getName(), "password_salt"); + recordPasswordChange(p.getName(), oldH, oldS); + String nSalt = PasswordUtils.generateSalt(); + String nHash = PasswordUtils.hash(newPwd, nSalt); + db.setField(p.getName(), "password_hash", nHash); + db.setField(p.getName(), "password_salt", nSalt); + db.setField(p.getName(), "temp_password", ""); + db.setField(p.getName(), "temp_pw_expire", 0L); + needsPasswordChange.remove(p.getName()); + p.sendMessage("§a[Sdf1_login] §f密码修改成功!"); + p.sendMessage("§73分钟内可用 /sdf1_login undo <旧密码> 撤销"); + } else { + p.sendMessage("§c[Sdf1_login] §f旧密码错误!"); + } + } + return true; + case "undo": + if (!loggedIn.contains(p.getName())) { p.sendMessage(config.msg("not_logged_in")); return true; } + if (args.length < 2) { p.sendMessage("§c用法: /sdf1_login undo <旧密码>"); return true; } + { + PwdRollback rb = pwdRollback.get(p.getName()); + if (rb == null) { p.sendMessage("§c[Sdf1_login] §f没有可撤销的密码修改"); return true; } + if (System.currentTimeMillis() - rb.time > 180000L) { + pwdRollback.remove(p.getName()); + p.sendMessage("§c[Sdf1_login] §f已超过3分钟,无法撤销"); + return true; + } + if (!PasswordUtils.hash(args[1], rb.salt).equals(rb.hash)) { + p.sendMessage("§c[Sdf1_login] §f旧密码错误"); + return true; + } + db.setField(p.getName(), "password_hash", rb.hash); + db.setField(p.getName(), "password_salt", rb.salt); + db.setField(p.getName(), "temp_password", ""); + db.setField(p.getName(), "temp_pw_expire", 0L); + needsPasswordChange.remove(p.getName()); + pwdRollback.remove(p.getName()); + p.sendMessage("§a[Sdf1_login] §f密码已回滚到修改前"); + } + return true; + case "set": + if (!isAdmin(p)) { p.sendMessage(config.msg("admin_no_permission")); return true; } + if (args.length < 3) { p.sendMessage("§c用法: /sdf1_login set <玩家> <密码>"); return true; } + { + String sSalt = PasswordUtils.generateSalt(); + String sHash = PasswordUtils.hash(args[2], sSalt); + db.setField(args[1], "password_hash", sHash); + db.setField(args[1], "password_salt", sSalt); + db.setField(args[1], "temp_password", ""); + p.sendMessage(config.msg("admin_set_password", "user", args[1])); + } + return true; + case "del": + if (!isAdmin(p)) { p.sendMessage(config.msg("admin_no_permission")); return true; } + if (args.length < 2) { p.sendMessage("§c用法: /sdf1_login del <玩家> [密码]"); return true; } + if (args.length >= 3) { + String dSalt = (String) db.getField(p.getName(), "password_salt"); + if (dSalt == null) return true; + if (db.checkPassword(p.getName(), PasswordUtils.hash(args[2], dSalt))) { + db.deleteUser(args[1]); + p.sendMessage(config.msg("admin_delete_success", "user", args[1])); + } else { + p.sendMessage(config.msg("admin_delete_failed")); + } + return true; + } + chatInput.getState(p).type = ChatInputManager.InputType.ADMIN_DELETE_CONFIRM; + chatInput.getState(p).targetPlayer = args[1]; + p.sendMessage(config.msg("admin_delete_confirm")); + p.sendMessage("§c格式: <玩家名> <你的密码>"); + return true; + case "take": + openTaskPanel(p); + return true; + case "sign": + if (!loggedIn.contains(p.getName())) { p.sendMessage(config.msg("not_logged_in")); return true; } + if (dailySign(p)) gui.openTaskCenter(p); + else p.sendMessage(config.msg("checkin_already")); + return true; + case "kick": + if (!isAdmin(p)) { p.sendMessage(config.msg("admin_no_permission")); return true; } + handleAfkCommand(p, args); + return true; + case "add": + if (!isAdmin(p)) { p.sendMessage(config.msg("admin_no_permission")); return true; } + if (args.length < 2) { p.sendMessage("§c用法: /sdf1_login add <玩家>"); return true; } + config.afkWhitelist.add(args[1]); + p.sendMessage(config.msg("afk_whitelist_added", "user", args[1])); + return true; + case "remove": + if (!isAdmin(p)) { p.sendMessage(config.msg("admin_no_permission")); return true; } + if (args.length < 2) { p.sendMessage("§c用法: /sdf1_login remove <玩家>"); return true; } + config.afkWhitelist.remove(args[1]); + p.sendMessage(config.msg("afk_whitelist_removed", "user", args[1])); + return true; + case "get": + if (!isAdmin(p)) { p.sendMessage(config.msg("admin_no_permission")); return true; } + if (args.length < 2) { p.sendMessage("§c用法: /sdf1_login get <玩家>"); return true; } + { + Map gUser = db.getUser(args[1]); + if (gUser.isEmpty()) { p.sendMessage("§c未找到玩家: " + args[1]); return true; } + p.sendMessage("§e" + args[1] + " 礼包阶段: " + + gUser.getOrDefault("gift_stage", 0) + + " 已领: " + gUser.getOrDefault("gift_claimed", "")); + } + return true; + case "reload": + if (!isAdmin(p)) { p.sendMessage(config.msg("admin_no_permission")); return true; } + config.loadMessages(); config.loadSmtp(); config.loadSettings(); gift.loadStages(); + p.sendMessage("§a[Sdf1_login] §f配置已重载"); + return true; + case "open": + gui.openMain(p); + return true; + default: + sendHelp(p); + return true; + } + } + + private void sendHelp(Player p) { + p.sendMessage(config.msg("help_header")); + p.sendMessage(config.msg("help_1")); + p.sendMessage(config.msg("help_2")); + p.sendMessage(config.msg("help_3")); + p.sendMessage(config.msg("help_4")); + p.sendMessage(config.msg("help_5")); + if (isAdmin(p)) { + p.sendMessage(config.msg("help_6")); + p.sendMessage(config.msg("help_7")); + p.sendMessage(config.msg("help_8")); + p.sendMessage(config.msg("help_9")); + p.sendMessage(config.msg("help_10")); + p.sendMessage(config.msg("help_11")); + } + } + + /* ==================== AFK ==================== */ + + private boolean handleAfkCommand(CommandSender sender, String[] args) { + if (args.length >= 2) { + String param = args[1].toLowerCase(); + if (isAfkOn(param)) { config.afkEnabled = true; sendFeedback(sender, config.msg("afk_set_enabled")); } + else if (isAfkOff(param)) { config.afkEnabled = false; sendFeedback(sender, config.msg("afk_set_disabled")); } + } + if (args.length >= 3) { + int seconds = parseAfkTimeToSeconds(args[2]); + if (seconds > 0) { config.afkTimeout = seconds; sendFeedback(sender, config.msg("afk_set_time", "duration", (seconds / 60) + "分钟")); } + } + config.saveSettings(); + return true; + } + + private boolean isAfkOn(String s) { + return s.equals("on") || s.equals("开") || s.equals("启用") || s.equals("开启") || s.equals("true"); + } + + private boolean isAfkOff(String s) { + return s.equals("off") || s.equals("关") || s.equals("停用") || s.equals("关闭") || s.equals("false"); + } + + private int parseAfkTimeToSeconds(String input) { + String s = input.toLowerCase().trim(); + if (s.contains("小时") || s.contains("时")) { String n = s.replaceAll("[^0-9]", ""); if (!n.isEmpty()) return Integer.parseInt(n) * 3600; } + if (s.contains("分钟") || s.contains("分")) { String n = s.replaceAll("[^0-9]", ""); if (!n.isEmpty()) return Integer.parseInt(n) * 60; } + if (s.endsWith("h")) { String n = s.replaceAll("[^0-9]", ""); if (!n.isEmpty()) return Integer.parseInt(n) * 3600; } + if (s.endsWith("m")) { String n = s.replaceAll("[^0-9]", ""); if (!n.isEmpty()) return Integer.parseInt(n) * 60; } + String n = s.replaceAll("[^0-9]", ""); + if (!n.isEmpty()) return Integer.parseInt(n) * 60; + return -1; + } + + private void sendFeedback(CommandSender sender, String message) { + sender.sendMessage(message); + getLogger().info(message.replaceAll("\u00a7[0-9a-fk-orA-FK-OR]", "")); + } + + /* ==================== 事件 ==================== */ + + @EventHandler + public void onJoin(PlayerJoinEvent e) { + Player p = e.getPlayer(); + String name = p.getName(); + joinLoc.put(p.getUniqueId(), p.getLocation().clone()); + if (canAutoLogin(p) && db.userExists(name)) { + loggedIn.add(name); + db.setLoggedIn(name, true); + db.setField(name, "last_login_time", System.currentTimeMillis()); + db.setField(name, "last_online_check", System.currentTimeMillis()); + p.sendMessage(config.msg("auto_login_ip", "user", name)); + joinTime.remove(p.getUniqueId()); + 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, name, 0, 0); + } + } catch (Exception ignored) {} + return; + } + hideInventory(p); + if (!db.userExists(name)) p.sendMessage(config.msg("not_registered")); + else p.sendMessage(config.msg("not_logged_in")); + joinTime.put(p.getUniqueId(), System.currentTimeMillis()); + db.setField(name, "last_online_check", System.currentTimeMillis()); + } + + @EventHandler + public void onQuit(PlayerQuitEvent e) { + Player p = e.getPlayer(); + String name = p.getName(); + try { + Object lastObj = db.getField(name, "last_online_check"); + long last = lastObj != null ? ((Number) lastObj).longValue() : 0; + if (last > 0) { + long elapsed = System.currentTimeMillis() - last; + Object totalObj = db.getField(name, "total_online_time"); + long total = totalObj != null ? ((Number) totalObj).longValue() : 0; + db.setField(name, "total_online_time", total + elapsed); + } + } catch (Exception ignored) {} + loggedIn.remove(name); + needsPasswordChange.remove(name); + joinTime.remove(p.getUniqueId()); + joinLoc.remove(p.getUniqueId()); + savedInventory.remove(p.getUniqueId()); + savedArmor.remove(p.getUniqueId()); + savedExtra.remove(p.getUniqueId()); + savedLevel.remove(p.getUniqueId()); + savedExp.remove(p.getUniqueId()); + afk.remove(p.getUniqueId()); + chatInput.clear(p); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onMove(PlayerMoveEvent e) { + Player p = e.getPlayer(); + if (isFrozen(p)) { + 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()) { + Location saved = joinLoc.get(p.getUniqueId()); + if (saved != null) e.setTo(saved.clone()); + } + return; + } + afk.recordAction(p.getUniqueId()); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onInteract(PlayerInteractEvent e) { + if (isFrozen(e.getPlayer())) { e.setCancelled(true); return; } + afk.recordAction(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onBlockBreak(BlockBreakEvent e) { + if (isFrozen(e.getPlayer())) { e.setCancelled(true); return; } + String name = e.getPlayer().getName(); + db.setField(name, "blocks_broken", + ((Number) db.getField(name, "blocks_broken")).intValue() + 1); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onBlockPlace(BlockPlaceEvent e) { + if (isFrozen(e.getPlayer())) { e.setCancelled(true); return; } + String name = e.getPlayer().getName(); + db.setField(name, "blocks_placed", + ((Number) db.getField(name, "blocks_placed")).intValue() + 1); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onChat(AsyncPlayerChatEvent e) { + if (chatInput.handleInput(e.getPlayer(), e.getMessage(), this)) { + e.setCancelled(true); + return; + } + if (isFrozen(e.getPlayer())) e.setCancelled(true); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onCmd(PlayerCommandPreprocessEvent e) { + Player p = e.getPlayer(); + String raw = e.getMessage(); + String msg = raw.toLowerCase(); + if (msg.equals("/l") || msg.startsWith("/l ")) { + e.setCancelled(true); + String argStr = raw.length() > 2 ? raw.substring(2).trim() : ""; + loginMgr.handleLogin(p, argStr.isEmpty() ? new String[0] : argStr.split("\\s+")); + return; + } + if (isFrozen(p)) { + String[] white = { "/reg", "/login", "/sdf1_login", "/l", + "/注册", "/登录", "/改密码", "/设置邮箱", + "/撤销", "/玩家信息", "/删除", "/找回密码", "/签到" }; + boolean pass = false; + for (String w : white) { + if (msg.equals(w) || msg.startsWith(w + " ")) { pass = true; break; } + } + if (!pass) { e.setCancelled(true); p.sendMessage(config.msg("not_logged_in")); } + } + } + + @EventHandler + public void onDrop(PlayerDropItemEvent e) { + if (isFrozen(e.getPlayer())) 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 && isFrozen((Player) e.getEntity())) { + e.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onEntityAttack(EntityDamageByEntityEvent e) { + if (e.getDamager() instanceof Player && isFrozen((Player) e.getDamager())) { + e.setCancelled(true); + } + } + + /* ==================== GUI ==================== */ + + @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 t = e.getView().getTitle(); + int r = e.getRawSlot(); + + /* 临时密码强制改密检查 */ + if (needsPasswordChange.contains(p.getName()) + && !t.equals("§6§l任务面板")) { + e.setCancelled(true); + p.sendMessage("§c[Sdf1_login] §f请先修改密码!"); + p.sendMessage("§7用法: /sdf1_login pw <旧密码> <新密码> <确认新密码>"); + return; + } + + /* 主线/支线任务面板禁止取出物品 */ + if (t.equals("§d§l主线任务") || t.equals("§d§l支线任务")) { + e.setCancelled(true); + int size = e.getInventory().getSize(); + if (r == size - 1) gui.openTaskCenter(p); + return; + } + + /* 主界面 */ + if (t.equals(GUIManager.T_MAIN)) { + e.setCancelled(true); + if (r == 10) gui.openMyInfo(p); + else if (r == 12) gui.openInvite(p); + else if (r == 14) p.openInventory(points.createShopGUI(p)); + else if (r == 16) gui.openTaskCenter(p); + else if (r == 22 && isAdmin(p)) { + p.closeInventory(); + chatInput.getState(p).type = ChatInputManager.InputType.ADMIN_AUTH; + p.sendMessage("§e[Sdf1_login] §f请输入管理密码:"); + } + return; + } + + /* 个人信息 */ + if (t.equals(GUIManager.T_MY_INFO)) { + e.setCancelled(true); + if (r == 26) { + gui.openMain(p); + } else if (r == 10) { + if (checkIn.isCheckedInToday(p.getName())) { + p.sendMessage(config.msg("checkin_already")); + } else { + p.sendMessage(checkIn.checkIn(p)); + invite.onInviteeCheckIn(p.getName()); + gui.openMyInfo(p); + } + } else if (r == 11) { + chatInput.getState(p).type = ChatInputManager.InputType.CHANGE_PWD_STEP1; + p.closeInventory(); + p.sendMessage("§e[Sdf1_login] §f请输入当前密码:"); + } else if (r == 12) { + chatInput.getState(p).type = ChatInputManager.InputType.BACK_CHECKIN; + p.closeInventory(); + p.sendMessage("§e[Sdf1_login] §f请输入补签日期 (yyyy-MM-dd):"); + } else if (r == 13) { + String code = (String) db.getField(p.getName(), "invite_code"); + if (code == null || code.isEmpty()) code = invite.generateCode(p); + p.sendMessage(config.msg("invite_code_generated", "code", code)); + } else if (r == 14) { + chatInput.getState(p).type = ChatInputManager.InputType.SET_EMAIL; + p.closeInventory(); + p.sendMessage("§e[Sdf1_login] §f请输入邮箱地址:"); + } else if (r == 15) { + p.openInventory(points.createShopGUI(p)); + } + return; + } + + /* 管理面板 */ + if (t.equals(GUIManager.T_ADMIN)) { + e.setCancelled(true); + if (r == 22) { + gui.openMain(p); + } else if (r == 10) { + chatInput.getState(p).type = ChatInputManager.InputType.SMTP_STEP1; + p.closeInventory(); + p.sendMessage("§e[Sdf1_login] §f当前SMTP: " + config.getSmtp("smtp地址")); + p.sendMessage("§e[Sdf1_login] §f输入新地址(输入0跳过):"); + } else if (r == 11) { + gui.openUserManagement(p); + } else if (r == 12) { + gui.openMyInfo(p); + } else if (r == 14) { + p.openInventory(points.createShopGUI(p)); + } else if (r == 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[Sdf1_login] §f输入挂机踢出时长(如: 5分钟、10m、2小时):"); + } + } + return; + } + + /* 任务中心 */ + if (t.equals(GUIManager.T_TASK_CENTER)) { + e.setCancelled(true); + if (r == 22) gui.openMain(p); + else if (r == 10) gui.openGiftStages(p); + else if (r == 12) gui.openTaskList(p, "主线任务"); + else if (r == 14) gui.openTaskList(p, "支线任务"); + else if (r == 16) { + if (dailySign(p)) gui.openTaskCenter(p); + else p.sendMessage(config.msg("checkin_already")); + } + return; + } + + /* 新人任务阶段 */ + if (t.equals(GUIManager.T_GIFT_STAGES)) { + e.setCancelled(true); + if (r == 26) { + gui.openTaskCenter(p); + } else if (r >= 10 && r <= 18) { + int stage = r - 9; + if (gift.canClaim(p, stage)) { + gift.claimReward(p, stage); + gui.openGiftStages(p); + } else { + p.sendMessage(config.msg("gift_not_ready")); + } + } + return; + } + + /* 邀请 */ + if (t.equals(GUIManager.T_INVITE)) { + e.setCancelled(true); + if (r == 26) { + gui.openMain(p); + } else if (r == 10) { + String code = invite.generateCode(p); + p.sendMessage(config.msg("invite_code_generated", "code", code)); + gui.openInvite(p); + } else if (r == 16) { + chatInput.getState(p).type = ChatInputManager.InputType.INVITE_INPUT_CODE; + p.closeInventory(); + p.sendMessage("§e[Sdf1_login] §f请输入邀请码:"); + } + return; + } + + /* 玩家管理列表 */ + if (t.equals(GUIManager.T_USER_MGMT)) { + e.setCancelled(true); + if (r == 53) { + gui.openAdmin(p); + } else if (r >= 0 && r < 45) { + ItemStack item = e.getInventory().getItem(r); + if (item != null && item.hasItemMeta() && item.getItemMeta().hasDisplayName()) { + gui.openUserDetail(p, item.getItemMeta().getDisplayName().replace("§e", "").replace("§a", "")); + } + } + return; + } + + /* 玩家详情 */ + if (t.startsWith("§e§l管理: ")) { + e.setCancelled(true); + String prefix = "§e§l管理: "; + if (r == 22) { + gui.openUserManagement(p); + } else if (r == 10) { + String target = t.substring(prefix.length()); + chatInput.getState(p).type = ChatInputManager.InputType.ADMIN_SET_POINTS; + chatInput.getState(p).targetPlayer = target; + p.closeInventory(); + p.sendMessage("§e[Sdf1_login] §f输入" + target + "的新积分值:"); + } else if (r == 12) { + String target = t.substring(prefix.length()); + chatInput.getState(p).type = ChatInputManager.InputType.ADMIN_SET_PWD; + chatInput.getState(p).targetPlayer = target; + p.closeInventory(); + p.sendMessage("§e[Sdf1_login] §f输入" + target + "的新密码:"); + } else if (r == 16) { + String target = t.substring(prefix.length()); + chatInput.getState(p).type = ChatInputManager.InputType.ADMIN_DELETE_CONFIRM; + chatInput.getState(p).targetPlayer = target; + p.closeInventory(); + p.sendMessage(config.msg("admin_delete_confirm")); + p.sendMessage("§c格式: <玩家名> <你的密码>"); + } + return; + } + + /* 积分商城 */ + if (t.startsWith("§d§l积分商城")) { + e.setCancelled(true); + if (r == 49) gui.openMain(p); + else points.handleClick(p, r); + return; + } + + /* CY背包商城 */ + if (t.startsWith("§d§lCY背包商城")) { + e.setCancelled(true); + int size = e.getInventory().getSize(); + if (r == size - 1) p.openInventory(points.createShopGUI(p)); + else points.handleCYClick(p, r); + return; + } + + /* 任务面板 */ + if (t.equals("§6§l任务面板")) { + e.setCancelled(true); + if (r == 22) gui.openMain(p); + else if (r == 15) { + if (dailySign(p)) openTaskPanel(p); + else p.sendMessage(config.msg("checkin_already")); + } + return; + } + } // onInvClick 结束 + + @EventHandler + public void onInvDrag(InventoryDragEvent e) { + if (!(e.getWhoClicked() instanceof Player)) return; + Player p = (Player) e.getWhoClicked(); + if (isFrozen(p)) { e.setCancelled(true); return; } + String t = e.getView().getTitle(); + if (t.equals("§d§l主线任务") || t.equals("§d§l支线任务")) { + e.setCancelled(true); + } + } + + /* ==================== TabComplete ==================== */ + + @Override + public List onTabComplete(CommandSender sender, Command cmd, + String label, String[] args) { + if (!cmd.getName().equalsIgnoreCase("sdf1_login")) return null; + List opts = new ArrayList<>(); + boolean op = isAdmin(sender); + if (args.length == 1) { + opts.addAll(Arrays.asList("open", "reset", "email", "password", "pw", "undo", "take", "sign")); + if (op) opts.addAll(Arrays.asList("set", "del", "kick", "add", "remove", "get", "reload")); + return filterTab(opts, args[0]); + } + if (args.length == 2) { + String sub = args[0].toLowerCase(); + if (op && (sub.equals("set") || sub.equals("del") || sub.equals("get") + || sub.equals("add") || sub.equals("remove"))) { + for (Player op2 : Bukkit.getOnlinePlayers()) opts.add(op2.getName()); + } + if (op && sub.equals("kick")) { + opts.addAll(Arrays.asList("on", "off", "开", "关", "启用", "停用")); + } + } + if (args.length == 3 && args[0].equalsIgnoreCase("kick")) { + opts.add("<时间>"); + } + return filterTab(opts, args[args.length - 1]); + } + + private List filterTab(List opts, String prefix) { + List r = new ArrayList<>(); + for (String o : opts) { + if (o.toLowerCase().startsWith(prefix.toLowerCase())) r.add(o); + } + return r; + } +} diff --git a/src/main/java/Sdf1_login/PasswordUtils.java b/src/main/java/Sdf1_login/PasswordUtils.java new file mode 100644 index 0000000..910a63f --- /dev/null +++ b/src/main/java/Sdf1_login/PasswordUtils.java @@ -0,0 +1,72 @@ +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/src/main/java/Sdf1_login/PointsManager.java b/src/main/java/Sdf1_login/PointsManager.java new file mode 100644 index 0000000..2a89b84 --- /dev/null +++ b/src/main/java/Sdf1_login/PointsManager.java @@ -0,0 +1,328 @@ +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/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..6e16466 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,47 @@ +name: Sdf1_login +version: 1.0 +main: Sdf1_login.Main +api-version: '1.21' +commands: + sdf1_login: + description: Sdf1_login主命令 + usage: /sdf1_login + reg: + description: 注册 + usage: /reg <密码> + login: + description: 登录 + usage: /login <密码> + l: + description: 登录快捷版 + usage: /l <密码> + 注册: + description: 注册(中文) + usage: /注册 <密码> + 登录: + description: 登录(中文) + usage: /登录 <密码> + 玩家信息: + description: 打开面板(中文) + usage: /玩家信息 + 改密码: + description: 修改密码(中文) + usage: /改密码 <旧密码> <新密码> <确认新密码> + 设置邮箱: + description: 设置邮箱(中文) + usage: /设置邮箱 <邮箱地址> + 撤销: + description: 撤销密码修改(中文) + usage: /撤销 <旧密码> + 删除: + description: 删除玩家(中文) + usage: /删除 <玩家> <密码> + 找回密码: + description: 找回密码(中文) + usage: /找回密码 + debug: + description: 调试指令 + usage: /debug <玩家> take.<阶段> + 签到: + description: 每日签到(中文) + usage: /签到