package Sdf1_login; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import org.bukkit.event.inventory.*; import org.bukkit.event.player.PlayerInteractEvent; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.UUID; import java.util.LinkedHashMap; import java.util.concurrent.ConcurrentHashMap; import org.bukkit.NamespacedKey; import org.bukkit.persistence.PersistentDataType; import java.util.Date; import org.bukkit.command.CommandSender; import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffect; import org.bukkit.scheduler.BukkitTask; 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.player.AsyncPlayerChatEvent; import org.bukkit.event.player.PlayerDropItemEvent; 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 org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.player.PlayerFishEvent; import org.bukkit.scheduler.BukkitTask; import java.util.HashMap; public class Main extends JavaPlugin implements CommandExecutor, Listener, TabCompleter { private DatabaseManager db; private MenuIconManager menuIconMgr; private ConfigManager config; private EmailManager email; private LoginManager loginMgr; private AFKManager afk; private GiftManager gift; private CheckInManager checkIn; private InviteManager invite; private PointsManager points; public 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; public RadioManager radio; // 在 lastActivity 字段后面添加: // [ADDED] 任务追踪系统 private QuestTracker questTracker; // [FIX] 垃圾箱防抖锁(记录上次操作时间戳) private final Map garbageBusy = new ConcurrentHashMap<>(); // [ADDED] 群系检测节流 private final Map lastBiomeCheck = new ConcurrentHashMap<>(); // 在 private WelcomeManager welcome; 字段(若不存在)添加: private WelcomeManager welcome; private final Set pendingAdminAuth = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); /* 资源包下载部分控制,不经过RadioDownloadListener的熔断控制 */ private RadioDownloadListener radioDL; private final Set chatInputActive = ConcurrentHashMap.newKeySet(); // 补签确认:玩家名 → "日期:总积分" public final Map pendingBackCheck = new HashMap<>(); public MenuManager menu; 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 ipAutoLogin = new HashMap<>(); private static final long IP_SKIP_MS = 300000L; private SalesStatsManager salesStats; private final Map lastActivity = new ConcurrentHashMap<>(); private CommissionManager commission; // 余额操作待处理 private String balanceTarget = null; private boolean balanceGiveMode = false; private BondManager bondManager; private CDKManager cdkManager; private UserGroupManager userGroupManager; private CypayCommand cypayCommand; public BondManager getBonds() { return bondManager; } public CDKManager getCDK() { return cdkManager; } public UserGroupManager getUserGroup() { return userGroupManager; } public CypayCommand getCypay() { return cypayCommand; } // 在 Main.java 字段声明区域(bondManager 附近)添加: // 在 bondManager 字段附近添加: private BondPrinter bondPrinter; private OrderManager orderManager; public OrderManager getOrderManager() { return orderManager; } public AreaProtection areaProtection; // 钱包流水查看目标(玩家名 → 查看目标) private final Map walletViewTarget = new ConcurrentHashMap<>(); // 流水格式化 private static final java.text.SimpleDateFormat TX_SDF = new java.text.SimpleDateFormat( "yyyy-MM-dd HH:mm:ss"); private ShopManager shopManager; //商店 public CommissionManager getCommission() { return commission; } //pvp private PVPManager pvpManager; 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 String pendingDeleteName = null; private BukkitRunnable pendingDeleteTask = null; private final Map pwdRollback = new ConcurrentHashMap<>(); private final Map menuChatField = new java.util.concurrent.ConcurrentHashMap<>(); private final Map menuChatIdx = new java.util.concurrent.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 QuestTracker getQuestTracker() { return questTracker; } public ChatFilterManager getChatFilter() { return chatFilter; } public GarbageManager getGarbage() { return garbage; } public Set getLoggedIn() { return loggedIn; } public Set getNeedsPasswordChange() { return needsPasswordChange; } @EventHandler public void onPlayerQuit(PlayerQuitEvent e) { if (areaProtection != null) { Player p = e.getPlayer(); areaProtection.onPlayerOffline( p.getUniqueId(), p.getName(), ""); if (areaProtection != null) { areaProtection.onPlayerQuit(p.getName()); } } } // public RadioManager radio; @Override public void onEnable() { getDataFolder().mkdirs(); salesStats = new SalesStatsManager(this); // ===== 1. 基础配置 ===== config = new ConfigManager(getDataFolder()); config.loadMessages(); config.loadSmtp(); config.loadSettings(); configMgr = new ConfigManager(getDataFolder()); configMgr.loadSettings(); configMgr.loadMessages(); orderManager = new OrderManager(this); // ===== 2. 数据库 ===== db = new DatabaseManager(getDataFolder()); db.init(); menuIconMgr = new MenuIconManager(this); // ===== 3. 菜单 ===== menu = new MenuManager(this); menu.loadMenu(); // ===== 4. GUI(依赖 menu) ===== gui = new GUIManager(this); shopManager = new ShopManager(this); Bukkit.getPluginManager().registerEvents(shopManager, this); // shopManager.loadCategories(); // 构造器内已调用,此处重复 getServer().getPluginManager() .registerEvents(gui, this); // ===== 5. 其他管理器 ===== 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); 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); commission = new CommissionManager(this); questTracker = new QuestTracker(this); chatFilter = new ChatFilterManager(this); chatFilter.loadConfig(); welcome = new WelcomeManager(this); // ===== 6. 垃圾箱 ===== garbage = new GarbageManager(this); garbage.init(); garbage.loadConfig(); garbage.startAutoCleanup(); // ===== 7. Radio ===== radio = new RadioManager(this); radio.init(); radioDL = new RadioDownloadListener(this); getServer().getPluginManager() .registerEvents(radioDL, this); // ===== 铁块电梯 ===== getServer().getPluginManager() .registerEvents(new IronBlockElevator(), this); // ===== 8. 经济 ===== setupEconomy(); // ===== 9. 注册命令 ===== 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("oa") != null) getCommand("oa").setExecutor(this);*/ if (getCommand("menu") != null) getCommand("menu").setExecutor(this); if (getCommand("printer") != null) { getCommand("printer").setExecutor(this); getCommand("shop").setExecutor(this); getCommand("商店").setExecutor(this); // getCommand("testorder").setExecutor(this); if (getCommand("printer") != null) { getCommand("printer").setExecutor(this); getCommand("shop").setExecutor(this); getCommand("商店").setExecutor(this); } // protect 命令独立注册 if (getCommand("protect") != null) { getCommand("protect").setExecutor(this); getCommand("protect").setTabCompleter(this); } } // ===== 10. 注册事件 ===== getServer().getPluginManager() .registerEvents(this, this); // ===== 11. 定时任务 ===== afk.startCheck(); startLoginReminder(); startTimeoutCheck(); startPasswordReminder(); startTicketAutoProcess(); radio.startMainRadio(); new BukkitRunnable() { public void run() { if (questTracker == null) return; for (Player p : Bukkit.getOnlinePlayers()) { if (!isFrozen(p)) questTracker.onPlayTime( p.getName(), 30000L); } } }.runTaskTimer(this, 600L, 600L); // [新增] Printer 文件清理(每小时检测一次) new BukkitRunnable() { public void run() { if (bondPrinter != null) bondPrinter.cleanupOldFiles(); } }.runTaskTimer(this, 1200L, 72000L); // 12 ===PVP=== pvpManager = new PVPManager(this); getServer().getPluginManager() .registerEvents(pvpManager, this); // PVP区域检测定时器 getServer().getScheduler() .runTaskTimer(this, () -> pvpManager.tickRegions(), 20L, 20L); setupPVPTab(); // 13 ====cypay债券==== // 债券系统(独立DB) bondManager = new BondManager(this); cdkManager = new CDKManager(this); userGroupManager = new UserGroupManager(this); cypayCommand = new CypayCommand(this); /*加载CDK文件 cdkManager.loadCDKsFromDir();*/ // cypay 命令 if (getCommand("cypay") != null) { getCommand("cypay").setExecutor(cypayCommand); getCommand("cypay").setTabCompleter(cypayCommand); } bondPrinter = new BondPrinter(this); // cdkManager.loadCDKsFromDir(); //14 ====商店==== // ★ 构造器内已加载完毕,此处重复 // if (shopManager != null) { // shopManager.loadCategories(); // } configMgr = new ConfigManager(getDataFolder()); configMgr.loadSettings(); orderManager = new OrderManager(this); // 15 ======区域保护========= areaProtection = new AreaProtection(this); getServer().getPluginManager() .registerEvents(areaProtection, this); // 效果清除定时器(只留一个) getServer().getScheduler().runTaskTimer(this, () -> { if (areaProtection != null) { } }, 20L, 20L); // 和平模式独立检测 getServer().getScheduler().runTaskTimer(this, () -> { if (areaProtection == null) return; for (Player p : Bukkit.getOnlinePlayers()) { AreaProtection.AreaConfig ac = areaProtection.getArea( p.getWorld().getName(), p.getLocation().getBlockX(), p.getLocation().getBlockY(), p.getLocation().getBlockZ()); if (ac != null) { // 无条件打印,不管 peaceMode 是啥 /* getLogger().info("[和平定时器] " + p.getName() + " 在[" + ac.name + "] peaceMode=" + ac.peaceMode + " 白名单=" + ac.peaceWhitelist);*/ if (ac.peaceMode) { areaProtection.banHostilesWithWhitelist(p, ac); } } } }, 40L, 40L); areaProtection.startEnforceTask(); if (areaProtection != null) { areaProtection.reload(); areaProtection.startEnforceTask(); } getLogger().info("§b[Sdf1_login]启动完毕\n§lS欢迎使用sdf1系列插件,如有问题,您可在\nGitHub和Gitee提交反馈"); getLogger().info("sdf1系列插件包含:\nsdf1自助兑奖插件\nCY_beibao云背包\nsdf1_login登陆插件\nSdf1_game娱乐游戏插件"); // 大字画 getLogger().info("\n" + " ____ _ __ _ _ _ \n" + " / ___| __| |/ _/ | | | ___ __ _(_)_ __ \n" + " \\___ \\ / _` | |_| | | |/ _ \\ / _` | | '_ \\ \n" + " ___) | (_| | _| | | | (_) | (_| | | | | | \n" + " |____/ \\__,_|_| |_|___|_|\\___/ \\__, |_|_| |_| \n" + " ____ _ __ |_____| _ |___/ _ \n" + " / ___| __| |/ _/ |_ __ | |_ _ __ _(_)_ __ \n" + " \\___ \\ / _` | |_| | '_ \\| | | | |/ _` | | '_ \\ \n" + " ___) | (_| | _| | |_) | | |_| | (_| | | | | | \n" + " |____/ \\__,_|_| |_| .__/|_|\\__,_|\\__, |_|_| |_|___ _ __ _ \n" + " _ __ _____ |_|___ _ __ | |___/_ _ / ___| __| |/ _/ |\n" + " | '_ \\ / _ \\ \\ /\\ / / _ \\ '__| | '_ \\| | | | \\___ \\ / _` | |_| |\n" + " | |_) | (_) \\ V V / __/ | | |_) | |_| | ___) | (_| | _| |\n" + " | .__/ \\___/ \\_/\\_/ \\___|_| |_.__/ \\__, | |____/ \\__,_|_| |_|\n" + " |_| |___/ " ); } public BondPrinter getBondPrinter() { return bondPrinter; } public ShopManager getShopManager() { return shopManager; } private ConfigManager configMgr; public ConfigManager getConfigMgr() { return configMgr; } private void setupPVPTab() { if (getCommand("pvp") != null) { getCommand("pvp") .setTabCompleter( (sender, cmd, alias, args) -> { List list = new ArrayList<>(); if (args.length == 1) { String[] sub = { "create", "stats", "list", "delete", "tool", "on", "off", "tempban"}; for (String s : sub) { if (s.startsWith( args[0] .toLowerCase())) { list.add(s); } } } else if (args.length == 2) { String sub = args[0].toLowerCase(); if (sub.equals("stats")) { for (Player pp : Bukkit .getOnlinePlayers()) { if (pp.getName() .toLowerCase() .startsWith(args[1] .toLowerCase())) { list.add(pp.getName()); } } } else if (sub.equals("delete") || sub.equals("on") || sub.equals("off") || sub.equals("tempban")) { for (String rn : pvpManager .getRegionNames()) { if (rn.toLowerCase() .startsWith(args[1] .toLowerCase())) { list.add(rn); } } } } return list; }); } } @Override public void onDisable() { if (shopManager != null) shopManager.saveAll(); // 取消未执行的删除任务 if (pendingDeleteTask != null) { pendingDeleteTask.cancel(); pendingDeleteTask = null; getLogger().info( "[Sdf1_login] 服务器关闭," + "取消待删除: " + pendingDeleteName); pendingDeleteName = null; } if (db != null) db.close(); if (garbage != null) garbage.close(); if (questTracker != null) questTracker.shutdown(); if (areaProtection != null) { areaProtection.stopEnforceTask(); } for (BukkitTask task : areaProtection.getPendingTasks()) { task.cancel(); } areaProtection.saveWhitelists(); areaProtection.stopEnforceTask(); // 取消所有延时清理任务,立即清理 if (areaProtection != null) { for (Map.Entry entry : areaProtection.getPendingClearTasks().entrySet()) { entry.getValue().cancel(); // 立即清理效果 Player p = Bukkit.getPlayer(entry.getKey()); if (p != null && p.isOnline()) { List marked = areaProtection.getPlayerMarkedEffects() .remove(entry.getKey()); if (marked != null) { for (PotionEffectType type : marked) { p.removePotionEffect(type); } } } } areaProtection.stopEnforceTask(); areaProtection.saveWhitelists(); areaProtection.getAlreadyForced().clear(); } } private void setupEconomy() { if (getServer().getPluginManager() .getPlugin("Vault") == null) return; RegisteredServiceProvider rsp = getServer().getServicesManager() .getRegistration( Economy.class); if (rsp != null) economy = rsp.getProvider(); } // ===== 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 String debugCodePoints(String s) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < s.length(); i++) { if (i > 0) sb.append(" "); sb.append(String.format("U+%04X", (int) s.charAt(i))); } return sb.toString(); } 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(); } } 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; } } 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); p.setAllowFlight(false); p.setFlying(false); 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); // 只在有有效备份时恢复 restoreInventory(p); giveMenuSnowball(p); p.sendMessage("§a[Sdf1_login] §f您已自动登录!"); if (welcome != null) welcome.onLogin(p); activateBeibao(p); pushPendingAlerts(p); } 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 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); // 打卡按钮(独立,slot 11) g.setItem(11, mkItem(Material.COMPASS, "§b§l群系打卡", "§7在不同群系打卡完成任务", "§7打卡范围: 32格去重")); // 新人任务(slot 15) g.setItem(15, mkItem(Material.BOOK, "§e新人任务", "§7完成新手阶段获取奖励")); // 返回 g.setItem(22, mkItem(Material.ARROW, "§7返回")); p.openInventory(g); } 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("_", " "); } } // ===== Events ===== @EventHandler public void onJoin(PlayerJoinEvent e) { Player p = e.getPlayer(); String name = p.getName(); String ip = getPlayerIP(p); // 强制重发资源包(清掉客户端拒绝记录) Bukkit.getScheduler() .runTaskLater(this, () -> { // ★ 资源包:只发送一次,延迟3秒确保客户端就绪 if (radio != null) { final Player jp = p; Bukkit.getScheduler().runTaskLater( this, () -> { if (jp.isOnline()) { radio.sendResourcePack(jp); } }, 60L); } }, 20L); // 重置聊天输入状态(防止上次退出时残留) chatInput.reset(p); if (needsPasswordChange.contains(name)) needsPasswordChange.remove(name); // 改完了直接打包,要打sdf1一起 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); } 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; } } // [ADDED] 资源包(延迟发送,不强制) // ★ 资源包:只发送一次,延迟3秒确保客户端就绪 if (radio != null) { final Player jp = p; Bukkit.getScheduler().runTaskLater( this, () -> { if (jp.isOnline()) { radio.sendResourcePack(jp); } }, 60L); } 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); // [ADDED] 发送资源包 // ★ 资源包:只发送一次,延迟3秒确保客户端就绪 if (radio != null) { final Player jp = p; Bukkit.getScheduler().runTaskLater( this, () -> { if (jp.isOnline()) { radio.sendResourcePack(jp); } }, 60L); } // [ADDED] 发送资源包(延迟5秒确保客户端就绪) // ★ 资源包:只发送一次,延迟3秒确保客户端就绪 if (radio != null) { final Player jp = p; Bukkit.getScheduler().runTaskLater( this, () -> { if (jp.isOnline()) { radio.sendResourcePack(jp); } }, 60L); } 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(); String name = p.getName(); chatInput.reset(p); boolean wasLoggedIn = loggedIn.contains(name); // 判断是否需要保存: // 1. 已登录 → 保存当前背包 // 2. 未登录但有真实物品 → 保存 // (插件安装前的老玩家) // 3. 未登录且背包被插件清空 → 不保存 // (避免覆盖有效备份) 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); } lastActivity.remove(p.getUniqueId()); joinTime.remove(p.getUniqueId()); joinLoc.remove(p.getUniqueId()); needsPasswordChange.remove(name); // [ADDED] 清除 Radio 熔断状态 // [ADDED] 清除 Radio 熔断状态 if (radioDL != null) radioDL.onPlayerQuit( p.getUniqueId()); pendingAdminAuth.remove(name); } private static final String MENU_SNOWBALL_TAG = "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; if (isCustomMenuTrigger(it)) return; } ItemStack customIcon = menuIconMgr.getIcon(p); if (customIcon != null) { tagAsMenuTrigger(customIcon); // 加外观标识 ItemMeta cm = customIcon.getItemMeta(); if (cm != null) { String originalName = cm.getDisplayName(); if (originalName == null || originalName.isEmpty() || originalName.equals( customIcon.getType() .name())) { originalName = "自定义物品"; } cm.setDisplayName( "\u00a7e\u00a7l[菜单] \u00a7f" + originalName); List cmLore = cm.hasLore() ? new ArrayList<>(cm.getLore()) : new ArrayList<>(); // 避免重复添加 if (cmLore.isEmpty() || !cmLore.get(0).contains( "\u00a77右键打开主菜单")) { cmLore.add(0, "\u00a77右键打开主菜单"); } cm.setLore(cmLore); customIcon.setItemMeta(cm); } int slot = p.getInventory() .firstEmpty(); if (slot >= 0) { p.getInventory().setItem( slot, customIcon); } else { p.getWorld().dropItemNaturally( p.getLocation(), customIcon); } return; } ItemStack snow = new ItemStack( Material.SNOWBALL); ItemMeta im = snow.getItemMeta(); if (im != null) { im.setDisplayName( "\u00a7e\u00a7l[菜单] \u00a7f右键打开主菜单"); List lore = new ArrayList<>(); lore.add("\u00a77右键点击打开功能菜单"); lore.add("\u00a78" + 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; } // 运行时初始化版本(避免静态加载问题) private NamespacedKey getTriggerKey() { return new NamespacedKey(this, "menu_trigger"); } /** * 给物品打上菜单触发标记 */ private void tagAsMenuTrigger(ItemStack item) { if (item == null) return; ItemMeta meta = item.getItemMeta(); if (meta == null) return; meta.getPersistentDataContainer().set( getTriggerKey(), PersistentDataType.STRING, "true"); item.setItemMeta(meta); } /** * 检查物品是否带菜单触发标记 */ private boolean isCustomMenuTrigger( ItemStack item) { if (item == null) return false; ItemMeta meta = item.getItemMeta(); if (meta == null) return false; return meta.getPersistentDataContainer() .has(getTriggerKey(), PersistentDataType.STRING); } @EventHandler public void onMove(PlayerMoveEvent e) { Player p = e.getPlayer(); Location from = e.getFrom(); Location to = e.getTo(); if (to == null) return; // 冻结处理(未登录玩家不能移动) if (isFrozen(p)) { if (from.getBlockX() == to.getBlockX() && from.getBlockY() == to.getBlockY() && from.getBlockZ() == to.getBlockZ()) { return; } e.setCancelled(true); p.setAllowFlight(true); } } 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; } /* 雪球菜单: 同时检测左右手 */ @EventHandler public void onInteract(PlayerInteractEvent e) { Player p = e.getPlayer(); if (isFrozen(p)) { e.setCancelled(true); return; } ItemStack hand = p.getInventory() .getItemInMainHand(); ItemStack offhand = p.getInventory() .getItemInOffHand(); if (isMenuSnowball(hand) || isMenuSnowball(offhand)) { e.setCancelled(true); gui.openMain(p); return; } if (isCustomMenuTrigger(hand) || isCustomMenuTrigger(offhand)) { if (db.hasMenuIcon(p.getName())) { 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()); // [ADDED] 任务计数 if (questTracker != null) questTracker.onBlockMined(p.getName(), e.getBlock().getType().name()); } @EventHandler public void onBlockPlace2(BlockPlaceEvent e) { Player p = e.getPlayer(); if (isFrozen(p)) return; afk.recordAction(p.getUniqueId()); if (questTracker != null) questTracker.onBlockPlaced( p.getName(), e.getBlock().getType().name()); } @EventHandler public void onChatTrack(AsyncPlayerChatEvent e) { if (isFrozen(e.getPlayer())) return; if (questTracker != null) questTracker.onPlayerChat( e.getPlayer().getName()); } @EventHandler public void onBlockPlace(BlockPlaceEvent e) { if (isFrozen(e.getPlayer())) 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); } // [ADDED] 击杀追踪 @EventHandler public void onEntityDeath(EntityDeathEvent e) { if (questTracker == null) return; Player killer = e.getEntity().getKiller(); if (killer == null || isFrozen(killer)) return; if (e.getEntity() instanceof Player) questTracker.onPlayerKill(killer.getName()); else questTracker.onMobKill(killer.getName(), e.getEntityType().name()); } // [ADDED] 合成追踪 @EventHandler public void onCraft(CraftItemEvent e) { if (e.getWhoClicked() instanceof Player) { Player p = (Player) e.getWhoClicked(); if (!isFrozen(p) && questTracker != null) questTracker.onItemCrafted(p.getName()); } } // [ADDED] 钓鱼追踪 @EventHandler public void onFish(PlayerFishEvent e) { Player p = e.getPlayer(); if (p == null || isFrozen(p)) return; if (questTracker != null && e.getState() == PlayerFishEvent.State.CAUGHT_FISH) questTracker.onFishCaught(p.getName()); } // [ADDED] 村民交易追踪 @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onVillagerTrade(InventoryClickEvent e) { if (!(e.getWhoClicked() instanceof Player)) return; Player p = (Player) e.getWhoClicked(); if (isFrozen(p)) return; if (questTracker == null) return; Inventory topInv = e.getView().getTopInventory(); if (topInv == null || topInv.getHolder() == null) return; if (!(topInv.getHolder() instanceof org.bukkit.entity.Villager)) return; // 结果槽 = 2 if (e.getRawSlot() != 2) return; ItemStack result = topInv.getItem(2); if (result == null || result.getType() == Material.AIR) return; final Player fp = p; Bukkit.getScheduler().runTaskLater(this, () -> { if (fp.isOnline()) questTracker.onVillagerTrade( fp.getName()); }, 2L); } @EventHandler public void onChat(AsyncPlayerChatEvent e) { Player p = e.getPlayer(); String msg = e.getMessage(); // ★ CDK/经济/债券输入拦截 — 必须在所有聊天处理之前 if (this.getCDK() != null && this.getCDK().isListening(p.getName())) { e.setCancelled(true); this.getCDK().onChat(p, msg); return; } // ===== 菜单聊天输入 ===== if (getMenu().isEditing(p.getName())) { e.setCancelled(true); getMenu().onChat(p, msg); return; } // ===== 冻结检查 ===== if (isFrozen(p)) { e.setCancelled(true); return; } // ===== 聊天输入流程(改密码/绑邮箱等) ===== if (chatInput.isInFlow(p)) { e.setCancelled(true); chatInput.handleInput(p, msg, this); return; } // ===== 管理员密码验证 ===== if (pendingAdminAuth.contains(p.getName())) { e.setCancelled(true); pendingAdminAuth.remove(p.getName()); if (msg.equals(config.adminPassword)) { p.sendMessage("§a验证通过"); p.sendMessage("§7输入的密码已验证"); } else { p.sendMessage("§c密码错误"); } return; } // ===== 聊天过滤 - 开关检查 ===== if (!chatFilter.isEnabled()) return; // ===== 白名单玩家 ===== if (chatFilter.isPlayerWhitelisted( p.getName())) return; // ===== 禁言检查 ===== if (chatFilter.isMuted(p.getName())) { e.setCancelled(true); p.sendMessage(chatFilter.msg( "chat_muted")); return; } // ===== URL检测 ===== List urls = chatFilter.extractUrls(msg); if (urls.isEmpty()) return; // 过滤白名单 List blocked = new ArrayList<>(); for (String url : urls) { if (!chatFilter.isWhitelisted(url)) { blocked.add(url); } } if (blocked.isEmpty()) return; // 取消消息 e.setCancelled(true); for (String url : blocked) { p.sendMessage(chatFilter.msg( "chat_url_blocked", "url", url)); } // 增加违规计数 chatFilter.incrementViolation(p.getName()); int count = chatFilter.getViolationCount( p.getName()); p.sendMessage(chatFilter.msg( "chat_url_violation", "count", String.valueOf(count))); // 通知管理员 if (chatFilter.isNotifyAdmin()) { String notify = chatFilter.msg( "chat_url_admin_notify", "player", p.getName(), "url", blocked.get(0), "count", String.valueOf(count)); for (Player op : Bukkit.getOnlinePlayers()) { if (op.hasPermission( "sdf1.admin") || op.isOp()) { op.sendMessage(notify); } } } // 通报 if (chatFilter.isNotifyAll()) { Bukkit.broadcastMessage( chatFilter.msg( "chat_url_broadcast", "player", p.getName())); } // 处罚 chatFilter.applyPunishment(p, count); // ===== 补签确认 ===== if (pendingBackCheck.containsKey( p.getName())) { e.setCancelled(true); String data = pendingBackCheck .remove(p.getName()); String[] parts = data.split(":"); String dateStr = parts[0]; int totalPoints = Integer.parseInt(parts[1]); if (msg.trim().equals("1") || msg.trim() .equalsIgnoreCase("yes") || msg.trim().contains("是") || msg.trim().contains("愿意")) { int extra = totalPoints - 10; if (extra > 0 && !db.deductPoints( p.getName(), extra)) { db.addPoints(p.getName(), 10); p.sendMessage("§c积分也不足," + "10积分已退还"); return; } checkIn.doBackCheckIn(p, dateStr); p.sendMessage("§a补签成功!日期: " + dateStr + " §7(扣除" + totalPoints + "积分,免经济)"); playSuccessSound(p); } else { db.addPoints(p.getName(), 10); p.sendMessage("§c补签已取消," + "10积分已退还"); } return; } } // 在 getter 区域添加 public MenuManager getMenu() { return menu; } public PVPManager getPVPManager() { return pvpManager; } // 在 Main.java 中(与其他 getter 放一起) public AreaProtection getAreaProtection() { return areaProtection; } // ==================== 我的钱包 ==================== public void openMyWallet(Player p) { openWalletGUI(p, p.getName()); } private void openWalletGUI(Player p, String target) { Inventory g = Bukkit.createInventory( null, 27, "§6§l我的钱包"); ItemStack glass = new ItemStack( Material.GRAY_STAINED_GLASS_PANE); ItemMeta gm = glass.getItemMeta(); if (gm != null) { gm.setDisplayName(" "); glass.setItemMeta(gm); } for (int i = 0; i < 27; i++) g.setItem(i, glass); // 债券余额 (slot 4) int balance = bondManager.getBonds(target); g.setItem(4, mkItem(Material.GOLD_INGOT, "§6§l债券余额: §e" + balance + " §6枚", "§7当前持有的债券数量")); // 账户状态 (slot 10) String status = bondManager .getAccountStatus(target); if ("frozen".equals(status)) { g.setItem(10, mkItem(Material.REDSTONE, "§c§l账户状态: 冻结中", "§7无法进行任何资金交易")); } else { g.setItem(10, mkItem(Material.EMERALD, "§a§l账户状态: 正常", "§7账户运行正常")); } // 同IP玩家 (slot 11) if (target.equals(p.getName())) { List sameIP = bondManager .getSameIPPlayers(target); List lore = new ArrayList<>(); lore.add("§7同IP下注册的其他玩家:"); if (sameIP.isEmpty()) { lore.add("§a 无"); } else { for (String n : sameIP) lore.add("§c - " + n); } lore.add("§7"); lore.add("§7转账限制:"); lore.add("§7 不能与同IP玩家转账"); g.setItem(11, mkItem(Material.PAPER, "§e§l同IP玩家", lore.toArray( new String[0]))); } // 近7天流水 (slot 12) g.setItem(12, mkItem(Material.BOOK, "§e近7天流水", "§7点击查看最近7天交易记录")); // 近14天流水 (slot 13) g.setItem(13, mkItem(Material.BOOK, "§e近14天流水", "§7点击查看最近14天交易记录", "§8(系统最大支持14天)")); // 转账记录 (slot 14) g.setItem(14, mkItem(Material.PAPER, "§e转账记录", "§7点击查看所有转账明细")); // 返回 (slot 22) g.setItem(22, mkItem(Material.ARROW, "§7← 返回")); walletViewTarget.put(p.getName(), target); p.openInventory(g); } // ==================== 流水记录GUI ==================== public void openWalletTransactions( Player p, int days) { String target = walletViewTarget.getOrDefault( p.getName(), p.getName()); openWalletTxGUI(p, target, days, false); } public void openWalletTransfers(Player p) { String target = walletViewTarget.getOrDefault( p.getName(), p.getName()); openWalletTxGUI(p, target, 14, true); } private void openWalletTxGUI(Player p, String target, int days, boolean transfersOnly) { days = Math.min(days, 14); // 普通玩家最大14天 String title = transfersOnly ? "§e§l转账记录 - " + target : "§e§l流水记录 - 近" + days + "天 - " + target; Inventory g = Bukkit.createInventory( null, 54, title); List> txs = transfersOnly ? bondManager.getTransferTransactions( target, days) : bondManager.getTransactions( target, days); int slot = 0; for (Map tx : txs) { if (slot >= 45) break; String type = (String) tx.get("type"); int amount = ((Number) tx.get("amount")) .intValue(); String tgt = (String) tx.get("target_player"); String reason = (String) tx.get("reason"); int bal = ((Number) tx.get("balance_after")) .intValue(); long time = ((Number) tx.get("time")) .longValue(); boolean isExpense = "shop_buy".equals(type) || "shop_packing".equals(type); boolean pos = isExpense ? false : (amount > 0); String sign = pos ? "+" : (isExpense ? "-" : ""); String color = pos ? "§a" : (isExpense ? "§c" : (amount < 0 ? "§c" : "")); // 金额取绝对值显示 int displayAmount = Math.abs(amount); // 用 if-else 替代 switch 避免编译问题 Material mat = Material.PAPER; if (BondManager.TX_TRANSFER_OUT.equals(type)) { mat = Material.RED_WOOL; } else if (BondManager.TX_TRANSFER_IN .equals(type)) { mat = Material.LIME_WOOL; } else if (BondManager.TX_ADMIN_GIVE .equals(type)) { mat = Material.GOLD_BLOCK; } else if (BondManager.TX_ADMIN_DEDUCT .equals(type)) { mat = Material.REDSTONE_BLOCK; } else if (BondManager.TX_REDEEM .equals(type)) { mat = Material.NAME_TAG; } else if (BondManager.TX_SHOP_BUY .equals(type)) { mat = Material.CHEST; } else if (BondManager.TX_FREEZE .equals(type)) { mat = Material.BARRIER; } else if (BondManager.TX_UNFREEZE .equals(type)) { mat = Material.EMERALD_BLOCK; } else if (pos) { mat = Material.LIME_WOOL; } else { mat = Material.RED_WOOL; } String typeName = walletFormatType(type); List lore = new ArrayList<>(); lore.add("§7类型: " + typeName); lore.add("§7金额: " + color + sign + displayAmount + " §6枚"); if (tgt != null && !tgt.isEmpty()) lore.add("§7对象: §e" + tgt); if (reason != null && !reason.isEmpty()) lore.add("§7理由: §7" + reason); lore.add("§7余额: §f" + bal + " §6枚"); lore.add("§8" + TX_SDF.format( new Date(time))); ItemStack item = new ItemStack(mat); ItemMeta im = item.getItemMeta(); if (im != null) { im.setDisplayName(color + sign + displayAmount + " §6枚 - " + typeName); im.setLore(lore); item.setItemMeta(im); } g.setItem(slot, item); slot++; } if (txs.isEmpty()) { ItemStack empty = new ItemStack( Material.BARRIER); ItemMeta em = empty.getItemMeta(); if (em != null) { em.setDisplayName("§7暂无记录"); empty.setItemMeta(em); } g.setItem(22, empty); } else if (txs.size() > 45) { g.setItem(49, mkItem(Material.PAPER, "§7还有 " + (txs.size() - 45) + " 条未显示")); } g.setItem(53, mkItem(Material.ARROW, "§7← 返回钱包")); p.openInventory(g); } private String walletFormatType(String type) { if (type == null) return "未知"; switch (type) { case BondManager.TX_ADMIN_GIVE: return "管理员发放"; case BondManager.TX_ADMIN_DEDUCT: return "管理员扣除"; case BondManager.TX_TRANSFER_OUT: return "转出"; case BondManager.TX_TRANSFER_IN: return "转入"; case BondManager.TX_REDEEM: return "CDK兑换"; case BondManager.TX_SHOP_BUY: return "商城购买"; case BondManager.TX_DAILY_SIGN: return "签到奖励"; case BondManager.TX_FREEZE: return "冻结"; case BondManager.TX_UNFREEZE: return "解冻"; default: return type; } } // ===== Inventory Click(旧逻辑 + 新主菜单布局 + 新功能) ===== @EventHandler public void onInvClick(InventoryClickEvent e) { if (e.isCancelled()) return; 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(); // ===== 工单 ===== 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我处理中的工单")) { if (e.getClickedInventory() == null) return; if (e.getClickedInventory() != e.getView().getTopInventory()) return; e.setCancelled(true); ticket.handleClick(p, title, slot); return; } // ===== 垃圾箱 ===== // ===== 垃圾回收站 ===== if (title.equals("§6§l垃圾回收站")) { if (e.getClickedInventory() == null) return; // 玩家背包普通点击 → 完全不管 if (e.getClickedInventory() == p.getInventory() && !e.isShiftClick()) { return; } // Shift+Click 从玩家背包存入 if (e.isShiftClick() && e.getClickedInventory() == p.getInventory() && e.getCurrentItem() != null && e.getCurrentItem().getType() != Material.AIR && !isMenuSnowball( e.getCurrentItem())) { e.setCancelled(true); long now = System.currentTimeMillis(); Long last = garbageBusy.get( p.getUniqueId()); if (last != null && now - last < 200L) return; garbageBusy.put(p.getUniqueId(), now); ItemStack toSave = e.getCurrentItem().clone(); p.getInventory() .setItem(e.getSlot(), null); garbage.saveItem(toSave); Bukkit.getScheduler() .runTaskLater(this, () -> { garbage.openRecyclePage(p, 0); }, 1L); return; } // GUI 内点击 → 全部取消,由 garbage.handleClick 处理 e.setCancelled(true); // 功能格 49-53 if (slot >= 49 && slot <= 53) { garbage.handleClick(p, title, slot); return; } // 光标有物品 → 存入 if (slot < 54 && e.getCursor() != null && e.getCursor().getType() != Material.AIR && !isMenuSnowball( e.getCursor())) { ItemStack toSave = e.getCursor().clone(); p.getOpenInventory().setCursor(null); garbage.saveItem(toSave); Bukkit.getScheduler() .runTaskLater(this, () -> { garbage.openRecyclePage(p, 0); }, 2L); return; } // 点击有 ID 的物品 → 取出 if (slot < 54) { ItemStack inSlot = e.getInventory().getItem(slot); if (inSlot != null && inSlot.hasItemMeta() && inSlot.getItemMeta() .hasLore()) { List lore = inSlot.getItemMeta().getLore(); if (lore != null && !lore.isEmpty() && lore.get(0).startsWith( "§7ID: #")) { 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(); if (gm == null) gm = Bukkit .getItemFactory() .getItemMeta( give .getType()); gm.setLore(null); give.setItemMeta(gm); p.getInventory() .addItem(give); e.getInventory() .setItem( slot, null); } } catch (Exception ex) { p.sendMessage( "§c取出失败"); } } } } return; } // ===== 二级菜单(.txt)—— 移到前面 ===== if (title.startsWith("§6§l") && !title.equals(GUIManager.T_MAIN) && !title.equals(GUIManager.T_ADMIN) && !title.equals(GUIManager.T_MY_INFO) && !title.equals(GUIManager.T_INVITE) && !title.equals( GUIManager.T_TASK_CENTER) && !title.equals( GUIManager.T_GIFT_STAGES) && !title.equals("§6§l任务面板") && !title.equals( "§6§l垃圾回收站")) { e.setCancelled(true); if (slot == 49) { gui.openMain(p); return; } // ★ bounds检查 ★ if (slot < 45 && slot < e.getInventory() .getSize() && e.getCurrentItem() != null) { ItemMeta subLm = e.getCurrentItem().getItemMeta(); if (subLm != null && subLm.getLore() != null) { for (String subLn : subLm.getLore()) { if (subLn.contains( "§7指令: §f")) { String cmd = subLn.replace( "§7指令: §f", "").trim(); if (cmd.isEmpty() || cmd.equals("/") || cmd.equals( "null")) { p.sendMessage( "§c指令为空"); return; } p.closeInventory(); if (cmd.startsWith("/")) cmd = cmd.substring(1); Bukkit.dispatchCommand( p, cmd); return; } } } } return; } // ===== [DEBUG] 标题追踪 ===== { String dt = title; if (dt.contains("钱包") || dt.contains("余额") || dt.contains("流水") || dt.contains("转账")) { getLogger().info( "[DEBUG-CLICK] " + "player=" + p.getName() + " title=[" + dt + "]" + " slot=" + slot + " codePoints=" + debugCodePoints(dt)); } } // ===== 二级菜单(.txt)—— 排除所有固定GUI ===== if (title.startsWith("§6§l") && !title.equals(GUIManager.T_MAIN) && !title.equals(GUIManager.T_ADMIN) && !title.equals(GUIManager.T_MY_INFO) && !title.equals(GUIManager.T_INVITE) && !title.equals( GUIManager.T_TASK_CENTER) && !title.equals( GUIManager.T_GIFT_STAGES) && !title.equals("§6§l任务面板") && !title.equals("§6§l垃圾回收站") && !title.equals("§6§l我的钱包") && !title.equals("§6§l余额操作")) { e.setCancelled(true); if (slot == 49) { gui.openMain(p); return; } if (slot < 45 && slot < e.getInventory() .getSize() && e.getCurrentItem() != null) { ItemMeta subLm = e.getCurrentItem() .getItemMeta(); if (subLm != null && subLm.getLore() != null) { for (String subLn : subLm.getLore()) { if (subLn.contains( "§7指令: §f")) { String cmd = subLn .replace("§7指令: §f", "") .trim(); if (cmd.isEmpty() || cmd.equals("/") || cmd.equals("null")) { p.sendMessage("§c指令为空"); return; } p.closeInventory(); if (cmd.startsWith("/")) { cmd = cmd.substring(1); } Bukkit.dispatchCommand(p, cmd); return; } } } } return; } // ===== 我的钱包(唯一handler) ===== if (title.equals("§6§l我的钱包")) { e.setCancelled(true); if (slot == 22) { gui.openMain(p); } else if (slot == 12) { openWalletTransactions(p, 7); } else if (slot == 13) { openWalletTransactions(p, 14); } else if (slot == 14) { openWalletTransfers(p); } return; } // ===== 流水/转账记录(唯一handler) ===== if (title.startsWith("§e§l流水记录") || title.startsWith("§e§l转账记录")) { e.setCancelled(true); if (slot == 53) { openMyWallet(p); } return; } // ===== 余额操作(唯一handler) ===== if (title.equals("§d§l余额操作")) { e.setCancelled(true); if (slot == 49) { gui.openMain(p); return; } if (slot >= 0 && slot < e.getInventory().getSize()) { ItemStack item = e.getInventory() .getItem(slot); if (item == null || !item.hasItemMeta() || !item.getItemMeta() .hasDisplayName()) { return; } // 在 slot == 49 的 return 之后、读取 item 之前 // ItemStack item = e.getInventory().getItem(slot); if (item == null) return; Material mat = item.getType(); // 以下是常见的装饰性玻璃板材质,按需扩展 if (mat.name().contains("STAINED_GLASS") || mat.name().contains("GLASS_PANE") || mat == Material.LIGHT_BLUE_STAINED_GLASS_PANE || mat == Material.GRAY_STAINED_GLASS_PANE // ... 其他装饰物 || mat == Material.BLACK_STAINED_GLASS_PANE) { e.setCancelled(true); return; // 装饰物品,不处理 } String targetName = item.getItemMeta() .getDisplayName() .replace("§e", "") .replace("§a", ""); if (targetName.equals(p.getName())) { p.sendMessage("§c不能操作自己"); return; } if (e.isLeftClick()) { balanceTarget = targetName; balanceGiveMode = true; p.closeInventory(); p.sendMessage( "§e请输入金额和理由"); p.sendMessage( "§7例: +100 给款 / -100 扣款"); } else if (e.isRightClick()) { if (this.getCDK() != null) { this.getCDK().requestInput( p, "bond", targetName); } p.closeInventory(); p.sendMessage( "§e【债券操作】对 §a" + targetName + " §e进行债券操作"); p.sendMessage( "§e输入 +金额 给债券," + " -金额 扣债券"); p.sendMessage( "§7可附加理由: §a+100 " + "活动奖励 §7不填默认:" + "管理员手动调整"); } } return; } // ===== 菜单管理 ===== if (title.equals("§c§l菜单管理")) { e.setCancelled(true); if (slot == 26) { gui.openMain(p); } else if (slot == 22) { gui.openEditor(p, -1); } else if (slot >= 0 && slot < 9) { List items = getMenu().getItems(); if (slot < items.size()) { if (e.isLeftClick()) { gui.openEditor(p, slot); } else if (e.isRightClick()) { MenuManager.MenuItem mi = items.get(slot); if ("OP".equals(mi.permType)) { mi.permType = "玩家"; } else { mi.permType = "OP"; } gui.openMenuManager(p); p.sendMessage( "§a" + mi.title + " §7权限切换为: §e" + mi.permType); } } } return; } // ===== 全局 slot 检查(放后面) ===== if (e.getClickedInventory() == null) return; if (e.getClickedInventory() != e.getView().getTopInventory()) return; if (slot < 0 || slot >= e.getInventory() .getSize()) return; if (e.getCurrentItem() == null) return; // ==================== 群系传送(新) ==================== if (title.equals(GUIManager.T_BIOME)) { e.setCancelled(true); if (slot == 49) { gui.openMain(p); return; } if (slot < 12) { String[] cats = { "海洋", "平原", "森林", "沙漠", "恶地", "雪原", "沼泽", "河流", "地下", "蘑菇岛", "下界", "末地" }; p.closeInventory(); gui.teleportToBiome(p, cats[slot]); } return; } // ===== 编辑器 ===== if (title.equals(GUIManager.T_EDITOR)) { e.setCancelled(true); if (slot >= 27) return; List el = getMenu().getItems(); int ei = el.size() - 1; if (ei < 0) return; MenuManager.MenuItem em = el.get(ei); switch (slot) { case 10: getMenu().editTitle(p, ei); break; case 11: getMenu().editCommand(p, ei); break; case 20: em.permType = "玩家".equals(em.permType) ? "OP" : "玩家"; gui.openEditor(p, ei); break; // 编辑器 case 22(保存) case 22: if (getMenu() != null) { MenuManager.MenuItem last = getMenu().getItems().get(ei); if (last.command == null || last.command.trim().isEmpty() || last.command.trim() .equals("/")) { p.sendMessage( "§c指令不能为空,请先填写指令"); gui.openEditor(p, ei); return; } getMenu().saveMenu(); p.closeInventory(); p.sendMessage("§a已保存!"); gui.openMain(p); } break; case 24: getMenu().loadMenu(); // ★ 如果最后一项是空指令的新项,删掉 ★ List cancelItems = getMenu().getItems(); if (!cancelItems.isEmpty()) { MenuManager.MenuItem last = cancelItems .get(cancelItems.size() - 1); if ((last.command == null || last.command.trim().isEmpty() || last.command.trim().equals("/")) && "新菜单项" .equals(last.title)) { cancelItems.remove( cancelItems.size() - 1); } } p.closeInventory(); p.sendMessage("§c已取消"); gui.openMain(p); break; } } // ==================== 主菜单(新布局) ==================== // ==================== 主菜单(新布局) ==================== if (title.equals(GUIManager.T_MAIN)) { e.setCancelled(true); // ★ 统一:所有自定义菜单项通过 lore 识别 ★ if (e.getCurrentItem() != null) { ItemMeta meta = e.getCurrentItem() .getItemMeta(); if (meta != null && meta.getLore() != null) { for (String ln : meta.getLore()) { if (ln.contains("§7指令: §f")) { String cmd = ln.replace( "§7指令: §f", "").trim(); if (cmd.isEmpty() || cmd.equals("/") || cmd.equals("null")) { p.sendMessage("§c指令为空"); return; } p.closeInventory(); if (cmd.startsWith("/")) { cmd = cmd.substring(1); } if (cmd.endsWith(".txt")) { gui.openSubMenu(p, cmd.substring(1)); } else { Bukkit.dispatchCommand(p, cmd); } return; } } } } // 固定按钮 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.openBiomeMenu(p); } else if (slot == 19) { ticket.openMain(p); } else if (slot == 20) { openMyWallet(p); } else if (slot == 21) { dailySignWithReward(p); gui.openMain(p); } else if (slot == 23) { gui.openTaskCenter(p); } else if (slot == 25) { if (p.isOp() || isAdmin(p)) { garbage.openRecycle(p); } } else if (slot == 29) { if (isAdmin(p)) { p.closeInventory(); chatInput.getState(p).type = ChatInputManager.InputType.ADMIN_AUTH; p.sendMessage("请输入管理密码:"); } } else if (slot == 31) { if (p.isOp()) { gui.openMenuManager(p); } } else if (slot == 34) { if (p.isOp() || isAdmin(p)) { gui.openBalanceOps(p); } } return; } // ==================== 新人礼包 ==================== if (title.equals(GUIManager.T_GIFT_STAGES)) { e.setCancelled(true); if (slot == 26) { gui.openTaskCenter(p); } else if (slot >= 10 && slot <= 18) { QuestTracker qt = getQuestTracker(); if (qt != null) { List quests = qt.getQuests("新人任务"); int idx = slot - 10; if (idx >= 0 && idx < quests.size()) { QuestTracker.QuestFile qf = quests.get(idx); String pn = p.getName(); if (qt.hasClaimed(pn, qf)) { p.sendMessage( "§c该阶段奖励已领取"); } else if (!qt.isStageCompleted( pn, qf)) { p.sendMessage("§c条件未满足"); } else { qt.claimRewards(p, qf); qt.markClaimed(pn, qf); gui.openGiftStages(p); playSuccessSound(p); } } } else { int stage = slot - 9; if (gift.canClaim(p, stage)) { gift.claimReward(p, stage); gui.openGiftStages(p); } else { p.sendMessage(config.msg( "gift_not_ready")); } } } return; } // ==================== 主线/支线任务 ==================== if (title.equals("§d§l主线任务") || title.equals("§d§l支线任务")) { e.setCancelled(true); if (slot == e.getInventory().getSize() - 1) { gui.openTaskCenter(p); } else if (questTracker != null) { String cat = title.contains("主线") ? "主线任务" : "支线任务"; List quests = questTracker.getQuests(cat); if (slot < quests.size()) { QuestTracker.QuestFile qf = quests.get(slot); if (!questTracker.isStageCompleted( p.getName(), qf)) { p.sendMessage("§c条件未满足"); } else if (questTracker.hasClaimed( p.getName(), qf)) { p.sendMessage("§c已领取过"); } else { questTracker.claimRewards(p, qf); questTracker.markClaimed( p.getName(), qf); gui.openTaskList(p, cat); playSuccessSound(p); } } } return; } // ==================== 我的信息 ==================== if (title.equals(GUIManager.T_MY_INFO)) { e.setCancelled(true); // ★ 只在图标槽(slot 4)调用,不干扰按钮 if (slot == 4 && gui.handleMyInfoIconClick(p, e)) { return; } 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) { if (questTracker != null) { Location loc = p.getLocation(); String result = questTracker .checkInAtPosition( p.getName(), p.getWorld() .getName(), loc.getX(), loc.getY(), loc.getZ()); p.sendMessage(result); playSuccessSound(p); } } else if (slot == 14) gui.openTaskList(p, "主线任务"); else if (slot == 16) gui.openTaskList(p, "支线任务"); else if (slot == 18) ticket.openMain(p); else if (slot == 20) { dailySignWithReward(p); gui.openMain(p); } 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 == 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) { 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; } // ==================== 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("§6§l任务面板")) { e.setCancelled(true); if (slot == 22) gui.openMain(p); else if (slot == 11) { Location loc = p.getLocation(); String result = questTracker.checkInAtPosition( p.getName(), p.getWorld().getName(), loc.getX(), loc.getY(), loc.getZ()); p.sendMessage(result); playSuccessSound(p); openTaskPanel(p); } else if (slot == 15) { gui.openGiftStages(p); } return; } // ==================== 账号请求面板 ==================== 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); } } } /* * 签到 + 盲盒经济奖励 */ public void dailySignWithReward(Player p) { String name = p.getName(); if (checkIn.isCheckedInToday(name)) { p.sendMessage( config.msg("checkin_already")); return; } // ===== 1. 执行签到(内部已加积分) ===== p.sendMessage(checkIn.checkIn(p)); invite.onInviteeCheckIn(name); // ===== 2. 获取连续天数 ===== int streak = checkIn.getStreak(name); double multiplier = 1.0; if (streak >= 21) { multiplier = 1.3; } else if (streak >= 14) { multiplier = 1.2; } else if (streak >= 7) { multiplier = 1.1; } // ===== 3. 经济奖励(不再重复加积分) ===== if (economy == null) { playSuccessSound(p); return; } // 固定金额模式 if ("fixed".equals( config.checkinRewardType) && config.checkinRewardFixed > 0) { final double reward = config.checkinRewardFixed; final double m = multiplier; p.sendMessage("§e§l[签到] §f" + "正在生成今日奖励..."); Bukkit.getScheduler() .runTaskLater(this, () -> { double rewardFinal = Math.round( reward * m * 100.0) / 100.0; double before = economy .getBalance(p); economy.depositPlayer( p, rewardFinal); double after = economy .getBalance(p); p.sendMessage( "§6§l[签到] §f" + "获得奖励: §a§l$" + String.format( "%.2f", rewardFinal)); p.sendMessage("§7余额: §e" + String.format( "%.2f", before) + " §7→ §a" + String.format( "%.2f", after)); playSuccessSound(p); }, 30L); return; } // 盲盒范围模式 if ("range".equals( config.checkinRewardType) && config.checkinRewardMin > 0 && config.checkinRewardMax > config.checkinRewardMin) { final double min = config.checkinRewardMin; final double max = config.checkinRewardMax; final double m = multiplier; getLogger().info( "[签到] 盲盒: min=" + min + " max=" + max + " multi=" + m); p.sendMessage("§e§l[签到盲盒] " + "§f正在生成今日惊喜..."); Bukkit.getScheduler() .runTaskLater(this, () -> { double rand = Math.random(); double reward = min + rand * (max - min); reward = Math.round( reward * m * 100.0) / 100.0; getLogger().info( "[签到] 盲盒结果: rand=" + String.format( "%.4f", rand) + " reward=" + reward); double before = economy .getBalance(p); economy.depositPlayer( p, reward); double after = economy .getBalance(p); p.sendMessage( "§6§l====== 签到盲盒 ======"); p.sendMessage( "§7开出: §a§l$" + String.format( "%.2f", reward)); p.sendMessage("§7余额: §e" + String.format( "%.2f", before) + " §7→ §a" + String.format( "%.2f", after)); p.sendMessage( "§6§l========================"); playSuccessSound(p); }, 40L); return; } // 无经济奖励 playSuccessSound(p); } public BondManager getBondManager() { return bondManager; } @EventHandler public void onVillagerTrade( org.bukkit.event.inventory.TradeSelectEvent e) { // TradeSelectEvent 在玩家点击交易时触发 if (!(e.getWhoClicked() instanceof Player)) return; Player p = (Player) e.getWhoClicked(); if (isFrozen(p) || questTracker == null) return; questTracker.onVillagerTrade(p.getName()); } @EventHandler public void onInvDrag(InventoryDragEvent e) { if (!(e.getWhoClicked() instanceof Player)) return; Player p = (Player) e.getWhoClicked(); if (isFrozen(p)) { e.setCancelled(true); return; } String t = e.getView().getTitle(); // 禁止T_MY_INFO拖拽,只允许点击操作 // 我的信息:允许拖入图标槽(31) if (GUIManager.T_MY_INFO.equals( e.getView().getTitle())) { boolean toIconSlot = false; for (int raw : e.getRawSlots()) { if (raw == 31) { toIconSlot = true; break; } } if (toIconSlot) { ItemStack dragged = e.getOldCursor().clone(); String b64 = gui.serializeItem(dragged); if (b64 != null) { db.saveMenuIcon(p.getName(), b64, dragged.getType().name()); } p.sendMessage("§a已保存菜单图标: " + dragged.getType().name()); // 刷新GUI显示 Bukkit.getScheduler() .runTaskLater(this, () -> gui.openMyInfo(p), 2L); } e.setCancelled(true); return; } // 回收站拖拽 if (t.equals("§6§l垃圾回收站")) { boolean toGUI = false; for (int rawSlot : e.getRawSlots()) { if (rawSlot < 54) { toGUI = true; break; } } if (toGUI) { ItemStack cur = e.getCursor(); if (cur != null && cur.getType() != Material.AIR && !isMenuSnowball(cur)) { e.setCancelled(true); p.getOpenInventory().setCursor(null); garbage.saveItem(cur.clone()); Bukkit.getScheduler() .runTaskLater(this, () -> { garbage.openRecyclePage( p, garbage .getRecyclePage( p .getName())); }, 2L); return; } } return; } //服务商工单系统 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); } } /** 验证玩家是否在主库中真实存在 */ public boolean isRealPlayer(String name) { if (name == null || name.isEmpty()) return false; try { Map user = getDb().getUser(name); return user != null && !user.isEmpty() && user.containsKey("player_name"); } catch (Exception e) { return false; } } // ===== Commands ===== @Override public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { String cmdName = cmd.getName().toLowerCase(); // 区域防护(必须在最前面) if (cmd.getName().equalsIgnoreCase("protect") || cmd.getName().equals("区域保护")) { if (areaProtection == null) { sender.sendMessage("§c区域防护未初始化"); return true; } if (args.length == 0) { sender.sendMessage("§a§l===== 区域防护 ====="); sender.sendMessage("§e/protect 工具 §7获取选区工具"); sender.sendMessage("§e/protect 创建 <区域名> §7创建区域"); sender.sendMessage("§e/protect 列表 §7查看所有区域"); sender.sendMessage("§e/protect 重载 §7重载配置"); sender.sendMessage("§e/protect add <玩家> §7全局加白"); sender.sendMessage("§e/protect add <区域> <玩家> §7区域加白"); sender.sendMessage("§e/protect remove <玩家> §7全局删白"); sender.sendMessage("§e/protect remove <区域> <玩家> §7区域删白"); sender.sendMessage("§e/protect additem <物品ID> §7全局加黑"); sender.sendMessage("§e/protect additem <区域> <物品ID> §7区域加黑"); sender.sendMessage("§e/protect removeitem <物品ID> §7全局删黑"); sender.sendMessage("§e/protect removeitem <区域> <物品ID> §7区域删黑"); sender.sendMessage("§e/protect list §7全局白名单"); sender.sendMessage("§e/protect list <区域> §7区域白名单"); sender.sendMessage("§e/protect listitem §7全局物品黑名单"); sender.sendMessage("§e/protect listitem <区域> §7区域物品黑名单"); sender.sendMessage("§e/protect expand [格数] §7扩建选区"); sender.sendMessage("§e/protect contraction [格数] §7收缩选区"); sender.sendMessage("§e/protect addname <区域> <名字> §7添加和平白名单生物"); sender.sendMessage("§e/protect removename <区域> <名字> §7移除和平白名单生物"); sender.sendMessage("§e/protect addwhite <玩家> §7添加模式排除玩家"); sender.sendMessage("§e/protect removewhite <玩家> §7移除模式排除玩家"); sender.sendMessage("§e/protect listname <区域> §7查看和平白名单"); sender.sendMessage("§e/protect listwhite §7查看模式排除名单"); return true; } // 其他子命令交给 areaProtection try { return areaProtection.handleCommand(sender, args); } catch (Exception e) { sender.sendMessage("§c执行出错: " + e.getMessage()); e.printStackTrace(); return true; } } if (cmd.getName().equalsIgnoreCase("shop") || cmd.getName().equals("商店")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可使用"); return true; } if (shopManager != null) { shopManager.openShopMain((Player) sender); } else { sender.sendMessage("§c商店系统未加载"); } return true; } String sub = (args.length > 0) ? args[0].toLowerCase() : ""; // ===== 在 onCommand 方法体内,最开头加: ===== if (cmd.getName().equalsIgnoreCase("sdf1_login") && args.length > 0 && args[0].equalsIgnoreCase("shop")) { if (shopManager == null) { sender.sendMessage("§c商店未加载"); return true; } return shopManager.handleCommand(sender, args); } // 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 (!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); // ★ 立即发邮件(踢人之前) String emailAddr = (String) db.getField(tgt, "email"); if (emailAddr != null && !emailAddr.isEmpty()) { // 获取最近登录信息 Object lastLoginObj = db.getField(tgt, "last_login_time"); long lastLogin = lastLoginObj != null ? ((Number) lastLoginObj).longValue() : 0; String lastIP = (String) db.getField(tgt, "last_ip"); if (lastIP == null || lastIP.isEmpty()) lastIP = "未知"; java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String loginTimeStr = lastLogin > 0 ? sdf.format(new java.util.Date(lastLogin)) : "未知时间"; final String to = emailAddr; final String fName = tgt; final String pwd = tempPwd; final String ipInfo = lastIP; final String timeInfo = loginTimeStr; final Main self = this; Bukkit.getScheduler().runTaskAsynchronously(this, () -> { // ★ 发送临时密码邮件(带登录信息) boolean sent = email.sendTempPassword( to, fName, pwd); // ★ 额外发送安全通知邮件 if (sent) { String body = "玩家 " + fName + ":\n\n" + "管理员已为您的账号设置了临时密码。\n\n" + "临时密码: " + pwd + "\n" + "过期时间: 5分钟内\n\n" + "最近登录记录:\n" + " 时间: " + timeInfo + "\n" + " IP地址: " + ipInfo + "\n\n" + "如果您本人未操作,请忽略此邮件。\n" + "如果您怀疑账号被盗,请立即修改密码。"; email.sendBody(to, "【安全通知】账号临时密码已设置", body); } Bukkit.getScheduler().runTask(self, () -> { if (sent) { sender.sendMessage("§a临时密码已发送至 " + tgt + " 的邮箱"); } }); }); } else { sender.sendMessage("§7该玩家未绑定邮箱,跳过邮件通知"); } // 踢人 Player tp = Bukkit.getPlayer(tgt); if (tp != null && tp.isOnline()) tp.kickPlayer("§c管理员已设置了临时密码,请用新密码登录"); sender.sendMessage("§a已为 " + tgt + " 设置临时密码"); return true; } if (label.equalsIgnoreCase("pvp")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } Player p = (Player) sender; return pvpManager.onCommand(p, args); } if (cmdName.equals("cypay")) { if (args.length > 0) { String target = args[0]; if (!getDb().userExists(target)) { sender.sendMessage("§c玩家 " + target + " 不存在于服务器"); return true; } } return cypayCommand.onCommand( sender, cmd, label, args); } // ===== /printer [玩家名] ===== if (label.equalsIgnoreCase("printer")) { if (bondPrinter == null) { sender.sendMessage("§cPrinter未初始化"); return true; } if (!sender.isOp() && !isAdmin(sender)) { sender.sendMessage("§c权限不足"); return true; } if (args.length == 0) { // 打印全服 sender.sendMessage( "§e正在导出全服流水..."); bondPrinter.printAll(sender); return true; } if (args.length == 1) { // 打印指定玩家 String target = args[0]; sender.sendMessage("§e正在导出 " + target + " 的流水..."); bondPrinter.printPlayer( target, sender); return true; } sender.sendMessage( "§7用法: /printer [玩家名]"); return true; } // ===== /chat 独立命令 ===== if (cmdName.equals("chat") || cmdName.equals("聊天")) { if (sender instanceof Player) { Player chatP = (Player) sender; if (!chatP.getScoreboardTags() .contains(config.adminTag)) { sender.sendMessage("§c权限不足"); return true; } } if (args.length < 1) { sender.sendMessage( "§e/chat "); return true; } String cs = args[0].toLowerCase(); if (cs.equals("reload")) { chatFilter.loadConfig(); sender.sendMessage("§a聊天配置已重载"); return true; } if (cs.equals("开") || cs.equals("on")) { chatFilter.setEnabled(true); chatFilter.saveConfig(); sender.sendMessage("§aURL过滤已开启"); return true; } if (cs.equals("关") || cs.equals("off")) { chatFilter.setEnabled(false); chatFilter.saveConfig(); sender.sendMessage("§cURL过滤已关闭"); return true; } if (cs.equals("add")) { if (args.length < 2) { sender.sendMessage("§e/chat add "); return true; } chatFilter.addUrl(args[1]); sender.sendMessage("§a已添加白名单: " + args[1]); return true; } if (cs.equals("remove")) { if (args.length < 2) { sender.sendMessage("§e/chat remove "); return true; } if (chatFilter.removeUrl(args[1])) sender.sendMessage("§c已移除: " + args[1]); else sender.sendMessage("§c未找到: " + args[1]); return true; } if (cs.equals("addplayer")) { if (args.length < 2) { sender.sendMessage("§e/chat addplayer <玩家>"); return true; } chatFilter.addWhitelistPlayer(args[1]); sender.sendMessage("§a白名单玩家: " + args[1]); return true; } if (cs.equals("takeplayer")) { if (args.length < 2) { sender.sendMessage("§e/chat takeplayer <玩家>"); return true; } if (chatFilter.removeWhitelistPlayer(args[1])) sender.sendMessage("§c已移除白名单: " + args[1]); else sender.sendMessage("§c未找到: " + args[1]); return true; } if (cs.equals("unmute")) { if (args.length < 2) { sender.sendMessage("§e/chat unmute <玩家>"); return true; } chatFilter.unmutePlayer(args[1]); sender.sendMessage("§a已解禁: " + args[1]); return true; } if (cs.equals("reset")) { if (args.length < 2) { sender.sendMessage("§e/chat reset <玩家>"); return true; } chatFilter.resetPlayer(args[1]); sender.sendMessage("§a已重置: " + args[1]); return true; } sender.sendMessage("§c未知子命令"); return true; } if (label.equalsIgnoreCase("sdf1_login") && args.length >= 2 && args[0].equalsIgnoreCase("give")) { if (!sender.isOp()) { sender.sendMessage("§c仅OP可用"); return true; } Player target = Bukkit.getPlayer(args[1]); if (target == null || !target.isOnline()) { sender.sendMessage( "§c玩家不在线: " + args[1]); return true; } giveMenuSnowball(target); sender.sendMessage("§a已发放菜单雪球给 " + target.getName()); target.sendMessage( "§a管理员已发放菜单雪球给你"); return true; } 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 (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; } 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); // [ADDED] 欢迎仪式 if (welcome != null && loggedIn.contains(p.getName())) { welcome.onRegister(p); } } catch (Exception e) { getLogger().severe( "[Sdf1_login] reg命令异常: " + e.getMessage()); e.printStackTrace(); p.sendMessage("§c注册出错,请联系管理员"); } 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())) { p.sendMessage("§c您已登录"); return true; } try { loginMgr.handleLogin(p, args); // [FIX] 手动登录后关闭飞行(仅一次) if (loggedIn.contains(p.getName()) && p.getAllowFlight()) { p.setAllowFlight(false); p.setFlying(false); } // [ADDED] 欢迎仪式 if (welcome != null && loggedIn.contains(p.getName())) { welcome.onLogin(p); } } catch (Exception e) { getLogger().severe( "[Sdf1_login] login命令异常: " + e.getMessage()); e.printStackTrace(); p.sendMessage("§c登录出错,请联系管理员"); } 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; } // /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; } // ===== del / confirm / cancel(最先处理) ===== if (sub.equals("del")) { if (!isAdmin(sender)) { sender.sendMessage("§c权限不足"); return true; } if (args.length < 2) { sender.sendMessage( "§e用法: /sdf1_login del <玩家>"); return true; } String target = args[1]; if (!db.userExists(target)) { sender.sendMessage( "§c玩家 " + target + " 不存在"); return true; } if (pendingDeleteTask != null) { pendingDeleteTask.cancel(); String old = pendingDeleteName; pendingDeleteTask = null; pendingDeleteName = null; getLogger().info( "[Sdf1_login] 取消旧待删除: " + old); } pendingDeleteName = target; pendingDeleteTask = new BukkitRunnable() { @Override public void run() { String name = pendingDeleteName; pendingDeleteName = null; pendingDeleteTask = null; if (name == null) return; db.deleteUser(name); getLogger().info( "[Sdf1_login] 已删除: " + name); Player tp = Bukkit.getPlayer(name); if (tp != null && tp.isOnline()) tp.kickPlayer( "§c账号已被管理员删除"); } }; pendingDeleteTask.runTaskLater(this, 1200L); sender.sendMessage("§e§l[删除确认] 目标: §c" + target); sender.sendMessage("§760秒后执行," + "§a/sdf1_login confirm §7确认," + "§c/sdf1_login cancel §7取消"); getLogger().warning("[Sdf1_login] 待删除: " + target + " by " + sender.getName()); return true; } if (sub.equals("confirm")) { if (!isAdmin(sender)) { sender.sendMessage("§c权限不足"); return true; } if (pendingDeleteName == null || pendingDeleteTask == null) { sender.sendMessage( "§c当前没有待删除的账号"); return true; } pendingDeleteTask.cancel(); String name = pendingDeleteName; pendingDeleteName = null; pendingDeleteTask = null; db.deleteUser(name); sender.sendMessage("§a已立即删除: " + name); Player tp = Bukkit.getPlayer(name); if (tp != null && tp.isOnline()) tp.kickPlayer("§c账号已被管理员删除"); return true; } if (sub.equals("cancel")) { if (!isAdmin(sender)) { sender.sendMessage("§c权限不足"); return true; } if (pendingDeleteName == null || pendingDeleteTask == null) { sender.sendMessage( "§c当前没有待删除的账号"); return true; } pendingDeleteTask.cancel(); String name = pendingDeleteName; pendingDeleteName = null; pendingDeleteTask = null; sender.sendMessage("§a已取消删除: " + name); return true; } // ===== stop ===== if (sub.equals("stop")) { if (!(sender instanceof Player)) { if (args.length >= 2) { db.clearTempPassword(args[1]); sender.sendMessage("§a已报废 " + args[1] + " 的临时密码"); } else { db.clearAllTempPasswords(); sender.sendMessage("§a已报废所有临时密码"); } 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(); if (radio != null) radio.reload(); if (getMenu() != null) { getMenu().loadMenu(); } if (shopManager != null) { shopManager.loadCategories(); } if (shopManager != null) shopManager.loadCategories(); if (areaProtection != null) { areaProtection.clearAllPlayerAreas(); } if (questTracker != null) { questTracker.reload(); } sender.sendMessage("§a§l配置已重载!"); return true; } // ===== back ===== if (sub.equals("back")) { if (!isAdmin(sender)) { sender.sendMessage("§c权限不足"); return true; } // ... 保持原有back代码不变 ... if (args.length < 2) { sender.sendMessage("§e用法: /sdf1_login back <玩家> [#编号]"); return true; } // 原有back逻辑 String backTarget = args[1]; if (args.length >= 3 && args[2].startsWith("#")) { 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 { 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恢复"); } return true; } // ===== pw / password ===== if (sub.equals("pw") || sub.equals("password")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } Player p2 = (Player) sender; if (args.length >= 3) { String oldPwd = args[1]; String newPwd = args[2]; if (!PasswordUtils.validate(newPwd)) { p2.sendMessage(config.msg("password_format_error")); return true; } String salt = (String) db.getField( p2.getName(), "password_salt"); String hash = PasswordUtils.hash(oldPwd, salt); boolean mainOk = db.checkPassword( p2.getName(), hash); boolean tempOk = db.checkPasswordOrTemp( p2.getName(), hash); if (!mainOk && !tempOk) { p2.sendMessage(config.msg("password_wrong")); return true; } String oldHash = (String) db.getField( p2.getName(), "password_hash"); String newSalt = PasswordUtils.generateSalt(); String newHash = PasswordUtils.hash(newPwd, newSalt); if (newHash.equals(oldHash)) { p2.sendMessage(config.msg("password_same")); return true; } recordPasswordChange(p2.getName(), oldHash, salt); db.setField(p2.getName(), "password_hash", newHash); db.setField(p2.getName(), "password_salt", newSalt); if (!mainOk && tempOk) db.clearTempPassword(p2.getName()); // pw 命令改密码成功后 needsPasswordChange.remove(p2.getName()); // 清除临时密码 if (!mainOk && tempOk) db.clearTempPassword(p2.getName()); return true; } if (args.length == 2) { String oldPwd = args[1]; String salt = (String) db.getField( p2.getName(), "password_salt"); String hash = PasswordUtils.hash(oldPwd, salt); boolean mainOk = db.checkPassword( p2.getName(), hash); boolean tempOk = db.checkPasswordOrTemp( p2.getName(), hash); if (!mainOk && !tempOk) { p2.sendMessage(config.msg("password_wrong")); return true; } chatInput.getState(p2).type = ChatInputManager.InputType.CHANGE_PWD_STEP2; chatInput.getState(p2).ticketTitle = mainOk ? "main" : "temp"; p2.sendMessage("§e请输入新密码:"); return true; } chatInput.getState(p2).type = ChatInputManager.InputType.CHANGE_PWD_STEP1; p2.sendMessage("§e请输入当前密码:"); return true; } // ===== email ===== if (sub.equals("email")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } Player p2 = (Player) sender; if (args.length >= 2) { String emailAddr = args[1]; if (!emailAddr.contains("@") || !emailAddr.contains(".")) { p2.sendMessage("§c邮箱格式不正确"); return true; } String oldEmail = (String) db.getField( p2.getName(), "email"); if (oldEmail != null && !oldEmail.isEmpty()) { chatInput.getState(p2).type = ChatInputManager.InputType.SET_EMAIL_VERIFY; chatInput.getState(p2).targetPlayer = emailAddr; chatInput.getState(p2).ticketTitle = "need_pwd"; p2.sendMessage("§e您已绑定邮箱: " + oldEmail); p2.sendMessage("§e请输入登录密码以确认修改:"); } else { String code = String.valueOf( (int) (Math.random() * 900000 + 100000)); chatInput.getState(p2).type = ChatInputManager.InputType.SET_EMAIL_VERIFY; chatInput.getState(p2).targetPlayer = emailAddr; chatInput.getState(p2).ticketTitle = code; final String to = emailAddr; final String c = code; final Player fp = p2; Bukkit.getScheduler() .runTaskAsynchronously(this, () -> { boolean sent = email.sendVerifyCode(to, c); Bukkit.getScheduler() .runTask(this, () -> { if (sent) { fp.sendMessage("§a验证码已发送到 " + to); fp.sendMessage("§e输入6位验证码(0取消):"); } else { fp.sendMessage("§c发送失败"); } }); }); } } else { chatInput.getState(p2).type = ChatInputManager.InputType.SET_EMAIL; p2.sendMessage("§e请输入邮箱:"); } return true; } // ===== sign ===== if (sub.equals("sign")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } dailySignWithReward((Player) sender); return true; } // ===== reset ===== if (sub.equals("reset")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } loginMgr.handleReset((Player) sender); return true; } // ===== undo(密码撤销) ===== if (sub.equals("undo")) { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } Player p2 = (Player) sender; PwdRollback rb = pwdRollback.get(p2.getName()); if (rb == null) { p2.sendMessage("§c无可撤销记录"); return true; } if (System.currentTimeMillis() - rb.time > 600000L) { pwdRollback.remove(p2.getName()); p2.sendMessage("§c撤销已过期"); return true; } db.setField(p2.getName(), "password_hash", rb.hash); db.setField(p2.getName(), "password_salt", rb.salt); pwdRollback.remove(p2.getName()); p2.sendMessage("§a密码已撤销!"); return true; } // radio子命令(控制台+玩家) // sdf1_login radio → 停止全服广播 // sdf1_login radio reload → 重载 // sdf1_login radio <歌名> → 全服播放 // sdf1_login radio <歌名> <玩家名> → 指定玩家播放 // sdf1_login radio <歌名> login → 推送给登录中玩家 if (sub.equals("radio")) { if (args.length < 2) { // 无参数 = 停止 radio.stopMainRadio(); sender.sendMessage("§c全服广播已停止"); return true; } String arg1 = args[1]; // reload if (arg1.equalsIgnoreCase("reload")) { if (!isAdmin(sender)) { sender.sendMessage("§c权限不足"); return true; } radio.reload(); sender.sendMessage("§aRadio已重载"); return true; } // radio <歌名> → 全服播放 // radio <歌名> <玩家名> → 指定玩家 // radio <歌名> login → 登录阶段播放 String songName = arg1; if (args.length >= 3) { String target = args[2]; if (target.equalsIgnoreCase("login")) { if (radio.hasSong(songName)) { radio.playToLoginPlayers(songName); sender.sendMessage( "§a已推送 §e" + songName + " §a给登录中玩家"); } else { sender.sendMessage( "§c未找到歌曲: " + songName); return true; } } else { Player targetP = Bukkit.getPlayer(target); if (targetP != null && targetP.isOnline()) { if (radio.hasSong(songName)) { radio.playToPlayer( targetP, songName); sender.sendMessage( "§a已向 §e" + target + " §a播放 §e" + songName); targetP.sendMessage( "§6§l[广播] §f正在播放: §e" + songName); } else { sender.sendMessage( "§c未找到歌曲: " + songName); } } else { sender.sendMessage( "§c玩家不在线: " + target); } return true; } } else { if (radio.hasSong(songName)) { radio.playToAll(songName); sender.sendMessage( "§a全服播放: §e" + songName); } else { sender.sendMessage( "§c未找到歌曲: " + songName); } return true; } // 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()); } 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; } // 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; } // 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; } return true; } /** * 供SDF1反射调用的CDK兑换 * 返回: "success:金额:余额前:余额后" * 或 "fail:原因" */ public String redeemBondForExternal( String playerName, String code) { if (cdkManager == null) return "fail:CDK未初始化"; return cdkManager.redeem(code, playerName); } // 旧的(可能缺失或拼写错误) public SalesStatsManager getSalesStats() { return salesStats; } /** * 是否是垃圾站展示物品(带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 开/关 §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", "radio", "back", "shop")); } else { list.addAll(Arrays.asList( "pw", "email", "sign", "reset", "undo", "ticket", "oa", "stop")); } } if (cmd.getName().equalsIgnoreCase("protect") || cmd.getName().equals("区域保护")) { String sub = args.length > 0 ? args[0].toLowerCase() : ""; // ===== 第一层:子命令 ===== if (args.length == 1) { List opts = Arrays.asList( "工具", "创建", "列表", "重载", "删除", "add", "remove", "additem", "removeitem", "on", "off", "tempon", "expand", "contraction", "list", "listitem", "addname", "removename", "listname", "addwhite", "removewhite", "listwhite"); return filterTab(opts, args[0]); } // ===== 第二层 ===== if (args.length == 2) { // add/remove: 区域名 + global + 在线玩家 if (sub.equals("add") || sub.equals("remove")) { List all = new ArrayList<>(); all.add("global"); all.addAll(areaProtection.getAreaNames()); for (Player pl : Bukkit.getOnlinePlayers()) all.add(pl.getName()); return filterTab(all, args[1]); } // additem/removeitem: 区域名 + global if (sub.equals("additem") || sub.equals("removeitem")) { List all = new ArrayList<>(); all.add("global"); all.addAll(areaProtection.getAreaNames()); return filterTab(all, args[1]); } // list/listitem: 区域名 if (sub.equals("list") || sub.equals("listitem")) { return filterTab( new ArrayList<>(areaProtection.getAreaNames()), args[1]); } // expand/contraction: 数字 if (sub.equals("expand") || sub.equals("contraction")) { return Arrays.asList("1", "3", "5", "10", "20", "50"); } // 创建/删除: 区域名 if (sub.equals("创建") || sub.equals("删除")) { return filterTab( new ArrayList<>(areaProtection.getAreaNames()), args[1]); } // addname/removename/listname: 区域名 if (sub.equals("addname") || sub.equals("removename") || sub.equals("listname")) { return filterTab( new ArrayList<>(areaProtection.getAreaNames()), args[1]); } // addwhite/removewhite/listwhite: 区域名 if (sub.equals("addwhite") || sub.equals("removewhite") || sub.equals("listwhite")) { return filterTab( new ArrayList<>(areaProtection.getAreaNames()), args[1]); } } // ===== 第三层 ===== if (args.length == 3) { // add/remove: 第三层是玩家名 if (sub.equals("add") || sub.equals("remove")) { List players = new ArrayList<>(); for (Player pl : Bukkit.getOnlinePlayers()) players.add(pl.getName()); return filterTab(players, args[2]); } // additem/removeitem: 第三层是物品ID if (sub.equals("additem") || sub.equals("removeitem")) { List items = Arrays.asList( "TNT", "BROWN_MUSHROOM", "RED_MUSHROOM", "MUSHROOM_STEW", "POISONOUS_POTATO", "SPIDER_EYE", "POTION", "SPLASH_POTION", "OMINOUS_BOTTLE", "ENDER_PEARL", "BOW", "CROSSBOW", "ARROW", "FLINT_AND_STEEL"); return filterTab(items, args[2]); } // addname/removename: 第三层是生物名字(自由输入) if (sub.equals("addname") || sub.equals("removename")) { return null; } // addwhite/removewhite: 第三层是玩家名 if (sub.equals("addwhite") || sub.equals("removewhite")) { List players = new ArrayList<>(); for (Player pl : Bukkit.getOnlinePlayers()) players.add(pl.getName()); return filterTab(players, args[2]); } } return null; } if (cmd.getName().equalsIgnoreCase("chat") && args.length == 1 && isAdmin(sender)) { list.addAll(Arrays.asList( "reload", "add", "remove", "addplayer", "takeplayer", "unmute", "reset")); } if (cmd.getName().equalsIgnoreCase("cypay")) { return cypayCommand.onTabComplete(sender, cmd, label, args); } return list; } }