package Sdf1_login; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import org.bukkit.event.player.PlayerInteractEvent; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.Location; import org.bukkit.entity.Entity; import org.bukkit.entity.Villager; import Sdf1_login.ChatInputManager.InputType; import java.util.TreeSet; import java.util.UUID; import java.util.LinkedHashMap; import java.util.concurrent.ConcurrentHashMap; import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.inventory.CraftItemEvent; import org.bukkit.event.player.PlayerFishEvent; import net.milkbowl.vault.economy.Economy; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.Sound; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerPickupItemEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.command.TabCompleter; import org.bukkit.scheduler.BukkitRunnable; public class Main extends JavaPlugin implements CommandExecutor, Listener, TabCompleter { private DatabaseManager db; private ConfigManager config; private EmailManager email; private LoginManager loginMgr; private AFKManager afk; private GiftManager gift; private CheckInManager checkIn; private InviteManager invite; private PointsManager points; private GUIManager gui; private ChatInputManager chatInput; private Economy economy; private VerificationManager verification; private IPGroupManager ipGroup; private AccountRequestManager accountRequest; private TicketManager ticket; private ChatFilterManager chatFilter; private GarbageManager garbage; private final Set loggedIn = new TreeSet<>( String.CASE_INSENSITIVE_ORDER); private final Set needsPasswordChange = new TreeSet<>( String.CASE_INSENSITIVE_ORDER); private final Set frozenFlightPlayers = new TreeSet<>( String.CASE_INSENSITIVE_ORDER); private final Map joinTime = new ConcurrentHashMap<>(); private final Map joinLoc = new HashMap<>(); private final Map ipAutoLogin = new HashMap<>(); private static final long IP_SKIP_MS = 300000L; private final Map lastActivity = new ConcurrentHashMap<>(); // 登录后首次回收飞行(仅一次) private final Set removeFlightOnce = new TreeSet<>( String.CASE_INSENSITIVE_ORDER); private RadioManager radio; public RadioManager getRadio() { return radio; } private static class PwdRollback { final String hash; final String salt; final long time; PwdRollback(String h, String s, long t) { hash = h; salt = s; time = t; } } private final Map pwdRollback = new ConcurrentHashMap<>(); public void recordPasswordChange(String name, String oldHash, String oldSalt) { pwdRollback.put(name, new PwdRollback(oldHash, oldSalt, System.currentTimeMillis())); } // ===== Getters ===== public DatabaseManager getDb() { return db; } public ConfigManager getConfig2() { return config; } public EmailManager getEmail() { return email; } public Economy getEconomy() { return economy; } public LoginManager getLoginMgr() { return loginMgr; } public AFKManager getAfk() { return afk; } public GiftManager getGift() { return gift; } public CheckInManager getCheckIn() { return checkIn; } public InviteManager getInvite() { return invite; } public PointsManager getPoints() { return points; } public GUIManager getGui() { return gui; } public ChatInputManager getChatInput() { return chatInput; } public VerificationManager getVerification() { return verification; } public IPGroupManager getIPGroup() { return ipGroup; } public AccountRequestManager getAccountRequest() { return accountRequest; } public TicketManager getTicket() { return ticket; } public ChatFilterManager getChatFilter() { return chatFilter; } public GarbageManager getGarbage() { return garbage; } public Set getLoggedIn() { return loggedIn; } public Set getNeedsPasswordChange() { return needsPasswordChange; } private WelcomeManager welcome; private UpdateChecker updateChecker; private QuestTracker questTracker; private CommissionManager commission; public CommissionManager getCommission() { return commission; } // ===== Enable/Disable ===== @Override public void onEnable() { radio = new RadioManager(this); getDataFolder().mkdirs(); config = new ConfigManager(getDataFolder()); config.loadMessages(); config.loadSmtp(); config.loadSettings(); getCommand("oa").setExecutor(this); db = new DatabaseManager(getDataFolder()); db.init(); email = new EmailManager(config); loginMgr = new LoginManager(this); afk = new AFKManager(config, this); gift = new GiftManager(this); gift.loadStages(); checkIn = new CheckInManager(this); invite = new InviteManager(this); points = new PointsManager(this); gui = new GUIManager(this); chatInput = new ChatInputManager(); verification = new VerificationManager(this); ipGroup = new IPGroupManager(this, config.maxAccountsPerIP); accountRequest = new AccountRequestManager(this); accountRequest.setMode(config.approvalMode); accountRequest.setAutoDelayMinutes( config.autoApproveDelayMinutes); ticket = new TicketManager(this); garbage = new GarbageManager(this); garbage.init(); garbage.loadConfig(); garbage.startAutoCleanup(); chatFilter = new ChatFilterManager(this); chatFilter.loadConfig(); welcome = new WelcomeManager(this); updateChecker = new UpdateChecker(this); questTracker = new QuestTracker(this); commission = new CommissionManager(this); setupEconomy(); if (getCommand("sdf1_login") != null) { getCommand("sdf1_login").setExecutor(this); getCommand("sdf1_login").setTabCompleter(this); } if (getCommand("reg") != null) getCommand("reg").setExecutor(this); if (getCommand("login") != null) getCommand("login").setExecutor(this); if (getCommand("l") != null) getCommand("l").setExecutor(this); if (getCommand("签到") != null) getCommand("签到").setExecutor(this); if (getCommand("recycle") != null) getCommand("recycle").setExecutor(this); if (getCommand("垃圾清理") != null) getCommand("垃圾清理").setExecutor(this); if (getCommand("绑定邮箱") != null) getCommand("绑定邮箱").setExecutor(this); if (getCommand("找回密码") != null) getCommand("找回密码").setExecutor(this); if (getCommand("玩家信息") != null) getCommand("玩家信息").setExecutor(this); if (getCommand("sdf1debug") != null) getCommand("sdf1debug").setExecutor(this); if (getCommand("oa") != null) getCommand("oa").setExecutor(this); if (getCommand("menu") != null) getCommand("menu").setExecutor(this); if (getCommand("radio") != null) getCommand("radio").setExecutor(this); getServer().getPluginManager() .registerEvents(this, this); afk.startCheck(); startLoginReminder(); startTimeoutCheck(); startPasswordReminder(); startTicketAutoProcess(); startPlayTimeTracker(); // 启动后异步检查更新 updateChecker.checkOnEnable(); getLogger().info( "Sdf1_login v1.0 | 就绪"); for (Player online : Bukkit.getOnlinePlayers()) { radio.sendResourcePack(online); } } @Override public void onDisable() { if (questTracker != null) { questTracker.shutdown(); } if (radio != null) { radio.stopAllLoginRadio(); radio.stopMainRadio(); } if (questTracker != null) { } } // 没替换,只是粗略看了下,有几个问题 private void setupEconomy() { if (getServer().getPluginManager() .getPlugin("Vault") == null) return; RegisteredServiceProvider rsp = getServer().getServicesManager() .getRegistration( Economy.class); if (rsp != null) economy = rsp.getProvider(); } public WelcomeManager getWelcome() { return welcome; } public UpdateChecker getUpdateChecker() { return updateChecker; } public QuestTracker getQuestTracker() { return questTracker; } // ===== Timers ===== private void startTimeoutCheck() { new BukkitRunnable() { public void run() { long now = System.currentTimeMillis(); Iterator> it = joinTime.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = it.next(); if (now - entry.getValue() > config.loginTimeout * 1000L) { Player p = Bukkit.getPlayer( entry.getKey()); if (p != null && p.isOnline() && !loggedIn.contains( p.getName())) p.kickPlayer(config.msg( "login_timeout")); it.remove(); } } } }.runTaskTimer(this, 80L, 80L); } private void startLoginReminder() { new BukkitRunnable() { public void run() { Iterator it = joinTime.keySet().iterator(); while (it.hasNext()) { UUID uuid = it.next(); Player p = Bukkit.getPlayer(uuid); if (p == null || !p.isOnline()) { it.remove(); continue; } if (loggedIn.contains( p.getName())) { it.remove(); continue; } // 已注册→提示登录,未注册→提示注册 if (db.userExists(p.getName())) p.sendMessage(config.msg( "not_logged_in")); else p.sendMessage(config.msg( "not_registered")); } } }.runTaskTimer(this, 200L, 200L); } private void startPasswordReminder() { new BukkitRunnable() { public void run() { for (Player p : Bukkit.getOnlinePlayers()) { if (needsPasswordChange.contains( p.getName())) { p.sendTitle("§c§l请修改密码", "§f使用 /sdf1_login pw 修改密码", 10, 40, 10); p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 1.0f); } } } }.runTaskTimer(this, 200L, 200L); } private void startTicketAutoProcess() { new BukkitRunnable() { public void run() { if (ticket != null) ticket.autoProcessCompleted(); } }.runTaskTimer(this, 6000L, 6000L); } // ===== Utils ===== public String getPlayerIP(Player p) { InetSocketAddress addr = p.getAddress(); if (addr == null) return null; return addr.getAddress().getHostAddress(); } public void resetActivity(Player p) { lastActivity.put(p.getUniqueId(), System.currentTimeMillis()); } public boolean canAutoLogin(Player p) { String ip = getPlayerIP(p); if (ip == null) return false; Long last = ipAutoLogin.get(ip); return last != null && System.currentTimeMillis() - last < IP_SKIP_MS; } public void recordIPLogin(Player p) { String ip = getPlayerIP(p); if (ip != null) ipAutoLogin.put(ip, System.currentTimeMillis()); } public boolean isFrozen(Player p) { return !loggedIn.contains(p.getName()); } private boolean isAdmin(CommandSender sender) { if (sender instanceof Player) return ((Player) sender) .getScoreboardTags() .contains(config.adminTag); return true; } private void clearPlayer(Player p) { p.getInventory().clear(); p.getInventory().setArmorContents( new ItemStack[4]); p.getInventory().setExtraContents( new ItemStack[1]); p.setLevel(0); p.setExp(0f); } public void restoreInventory(Player p) { String name = p.getName(); try { String[] backup = db.loadInventoryBackup(name); if (backup == null || !isValidBackup(backup)) { return; } debugPrintBase64("恢复前", name, backup[0]); ItemStack[] contents = InventorySerializer .fromBase64(backup[0]); if (contents != null) p.getInventory() .setContents(contents); if (backup[1] != null && !backup[1].isEmpty()) { ItemStack[] armor = InventorySerializer .fromBase64( backup[1]); if (armor != null) p.getInventory() .setArmorContents( armor); } if (backup[2] != null && !backup[2].isEmpty()) { ItemStack[] extra = InventorySerializer .fromBase64( backup[2]); if (extra != null) p.getInventory() .setExtraContents( extra); } int lv = Integer.parseInt( backup[3]); if (lv > 0) p.setLevel(lv); double xp = Double.parseDouble( backup[4]); if (xp > 0) p.setExp((float) xp); debugPrintBase64("恢复后", name, InventorySerializer.toBase64( p.getInventory() .getContents())); getLogger().info( "[Sdf1_login] " + name + " 背包已恢复"); } catch (Exception e) { getLogger().severe( "[Sdf1_login] 还原背包失败: " + name); e.printStackTrace(); } } @EventHandler public void onBlockPlace_Quest( BlockPlaceEvent e) { if (questTracker == null) return; Player p = e.getPlayer(); if (isFrozen(p)) return; questTracker.onBlockPlaced( p.getName(), e.getBlock().getType().name()); } @EventHandler public void onMoveBiome( PlayerMoveEvent e) { if (questTracker == null) return; Player p = e.getPlayer(); if (isFrozen(p)) return; Location to = e.getTo(); if (to == null) return; String fromKey = e.getFrom().getBlock() .getBiome().getKey().getKey(); String toKey = to.getBlock() .getBiome().getKey().getKey(); if (fromKey.equals(toKey)) return; questTracker.onPlayerMove( p.getName(), toKey); } public void backupInventory(Player p) { String name = p.getName(); int cnt = countItems(p) + countArmor(p) + countExtra(p); if (cnt == 0) { getLogger().warning( "[Sdf1_login] " + name + " 背包为空,跳过备份"); return; } try { String invB64 = InventorySerializer.toBase64( p.getInventory() .getContents()); String armorB64 = InventorySerializer.toBase64( p.getInventory() .getArmorContents()); String extraB64 = InventorySerializer.toBase64( p.getInventory() .getExtraContents()); db.saveInventoryBackup(name, invB64, armorB64, extraB64, p.getLevel(), p.getExp()); getLogger().info( "[Sdf1_login] " + name + " 背包已备份 (物品:" + cnt + ")"); } catch (Exception e) { getLogger().severe( "[Sdf1_login] 背包备份失败: " + name); e.printStackTrace(); } } private void hideInventory(Player p) { String name = p.getName(); try { debugPrintBase64("清空前", name, InventorySerializer.toBase64( p.getInventory() .getContents())); // 清空背包(备份已在上次退出时保存) p.getInventory().clear(); p.getInventory() .setArmorContents(null); p.getInventory() .setExtraContents(null); p.setLevel(0); p.setExp(0); debugPrintBase64("清空后", name, InventorySerializer.toBase64( p.getInventory() .getContents())); } catch (Exception e) { getLogger().severe( "[Sdf1_login] hideInventory失败: " + name); e.printStackTrace(); } } private void hideInventoryDelayed(Player p, int attempt) { String name = p.getName(); if (!p.isOnline()) return; if (!isFrozen(p)) return; // 从数据库加载已有备份 String[] existing = db.loadInventoryBackup(name); // 调试打印 debugPrintBase64("加入-加载DB", name, existing != null ? existing[0] : null); // 无有效备份 → 跳过清空背包 if (existing == null || !isValidBackup(existing)) { getLogger().info( "[Sdf1_login] " + name + " 无有效备份," + "跳过清空背包"); return; } // 有有效备份 → 清空当前背包 hideInventory(p); } public void autoLogin(Player p, String registerType) { String name = p.getName(); String ip = getPlayerIP(p); String uuid = p.getUniqueId().toString(); String defaultPwd = "123456aA"; if (!db.userExists(name)) { if (config.maxAccountsPerIP > 0 && ip != null && !ipGroup.canRegister(ip)) { p.kickPlayer( buildIPKickMessage(p, ip)); return; } if (!"manual".equals( config.approvalMode)) { AccountRequestManager.Request req = accountRequest.createRequest( name, name, ip); if (req != null) { p.kickPlayer( config.msg("need_approval") .replace("{id}", String.valueOf( req.id))); return; } radio.stopLoginRadio(p); } String salt = PasswordUtils.generateSalt(); String hash = PasswordUtils.hash( defaultPwd, salt); db.createUser(name, hash, salt); db.setField(name, "premium_uuid", uuid); db.setField(name, "register_ip", ip); db.setField(name, "register_type", registerType); db.recordIP(name, ip); } loggedIn.add(name); removeFlightOnce.add(name); db.setLoggedIn(name, true); db.setField(name, "last_login_time", System.currentTimeMillis()); db.setField(name, "last_online_check", System.currentTimeMillis()); db.recordIP(name, ip); joinTime.remove(p.getUniqueId()); recordIPLogin(p); // 标记注册登录条件完成 if (questTracker != null) { questTracker.markConditionComplete( name, "完成注册登录"); } // 欢迎信息 this.getWelcome().onLogin(p); // 只在有有效备份时恢复 restoreInventory(p); giveMenuSnowball(p); p.sendMessage("§a[Sdf1_login] §f您已自动登录!"); activateBeibao(p); pushPendingAlerts(p); } // ===== 任务追踪事件 ===== @EventHandler public void onBlockMine(BlockBreakEvent event) { if (questTracker == null) return; // ← 新增 Player p = event.getPlayer(); if (isFrozen(p)) return; questTracker.onBlockMined( p.getName(), event.getBlock().getType().name()); } @EventHandler public void onMobDeath(EntityDeathEvent e) { if (questTracker == null) return; // ← 新增 if (e.getEntity().getKiller() == null) return; Player p = e.getEntity().getKiller(); questTracker.onMobKill( p.getName(), e.getEntity().getType().name()); } @EventHandler public void onPlayerDeath( PlayerDeathEvent e) { if (questTracker == null) return; // ← 新增 Player killer = e.getEntity().getKiller(); if (killer == null) return; questTracker.onPlayerKill( killer.getName()); } @EventHandler public void onCraft( CraftItemEvent e) { if (questTracker == null) return; // ← 新增 if (!(e.getWhoClicked() instanceof Player)) return; Player p = (Player) e.getWhoClicked(); if (isFrozen(p)) return; questTracker.onItemCrafted( p.getName()); } @EventHandler public void onFish( PlayerFishEvent e) { if (questTracker == null) return; // ← 新增 if (e.getState() != PlayerFishEvent .State.CAUGHT_FISH) return; if (!(e.getCaught() instanceof org.bukkit.entity.Item)) return; Player p = e.getPlayer(); questTracker.onFishCaught( p.getName()); } // 在线时长追踪(每分钟) public void startPlayTimeTracker() { Bukkit.getScheduler() .runTaskTimer(this, () -> { if (questTracker == null) return; for (Player p : Bukkit.getOnlinePlayers()) { if (isFrozen(p)) continue; questTracker.onPlayTime( p.getName(), 60000L); } }, 1200L, 1200L); } private void activateBeibao(Player p) { try { org.bukkit.plugin.Plugin cy = Bukkit.getPluginManager() .getPlugin("CY_beibao"); if (cy != null && cy.isEnabled()) cy.getClass().getMethod( "onSdf1Activation", String.class, int.class, int.class) .invoke(cy, p.getName(), 0, 0); } catch (Exception ignored) { } } public void triggerSecurityAlert(Player player, String suspiciousIP) { String name = player.getName(); db.recordSecurityAlert(name, suspiciousIP); Bukkit.broadcastMessage( "§c§l[安全警告] §e" + name + " §f账号疑似遭到 §c" + suspiciousIP + " §f的盗号尝试"); String emailAddr = (String) db.getField(name, "email"); if (emailAddr != null && !emailAddr.isEmpty()) { String body = "玩家 " + name + ":\n您的账号在 " + new java.text.SimpleDateFormat( "yyyy-MM-dd HH:mm:ss") .format(new java.util.Date()) + " 疑似遭到 IP: " + suspiciousIP + " 的盗号尝试。\n" + "建议立即修改密码。"; final String to = emailAddr; final String finalBody = body; Bukkit.getScheduler() .runTaskAsynchronously(this, () -> email .sendTempPassword( to, name, finalBody)); } } public void pushPendingAlerts(Player p) { String name = p.getName(); List> alerts = db.getUnnotifiedAlerts(name); if (alerts.isEmpty()) return; p.sendMessage("§c§l========== 安全报警 =========="); for (Map alert : alerts) { p.sendMessage("§e时间: §f" + alert.get("alert_date")); p.sendMessage("§e可疑IP: §c" + alert.get("alert_ip")); db.markAlertNotified( ((Number) alert.get("id")) .intValue()); } p.sendMessage( "§c建议使用 §e/sdf1_login pw §c修改密码"); p.sendMessage( "§c§l=================================="); } public void handleReset(Player p) { loginMgr.handleReset(p); } public boolean dailySign(Player p) { if (checkIn.isCheckedInToday(p.getName())) return false; p.sendMessage(checkIn.checkIn(p)); invite.onInviteeCheckIn(p.getName()); return true; } public void openAccountRequestPanel(Player p) { Inventory g = Bukkit.createInventory( null, 27, "§e§l账号请求"); ItemStack gl = new ItemStack( Material.GRAY_STAINED_GLASS_PANE); ItemMeta glm = gl.getItemMeta(); if (glm != null) { glm.setDisplayName(" "); gl.setItemMeta(glm); } for (int i = 0; i < 27; i++) g.setItem(i, gl); g.setItem(10, mkItem(Material.BARRIER, "§c申请删除账号", "§7申请删除自己名下的账号")); g.setItem(13, mkItem(Material.PAPER, "§6提交工单", "§7提交bug反馈、求助、举报等")); g.setItem(16, mkItem(Material.REDSTONE, "§e找回密码", "§7通过邮箱找回密码")); g.setItem(22, mkItem(Material.ARROW, "§7返回")); p.openInventory(g); } private void playSuccessSound(Player p) { p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_CHIME, 1.0f, 0.79f); p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1.0f, 0.79f); p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 0.79f); } private int countItems(Player p) { int c = 0; for (ItemStack it : p.getInventory() .getContents()) if (it != null && it.getType() != Material.AIR) c++; return c; } private int countArmor(Player p) { int c = 0; for (ItemStack it : p.getInventory() .getArmorContents()) if (it != null && it.getType() != Material.AIR) c++; return c; } private int countExtra(Player p) { int c = 0; for (ItemStack it : p.getInventory() .getExtraContents()) if (it != null && it.getType() != Material.AIR) c++; return c; } /** * 检查备份是否有效(包含非雪球的真实物品) */ public boolean isValidBackup(String[] backup) { if (backup == null || backup[0] == null || backup[0].isEmpty()) { return false; } try { ItemStack[] items = InventorySerializer .fromBase64(backup[0]); if (items == null) return false; for (ItemStack item : items) { if (item == null || item.getType() == Material.AIR) continue; if (isMenuSnowball(item)) continue; return true; } } catch (Exception e) { return false; } return false; } /** * 控制台打印 base64 前10位 */ private void debugPrintBase64(String label, String name, String base64) { if (base64 == null || base64.isEmpty()) { getLogger().info( "[BACKUP_DEBUG] " + label + " " + name + ": "); return; } String preview = base64.length() > 10 ? base64.substring(0, 10) : base64; getLogger().info( "[BACKUP_DEBUG] " + label + " " + name + ": " + preview); } private ItemStack mkItem(Material mat, String name, String... lore) { ItemStack it = new ItemStack(mat); ItemMeta im = it.getItemMeta(); if (im != null) { im.setDisplayName(name); if (lore.length > 0) { List list = new ArrayList<>(); for (String l : lore) if (!l.isEmpty()) list.add(l); if (!list.isEmpty()) im.setLore(list); } it.setItemMeta(im); } return it; } private String buildIPKickMessage(Player p, String ip) { List> accounts = db.getAccountDetailsByIP(ip); StringBuilder sb = new StringBuilder(); sb.append("§c§l======== IP注册已满 ========\n"); sb.append("§e您的IP §f").append(ip) .append(" §e下已注册 §f") .append(accounts.size()) .append(" §e个账号:\n"); java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat( "yyyy-MM-dd HH:mm"); int idx = 1; for (Map acc : accounts) { long regTime = ((Number) acc.get( "register_time")).longValue(); String dateStr = regTime > 0 ? sdf.format(new java.util.Date( regTime)) : "未知"; sb.append("§7").append(idx) .append(". §f") .append(acc.get("player_name")) .append(" §7注册于: §e") .append(dateStr).append("\n"); idx++; } sb.append("§7-----------------------------\n"); sb.append("§e请使用已有账号登录\n"); sb.append("§e登录后输入 §f/oa §e提交工单\n"); sb.append("§c§l============================"); return sb.toString(); } /** * 读取备份,返回物品名预览 */ private String getBackupItemPreview( int backupId) { try { String[] backup = db.getInventoryBackupById( backupId); if (backup == null || backup[0] == null || backup[0].isEmpty()) return "§7空"; ItemStack[] items = InventorySerializer .fromBase64( backup[0]); if (items == null) return "§7空"; Map counts = new LinkedHashMap<>(); for (ItemStack it : items) { if (it == null || it.getType() == Material.AIR) continue; String name = translateMaterial( it.getType().name()); counts.merge(name, it.getAmount(), Integer::sum); } if (counts.isEmpty()) return "§7空"; StringBuilder sb = new StringBuilder(); int shown = 0; for (Map.Entry en : counts.entrySet()) { if (shown >= 5) { sb.append(" §7..."); break; } if (shown > 0) sb.append("§7, "); sb.append("§f").append(en.getKey()) .append("§7x") .append(en.getValue()); shown++; } return sb.toString(); } catch (Exception e) { return "§7解析失败"; } } private String translateMaterial( String name) { switch (name) { case "GRASS_BLOCK": return "草方块"; case "OAK_LOG": return "橡木原木"; case "OAK_PLANKS": return "橡木木板"; case "STONE": return "石头"; case "COBBLESTONE": return "圆石"; case "DIRT": return "泥土"; case "SAND": return "沙子"; case "GRAVEL": return "砂砾"; case "DIAMOND": return "钻石"; case "DIAMOND_ORE": return "钻石矿石"; case "IRON_INGOT": return "铁锭"; case "GOLD_INGOT": return "金锭"; case "COAL": return "煤炭"; case "COAL_ORE": return "煤矿石"; case "RAW_IRON": return "粗铁"; case "RAW_GOLD": return "粗金"; case "OAK_LEAVES": return "橡树树叶"; case "OAK_SAPLING": return "橡树树苗"; case "STICK": return "木棍"; case "CRAFTING_TABLE": return "工作台"; case "FURNACE": return "熔炉"; case "CHEST": return "箱子"; case "TORCH": return "火把"; case "BEDROCK": return "基岩"; case "TNT": return "TNT"; case "REDSTONE": return "红石"; case "REDSTONE_ORE": return "红石矿石"; case "LAPIS_LAZULI": return "青金石"; case "EMERALD": return "绿宝石"; case "EMERALD_ORE": return "绿宝石矿石"; case "OBSIDIAN": return "黑曜石"; case "GLOWSTONE": return "萤石"; case "ARROW": return "箭"; case "BOW": return "弓"; case "IRON_SWORD": return "铁剑"; case "DIAMOND_SWORD": return "钻石剑"; case "IRON_PICKAXE": return "铁镐"; case "DIAMOND_PICKAXE": return "钻石镐"; case "IRON_AXE": return "铁斧"; case "DIAMOND_AXE": return "钻石斧"; case "IRON_HELMET": return "铁头盔"; case "IRON_CHESTPLATE": return "铁胸甲"; case "IRON_LEGGINGS": return "铁护腿"; case "IRON_BOOTS": return "铁靴子"; case "DIAMOND_HELMET": return "钻石头盔"; case "DIAMOND_CHESTPLATE": return "钻石胸甲"; case "DIAMOND_LEGGINGS": return "钻石护腿"; case "DIAMOND_BOOTS": return "钻石靴子"; case "BREAD": return "面包"; case "COOKED_BEEF": return "牛排"; case "COOKED_PORKCHOP": return "熟猪排"; case "COOKED_CHICKEN": return "熟鸡肉"; case "GOLDEN_APPLE": return "金苹果"; case "ENDER_PEARL": return "末影珍珠"; case "BLAZE_ROD": return "烈焰棒"; case "GHAST_TEAR": return "恶魂之泪"; case "NETHER_STAR": return "下界之星"; case "SHULKER_SHELL": return "潜影壳"; case "ELYTRA": return "鞘翅"; case "TOTEM_OF_UNDYING": return "不死图腾"; case "TRIDENT": return "三叉戟"; case "ENCHANTED_GOLDEN_APPLE": return "附魔金苹果"; default: // 转中文名格式:SANDSTONE -> 沙石 return name.toLowerCase() .replace("_", " "); } } @EventHandler public void onTeleportBiome( PlayerTeleportEvent e) { if (questTracker == null) return; Player p = e.getPlayer(); if (isFrozen(p)) return; Location to = e.getTo(); if (to == null) return; String toKey = to.getBlock() .getBiome().getKey().getKey(); questTracker.onPlayerMove( p.getName(), toKey); } @EventHandler public void onJoin(PlayerJoinEvent e) { Player p = e.getPlayer(); String name = p.getName(); String ip = getPlayerIP(p); // 重置聊天输入状态(防止上次退出时残留) chatInput.reset(p); if (needsPasswordChange.contains(name)) needsPasswordChange.remove(name); lastActivity.put(p.getUniqueId(), System.currentTimeMillis()); if (needsPasswordChange.contains(name)) needsPasswordChange.remove(name); boolean isBedrock = verification.isBedrockPlayer(p); boolean isOnlineMode = verification.isOnlineMode(); if (!db.userExists(name)) { if (config.maxAccountsPerIP > 0 && ip != null && !ipGroup.canRegister(ip)) { p.kickPlayer( buildIPKickMessage(p, ip)); return; } } if (isFrozen(p)) { p.setAllowFlight(true); frozenFlightPlayers.add(p.getName()); } if (questTracker != null) { questTracker.debugLogin(p.getName()); } if (!"manual".equals(config.approvalMode) && !db.userExists(name)) { AccountRequestManager.Request req = accountRequest.createRequest( name, name, ip); if (req != null) { p.kickPlayer( config.msg("need_approval") .replace("{id}", String.valueOf( req.id))); return; } } if (isOnlineMode) { autoLogin(p, "premium"); return; } if (isBedrock) { autoLogin(p, "bedrock"); return; } if (canAutoLogin(p)) { autoLogin(p, "ip_reconnect"); return; } afk.remove(p.getUniqueId()); afk.recordAction(p.getUniqueId()); hideInventoryDelayed(p, 0); verification.verifyPremiumAsync(p, isPremium -> { if (isPremium) { Bukkit.getScheduler() .runTask(this, () -> autoLogin(p, "premium")); } else { Bukkit.getScheduler() .runTask(this, () -> { joinTime.put( p.getUniqueId(), System.currentTimeMillis()); if (!db.userExists(name)) p.sendMessage(config.msg( "not_registered")); else p.sendMessage(config.msg( "not_logged_in")); }); } }); } @EventHandler public void onQuit(PlayerQuitEvent e) { Player p = e.getPlayer(); // 记录冻结状态的飞行 if (!db.userExists(p.getName()) || !loggedIn.contains(p.getName())) { p.setAllowFlight(true); frozenFlightPlayers.add(p.getName()); } String name = p.getName(); chatInput.reset(p); boolean wasLoggedIn = loggedIn.contains(name); boolean hasRealItems = false; for (ItemStack it : p.getInventory() .getContents()) { if (it != null && it.getType() != Material.AIR && !isMenuSnowball(it)) { hasRealItems = true; break; } } if (wasLoggedIn || hasRealItems) { try { String contents = InventorySerializer .toBase64( p.getInventory() .getContents()); String armor = InventorySerializer .toBase64( p.getInventory() .getArmorContents()); String extra = InventorySerializer .toBase64( p.getInventory() .getExtraContents()); int level = p.getLevel(); double exp = p.getExp(); debugPrintBase64( "退出保存", name, contents); db.saveInventoryBackup(name, contents, armor, extra, level, exp); } catch (Exception ex) { getLogger().severe( "[Sdf1_login] " + "退出保存背包失败: " + name); } } else { getLogger().info( "[BACKUP_DEBUG] 退出跳过 " + name + ": 背包为空" + "(插件已清空)"); } if (wasLoggedIn) { loggedIn.remove(name); db.setLoggedIn(name, false); recordIPLogin(p); } frozenFlightPlayers.remove(name); // ← 新增 lastActivity.remove(p.getUniqueId()); joinTime.remove(p.getUniqueId()); joinLoc.remove(p.getUniqueId()); needsPasswordChange.remove(name); } private static final String MENU_SNOWBALL_TAG = "sdf1_menu"; // 雪球菜单 public void giveMenuSnowball(Player p) { // 检查开关 Object val = db.getField( p.getName(), "menu_snowball"); if (val != null) { int on = val instanceof Number ? ((Number) val).intValue() : 1; if (on == 0) { return; // 开关关了,不发放 } } // 检查是否已有 for (ItemStack it : p.getInventory() .getContents()) { if (isMenuSnowball(it)) { return; } } // 发放 ItemStack snow = new ItemStack( Material.SNOWBALL); ItemMeta im = snow.getItemMeta(); if (im != null) { im.setDisplayName( "§e§l[菜单] §f右键打开主菜单"); List lore = new ArrayList<>(); lore.add("§7右键点击打开功能菜单"); lore.add("§8" + MENU_SNOWBALL_TAG); im.setLore(lore); snow.setItemMeta(im); } int slot = p.getInventory() .firstEmpty(); if (slot >= 0) { p.getInventory().setItem( slot, snow); } else { p.getWorld().dropItemNaturally( p.getLocation(), snow); } } /** * 手动发放雪球菜单(绕过开关检查) */ public void forceGiveMenuSnowball( Player p) { // 只检查是否已有 for (ItemStack it : p.getInventory() .getContents()) { if (isMenuSnowball(it)) { return; } } ItemStack snow = new ItemStack( Material.SNOWBALL); ItemMeta im = snow.getItemMeta(); if (im != null) { im.setDisplayName( "§e§l[菜单] §f右键打开主菜单"); List lore = new ArrayList<>(); lore.add("§7右键点击打开功能菜单"); lore.add("§8" + MENU_SNOWBALL_TAG); im.setLore(lore); snow.setItemMeta(im); } int slot = p.getInventory() .firstEmpty(); if (slot >= 0) { p.getInventory().setItem( slot, snow); } else { p.getWorld().dropItemNaturally( p.getLocation(), snow); } } private boolean isMenuSnowball(ItemStack item) { if (item == null || item.getType() != Material.SNOWBALL) return false; if (!item.hasItemMeta()) return false; ItemMeta im = item.getItemMeta(); if (im == null) return false; if (!im.hasLore()) return false; List lore = im.getLore(); if (lore == null) return false; for (String line : lore) { if (line.contains(MENU_SNOWBALL_TAG)) return true; } return false; } @EventHandler public void onMove(PlayerMoveEvent e) { Player p = e.getPlayer(); // 已登录:一次性关闭飞行 if (!isFrozen(p)) { if (frozenFlightPlayers.remove( p.getName())) { p.setAllowFlight(false); p.setFlying(false); } return; } // 未登录:检查是否能移动 Location from = e.getFrom(); Location to = e.getTo(); if (to == null) return; // 允许转头(无位移) if (from.getBlockX() == to.getBlockX() && from.getBlockY() == to.getBlockY() && from.getBlockZ() == to.getBlockZ()) { return; } // 有位移 → 取消移动 e.setCancelled(true); // 确保允许飞行(防止被踢出) if (!p.getAllowFlight()) { p.setAllowFlight(true); } // 确保已记录(首次加入时未触发移动的情况) if (!frozenFlightPlayers.contains( p.getName())) { frozenFlightPlayers.add(p.getName()); } } @EventHandler public void onInteract(PlayerInteractEvent e) { Player p = e.getPlayer(); ItemStack item = e.getItem(); if (item == null || item.getType() != Material.SNOWBALL) return; if (!isMenuSnowball(item)) return; if (isFrozen(p)) return; if (!loggedIn.contains(p.getName())) return; e.setCancelled(true); gui.openMain(p); } @EventHandler public void onBlockBreak(BlockBreakEvent e) { Player p = e.getPlayer(); if (isFrozen(p)) { e.setCancelled(true); return; } afk.recordAction(p.getUniqueId()); } @EventHandler public void onBlockPlace(BlockPlaceEvent e) { if (isFrozen(e.getPlayer())) e.setCancelled(true); } @EventHandler public void onDrop(PlayerDropItemEvent e) { if (isFrozen(e.getPlayer())) { e.setCancelled(true); return; } // 禁止丢弃菜单雪球 ItemStack drop = e.getItemDrop().getItemStack(); if (isMenuSnowball(drop)) { e.setCancelled(true); } } @EventHandler public void onPickup(PlayerPickupItemEvent e) { if (isFrozen(e.getPlayer())) e.setCancelled(true); } @EventHandler public void onDamage(EntityDamageEvent e) { if (e.getEntity() instanceof Player) { if (isFrozen((Player) e.getEntity())) e.setCancelled(true); } } @EventHandler public void onEntityAttack( EntityDamageByEntityEvent e) { if (e.getDamager() instanceof Player && isFrozen( (Player) e.getDamager())) e.setCancelled(true); if (e.getEntity() instanceof Player && isFrozen( (Player) e.getEntity())) e.setCancelled(true); } @EventHandler(priority = EventPriority.LOWEST) public void onChat(AsyncPlayerChatEvent e) { Player p = e.getPlayer(); String msg = e.getMessage(); if (chatInput.getState(p).type != ChatInputManager.InputType.NONE) { e.setCancelled(true); ChatInputManager.InputType inputType = chatInput.getState(p).type; if (ticket != null && ticket.handleChatInput( p, msg, inputType)) { return; } chatInput.handleInput(p, msg, this); return; } if (isFrozen(p)) { e.setCancelled(true); return; } // 在 onChat 方法中,isFrozen 检查之后 // (在 chatFilter 检查之前) if (!isFrozen(p) && questTracker != null) { final String pn = p.getName(); Bukkit.getScheduler().runTask(this, () -> questTracker .onPlayerChat(pn)); } if (chatFilter == null) return; // 安全兜底:任何输入状态下跳过过滤 if (chatInput.getState(p).type != ChatInputManager.InputType.NONE) { return; } if (chatFilter.isPlayerWhitelisted( p.getName())) return; // 联控:绑定邮箱/配置SMTP时跳过URL过滤 InputType inType = chatInput.getState(p).type; if (inType == InputType.SET_EMAIL || inType == InputType.SET_EMAIL_VERIFY || inType == InputType.SMTP_CONFIG || inType == InputType.SMTP_VERIFY) { return; } if (chatFilter.isMuted(p.getName())) { e.setCancelled(true); p.sendMessage( chatFilter.msg("chat_muted")); return; } List urls = chatFilter.extractUrls(msg); if (urls.isEmpty()) return; for (String u : urls) { if (chatFilter.isPlayerWhitelisted(u)) continue; if (!chatFilter.isLikelyDomain(u)) continue; e.setCancelled(true); chatFilter.incrementViolation( p.getName()); int count = chatFilter .getViolationCount(p.getName()); p.sendMessage(chatFilter .msg("chat_url_blocked") .replace("{url}", u)); p.sendMessage(chatFilter .msg("chat_url_violation") .replace("{count}", String.valueOf(count))); chatFilter.applyPunishment(p, count); if (chatFilter.isNotifyAdmin()) { String tag = config.adminTag; for (Player op : Bukkit.getOnlinePlayers()) { if (op.getScoreboardTags() .contains(tag) && !op.equals(p)) { op.sendMessage(chatFilter .msg("chat_url_admin_notify") .replace("{player}", p.getName()) .replace("{url}", u) .replace("{count}", String.valueOf( count))); } } } if (chatFilter.isNotifyAll()) { Bukkit.broadcastMessage(chatFilter .msg("chat_url_broadcast") .replace("{player}", p.getName())); } } } // ===== Inventory Click ===== // ===== Inventory Click ===== @EventHandler public void onInvClick(InventoryClickEvent e) { if (!(e.getWhoClicked() instanceof Player)) return; Player p = (Player) e.getWhoClicked(); if (isFrozen(p)) { e.setCancelled(true); return; } String title = e.getView().getTitle(); int slot = e.getRawSlot(); // Shift+点击背包物品 → 存入垃圾箱 if (e.isShiftClick() && e.getClickedInventory() != null && e.getClickedInventory() != e.getView().getTopInventory() && e.getCurrentItem() != null && e.getCurrentItem().getType() != Material.AIR && !isMenuSnowball( e.getCurrentItem()) && title.equals( "§6§l垃圾回收站")) { e.setCancelled(true); ItemStack clicked = e.getCurrentItem().clone(); getLogger().info( "[垃圾站] Shift存入: " + clicked.getType() + " x" + clicked.getAmount()); // 先存入数据库 garbage.saveItem(clicked); // 关闭背包强制同步服务器与客户端状态 // (解决Paper的shift-click取消后仍移动物品的问题) p.closeInventory(); // 1 tick后:移除物品 + 重新打开垃圾站 Bukkit.getScheduler() .runTaskLater(this, () -> { // 此时物品状态已同步 // 搜索并移除对应物品 boolean found = false; for (int i = 0; i < p.getInventory() .getSize(); i++) { ItemStack is = p.getInventory() .getItem(i); if (is != null && is.getType() == clicked .getType() && is.getAmount() == clicked .getAmount()) { p.getInventory() .setItem( i, null); found = true; getLogger() .info( "[垃圾站] " + "移除成功: slot=" + i); break; } } if (!found) { getLogger() .info( "[垃圾站] " + "物品已被移走"); } p.updateInventory(); // 重新打开垃圾站 garbage .openRecyclePage( p, garbage .getRecyclePage( p .getName())); }, 2L); return; } if (needsPasswordChange .contains(p.getName()) && !title.equals( "§6§l任务面板")) { e.setCancelled(true); p.sendMessage("§c请先修改密码!"); return; } // 任务中心 if (title.equals( GUIManager.T_TASK_CENTER)) { e.setCancelled(true); if (slot == 26) gui.openMain(p); else if (slot == 10) gui.openGiftStages(p); else if (slot == 12) gui.openTaskList(p, "主线任务"); else if (slot == 14) gui.openTaskList(p, "支线任务"); else if (slot == 16) { if (dailySign(p)) { gui.openTaskCenter(p); playSuccessSound(p); } else p.sendMessage(config.msg( "checkin_already")); } else if (slot == 21) { handleCheckInReport(p); } return; } // 工单GUI if (title.equals( TicketManager.T_TICKET_MAIN) || title.equals( TicketManager.T_TICKET_CREATE) || title.equals( TicketManager.T_TICKET_MY) || title.equals( TicketManager.T_TICKET_ADMIN) || title.equals( TicketManager.T_TICKET_DETAIL) || title.equals( "§d§l服务商面板") || title.equals( "§6§l抢单大厅") || title.equals( "§b§l我处理中的工单")) { e.setCancelled(true); ticket.handleClick( p, title, slot); return; } // 垃圾回收站 if (title.equals("§6§l垃圾回收站")) { if (slot < 0 || slot >= 54) return; // 功能格 49-53 if (slot >= 49 && slot <= 53) { e.setCancelled(true); garbage.handleClick( p, title, slot); return; } ItemStack inSlot = e.getInventory().getItem(slot); ItemStack cursor = e.getCursor(); // ===== 取出 ===== if (inSlot != null && inSlot.hasItemMeta() && inSlot.getItemMeta().hasLore()) { List lore = inSlot.getItemMeta().getLore(); if (lore != null && !lore.isEmpty() && lore.get(0) .startsWith("§7ID: #")) { e.setCancelled(true); try { String numStr = lore.get(0) .replace("§7ID: #", "") .trim(); int dbId = Integer.parseInt(numStr); if (garbage.removeItem(dbId)) { ItemStack give = inSlot.clone(); ItemMeta gm = give.getItemMeta(); gm.setLore(null); give.setItemMeta(gm); p.getInventory() .addItem(give); e.getInventory() .setItem(slot, null); } } catch (Exception ex) { p.sendMessage("§c取出失败"); } return; } } // ===== 光标存入 ===== if (cursor != null && cursor.getType() != Material.AIR && !isMenuSnowball(cursor)) { ItemStack toSave = cursor.clone(); getLogger().info( "[垃圾站] 存入: " + toSave.getType() + " x" + toSave.getAmount()); garbage.saveItem(toSave); e.setCancelled(true); Bukkit.getScheduler().runTaskLater( this, () -> { p.getOpenInventory() .setCursor(null); garbage.openRecyclePage(p, garbage.getRecyclePage( p.getName())); }, 1L); return; } // 默认取消 e.setCancelled(true); return; } // 主菜单 if (title.equals(GUIManager.T_MAIN)) { e.setCancelled(true); if (slot == 10) gui.openMyInfo(p); else if (slot == 12) gui.openInvite(p); else if (slot == 14) p.openInventory( points.createShopGUI(p)); else if (slot == 16) gui.openTaskCenter(p); else if (slot == 18) ticket.openMain(p); else if (slot == 20) { if (p.isOp() || p.getScoreboardTags() .contains(config.adminTag)) { garbage.openRecycle(p); } } else if (slot == 22 && isAdmin(p)) { p.closeInventory(); chatInput.getState(p).type = ChatInputManager.InputType .ADMIN_AUTH; p.sendMessage("请输入管理密码:"); } return; } // 我的信息 if (title.equals(GUIManager.T_MY_INFO)) { e.setCancelled(true); if (slot == 26) gui.openMain(p); else if (slot == 10) { if (checkIn.isCheckedInToday( p.getName())) p.sendMessage(config.msg( "checkin_already")); else { p.sendMessage( checkIn.checkIn(p)); invite.onInviteeCheckIn( p.getName()); playSuccessSound(p); gui.openMyInfo(p); } } else if (slot == 11) { chatInput.getState(p).type = ChatInputManager.InputType .CHANGE_PWD_STEP1; p.closeInventory(); p.sendMessage("§e请输入当前密码:"); } else if (slot == 12) { chatInput.getState(p).type = ChatInputManager.InputType .BACK_CHECKIN; p.closeInventory(); p.sendMessage("§e请输入补签日期:"); } else if (slot == 13) { String code = (String) db.getField( p.getName(), "invite_code"); if (code == null || code.isEmpty()) code = invite.generateCode(p); p.sendMessage("§e邀请码: " + code); } else if (slot == 14) { chatInput.getState(p).type = ChatInputManager.InputType .SET_EMAIL; p.closeInventory(); p.sendMessage("§e请输入邮箱:"); } else if (slot == 15) p.openInventory( points.createShopGUI(p)); return; } // 管理面板 if (title.equals(GUIManager.T_ADMIN)) { e.setCancelled(true); if (slot == 22) gui.openMain(p); else if (slot == 10) { chatInput.getState(p).type = ChatInputManager.InputType .SMTP_CONFIG; p.closeInventory(); p.sendMessage("§e当前SMTP: " + config.getSmtp("smtp地址")); p.sendMessage("§e输入新地址(0跳过):"); } else if (slot == 11) gui.openUserManagement(p); else if (slot == 12) gui.openMyInfo(p); else if (slot == 14) p.openInventory( points.createShopGUI(p)); else if (slot == 16) { if (e.getClick().isLeftClick()) { config.afkEnabled = !config.afkEnabled; p.sendMessage(config.msg( config.afkEnabled ? "afk_set_enabled" : "afk_set_disabled")); config.saveSettings(); gui.openAdmin(p); } else if (e.getClick() .isRightClick()) { p.closeInventory(); chatInput.getState(p).type = ChatInputManager.InputType .ADMIN_SET_AFK_TIME; p.sendMessage( "§e输入挂机时长(分钟):"); } } return; } // 任务中心 if (title.equals( GUIManager.T_TASK_CENTER)) { e.setCancelled(true); if (slot == 22) gui.openMain(p); else if (slot == 10) gui.openGiftStages(p); else if (slot == 12) gui.openTaskList(p, "主线任务"); else if (slot == 14) gui.openTaskList(p, "支线任务"); else if (slot == 16) { if (dailySign(p)) { gui.openTaskCenter(p); playSuccessSound(p); } else p.sendMessage(config.msg( "checkin_already")); } return; } // 新人任务 if (title.equals( GUIManager.T_GIFT_STAGES)) { e.setCancelled(true); if (slot == 53) { gui.openTaskCenter(p); } else if (slot == 52) { // 打卡上报 handleCheckInReport(p); } else if (slot == 4) { // 进度标题 } else if (slot >= 10 && slot < 53) { int idx = slot - 10; QuestTracker qt = questTracker; List quests = qt.getQuests("新人任务"); if (idx < quests.size()) { QuestTracker.QuestFile qf = quests.get(idx); boolean done = qt.isStageCompleted( p.getName(), qf); if (done && !qt.hasClaimed( p.getName(), qf)) { qt.markClaimed( p.getName(), qf); qt.claimRewards(p, qf); gui.openGiftStages(p); } else if (done && qt.hasClaimed( p.getName(), qf)) { p.sendMessage( "§c该阶段奖励已领取"); } else { p.sendMessage( "§c该阶段尚未完成"); } } } return; } // 邀请 if (title.equals(GUIManager.T_INVITE)) { e.setCancelled(true); if (slot == 26) gui.openMain(p); else if (slot == 10) { String code = invite.generateCode(p); p.sendMessage("§e邀请码: " + code); gui.openInvite(p); } else if (slot == 16) { chatInput.getState(p).type = ChatInputManager.InputType .INVITE_INPUT_CODE; p.closeInventory(); p.sendMessage("§e请输入邀请码:"); } return; } // 用户管理 if (title.equals( GUIManager.T_USER_MGMT)) { e.setCancelled(true); if (slot == 53) gui.openAdmin(p); else if (slot >= 0 && slot < 45) { ItemStack item = e.getInventory().getItem(slot); if (item != null && item.hasItemMeta() && item.getItemMeta() .hasDisplayName()) gui.openUserDetail(p, item.getItemMeta() .getDisplayName() .replace("§e", "") .replace("§a", "")); } return; } // 用户详情 if (title.startsWith("§e§l管理: ")) { e.setCancelled(true); String tgt = title.substring( "§e§l管理: ".length()); if (slot == 22) gui.openUserManagement(p); else if (slot == 10) { chatInput.getState(p).type = ChatInputManager.InputType .ADMIN_SET_POINTS; chatInput.getState(p) .targetPlayer = tgt; p.closeInventory(); p.sendMessage("§e输入" + tgt + "的新积分:"); } else if (slot == 12) { chatInput.getState(p).type = ChatInputManager.InputType .ADMIN_SET_PWD; chatInput.getState(p) .targetPlayer = tgt; p.closeInventory(); p.sendMessage("§e输入" + tgt + "的临时密码:"); } else if (slot == 13) { gui.openCheckInRecords(p, tgt); } else if (slot == 14) { Player tp = Bukkit.getPlayer(tgt); if (tp != null && tp.isOnline()) { forceGiveMenuSnowball(tp); p.sendMessage("§a已发放雪球菜单给 " + tgt); } else { p.sendMessage("§c玩家不在线"); } } else if (slot == 16) { } else if (slot == 16) { chatInput.getState(p).type = ChatInputManager.InputType .ADMIN_DELETE_CONFIRM; chatInput.getState(p) .targetPlayer = tgt; p.closeInventory(); p.sendMessage(config.msg( "admin_delete_confirm")); } return; } // 积分商城 if (title.startsWith("§d§l积分商城")) { e.setCancelled(true); if (slot == 49) gui.openMain(p); else if (slot == 50 && isAdmin(p)) { gui.openShopAdmin(p); } else points.handleClick(p, slot); return; } // 商城管理 if (title.equals("§c§l商城管理")) { e.setCancelled(true); if (slot == 49) { p.openInventory( points.createShopGUI(p)); return; } return; } // CY背包商城 if (title.startsWith("§d§lCY背包商城")) { e.setCancelled(true); int size = e.getInventory().getSize(); if (slot == size - 1) p.openInventory( points.createShopGUI(p)); else points.handleCYClick(p, slot); return; } // 账号请求面板 if (title.equals("§e§l账号请求")) { e.setCancelled(true); if (slot == 22) gui.openMain(p); else if (slot == 10) { p.closeInventory(); p.sendMessage("§c§l[确认] §f请输入:"); p.sendMessage("§e/sdf1_login del " + p.getName()); } else if (slot == 13) { ticket.openCreate(p); } else if (slot == 16) { p.closeInventory(); loginMgr.handleReset(p); } return; } // 主线/支线任务 if (title.equals("§d§l主线任务") || title.equals("§d§l支线任务")) { e.setCancelled(true); if (slot == e.getInventory().getSize() - 1) gui.openTaskCenter(p); return; } } /** * 打卡上报:验证当前阶段条件 */ private void handleCheckInReport(Player p) { QuestTracker qt = questTracker; if (qt == null) { p.sendMessage("§c任务系统未加载"); return; } p.sendMessage("§e§l[打卡] §f正在检测环境..."); // === 检测当前群系 === Location loc = p.getLocation(); String biomeKey = loc.getBlock() .getBiome().getKey().getKey(); String worldName = loc.getWorld().getName(); List biomeMatched = qt.checkInBiome( p.getName(), biomeKey, worldName, loc.getX(), loc.getY(), loc.getZ()); qt.recordCheckIn( p.getName(), biomeKey, qt.mapBiomeCategory( biomeKey, worldName), worldName, loc.getX(), loc.getY(), loc.getZ()); p.sendMessage("§7 群系: §e" + biomeKey); p.sendMessage("§7 分类: §e" + qt.mapBiomeCategory( biomeKey, worldName)); p.sendMessage("§7 世界: §e" + worldName); p.sendMessage("§7 坐标: §e" + String.format("%.0f, %.0f, %.0f", loc.getX(), loc.getY(), loc.getZ())); if (!biomeMatched.isEmpty()) { for (String m : biomeMatched) { p.sendMessage("§7 §a✓ " + m); } } // === 检测附近村庄(50格内有村民) === boolean nearVillage = false; for (Entity entity : p.getNearbyEntities(50, 50, 50)) { if (entity instanceof Villager) { nearVillage = true; break; } } if (nearVillage) { boolean counted = qt.onReportVillage( p.getName(), worldName, loc.getX(), loc.getY(), loc.getZ()); if (counted) { p.sendMessage( "§7 §a村庄上报计数成功!"); } else { p.sendMessage( "§7 §e村庄32格内已上报过"); } } // === 验证所有未完成的任务 === p.sendMessage(""); p.sendMessage("§e§l[打卡] §f正在验证" + "未完成的任务..."); List categories = Arrays.asList( "新人任务", "主线任务", "支线任务"); int totalMet = 0; int totalChecked = 0; int finishedCount = 0; for (String cat : categories) { List quests = qt.getQuests(cat); for (QuestTracker.QuestFile qf : quests) { if (qt.isStageCompleted( p.getName(), qf)) { finishedCount++; continue; } if (!qf.hasConditions()) continue; p.sendMessage("§e§l[" + qf.displayName + "]"); for (String cond : qf.conditions) { if (qt.isOptional(cond)) continue; totalChecked++; boolean met = qt.isConditionMet( p.getName(), cond); if (met) { totalMet++; p.sendMessage("§7 §a✓ " + qt.getProgress( p.getName(), cond)); } else { p.sendMessage("§7 §c✗ " + qt.getProgress( p.getName(), cond)); } } } } p.sendMessage(""); if (finishedCount > 0) { p.sendMessage("§7已跳过 " + finishedCount + " 个已完成的阶段"); } if (totalChecked == 0) { p.sendMessage( "§a§l[打卡] §f没有" + "未完成的条件"); } else if (totalMet >= totalChecked) { p.sendMessage( "§a§l[打卡] §f全部通过!(" + totalMet + "/" + totalChecked + ")"); playSuccessSound(p); } else { p.sendMessage( "§c§l[打卡] §f未通过 (" + totalMet + "/" + totalChecked + ") §c请继续完成"); } gui.openTaskCenter(p); } @EventHandler public void onInvDrag(InventoryDragEvent e) { if (!(e.getWhoClicked() instanceof Player)) return; Player p = (Player) e.getWhoClicked(); if (isFrozen(p)) { frozenFlightPlayers.add(p.getName()); e.setCancelled(true); return; } String t = e.getView().getTitle(); // 回收站拖拽 if (t.equals("§6§l垃圾回收站")) { for (int rawSlot : e.getRawSlots()) { if (rawSlot >= 49) { e.setCancelled(true); return; } } // 0-48允许拖入,不取消 // 延迟2tick后扫描格子 final Player fp = p; Bukkit.getScheduler() .runTaskLater(this, () -> { Inventory top = fp .getOpenInventory() .getTopInventory(); for (int rawSlot : e.getRawSlots()) { if (rawSlot < 0 || rawSlot >= 49) continue; ItemStack item = top.getItem(rawSlot); if (item != null && item.getType() != Material.AIR && !isMenuSnowball( item)) { garbage.saveItem( item.clone()); top.setItem( rawSlot, null); } } }, 2L); return; } //服务商工单系统 if (t.equals(TicketManager.T_TICKET_MAIN) || t.equals( TicketManager.T_TICKET_CREATE) || t.equals( TicketManager.T_TICKET_MY) || t.equals( TicketManager.T_TICKET_ADMIN) || t.equals( TicketManager.T_TICKET_DETAIL) || t.equals("§d§l服务商面板") || t.equals("§6§l抢单大厅") || t.equals( "§b§l我处理中的工单") || t.equals("§6§l我的历史工单") || t.equals("§c§l商城管理")) { e.setCancelled(true); } } // ===== Commands ===== @Override public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { String cmdName = cmd.getName().toLowerCase(); if (cmdName.equals("recycle")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } Player pr = (Player) sender; if (!pr.isOp() && !pr.getScoreboardTags() .contains(config.adminTag)) { pr.sendMessage("§c权限不足"); return true; } garbage.openRecycle(pr); return true; } if (cmd.getName().equalsIgnoreCase("radio")) { getLogger().info( "[Radio] 收到命令: " + Arrays.toString(args)); // 统一交给 RadioManager 处理 radio.handleCommand(sender, args); return true; } // /垃圾清理 if (cmdName.equals("垃圾清理")) { if (!isAdmin(sender)) { sender.sendMessage("§c权限不足"); return true; } int count = garbage.collectItems(); sender.sendMessage("§a已清理 " + count + " 个掉落物"); return true; } // /menu 开关雪球菜单 if (cmdName.equals("menu")) { if (!(sender instanceof Player)) { sender.sendMessage( "§c仅玩家可用"); return true; } Player p2 = (Player) sender; if (args.length == 0) { p2.sendMessage( "§e用法: /menu <开/关>"); return true; } String arg = args[0]; if (arg.equals("开")) { db.setField(p2.getName(), "menu_snowball", 1); p2.sendMessage( "§a雪球菜单已开启," + "下次进入将发放"); } else if (arg.equals("关")) { db.setField(p2.getName(), "menu_snowball", 0); p2.sendMessage( "§c雪球菜单已关闭," + "下次进入不再发放"); } else { p2.sendMessage( "§e用法: /menu <开/关>"); } return true; } // /reg if (cmdName.equals("reg") || cmdName.equals("注册")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } Player p = (Player) sender; chatInput.reset(p); if (db.userExists(p.getName())) { p.sendMessage( "§c您已注册过,请使用 /login"); return true; } try { loginMgr.handleRegister(p, args); } catch (Exception e) { getLogger().severe( "[Sdf1_login] reg命令异常: " + e.getMessage()); e.printStackTrace(); p.sendMessage("§c注册出错,请联系管理员"); } // 注册/登录成功后统一处理 if (loggedIn.contains(p.getName())) { frozenFlightPlayers.remove(p.getName()); p.setAllowFlight(false); p.setFlying(false); // 仅关闭冻结期间的飞行 if (frozenFlightPlayers.remove( p.getName())) { p.setAllowFlight(false); p.setFlying(false); removeFlightOnce.add(p.getName()); if (questTracker != null) { questTracker.markConditionComplete( p.getName(), "完成注册登录"); } this.getWelcome().onLogin(p); restoreInventory(p); giveMenuSnowball(p); } return true; } } if (cmdName.equals("login") || cmdName.equals("l") || cmdName.equals("登录")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } Player p = (Player) sender; chatInput.reset(p); if (loggedIn.contains(p.getName())) { frozenFlightPlayers.remove(p.getName()); p.setAllowFlight(false); p.setFlying(false); removeFlightOnce.add(p.getName()); p.sendMessage("§c您已登录"); return true; } try { loginMgr.handleLogin(p, args); } catch (Exception e) { getLogger().severe( "[Sdf1_login] login命令异常: " + e.getMessage()); e.printStackTrace(); p.sendMessage("§c登录出错,请联系管理员"); } // 登录成功后统一处理 if (loggedIn.contains(p.getName())) { if (questTracker != null) { questTracker.markConditionComplete( p.getName(), "完成注册登录"); } this.getWelcome().onLogin(p); restoreInventory(p); giveMenuSnowball(p); } return true; } // /找回密码 if (cmdName.equals("找回密码")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } handleReset((Player) sender); return true; } // /签到 if (cmdName.equals("签到")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } if (dailySign((Player) sender)) playSuccessSound((Player) sender); else ((Player) sender).sendMessage( config.msg("checkin_already")); return true; } // 绑定邮箱 if (cmdName.equals("绑定邮箱")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } Player p2 = (Player) sender; if (args.length >= 1) { String emailAddr = args[0]; if (!emailAddr.contains("@") || !emailAddr.contains(".")) { p2.sendMessage("§c邮箱格式不正确"); return true; } String oldEmail = (String) db.getField( p2.getName(), "email"); if (oldEmail != null && !oldEmail.isEmpty()) { chatInput.getState(p2).type = ChatInputManager.InputType .SET_EMAIL_VERIFY; chatInput.getState(p2) .targetPlayer = emailAddr; chatInput.getState(p2) .ticketTitle = "need_pwd"; p2.sendMessage("§e您已绑定邮箱: " + oldEmail); p2.sendMessage( "§e请输入登录密码以确认修改:"); } else { String code = String.valueOf( (int) (Math.random() * 900000 + 100000)); chatInput.getState(p2).type = ChatInputManager.InputType .SET_EMAIL_VERIFY; chatInput.getState(p2) .targetPlayer = emailAddr; chatInput.getState(p2) .ticketTitle = code; final String to = emailAddr; final String c = code; final Player fp = p2; Bukkit.getScheduler() .runTaskAsynchronously(this, () -> { boolean sent = email .sendVerifyCode(to, c); Bukkit.getScheduler().runTask( this, () -> { if (sent) { fp.sendMessage( "§a验证码已发送到 " + to); fp.sendMessage( "§e输入6位验证码(0取消):"); } else { fp.sendMessage( "§c发送失败"); } }); }); } } else { chatInput.getState(p2).type = ChatInputManager.InputType .SET_EMAIL; p2.sendMessage("§e请输入邮箱:"); } return true; } // /sdf1debug if (cmdName.equals("sdf1debug")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } Player p2 = (Player) sender; if (!isAdmin(sender)) { p2.sendMessage("§c权限不足"); return true; } if (args.length < 2) { p2.sendMessage("§e用法: /sdf1debug <玩家> <阶段>"); return true; } String tgt = args[0]; try { int stage = Integer.parseInt(args[1]); db.setField(tgt, "gift_stage", stage); p2.sendMessage("§a已将 " + tgt + " 的任务阶段设为 " + stage); } catch (NumberFormatException ex) { p2.sendMessage("§c请输入有效数字"); } return true; } // /oa if (cmdName.equals("oa")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } openAccountRequestPanel( (Player) sender); return true; } // /玩家信息 if (cmdName.equals("玩家信息")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } gui.openMain((Player) sender); return true; } // ===== /sdf1_login ===== if (!cmdName.equals("sdf1_login")) return false; if (args.length == 0) { if (!(sender instanceof Player)) { sender.sendMessage( "§e用法: /sdf1_login <子命令>"); return true; } gui.openMain((Player) sender); return true; } String sub = args[0].toLowerCase(); // update - 检查更新(控制台+玩家均可) if (sub.equals("update")) { if (!isAdmin(sender)) { sender.sendMessage("§c权限不足"); return true; } sender.sendMessage("§e[更新] 正在检测..."); Bukkit.getScheduler() .runTaskAsynchronously(this, () -> updateChecker .checkUpdate(sender)); return true; } // stop if (sub.equals("stop")) { if (!(sender instanceof Player)) { // 控制台 if (args.length >= 2) { db.clearTempPassword(args[1]); sender.sendMessage("§a已报废 " + args[1] + " 的临时密码"); } else { db.clearAllTempPasswords(); sender.sendMessage( "§a已报废所有临时密码"); } return true; } Player p2 = (Player) sender; if (args.length >= 2) { String tgt = args[1]; String myIP = getPlayerIP(p2); String targetIP = db.getPlayerIPByName(tgt); if (targetIP == null) { Player tp = Bukkit.getPlayer(tgt); if (tp != null && tp.isOnline()) targetIP = getPlayerIP(tp); } if (targetIP == null || !targetIP.equals(myIP)) { p2.sendMessage( "§c只能操作同IP下的玩家"); return true; } db.clearTempPassword(tgt); p2.sendMessage("§a已报废 " + tgt + " 的临时密码"); } else { db.clearTempPassword(p2.getName()); p2.sendMessage( "§a已报废您的临时密码"); } return true; } // reload if (sub.equals("reload")) { if (!isAdmin(sender)) { sender.sendMessage("§c权限不足"); return true; } config.loadMessages(); config.loadSmtp(); config.loadSettings(); chatFilter.loadConfig(); sender.sendMessage("§a配置已重载!"); return true; } // back - 恢复背包备份 if (sub.equals("back")) { if (!isAdmin(sender)) { sender.sendMessage("§c权限不足"); return true; } if (args.length < 2) { sender.sendMessage( "§e用法: /sdf1_login back <玩家> [#编号]"); return true; } String backTarget = args[1]; if (args.length >= 3 && args[2].startsWith("#")) { // /sdf1_login back Aplayer #1 int backupId; try { backupId = Integer.parseInt( args[2].replace("#", "")); } catch (NumberFormatException ex) { sender.sendMessage( "§c无效编号"); return true; } String[] backup = db.getInventoryBackupById( backupId); if (backup == null) { sender.sendMessage( "§c备份 #" + backupId + " 不存在"); return true; } // 如果玩家在线,直接恢复 Player tp = Bukkit.getPlayer( backTarget); if (tp != null && tp.isOnline()) { try { tp.getInventory() .setContents( InventorySerializer .fromBase64( backup[0])); if (backup[1] != null && !backup[1] .isEmpty()) tp.getInventory() .setArmorContents( InventorySerializer .fromBase64( backup[1])); if (backup[2] != null && !backup[2] .isEmpty()) tp.getInventory() .setExtraContents( InventorySerializer .fromBase64( backup[2])); int lv = Integer.parseInt( backup[3]); if (lv > 0) tp.setLevel(lv); tp.sendMessage( "§a管理员已恢复您的背包"); sender.sendMessage( "§a已恢复 " + backTarget + " 的背包 (备份#" + backupId + ")"); } catch (Exception e) { sender.sendMessage( "§c恢复失败: " + e.getMessage()); } } else { // 玩家离线:保存到数据库供下次登录恢复 db.saveInventoryBackup( backTarget, backup[0], backup[1], backup[2], Integer.parseInt( backup[3]), Double.parseDouble( backup[4])); sender.sendMessage( "§a" + backTarget + " 离线,备份已写入数据库," + "下次登录时恢复"); } } else { // /sdf1_login back Aplayer // 罗列最近5条备份 List> backups = db.getInventoryBackups( backTarget, 5); if (backups.isEmpty()) { sender.sendMessage( "§c" + backTarget + " 没有背包备份"); return true; } sender.sendMessage( "§e§l=== " + backTarget + " 背包备份 ==="); java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat( "MM-dd HH:mm:ss"); for (Map b : backups) { int bid = ((Number) b.get("id")) .intValue(); Object stObj = b.get("save_time"); long st = stObj != null ? ((Number) stObj) .longValue() : 0; String time = st > 0 ? sdf.format( new java.util.Date(st)) : "未知时间"; String itemNames = getBackupItemPreview( bid); sender.sendMessage( "§7#" + bid + " §f" + time + " §7: " + itemNames); } sender.sendMessage( "§7使用 §e/sdf1_login back " + backTarget + " #编号 §7恢复"); sender.sendMessage( "§e§l================================"); } } // pw / password if (sub.equals("pw") || sub.equals("password")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } Player p2 = (Player) sender; if (args.length >= 3) { // /sdf1_login pw 旧密码 新密码 String oldPwd = args[1]; String newPwd = args[2]; if (!PasswordUtils.validate( newPwd)) { p2.sendMessage(config.msg( "password_format_error")); return true; } String salt = (String) db.getField( p2.getName(), "password_salt"); String hash = PasswordUtils.hash( oldPwd, salt); boolean mainOk = db.checkPassword( p2.getName(), hash); boolean tempOk = db.checkPasswordOrTemp( p2.getName(), hash); if (!mainOk && !tempOk) { p2.sendMessage(config.msg( "password_wrong")); return true; } String oldHash = (String) db.getField( p2.getName(), "password_hash"); String newSalt = PasswordUtils.generateSalt(); String newHash = PasswordUtils.hash( newPwd, newSalt); if (newHash.equals(oldHash)) { p2.sendMessage(config.msg( "password_same")); return true; } recordPasswordChange( p2.getName(), oldHash, salt); db.setField(p2.getName(), "password_hash", newHash); db.setField(p2.getName(), "password_salt", newSalt); if (!mainOk && tempOk) { db.clearTempPassword( p2.getName()); } frozenFlightPlayers.remove(p2.getName()); p2.setAllowFlight(false); p2.setFlying(false); needsPasswordChange.remove( p2.getName()); p2.sendMessage(config.msg( "password_changed")); return true; } if (args.length == 2) { // /sdf1_login pw 旧密码 // 验证旧密码,然后聊天输入新密码 String oldPwd = args[1]; String salt = (String) db.getField( p2.getName(), "password_salt"); String hash = PasswordUtils.hash( oldPwd, salt); boolean mainOk = db.checkPassword( p2.getName(), hash); boolean tempOk = db.checkPasswordOrTemp( p2.getName(), hash); if (!mainOk && !tempOk) { p2.sendMessage(config.msg( "password_wrong")); return true; } chatInput.getState(p2).type = ChatInputManager.InputType .CHANGE_PWD_STEP2; chatInput.getState(p2) .ticketTitle = mainOk ? "main" : "temp"; p2.sendMessage( "§e请输入新密码:"); return true; } // /sdf1_login pw (无参数) chatInput.getState(p2).type = ChatInputManager.InputType .CHANGE_PWD_STEP1; p2.sendMessage( "§e请输入当前密码:"); return true; } // email if (sub.equals("email")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } Player p2 = (Player) sender; if (args.length >= 2) { String emailAddr = args[1]; if (!emailAddr.contains("@") || !emailAddr.contains(".")) { p2.sendMessage("§c邮箱格式不正确"); return true; } String oldEmail = (String) db.getField( p2.getName(), "email"); if (oldEmail != null && !oldEmail.isEmpty()) { // 已绑定:先验证密码 chatInput.getState(p2).type = ChatInputManager.InputType .SET_EMAIL_VERIFY; chatInput.getState(p2) .targetPlayer = emailAddr; chatInput.getState(p2) .ticketTitle = "need_pwd"; p2.sendMessage("§e您已绑定邮箱: " + oldEmail); p2.sendMessage( "§e请输入登录密码以确认修改:"); } else { // 未绑定:直接发验证码 String code = String.valueOf( (int) (Math.random() * 900000 + 100000)); chatInput.getState(p2).type = ChatInputManager.InputType .SET_EMAIL_VERIFY; chatInput.getState(p2) .targetPlayer = emailAddr; chatInput.getState(p2) .ticketTitle = code; final String to = emailAddr; final String c = code; final Player fp = p2; Bukkit.getScheduler() .runTaskAsynchronously(this, () -> { boolean sent = email .sendVerifyCode(to, c); Bukkit.getScheduler().runTask( this, () -> { if (sent) { fp.sendMessage( "§a验证码已发送到 " + to); fp.sendMessage( "§e输入6位验证码(0取消):"); } else { fp.sendMessage( "§c发送失败"); } }); }); } } else { chatInput.getState(p2).type = ChatInputManager.InputType .SET_EMAIL; p2.sendMessage("§e请输入邮箱:"); } return true; } // sign if (sub.equals("sign")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } if (dailySign((Player) sender)) playSuccessSound((Player) sender); else ((Player) sender).sendMessage( config.msg("checkin_already")); return true; } // reset if (sub.equals("reset")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } loginMgr.handleReset( (Player) sender); return true; } // undo if (sub.equals("undo")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } Player p2 = (Player) sender; PwdRollback rb = pwdRollback.get(p2.getName()); if (rb == null) { p2.sendMessage("§c无可撤销记录"); return true; } if (System.currentTimeMillis() - rb.time > 600000L) { pwdRollback.remove(p2.getName()); p2.sendMessage("§c撤销已过期"); return true; } db.setField(p2.getName(), "password_hash", rb.hash); db.setField(p2.getName(), "password_salt", rb.salt); pwdRollback.remove(p2.getName()); p2.sendMessage("§a密码已撤销!"); return true; } // take if (sub.equals("take")) { if (!isAdmin(sender)) { sender.sendMessage("§c权限不足"); return true; } if (args.length < 3) { sender.sendMessage( "§e用法: /sdf1_login take <玩家> <积分>"); return true; } int amount; try { amount = Integer.parseInt(args[2]); } catch (NumberFormatException ex) { sender.sendMessage("§c无效数字"); return true; } if (!db.deductPoints(args[1], amount)) { sender.sendMessage("§c积分不足"); return true; } sender.sendMessage("§a已扣除 " + args[1] + " " + amount + " 积分"); return true; } // set if (sub.equals("set")) { if (!isAdmin(sender)) { sender.sendMessage("§c权限不足"); return true; } if (args.length < 3) { sender.sendMessage( "§e用法: /sdf1_login set <玩家> <临时密码>"); return true; } String tgt = args[1]; String tempPwd = args[2]; if (!PasswordUtils.validate(tempPwd)) { sender.sendMessage("§c密码格式不符合要求"); return true; } if (!db.userExists(tgt)) { sender.sendMessage("§c玩家不存在"); return true; } String salt = (String) db.getField( tgt, "password_salt"); String hash = PasswordUtils.hash( tempPwd, salt); db.setField(tgt, "temp_password", hash); db.setField(tgt, "temp_pw_expire", System.currentTimeMillis() + 300000L); db.setField(tgt, "temp_pw_used", 0); sender.sendMessage("§a已为 " + tgt + " 设置临时密码"); Player tp = Bukkit.getPlayer(tgt); if (tp != null && tp.isOnline()) tp.kickPlayer( "§c管理员已设置临时密码"); // 邮件通知 String emailAddr = (String) db.getField( tgt, "email"); if (emailAddr != null && !emailAddr.isEmpty()) { String time = new java.text.SimpleDateFormat( "yyyy-MM-dd HH:mm:ss") .format(new java.util.Date()); String body = "玩家 " + tgt + ":\n您的密码已于 " + time + " 被管理员重置为临时密码。\n" + "如果是本人操作请忽略。\n" + "如非本人操作请立即联系管理员。"; final String to = emailAddr; final String fName = tgt; final String bodyF = body; Bukkit.getScheduler() .runTaskAsynchronously(this, () -> email .sendTempPassword( to, fName, bodyF)); } return true; } // give - 手动发放雪球菜单 if (sub.equals("give")) { if (args.length < 2) { sender.sendMessage( "§e用法: /sdf1_login give <玩家名>"); return true; } Player tp = Bukkit.getPlayer( args[1]); if (tp == null || !tp.isOnline()) { sender.sendMessage( "§c玩家不在线: " + args[1]); return true; } forceGiveMenuSnowball(tp); tp.sendMessage( "§a管理员已为您发放菜单雪球"); sender.sendMessage( "§a已发放雪球菜单给 " + args[1]); return true; } // shopadd if (sub.equals("shopadd")) { if (!isAdmin(sender)) { sender.sendMessage("§c权限不足"); return true; } if (args.length < 4) { sender.sendMessage( "§e用法: /sdf1_login shopadd <名称> <价格> <命令>"); return true; } String shopName = args[1]; int price; try { price = Integer.parseInt(args[2]); } catch (NumberFormatException ex) { sender.sendMessage("§c无效数字"); return true; } String shopCmd = args[3]; points.addItem(shopName, price, shopCmd); sender.sendMessage("§a已添加商品: " + shopName + " (" + price + "积分)"); return true; } // shopdel if (sub.equals("shopdel")) { if (!isAdmin(sender)) { sender.sendMessage("§c权限不足"); return true; } if (args.length < 2) { sender.sendMessage( "§e用法: /sdf1_login shopdel <序号>"); return true; } try { int idx = Integer.parseInt(args[1]); points.removeItem(idx); sender.sendMessage("§a已删除商品 #" + idx); } catch (NumberFormatException ex) { sender.sendMessage("§c无效数字"); } return true; } // get if (sub.equals("get")) { if (!isAdmin(sender)) { sender.sendMessage("§c权限不足"); return true; } if (args.length < 2) { sender.sendMessage( "§e用法: /sdf1_login get <玩家>"); return true; } Map user = db.getUser(args[1]); if (user.isEmpty()) { sender.sendMessage("§c玩家不存在"); return true; } sender.sendMessage( "§e=== " + args[1] + " ==="); sender.sendMessage("§7积分: §e" + user.getOrDefault("points", 0)); sender.sendMessage("§7邮箱: §e" + user.getOrDefault("email", "无")); sender.sendMessage("§7签到天数: §e" + user.getOrDefault( "total_checkin_days", 0)); sender.sendMessage("§7邀请码: §e" + user.getOrDefault( "invite_code", "无")); return true; } // del if (sub.equals("del")) { if (!isAdmin(sender)) { sender.sendMessage("§c权限不足"); return true; } if (args.length < 2) { sender.sendMessage( "§e用法: /sdf1_login del <玩家>"); return true; } db.deleteUser(args[1]); sender.sendMessage("§a已删除 " + args[1]); Player tp = Bukkit.getPlayer(args[1]); if (tp != null && tp.isOnline()) tp.kickPlayer("§c账号已被管理员删除"); return true; } // kick (挂机) if (sub.equals("kick")) { if (!isAdmin(sender)) { sender.sendMessage("§c权限不足"); return true; } if (args.length < 2) { sender.sendMessage( "§e用法: /sdf1_login kick <开关> [分钟]"); return true; } String mode = args[1].toLowerCase(); if (mode.equals("on") || mode.equals("开") || mode.equals("启用")) { config.afkEnabled = true; if (args.length >= 3) { try { config.afkTimeout = Integer.parseInt( args[2]) * 60; } catch (NumberFormatException ignored) { } } config.saveSettings(); int minutes = config.afkTimeout / 60; sender.sendMessage( "§a挂机踢出已开启,超时: " + minutes + " 分钟"); } else if (mode.equals("off") || mode.equals("关") || mode.equals("停用")) { config.afkEnabled = false; config.saveSettings(); sender.sendMessage( "§c挂机踢出已关闭"); } else { sender.sendMessage( "§c无效参数 on/off"); } return true; } // add if (sub.equals("add")) { if (!isAdmin(sender)) { sender.sendMessage("§c权限不足"); return true; } if (args.length < 3) { sender.sendMessage( "§e用法: /sdf1_login add <玩家> <积分>"); return true; } int amount; try { amount = Integer.parseInt(args[2]); } catch (NumberFormatException ex) { sender.sendMessage("§c无效数字"); return true; } db.addPoints(args[1], amount); sender.sendMessage("§a已为 " + args[1] + " 增加 " + amount + " 积分"); return true; } // ticket if (sub.equals("ticket") || sub.equals("工单")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } ticket.openMain((Player) sender); return true; } sender.sendMessage("§c未知参数"); return true; } /** * 是否是垃圾站展示物品(带ID lore) */ private boolean isTrashDisplayItem( ItemStack item) { if (item == null || item.getType() == Material.AIR) return false; if (!item.hasItemMeta()) return false; ItemMeta im = item.getItemMeta(); if (im == null || !im.hasLore()) return false; List lore = im.getLore(); if (lore == null || lore.isEmpty()) return false; return lore.get(0) .startsWith("§7ID: #"); } private void sendChatHelp(Player p) { p.sendMessage( "§e§l======== 聊天过滤帮助 =========="); p.sendMessage("§e/chat reload §7- 重载配置"); p.sendMessage("§e/chat add §7- 添加白名单URL"); p.sendMessage("§e/chat remove §7- 移除白名单URL"); p.sendMessage("§e/chat addplayer <玩家> §7- 添加白名单玩家"); p.sendMessage("§e/chat takeplayer <玩家> §7- 移除白名单玩家"); p.sendMessage("§e/chat unmute <玩家> §7- 解禁玩家"); p.sendMessage("§e/chat reset <玩家> §7- 重置违规记录"); p.sendMessage( "§e§l=================================="); } private int findEmptySlot(Inventory inv) { for (int i = 0; i < 49; i++) { ItemStack item = inv.getItem(i); if (item == null || item.getType() == Material.AIR) return i; } return -1; } // ===== TabComplete ===== @Override public List onTabComplete( CommandSender sender, Command cmd, String label, String[] args) { List list = new ArrayList<>(); if (cmd.getName().equalsIgnoreCase( "sdf1_login") && args.length == 1) { if (isAdmin(sender)) { list.addAll(Arrays.asList( "reload", "pw", "email", "sign", "reset", "undo", "get", "del", "set", "take", "add", "kick", "ticket", "oa", "stop", "shopadd", "shopdel","back","update")); } else { list.addAll(Arrays.asList( "pw", "email", "sign", "reset", "undo", "ticket", "oa", "stop")); } } if (cmd.getName().equalsIgnoreCase("chat") && args.length == 1 && isAdmin(sender)) { list.addAll(Arrays.asList( "reload", "add", "remove", "addplayer", "takeplayer", "unmute", "reset")); } return list; } }