diff --git a/src/main/java/org/xgqy/survival/Survival.java b/src/main/java/org/xgqy/survival/Survival.java index 7277202..00305b0 100644 --- a/src/main/java/org/xgqy/survival/Survival.java +++ b/src/main/java/org/xgqy/survival/Survival.java @@ -33,6 +33,7 @@ import org.xgqy.survival.command.TpFinCommandExecutor; import org.xgqy.survival.command.msgCommandExecutor; import org.xgqy.survival.event.ChatEvent; import org.xgqy.survival.event.ChooseTagEvent; +import org.xgqy.survival.event.ForceSurvival; import org.xgqy.survival.event.JoinEvent; import org.xgqy.survival.event.LandEvent; import org.xgqy.survival.event.LoginEvent; @@ -176,6 +177,7 @@ public final class Survival extends JavaPlugin { msg.add(ChatColor.GREEN + "赞助我们? https://starpavilion.xyz/sponsor.html"); msg.add(ChatColor.RED+"封禁者会在此处公示: https://starpavilion.xyz/bans.html"); msg.add(ChatColor.RED + "信用分过低将会导致你的账号受限甚至永久封禁!"); + msg.add(ChatColor.GREEN+"小小赞助一下吧~"); msg.add(ChatColor.GREEN + "信用分被扣除会提醒,如果您的信用分被莫名扣除,请及时到QQ群 717903781 申诉。"); new BukkitRunnable() { @@ -220,14 +222,21 @@ public final class Survival extends JavaPlugin { // 根据剩余信用分执行处罚 if (newPoint > 80) { plr.sendMessage(ChatColor.RED + "|采取处罚: " + ChatColor.YELLOW + "无"); - } else if (newPoint <= 0) { + plr.getScoreboard().getObjective("check").getScore(plr).setScore(0); + } else if(newPoint <= 0){ plr.kickPlayer(ChatColor.RED + "您好! 您由于在 " + formattedTime + " 信用分小于 0 ,我们决定对你的账号 " + plr.getName() + " 采取\n" + ChatColor.RED + ChatColor.BOLD + "永久封禁\n" + ChatColor.RED + "措施,如果您想解封您的账号,请到QQ群 717903781 申诉\n\n" + ChatColor.RED + "如果您对本次处罚不满,请采取以下措施:\n" + ChatColor.YELLOW + "1.如果您对自己的行为" + ChatColor.BOLD + " 问心无愧 " + ChatColor.YELLOW + "请立即向QQ 2213866559 发送解封申请\n" + ChatColor.YELLOW + "2.如果您属实有违规行为,请" + ChatColor.BOLD + "手写" + ChatColor.YELLOW + "一篇 " + ChatColor.BOLD + "100字以上且AIGC合格" + ChatColor.YELLOW + " 的检讨发送到申诉QQ群,态度诚恳我们将会对你进行解封"); - } else if (newPoint <= 20) { + }else if (newPoint <= 10) { plr.sendMessage(ChatColor.RED + "|采取处罚: " + ChatColor.YELLOW + "限制行为及发言,列入监管名单,限制功能使用"); - } else if (newPoint <= 50) { + plr.getScoreboard().getObjective("check").getScore(plr).setScore(4); + } else if (newPoint <= 30) { + plr.sendMessage(ChatColor.RED + "|采取处罚: " + ChatColor.YELLOW + "限制行为及发言,列入监管名单,限制功能使用"); + plr.getScoreboard().getObjective("check").getScore(plr).setScore(3); + } else if (newPoint <= 60) { plr.sendMessage(ChatColor.RED + "|采取处罚: " + ChatColor.YELLOW + "限制行为及发言,限制功能使用"); + plr.getScoreboard().getObjective("check").getScore(plr).setScore(2); } else if (newPoint <= 80) { plr.sendMessage(ChatColor.RED + "|采取处罚: " + ChatColor.YELLOW + "监管行为及发言,签到将奖励转换为信用分"); + plr.getScoreboard().getObjective("check").getScore(plr).setScore(1); } plr.sendMessage(ChatColor.RED + "|为了维护游戏环境,请不要违规!"); plr.sendMessage(ChatColor.RED + "----------------------------"); @@ -253,6 +262,7 @@ public final class Survival extends JavaPlugin { Bukkit.getPluginManager().registerEvents(new ChooseTagEvent(this), this); Bukkit.getPluginManager().registerEvents(new LoginEvent(this), this); Bukkit.getPluginManager().registerEvents(new LandEvent(this),this); + Bukkit.getPluginManager().registerEvents(new ForceSurvival(),this); // 注册命令执行器 getCommand("report").setExecutor(new ReportCommandExecutor(this)); @@ -274,7 +284,7 @@ public final class Survival extends JavaPlugin { getCommand("msg").setExecutor(new msgCommandExecutor(this)); getCommand("friend").setExecutor(new FriendCommandExecutor(this)); getCommand("point").setExecutor(new PointCommandExecutor(this)); - getCommand("selfkill").setExecutor(new KillCommandExecutor(this)); + getCommand("home").setExecutor(new KillCommandExecutor(this)); getCommand("land").setExecutor(new LandCommandExecutor(this)); getCommand("qd").setExecutor(new LandCommandExecutor(this)); getCommand("notice").setExecutor(new NoticeCommandExecutor(this)); @@ -285,7 +295,7 @@ public final class Survival extends JavaPlugin { // 所有需要补全的命令列表 List commands = Arrays.asList( "handle", "tpacc", "friend", "party", "settag", "teleport", - "point", "land", "pvp", "help", "tag", "hub", "selfkill", + "point", "land", "pvp", "help", "tag", "hub", "home", "qd", "reg", "login", "msg", "inventory", "notice" ); @@ -309,7 +319,6 @@ public final class Survival extends JavaPlugin { try { FriendCommandExecutor executor = (FriendCommandExecutor) this.getCommand("friend").getExecutor(); if (executor != null) { - executor.saveFriendData(); getLogger().info("好友数据已保存!"); } } catch (Exception e) { diff --git a/src/main/java/org/xgqy/survival/command/CommandTabExecutor.java b/src/main/java/org/xgqy/survival/command/CommandTabExecutor.java index a917b2b..f90bfa2 100644 --- a/src/main/java/org/xgqy/survival/command/CommandTabExecutor.java +++ b/src/main/java/org/xgqy/survival/command/CommandTabExecutor.java @@ -77,7 +77,7 @@ public class CommandTabExecutor implements TabCompleter { case "help": case "tag": case "hub": - case "selfkill": + case "home": case "qd": case "reg": case "login": diff --git a/src/main/java/org/xgqy/survival/command/FriendCommandExecutor.java b/src/main/java/org/xgqy/survival/command/FriendCommandExecutor.java index b2f8b2a..c10dda1 100644 --- a/src/main/java/org/xgqy/survival/command/FriendCommandExecutor.java +++ b/src/main/java/org/xgqy/survival/command/FriendCommandExecutor.java @@ -1,9 +1,8 @@ package org.xgqy.survival.command; -import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.ClickEvent; -import net.md_5.bungee.api.chat.ComponentBuilder; -import org.bukkit.Bukkit; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; import org.bukkit.ChatColor; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; @@ -11,476 +10,577 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; import org.jetbrains.annotations.NotNull; -import org.xgqy.survival.FriendData; import org.xgqy.survival.Survival; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; +import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; public class FriendCommandExecutor implements CommandExecutor { - + // 命令前缀 + private final String prefix = ChatColor.LIGHT_PURPLE + "好友系统 " + ChatColor.WHITE + "| "; + // 好友关系存储(双向绑定) + private Map> friends; + // 待处理好友请求(key:接收者,value:请求列表) + private Map> waitingRequests; + // Gson实例(JSON序列化) + private final Gson gson = new GsonBuilder().setPrettyPrinting().create(); + // 数据文件 + private final File dataFile; + // 插件实例 private final Survival plugin; - // 好友请求超时任务缓存(使用字符串键确保精准匹配) - private final Map timeoutTasks = new ConcurrentHashMap<>(); - // 待确认的删除请求 - private final Map pendingDelete = new ConcurrentHashMap<>(); - // 删除确认超时任务缓存 - private final Map deleteTimeoutTasks = new ConcurrentHashMap<>(); - // 持久化文件 - private final File friendDataFile; + // 请求有效期(5分钟 = 300000毫秒) + private static final long REQUEST_EXPIRE_TIME = 5 * 60 * 1000; public FriendCommandExecutor(Survival plugin) { this.plugin = plugin; - this.friendDataFile = new File(plugin.getDataFolder(), "friend_data.ser"); - loadFriendData(); // 加载数据 + // 初始化数据文件 + this.dataFile = new File(plugin.getDataFolder(), "friend.json"); + // 确保数据文件夹存在 + if (!plugin.getDataFolder().exists()) { + plugin.getDataFolder().mkdirs(); + } + // 加载历史数据 + loadData(); + // 启动请求过期清理任务 + startExpireCheckTask(); } - // 提供外部访问任务的方法(用于插件禁用时清理) - public Map getTimeoutTasks() { - return timeoutTasks; + /** + * 好友请求实体(存储发送者UUID和时间戳,支持序列化) + */ + private static class FriendRequest { + private final String senderUuid; + private final long timestamp; + + public FriendRequest(Player sender) { + this.senderUuid = sender.getUniqueId().toString(); + this.timestamp = System.currentTimeMillis(); + } + + // Getter + public String getSenderUuid() { + return senderUuid; + } + + public long getTimestamp() { + return timestamp; + } } - public Map getDeleteTimeoutTasks() { - return deleteTimeoutTasks; + /** + * JSON序列化辅助类 + */ + private static class FriendData { + private Map> friends; + private Map> waitingRequests; + + // 必须保留默认构造函数(Gson反序列化用) + public FriendData() {} + + public FriendData(Map> friends, Map> waitingRequests) { + this.friends = friends; + this.waitingRequests = waitingRequests; + } + + // Getter & Setter + public Map> getFriends() { + return friends; + } + + public void setFriends(Map> friends) { + this.friends = friends; + } + + public Map> getWaitingRequests() { + return waitingRequests; + } + + public void setWaitingRequests(Map> waitingRequests) { + this.waitingRequests = waitingRequests; + } } @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (!(sender instanceof Player player)) { - sender.sendMessage(ChatColor.RED + "❌ 无法对非玩家类使用该命令!"); + // 仅玩家可使用 + if (!(sender instanceof Player)) { + sender.sendMessage(prefix + ChatColor.RED + "仅玩家能使用该命令!"); return true; } - // 处理隐式命令(点击按钮触发) - if (args.length >= 3 && args[0].equals("confirmDelete")) { - handleConfirmDelete(player, args[1], args[2]); - return true; - } - if (args.length >= 2 && args[0].equals("prepareDelete")) { - handlePrepareDeleteCommand(player, args); + Player player = (Player) sender; + + // 无参数时显示帮助 + if (args.length == 0) { + showHelp(player); return true; } - // 基础参数校验 - if (args.length < 1) { - sendUsage(player); - return true; + // 处理子命令 + switch (args[0].toLowerCase()) { + case "add": + handleAdd(player, args); + break; + case "remove": + handleRemove(player, args); + break; + case "list": + handleList(player); + break; + case "accept": + handleAccept(player, args); + break; + case "deny": + handleDeny(player, args); + break; + default: + player.sendMessage(prefix + ChatColor.RED + "未知命令!输入 /friend 查看帮助"); } - - String subCommand = args[0].toLowerCase(); - if (subCommand.equals("list")) { - handleList(player); - return true; - } - - if (args.length < 2) { - sendUsage(player); - return true; - } - - String targetName = args[1]; - Player target = Bukkit.getPlayerExact(targetName); // 精准匹配玩家名(修复) - - // 目标玩家有效性校验 - if (!subCommand.equals("remove") && (target == null || !target.isOnline())) { - player.sendMessage(ChatColor.RED + "❌ 目标玩家不存在或未在线!"); - return true; - } - - // 分发子命令 - switch (subCommand) { - case "add" -> handleAdd(player, target); - case "remove" -> handleRemove(player, targetName); - case "allow" -> handleAllow(player, target); - case "deny" -> handleDeny(player, target); - default -> sendUsage(player); - } - return true; } /** - * 加载历史好友数据 + * 处理添加好友命令 */ - public void loadFriendData() { - if (!friendDataFile.exists()) { - plugin.getLogger().info("好友数据文件不存在,初始化空数据!"); + private void handleAdd(Player sender, String[] args) { + // 参数校验 + if (args.length != 2) { + sender.sendMessage(prefix + ChatColor.RED + "用法错误!正确格式:/friend add <玩家名>"); return; } - try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(friendDataFile))) { - FriendData data = (FriendData) ois.readObject(); - Map> uuidFriends = data.getFriends(); - Map> uuidWaiting = data.getWaiting(); + String targetName = args[1]; + Player target = plugin.getServer().getPlayer(targetName); - // 转换好友数据:UUID -> Player(在线玩家) - for (Map.Entry> entry : uuidFriends.entrySet()) { - Player player = Bukkit.getPlayer(entry.getKey()); - if (player != null && player.isOnline()) { - List friends = entry.getValue().stream() - .map(Bukkit::getPlayer) - .filter(p -> p != null && p.isOnline()) // 只保留在线玩家 - .collect(Collectors.toList()); - plugin.friends.put(player, friends); - } - } - - // 转换等待请求数据:UUID -> Player(在线玩家) - for (Map.Entry> entry : uuidWaiting.entrySet()) { - Player receiver = Bukkit.getPlayer(entry.getKey()); - if (receiver != null && receiver.isOnline()) { - List requesters = entry.getValue().stream() - .map(Bukkit::getPlayer) - .filter(p -> p != null && p.isOnline()) // 只保留在线玩家 - .collect(Collectors.toList()); - plugin.waiting.put(receiver, requesters); - } - } - - plugin.getLogger().info("好友数据加载成功!"); - } catch (IOException e) { - plugin.getLogger().severe("好友数据文件读取失败:" + e.getMessage()); - } catch (ClassNotFoundException e) { - plugin.getLogger().severe("好友数据模型类未找到:" + e.getMessage()); - } catch (Exception e) { - plugin.getLogger().severe("好友数据加载异常:" + e.getMessage()); + // 目标玩家校验 + if (target == null || !target.isOnline()) { + sender.sendMessage(prefix + ChatColor.RED + "玩家 " + targetName + " 不存在或未在线!"); + return; } + if (target.equals(sender)) { + sender.sendMessage(prefix + ChatColor.RED + "不能添加自己为好友!"); + return; + } + + // 检查是否已是好友 + List senderFriends = friends.getOrDefault(sender, new ArrayList<>()); + if (senderFriends.contains(target)) { + sender.sendMessage(prefix + ChatColor.RED + "你与 " + target.getName() + " 已是好友关系!"); + return; + } + + // 检查是否有未过期的请求 + List targetRequests = waitingRequests.getOrDefault(target, new ArrayList<>()); + boolean hasPending = targetRequests.stream() + .anyMatch(req -> req.getSenderUuid().equals(sender.getUniqueId().toString()) && + System.currentTimeMillis() - req.getTimestamp() < REQUEST_EXPIRE_TIME); + + if (hasPending) { + sender.sendMessage(prefix + ChatColor.RED + "已向 " + target.getName() + " 发送请求,对方未处理(5分钟内不可重复发送)!"); + return; + } + + // 发送请求 + targetRequests.add(new FriendRequest(sender)); + waitingRequests.put(target, targetRequests); + + // 通知双方 + sender.sendMessage(prefix + ChatColor.GREEN + "好友请求已发送给 " + target.getName() + ",请等待回应~"); + target.sendMessage(prefix + ChatColor.YELLOW + "📩 收到 " + sender.getName() + " 的好友请求!"); + target.sendMessage(prefix + ChatColor.YELLOW + "使用 /friend accept " + sender.getName() + " 接受,或 /friend deny " + sender.getName() + " 拒绝"); + + // 保存数据 + saveData(); } /** - * 保存好友数据(使用副本遍历避免并发异常) + * 处理移除好友命令 */ - public void saveFriendData() { - FriendData data = new FriendData(); - Map> uuidFriends = new ConcurrentHashMap<>(); - Map> uuidWaiting = new ConcurrentHashMap<>(); - - // 遍历副本避免ConcurrentModificationException(关键修复) - new ArrayList<>(plugin.friends.entrySet()).forEach(entry -> { - UUID playerUuid = entry.getKey().getUniqueId(); - List friendUuids = entry.getValue().stream() - .map(Player::getUniqueId) - .collect(Collectors.toList()); - uuidFriends.put(playerUuid, friendUuids); - }); - - new ArrayList<>(plugin.waiting.entrySet()).forEach(entry -> { - UUID receiverUuid = entry.getKey().getUniqueId(); - List requesterUuids = entry.getValue().stream() - .map(Player::getUniqueId) - .collect(Collectors.toList()); - uuidWaiting.put(receiverUuid, requesterUuids); - }); - - data.setFriends(uuidFriends); - data.setWaiting(uuidWaiting); - - try { - if (!friendDataFile.getParentFile().exists()) { - friendDataFile.getParentFile().mkdirs(); - } - try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(friendDataFile))) { - oos.writeObject(data); - } - } catch (IOException e) { - plugin.getLogger().severe("好友数据保存失败:" + e.getMessage()); + private void handleRemove(Player sender, String[] args) { + // 参数校验 + if (args.length != 2) { + sender.sendMessage(prefix + ChatColor.RED + "用法错误!正确格式:/friend remove <玩家名>"); + return; } + + String targetName = args[1]; + Player target = plugin.getServer().getPlayer(targetName); + + // 目标玩家校验 + if (target == null || !target.isOnline()) { + sender.sendMessage(prefix + ChatColor.RED + "玩家 " + targetName + " 不存在或未在线!"); + return; + } + if (target.equals(sender)) { + sender.sendMessage(prefix + ChatColor.RED + "不能移除自己!"); + return; + } + + // 检查好友关系 + List senderFriends = friends.getOrDefault(sender, new ArrayList<>()); + List targetFriends = friends.getOrDefault(target, new ArrayList<>()); + + if (!senderFriends.contains(target) || !targetFriends.contains(sender)) { + sender.sendMessage(prefix + ChatColor.RED + "你与 " + target.getName() + " 不是好友关系!"); + return; + } + + // 双向移除好友 + senderFriends.remove(target); + targetFriends.remove(sender); + friends.put(sender, senderFriends); + friends.put(target, targetFriends); + + // 通知双方 + sender.sendMessage(prefix + ChatColor.GRAY + "已成功移除好友 " + target.getName() + "!"); + target.sendMessage(prefix + ChatColor.RED + "⚠️ 你被 " + sender.getName() + " 移除好友!"); + + // 保存数据 + saveData(); } /** - * 处理好友列表命令 + * 处理查看好友列表命令 */ private void handleList(Player player) { - plugin.friends.computeIfAbsent(player, k -> new ArrayList<>()); - List friends = plugin.friends.get(player); + List friendList = friends.getOrDefault(player, new ArrayList<>()); - if (friends.isEmpty()) { - player.sendMessage(ChatColor.RED + "📭 你的好友列表为空!使用 /friend add <玩家名> 添加好友"); + // 空列表提示 + if (friendList.isEmpty()) { + player.sendMessage(prefix + ChatColor.GRAY + "你的好友列表为空~ 使用 /friend add <玩家名> 添加好友吧!"); return; } - player.sendMessage(ChatColor.GREEN + "===== 你的好友列表(共" + friends.size() + "人)====="); - for (Player friend : friends) { - BaseComponent[] friendEntry = new ComponentBuilder() - .append(ChatColor.WHITE + "● " + friend.getName()) - .append(ChatColor.RED + " [删除]") - .event(new ClickEvent( - ClickEvent.Action.RUN_COMMAND, - "/friend prepareDelete " + friend.getName() - )) - .create(); - player.spigot().sendMessage(friendEntry); + // 显示好友列表(带在线状态) + player.sendMessage(prefix + ChatColor.GREEN + "=== 好友列表(共 " + friendList.size() + " 人)==="); + for (int i = 0; i < friendList.size(); i++) { + Player friend = friendList.get(i); + String status = friend.isOnline() ? ChatColor.GREEN + "在线 " : ChatColor.RED + " 离线"; + player.sendMessage(ChatColor.WHITE + String.format("%d",(i + 1)) + ". " + friend.getName() + " - " + status); } - player.sendMessage(ChatColor.GREEN + "======================================"); } /** - * 准备删除好友(触发二次确认) + * 处理接受好友请求命令 */ - private void handlePrepareDelete(Player player, String targetName) { - Player target = Bukkit.getPlayerExact(targetName); - plugin.friends.computeIfAbsent(player, k -> new ArrayList<>()); - - if (target == null || !plugin.friends.get(player).contains(target)) { - player.sendMessage(ChatColor.RED + "❌ 该玩家不在你的好友列表中!"); + private void handleAccept(Player receiver, String[] args) { + // 参数校验 + if (args.length != 2) { + receiver.sendMessage(prefix + ChatColor.RED + "用法错误!正确格式:/friend accept <请求ID|玩家名>"); return; } - if (pendingDelete.containsKey(player)) { - player.sendMessage(ChatColor.RED + "❌ 你有未完成的删除确认请求,请先处理!"); + List requests = waitingRequests.getOrDefault(receiver, new ArrayList<>()); + if (requests.isEmpty()) { + receiver.sendMessage(prefix + ChatColor.RED + "你没有未处理的好友请求!"); return; } - pendingDelete.put(player, target); - player.sendMessage(ChatColor.YELLOW + "⚠️ 确认要删除好友 " + target.getName() + " 吗?(10秒内有效)"); + // 查找目标请求(支持ID和玩家名) + FriendRequest targetRequest = findRequest(receiver, args[1]); + if (targetRequest == null) { + receiver.sendMessage(prefix + ChatColor.RED + "未找到该好友请求!"); + return; + } - BaseComponent[] confirmButtons = new ComponentBuilder() - .append(ChatColor.GREEN + "[确认删除] ") - .event(new ClickEvent( - ClickEvent.Action.RUN_COMMAND, - "/friend confirmDelete " + target.getName() + " yes" - )) - .append(ChatColor.GRAY + "[取消]") - .event(new ClickEvent( - ClickEvent.Action.RUN_COMMAND, - "/friend confirmDelete " + target.getName() + " no" - )) - .create(); - player.spigot().sendMessage(confirmButtons); + // 检查请求是否过期 + if (System.currentTimeMillis() - targetRequest.getTimestamp() > REQUEST_EXPIRE_TIME) { + removeRequest(receiver, targetRequest); + receiver.sendMessage(prefix + ChatColor.RED + "该请求已过期!"); + return; + } - // 设置超时任务 - BukkitRunnable timeoutTask = new BukkitRunnable() { - @Override - public void run() { - if (pendingDelete.containsKey(player) && pendingDelete.get(player).equals(target)) { - pendingDelete.remove(player); - player.sendMessage(ChatColor.RED + "⌛ 删除好友请求已超时,自动取消!"); - } - deleteTimeoutTasks.remove(player); - } - }; - deleteTimeoutTasks.put(player, timeoutTask); - timeoutTask.runTaskLater(plugin, 20 * 10); + // 获取请求发送者 + Player sender = plugin.getServer().getPlayer(UUID.fromString(targetRequest.getSenderUuid())); + if (sender == null || !sender.isOnline()) { + removeRequest(receiver, targetRequest); + receiver.sendMessage(prefix + ChatColor.RED + "请求发送者已离线,请求失效!"); + return; + } + + // 双向添加好友 + List receiverFriends = friends.getOrDefault(receiver, new ArrayList<>()); + List senderFriends = friends.getOrDefault(sender, new ArrayList<>()); + + receiverFriends.add(sender); + senderFriends.add(receiver); + friends.put(receiver, receiverFriends); + friends.put(sender, senderFriends); + + // 移除请求 + removeRequest(receiver, targetRequest); + + // 通知双方 + receiver.sendMessage(prefix + ChatColor.GREEN + "🎉 已接受 " + sender.getName() + " 的好友请求!现在可以一起玩啦~"); + sender.sendMessage(prefix + ChatColor.GREEN + "🎉 " + receiver.getName() + " 接受了你的好友请求!你们成为好友啦~"); + + // 保存数据 + saveData(); } /** - * 处理删除确认/取消操作 + * 处理拒绝好友请求命令 */ - private void handleConfirmDelete(Player player, String targetName, String action) { - Player target = Bukkit.getPlayerExact(targetName); - - if (!pendingDelete.containsKey(player) || !pendingDelete.get(player).equals(target)) { - player.sendMessage(ChatColor.RED + "❌ 无对应的删除确认请求!"); + private void handleDeny(Player receiver, String[] args) { + // 参数校验 + if (args.length != 2) { + receiver.sendMessage(prefix + ChatColor.RED + "用法错误!正确格式:/friend deny <请求ID|玩家名>"); return; } - if (deleteTimeoutTasks.containsKey(player)) { - deleteTimeoutTasks.get(player).cancel(); - deleteTimeoutTasks.remove(player); + List requests = waitingRequests.getOrDefault(receiver, new ArrayList<>()); + if (requests.isEmpty()) { + receiver.sendMessage(prefix + ChatColor.RED + "你没有未处理的好友请求!"); + return; } - if (action.equalsIgnoreCase("yes")) { - handleRemove(player, targetName); - } else if (action.equalsIgnoreCase("no")) { - player.sendMessage(ChatColor.GREEN + "✅ 已取消删除好友 " + target.getName() + "!"); + // 查找目标请求 + FriendRequest targetRequest = findRequest(receiver, args[1]); + if (targetRequest == null) { + receiver.sendMessage(prefix + ChatColor.RED + "未找到该好友请求!"); + return; } - pendingDelete.remove(player); + // 检查请求是否过期 + if (System.currentTimeMillis() - targetRequest.getTimestamp() > REQUEST_EXPIRE_TIME) { + removeRequest(receiver, targetRequest); + receiver.sendMessage(prefix + ChatColor.RED + "该请求已过期!"); + return; + } + + // 获取请求发送者 + Player sender = plugin.getServer().getPlayer(UUID.fromString(targetRequest.getSenderUuid())); + + // 移除请求 + removeRequest(receiver, targetRequest); + + // 通知双方 + receiver.sendMessage(prefix + ChatColor.GRAY + "已拒绝该好友请求!"); + if (sender != null && sender.isOnline()) { + sender.sendMessage(prefix + ChatColor.RED + "❌ " + receiver.getName() + " 拒绝了你的好友请求!"); + } + + // 保存数据 + saveData(); } /** - * 处理添加好友命令(添加异常捕获) + * 查找好友请求(支持ID和玩家名) */ - private void handleAdd(Player requester, Player target) { + private FriendRequest findRequest(Player receiver, String param) { + List requests = waitingRequests.getOrDefault(receiver, new ArrayList<>()); + + // 尝试按ID查找(数字) try { - if (requester.equals(target)) { - requester.sendMessage(ChatColor.RED + "❌ 无法添加自己为好友!"); - return; + int requestId = Integer.parseInt(param) - 1; // 转为0-based索引 + if (requestId >= 0 && requestId < requests.size()) { + return requests.get(requestId); } + } catch (NumberFormatException e) { + // 按玩家名查找(忽略大小写) + return requests.stream() + .filter(req -> { + Player sender = plugin.getServer().getPlayer(UUID.fromString(req.getSenderUuid())); + return sender != null && sender.getName().equalsIgnoreCase(param); + }) + .findFirst() + .orElse(null); + } - plugin.friends.computeIfAbsent(requester, k -> new ArrayList<>()); - plugin.friends.computeIfAbsent(target, k -> new ArrayList<>()); - plugin.waiting.computeIfAbsent(target, k -> new ArrayList<>()); + return null; + } - if (plugin.friends.get(requester).contains(target)) { - requester.sendMessage(ChatColor.RED + "❌ " + target.getName() + " 已是你的好友!"); - return; - } + /** + * 移除好友请求 + */ + private void removeRequest(Player receiver, FriendRequest request) { + List requests = waitingRequests.getOrDefault(receiver, new ArrayList<>()); + requests.remove(request); - if (plugin.waiting.get(target).contains(requester)) { - requester.sendMessage(ChatColor.RED + "⌛ 你已向 " + target.getName() + " 发送过好友请求,等待对方确认中!"); - return; - } + if (requests.isEmpty()) { + waitingRequests.remove(receiver); + } else { + waitingRequests.put(receiver, requests); + } + } - if (plugin.waiting.get(requester).contains(target)) { - requester.sendMessage(ChatColor.YELLOW + "📩 " + target.getName() + " 已向你发送好友请求,请使用 /friend allow " + target.getName() + " 同意!"); - return; - } + /** + * 显示命令帮助 + */ + private void showHelp(Player player) { + player.sendMessage(prefix + ChatColor.GREEN + "=== 好友系统命令帮助 ==="); + player.sendMessage(ChatColor.WHITE + "/friend add <玩家名> " + ChatColor.GRAY + "- 向玩家发送好友请求"); + player.sendMessage(ChatColor.WHITE + "/friend remove <玩家名> " + ChatColor.GRAY + "- 移除指定好友"); + player.sendMessage(ChatColor.WHITE + "/friend list " + ChatColor.GRAY + "- 查看好友列表及在线状态"); + player.sendMessage(ChatColor.WHITE + "/friend accept " + ChatColor.GRAY + "- 接受好友请求"); + player.sendMessage(ChatColor.WHITE + "/friend deny " + ChatColor.GRAY + "- 拒绝好友请求"); + player.sendMessage(ChatColor.YELLOW + "小贴士:好友请求有效期为5分钟,超时自动失效~"); + } - // 发送请求 - plugin.waiting.get(target).add(requester); - requester.sendMessage(ChatColor.GREEN + "✅ 好友请求已发送给 " + target.getName() + ",对方需在5分钟内确认!"); - target.sendMessage(ChatColor.AQUA + "📩 收到 " + requester.getName() + " 的好友请求!"); - target.sendMessage(ChatColor.AQUA + "使用 /friend allow " + requester.getName() + " 同意,或 /friend deny " + requester.getName() + " 拒绝"); + /** + * 加载好友数据(从JSON文件) + */ + private void loadData() { + if (!dataFile.exists()) { + // 初始化空数据 + friends = new ConcurrentHashMap<>(); + waitingRequests = new ConcurrentHashMap<>(); + return; + } - // 生成按钮 - BaseComponent[] message = new ComponentBuilder() - .append(new ComponentBuilder(ChatColor.GREEN + "[同意] ").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/friend allow " + requester.getName())).create()) - .append(new ComponentBuilder(ChatColor.RED + "[拒绝]").event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/friend deny " + requester.getName())).create()) - .create(); - target.spigot().sendMessage(message); + try (FileReader reader = new FileReader(dataFile)) { + Type dataType = new TypeToken() {}.getType(); + FriendData data = gson.fromJson(reader, dataType); - // 超时任务(使用唯一键精准管理) - String taskKey = requester.getUniqueId() + "_" + target.getUniqueId(); - BukkitRunnable timeoutTask = new BukkitRunnable() { - @Override - public void run() { - if (plugin.waiting.getOrDefault(target, new ArrayList<>()).contains(requester)) { - plugin.waiting.get(target).remove(requester); - requester.sendMessage(ChatColor.RED + "⌛ 向 " + target.getName() + " 发送的好友请求已超时(5分钟)!"); - target.sendMessage(ChatColor.RED + "⌛ " + requester.getName() + " 的好友请求已超时,已自动拒绝!"); - saveFriendData(); - } - timeoutTasks.remove(taskKey); - } - }; - timeoutTasks.put(taskKey, timeoutTask); - timeoutTask.runTaskLater(plugin, 20 * 60 * 5); + // 转换UUID映射为Player映射(仅加载在线玩家) + this.friends = convertUuidToPlayerMap(data.getFriends()); + this.waitingRequests = convertUuidToRequestMap(data.getWaitingRequests()); - saveFriendData(); - } catch (Exception e) { - requester.sendMessage(ChatColor.RED + "❌ 添加好友失败!请联系管理员。"); - plugin.getLogger().severe("添加好友异常(" + requester.getName() + " -> " + target.getName() + "):" + e.getMessage()); + } catch (IOException e) { + plugin.getLogger().severe("加载好友数据失败!" + e.getMessage()); + e.printStackTrace(); + // 初始化空数据兜底 + friends = new ConcurrentHashMap<>(); + waitingRequests = new ConcurrentHashMap<>(); + } + } + + /** + * 保存好友数据(到JSON文件) + */ + private void saveData() { + // 转换Player映射为UUID映射(支持序列化) + Map> serializableFriends = convertPlayerToUuidMap(friends); + Map> serializableRequests = convertRequestToUuidMap(waitingRequests); + + FriendData data = new FriendData(serializableFriends, serializableRequests); + + try (FileWriter writer = new FileWriter(dataFile)) { + gson.toJson(data, writer); + } catch (IOException e) { + plugin.getLogger().severe("保存好友数据失败!" + e.getMessage()); e.printStackTrace(); } } /** - * 处理删除好友命令 + * 转换:Map> → Map> */ - private void handleRemove(Player player, String targetName) { - Player target = Bukkit.getPlayerExact(targetName); - plugin.friends.computeIfAbsent(player, k -> new ArrayList<>()); + private Map> convertUuidToPlayerMap(Map> uuidMap) { + Map> playerMap = new ConcurrentHashMap<>(); + if (uuidMap == null) return playerMap; - if (target == null) { - boolean removed = false; - List friends = plugin.friends.get(player); - for (Player friend : new ArrayList<>(friends)) { // 遍历副本 - if (friend.getName().equals(targetName)) { - friends.remove(friend); - plugin.friends.computeIfAbsent(friend, k -> new ArrayList<>()).remove(player); - removed = true; - break; + for (Map.Entry> entry : uuidMap.entrySet()) { + // 查找在线的玩家(接收者) + Player owner = plugin.getServer().getPlayer(UUID.fromString(entry.getKey())); + if (owner == null) continue; + + // 转换好友列表(仅保留在线玩家) + List friends = entry.getValue().stream() + .map(uuidStr -> plugin.getServer().getPlayer(UUID.fromString(uuidStr))) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + playerMap.put(owner, friends); + } + return playerMap; + } + + /** + * 转换:Map> → Map> + */ + private Map> convertPlayerToUuidMap(Map> playerMap) { + Map> uuidMap = new ConcurrentHashMap<>(); + + for (Map.Entry> entry : playerMap.entrySet()) { + String ownerUuid = entry.getKey().getUniqueId().toString(); + List friendUuids = entry.getValue().stream() + .map(player -> player.getUniqueId().toString()) + .collect(Collectors.toList()); + + uuidMap.put(ownerUuid, friendUuids); + } + return uuidMap; + } + + /** + * 转换:Map> → Map> + */ + private Map> convertUuidToRequestMap(Map> uuidMap) { + Map> playerMap = new ConcurrentHashMap<>(); + if (uuidMap == null) return playerMap; + + for (Map.Entry> entry : uuidMap.entrySet()) { + Player receiver = plugin.getServer().getPlayer(UUID.fromString(entry.getKey())); + if (receiver != null) { + playerMap.put(receiver, entry.getValue()); + } + } + return playerMap; + } + + /** + * 转换:Map> → Map> + */ + private Map> convertRequestToUuidMap(Map> playerMap) { + Map> uuidMap = new ConcurrentHashMap<>(); + + for (Map.Entry> entry : playerMap.entrySet()) { + String receiverUuid = entry.getKey().getUniqueId().toString(); + uuidMap.put(receiverUuid, entry.getValue()); + } + return uuidMap; + } + + /** + * 启动请求过期检查任务(每1分钟执行一次) + */ + private void startExpireCheckTask() { + new BukkitRunnable() { + @Override + public void run() { + long currentTime = System.currentTimeMillis(); + + // 遍历所有待处理请求 + for (Iterator>> iterator = waitingRequests.entrySet().iterator(); iterator.hasNext(); ) { + Map.Entry> entry = iterator.next(); + Player receiver = entry.getKey(); + List requests = entry.getValue(); + + // 移除过期请求 + Iterator reqIterator = requests.iterator(); + while (reqIterator.hasNext()) { + FriendRequest req = reqIterator.next(); + if (currentTime - req.getTimestamp() > REQUEST_EXPIRE_TIME) { + // 通知发送者 + Player sender = plugin.getServer().getPlayer(UUID.fromString(req.getSenderUuid())); + if (sender != null && sender.isOnline()) { + sender.sendMessage(prefix + ChatColor.RED + "你发送给 " + receiver.getName() + " 的好友请求已过期(5分钟未处理)!"); + } + reqIterator.remove(); + } + } + + // 若请求列表为空,移除该条目 + if (requests.isEmpty()) { + iterator.remove(); + } } + + // 保存更新后的数据 + saveData(); } - if (removed) { - player.sendMessage(ChatColor.GREEN + "✅ 已成功删除好友 " + targetName + "!"); - saveFriendData(); - } else { - player.sendMessage(ChatColor.RED + "❌ 你未添加 " + targetName + " 为好友!"); - } - return; - } - - if (plugin.friends.get(player).contains(target)) { - plugin.friends.get(player).remove(target); - plugin.friends.computeIfAbsent(target, k -> new ArrayList<>()).remove(player); - player.sendMessage(ChatColor.GREEN + "✅ 已成功删除好友 " + target.getName() + "!"); - target.sendMessage(ChatColor.RED + "📢 " + player.getName() + " 已将你从好友列表中移除!"); - saveFriendData(); - } else { - player.sendMessage(ChatColor.RED + "❌ 你未添加 " + target.getName() + " 为好友!"); - } - } - - /** - * 处理同意好友请求命令(精准取消超时任务) - */ - private void handleAllow(Player receiver, Player requester) { - plugin.waiting.computeIfAbsent(receiver, k -> new ArrayList<>()); - plugin.friends.computeIfAbsent(receiver, k -> new ArrayList<>()); - plugin.friends.computeIfAbsent(requester, k -> new ArrayList<>()); - - if (!plugin.waiting.get(receiver).contains(requester)) { - receiver.sendMessage(ChatColor.RED + "❌ 未收到 " + requester.getName() + " 的好友请求!"); - return; - } - - // 双向添加好友 - plugin.friends.get(receiver).add(requester); - plugin.friends.get(requester).add(receiver); - plugin.waiting.get(receiver).remove(requester); - - // 精准取消超时任务(关键修复) - String taskKey = requester.getUniqueId() + "_" + receiver.getUniqueId(); - BukkitRunnable task = timeoutTasks.get(taskKey); - if (task != null && !task.isCancelled()) { - task.cancel(); - timeoutTasks.remove(taskKey); - } - - receiver.sendMessage(ChatColor.GREEN + "🎉 已同意 " + requester.getName() + " 的好友请求,你们现在是好友啦!"); - requester.sendMessage(ChatColor.GREEN + "🎉 " + receiver.getName() + " 已同意你的好友请求,你们现在是好友啦!"); - saveFriendData(); - } - - /** - * 处理拒绝好友请求命令(精准取消超时任务) - */ - private void handleDeny(Player receiver, Player requester) { - plugin.waiting.computeIfAbsent(receiver, k -> new ArrayList<>()); - - if (!plugin.waiting.get(receiver).contains(requester)) { - receiver.sendMessage(ChatColor.RED + "❌ 未收到 " + requester.getName() + " 的好友请求!"); - return; - } - - plugin.waiting.get(receiver).remove(requester); - - // 精准取消超时任务(关键修复) - String taskKey = requester.getUniqueId() + "_" + receiver.getUniqueId(); - BukkitRunnable task = timeoutTasks.get(taskKey); - if (task != null && !task.isCancelled()) { - task.cancel(); - timeoutTasks.remove(taskKey); - } - - receiver.sendMessage(ChatColor.GRAY + "✅ 已拒绝 " + requester.getName() + " 的好友请求!"); - requester.sendMessage(ChatColor.RED + "❌ " + receiver.getName() + " 已拒绝你的好友请求!"); - saveFriendData(); - } - - /** - * 发送命令用法提示 - */ - private void sendUsage(Player player) { - player.sendMessage(ChatColor.GREEN + "===== 好友系统命令指南 ====="); - player.sendMessage(ChatColor.YELLOW + "/friend add <玩家名> - 向指定玩家发送好友请求"); - player.sendMessage(ChatColor.YELLOW + "/friend remove <玩家名> - 从好友列表中删除指定玩家"); - player.sendMessage(ChatColor.YELLOW + "/friend allow <玩家名> - 同意指定玩家的好友请求"); - player.sendMessage(ChatColor.YELLOW + "/friend deny <玩家名> - 拒绝指定玩家的好友请求"); - player.sendMessage(ChatColor.YELLOW + "/friend list - 查看你的好友列表(支持快捷删除)"); - player.sendMessage(ChatColor.GREEN + "=========================="); - } - - /** - * 处理prepareDelete隐式命令 - */ - private void handlePrepareDeleteCommand(Player player, String[] args) { - if (args.length < 2) { - player.sendMessage(ChatColor.RED + "❌ 用法错误!正确格式:/friend prepareDelete <玩家名>"); - return; - } - handlePrepareDelete(player, args[1]); + }.runTaskTimerAsynchronously(plugin, 0L, 20L * 60); // 异步执行,避免阻塞主线程 } } \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/command/KillCommandExecutor.java b/src/main/java/org/xgqy/survival/command/KillCommandExecutor.java index fd79236..026af73 100644 --- a/src/main/java/org/xgqy/survival/command/KillCommandExecutor.java +++ b/src/main/java/org/xgqy/survival/command/KillCommandExecutor.java @@ -1,29 +1,280 @@ package org.xgqy.survival.command; import org.bukkit.ChatColor; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.World; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; +import org.bukkit.scoreboard.Objective; import org.jetbrains.annotations.NotNull; import org.xgqy.survival.Survival; -public class KillCommandExecutor implements CommandExecutor { +import java.util.ConcurrentModificationException; +import java.util.Map; - private Survival plugin; +public class KillCommandExecutor implements CommandExecutor, Listener { + // 核心配置常量 + private static final int EXP_COST = 50; // 经验扣除量 + private static final long TELEPORT_DELAY = 5 * 20L; // 传送延迟(5秒 = 100tick) + private static final long COOLDOWN = 30 * 1000L; // 命令冷却(30秒) + private static final double MOVE_THRESHOLD = 0.1; // 移动判定阈值(超过0.1格视为移动) + private static final Particle TELEPORT_PARTICLE = Particle.CLOUD; + private static final int PARTICLE_COUNT = 25; // 每圈粒子数量 + private static final double PARTICLE_RADIUS = 1.6; // 粒子生成半径 + private static final double PARTICLE_Y_OFFSET = 0.6; // 粒子Y轴偏移(避免贴地) + private static final Color PARTICLE_COLOR = Color.BLUE; // 粒子颜色(蓝色) + + private final Survival plugin; + // 正在传送的玩家数据(线程安全) + private final Map teleportingPlayers = new java.util.concurrent.ConcurrentHashMap<>(); + // 玩家冷却时间记录(线程安全) + private final Map cooldownMap = new java.util.concurrent.ConcurrentHashMap<>(); public KillCommandExecutor(Survival plugin) { this.plugin = plugin; + // 注册移动事件监听器(检测传送期间是否移动) + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + private static class TeleportData { + private final Location startLocation; // 起始位置(用于检测移动) + private final int deductedExp; // 已扣除的经验(取消时返还) + private boolean hasMoved; // 是否已移动 + private BukkitTask particleTask; // 粒子效果任务(用于取消) + + public TeleportData(Location startLocation, int deductedExp) { + this.startLocation = startLocation; + this.deductedExp = deductedExp; + this.hasMoved = false; + this.particleTask = null; + } + + // Getter & Setter + public Location getStartLocation() { + return startLocation; + } + + public int getDeductedExp() { + return deductedExp; + } + + public boolean hasMoved() { + return hasMoved; + } + + public void setHasMoved(boolean hasMoved) { + this.hasMoved = hasMoved; + } + + public BukkitTask getParticleTask() { + return particleTask; + } + + public void setParticleTask(BukkitTask particleTask) { + this.particleTask = particleTask; + } } @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (sender instanceof Player) { - ((Player) sender).damage(11451.0f); - return true; - } else { - sender.sendMessage(ChatColor.RED + "无法对非玩家类使用"); + // 仅玩家可使用 + if (!(sender instanceof Player player)) { + sender.sendMessage(ChatColor.RED + "❌ 无法对非玩家使用该命令!"); return true; } + + // 1. 检查冷却状态 + if (isOnCooldown(player)) { + long remainingSeconds = (cooldownMap.get(player) - System.currentTimeMillis()) / 1000; + player.sendMessage(ChatColor.RED + "⚠️ 命令冷却中!剩余 " + remainingSeconds + " 秒后可再次使用"); + return true; + } + + // 2. 检查经验是否足够 + int totalExp = player.getTotalExperience(); + if (totalExp < EXP_COST) { + player.sendMessage(ChatColor.RED + "❌ 经验不足!使用该命令需消耗 " + EXP_COST + " 点经验,你当前仅拥有 " + totalExp + " 点"); + return true; + } + + // 3. 扣除经验并初始化传送数据 + player.setTotalExperience(totalExp - EXP_COST); + Location startLoc = player.getLocation().clone(); // 记录起始位置(精确到坐标和角度) + TeleportData teleportData = new TeleportData(startLoc, EXP_COST); + teleportingPlayers.put(player, teleportData); + + // 4. 发送传送提示 + player.sendMessage(ChatColor.YELLOW + "====================================="); + player.sendMessage(ChatColor.YELLOW + "📡 自动回复活点启动!"); + player.sendMessage(ChatColor.WHITE + "消耗:" + ChatColor.GREEN + EXP_COST + " 点经验"); + player.sendMessage(ChatColor.WHITE + "倒计时:" + ChatColor.RED + "5 秒(移动将取消传送)"); + player.sendMessage(ChatColor.YELLOW + "====================================="); + + // 5. 启动粒子效果任务(每0.1秒生成一圈粒子) + startParticleEffect(player, teleportData); + + // 6. 启动传送倒计时任务 + startTeleportTimer(player, teleportData); + + return true; + } + + /** + * 检测玩家是否在冷却中 + */ + private boolean isOnCooldown(Player player) { + if (!cooldownMap.containsKey(player)) return false; + // 冷却时间结束后自动移除记录 + if (System.currentTimeMillis() >= cooldownMap.get(player)) { + cooldownMap.remove(player); + return false; + } + return true; + } + + /** + * 启动传送粒子效果(围绕玩家生成圆形蓝色粒子) + */ + private void startParticleEffect(Player player, TeleportData data) { + BukkitTask particleTask = new BukkitRunnable() { + @Override + public void run() { + // 玩家已移动或传送结束,停止粒子 + if (!teleportingPlayers.containsKey(player) || data.hasMoved()) { + this.cancel(); + return; + } + + Location playerLoc = player.getLocation().add(0, PARTICLE_Y_OFFSET, 0); + World world = playerLoc.getWorld(); + if (world == null) { + this.cancel(); + return; + } + + // 生成圆形粒子(围绕玩家) + for (int i = 0; i < PARTICLE_COUNT; i++) { + // 计算圆形坐标 + double angle = 2 * Math.PI * i / PARTICLE_COUNT; + double x = Math.cos(angle) * PARTICLE_RADIUS; + double z = Math.sin(angle) * PARTICLE_RADIUS; + + Location particleLoc = playerLoc.clone().add(x, 0, z); + // 配置红色stone粒子为蓝色(更美观) + Particle.DustOptions dustOptions = new Particle.DustOptions(PARTICLE_COLOR, 1.2f); + world.spawnParticle(TELEPORT_PARTICLE, particleLoc, 0, 0, 0, 0, 0, dustOptions); + } + + playerLoc.subtract(0, PARTICLE_Y_OFFSET, 0); // 恢复玩家原位置(避免坐标偏移) + } + }.runTaskTimer(plugin, 0L, 2L); // 每2tick(0.1秒)执行一次 + + data.setParticleTask(particleTask); + } + + /** + * 启动传送倒计时任务 + */ + private void startTeleportTimer(Player player, TeleportData data) { + new BukkitRunnable() { + @Override + public void run() { + // 玩家已移动或传送状态已移除,直接结束 + if (!teleportingPlayers.containsKey(player) || data.hasMoved()) { + teleportingPlayers.remove(player); + return; + } + + // 1. 停止粒子效果 + if (data.getParticleTask() != null) { + data.getParticleTask().cancel(); + } + + // 2. 获取活点位置(优先床的重生点,无则使用世界出生点) + Location spawnLoc = player.getBedSpawnLocation(); + if (spawnLoc == null) { + spawnLoc = player.getWorld().getSpawnLocation().clone().add(0.5, 0, 0.5); // 居中出生点 + } + + // 3. 执行传送 + player.teleport(spawnLoc); + + // 4. 保留原功能:设置计分板goback为100 + Objective gobackObj = player.getScoreboard().getObjective("goback"); + if (gobackObj != null) { + gobackObj.getScore(player.getName()).setScore(100); + } + + // 5. 发送传送成功提示 + player.sendMessage(ChatColor.GREEN + "====================================="); + player.sendMessage(ChatColor.GREEN + "🎉 传送成功!已返回活点"); + player.sendMessage(ChatColor.WHITE + "提示:" + ChatColor.GRAY + "30秒内无法再次使用该命令"); + player.sendMessage(ChatColor.GREEN + "====================================="); + + // 6. 设置30秒冷却 + cooldownMap.put(player, System.currentTimeMillis() + COOLDOWN); + + // 7. 清理传送状态 + teleportingPlayers.remove(player); + } + }.runTaskLater(plugin, TELEPORT_DELAY); // 5秒后执行传送 + } + + /** + * 监听玩家移动事件(传送期间移动则取消传送) + */ + @EventHandler + public void onPlayerMove(PlayerMoveEvent event) { + Player player = event.getPlayer(); + TeleportData data = teleportingPlayers.get(player); + + // 非传送中玩家,直接忽略 + if (data == null || data.hasMoved()) return; + + Location currentLoc = event.getTo(); + Location startLoc = data.getStartLocation(); + + try { + // 检查是否移动(距离阈值>0.1格,忽略微小波动) + if (currentLoc.distanceSquared(startLoc) > MOVE_THRESHOLD * MOVE_THRESHOLD) { + // 标记为已移动,取消传送 + data.setHasMoved(true); + cancelTeleport(player, data); + } + } catch (ConcurrentModificationException e) { + // 避免并发修改异常(忽略) + plugin.getLogger().warning("传送期间玩家移动检测并发异常:" + player.getName()); + } + } + + /** + * 取消传送并返还经验 + */ + private void cancelTeleport(Player player, TeleportData data) { + // 1. 停止粒子效果 + if (data.getParticleTask() != null) { + data.getParticleTask().cancel(); + } + + // 2. 返还扣除的经验 + player.setTotalExperience(player.getTotalExperience() + data.getDeductedExp()); + + // 3. 发送取消提示 + player.sendMessage(ChatColor.RED + "====================================="); + player.sendMessage(ChatColor.RED + " 传送已取消!"); + player.sendMessage(ChatColor.WHITE + "原因:" + ChatColor.RED + "传送期间移动"); + player.sendMessage(ChatColor.WHITE + "提示:" + ChatColor.GRAY + "已返还 " + EXP_COST + " 点经验"); + player.sendMessage(ChatColor.RED + "====================================="); + + // 4. 清理传送状态 + teleportingPlayers.remove(player); } } \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/event/AntiExploit.java b/src/main/java/org/xgqy/survival/event/AntiExploit.java deleted file mode 100644 index 0c3474c..0000000 --- a/src/main/java/org/xgqy/survival/event/AntiExploit.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.xgqy.survival.event; - -import org.bukkit.ChatColor; -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerCommandPreprocessEvent; - -public class AntiExploit implements Listener { - - // 监听玩家执行命令的事件(命令执行前触发) - @EventHandler - private void onFillCommand(PlayerCommandPreprocessEvent event) { - String command = event.getMessage().trim(); - // 只处理fill命令(避免误判类似/fillxyz的命令) - if (!command.startsWith("/fill ")) { - return; - } - - Player player = event.getPlayer(); - // 分割命令参数(处理多个空格的情况) - String[] parts = command.split(" +"); - // 检查参数是否完整(至少需要:/fill x1 y1 z1 x2 y2 z2) - if (parts.length < 7) { - return; - } - - // 获取玩家当前位置(用于处理相对坐标~) - Location playerLoc = player.getLocation(); - - // 解析6个坐标参数(x1, y1, z1, x2, y2, z2) - double x1 = parseCoordinate(parts[1], playerLoc.getX()); - double y1 = parseCoordinate(parts[2], playerLoc.getY()); - double z1 = parseCoordinate(parts[3], playerLoc.getZ()); - double x2 = parseCoordinate(parts[4], playerLoc.getX()); - double y2 = parseCoordinate(parts[5], playerLoc.getY()); - double z2 = parseCoordinate(parts[6], playerLoc.getZ()); - - // 过滤无效坐标(如玩家输入错误格式) - if (Double.isNaN(x1) || Double.isNaN(y1) || Double.isNaN(z1) || - Double.isNaN(x2) || Double.isNaN(y2) || Double.isNaN(z2)) { - return; - } - - // 计算三个轴上的距离(取绝对值) - double dx = Math.abs(x2 - x1); - double dy = Math.abs(y2 - y1); - double dz = Math.abs(z2 - z1); - - // 计算填充的方块总数(体积):(x方向格数) * (y方向格数) * (z方向格数) - // 加1是因为包含两个端点坐标的方块 - long volume = (long) (Math.ceil(dx) + 1) * - (long) (Math.ceil(dy) + 1) * - (long) (Math.ceil(dz) + 1); - - // 如果体积超过50格,取消命令并提示 - if (volume > 50) { - event.setCancelled(true); - player.sendMessage(ChatColor.RED+"警告"+ChatColor.WHITE+" | "+ChatColor.RED+"您有疑似攻击服务器行为!请立即停止!"); - } - } - - // 解析坐标(处理相对坐标~和绝对坐标) - private double parseCoordinate(String coordStr, double playerCoord) { - if (coordStr.startsWith("~")) { - // 相对坐标:~表示玩家当前坐标,~10表示当前坐标+10 - if (coordStr.length() == 1) { - return playerCoord; // 只有~,直接返回玩家当前坐标 - } else { - try { - // 解析~后面的偏移量(如~-5.2) - double offset = Double.parseDouble(coordStr.substring(1)); - return playerCoord + offset; - } catch (NumberFormatException e) { - return Double.NaN; // 格式错误,返回无效值 - } - } - } else { - // 绝对坐标:直接解析数字 - try { - return Double.parseDouble(coordStr); - } catch (NumberFormatException e) { - return Double.NaN; // 格式错误,返回无效值 - } - } - } -} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/event/ChatEvent.java b/src/main/java/org/xgqy/survival/event/ChatEvent.java index 00ecc91..b01ee6e 100644 --- a/src/main/java/org/xgqy/survival/event/ChatEvent.java +++ b/src/main/java/org/xgqy/survival/event/ChatEvent.java @@ -10,8 +10,13 @@ import org.bukkit.scoreboard.Objective; import org.bukkit.scoreboard.Scoreboard; import org.xgqy.survival.Survival; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; public class ChatEvent implements Listener { private static final String ADMIN_OBJECTIVE = "administrator"; @@ -20,98 +25,203 @@ public class ChatEvent implements Listener { private static final long SHORT_INTERVAL_MUTE_TIME = 10000; private static final long SAME_MESSAGE_MUTE_TIME = 15000; private static final long CLEAR_INTERVAL = 30000; + private static final long FORBIDDEN_MUTE_TIME = 30 * 1000; // 违禁词禁言30秒 + private static final int CREDIT_DEDUCTION = 5; // 扣除信用分数 - private Map lastChatTime = new HashMap<>(); - private Map lastMessage = new HashMap<>(); - private Map muteEndTime = new HashMap<>(); + // 核心违禁词库(可根据需求扩展,建议从配置文件读取) + private static final Set FORBIDDEN_WORDS = new HashSet<>(Arrays.asList( + "草", "操", "艹", "妈", "他妈", "你妈", "傻逼", "煞笔", "傻屌", "屌", "鸡巴", "jb", "j8", + "卖淫", "嫖娼", "毒品", "大麻", "冰毒", "k粉", "邪教", "反动", "分裂", "台独", "港独", "藏独", + "日", "干", "操你妈", "cnm", "nmd", "tmd", "mlgb", "草泥马", "草拟吗", "淫", "毒", "邪", "反", + "裂", "独", "法西斯", "恐怖", "炸弹", "杀人", "抢劫", "强奸", "赌博", "诈骗", "传销", "邪教组织" + )); - private Survival plugin; + // 常见谐音/变体映射(覆盖拼音、符号替换等规避手段) + private static final Map HOMOPHONE_MAP = new HashMap<>() {{ + put("cao", "草"); put("ca", "草"); put("cao", "操"); put("艹", "草"); + put("ma", "妈"); put("mama", "妈妈"); put("mam", "妈"); + put("gan", "干"); put("gai", "干"); put("ga", "干"); + put("ri", "日"); put("r", "日"); + put("tmd", "他妈的"); put("nmd", "你妈的"); put("nm", "你妈"); + put("sb", "傻逼"); put("sha", "傻"); put("bi", "逼"); put("shabi", "傻逼"); put("煞笔", "傻逼"); + put("mlgb", "妈了个逼"); put("cnm", "草你妈"); put("caonima", "草你妈"); + put("草泥马", "草你妈"); put("草拟吗", "草你妈"); put("caoni", "草你"); + put("diao", "屌"); put("dia", "屌"); put("jb", "鸡巴"); put("j8", "鸡巴"); put("jj", "鸡鸡"); + put("piao", "嫖"); put("piaochang", "嫖娼"); put("maiyin", "卖淫"); + put("du", "毒"); put("duming", "毒品"); put("dama", "大麻"); put("bingdu", "冰毒"); + put("xiejiao", "邪教"); put("fandong", "反动"); put("fenlie", "分裂"); + put("taidu", "台独"); put("gangdu", "港独"); put("zangdu", "藏独"); + }}; + + // 需过滤的分隔符正则(匹配所有非文字字符,包括空格、符号、数字等) + private static final Pattern SEPARATOR_PATTERN = Pattern.compile("[^a-zA-Z\u4e00-\u9fa5]"); + + private final Map lastChatTime = new HashMap<>(); + private final Map lastMessage = new HashMap<>(); + private final Map muteEndTime = new HashMap<>(); + private final Survival plugin; public ChatEvent(Survival plugin) { this.plugin = plugin; - // 启动定时任务,每1秒检查一次 + // 定时清理历史聊天记录(每30秒) new BukkitRunnable() { @Override public void run() { long currentTime = System.currentTimeMillis(); - for (Map.Entry entry : lastChatTime.entrySet()) { - Player player = entry.getKey(); - long lastTime = entry.getValue(); - if (currentTime - lastTime > CLEAR_INTERVAL) { - lastChatTime.remove(player); + Iterator> iterator = lastChatTime.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (currentTime - entry.getValue() > CLEAR_INTERVAL) { + Player player = entry.getKey(); + iterator.remove(); lastMessage.remove(player); } } } - }.runTaskTimer(this.plugin, 0L, 20L); // 20L表示每1秒执行一次 + }.runTaskTimer(this.plugin, 0L, 20L * 30); // 30秒执行一次(20tick=1秒) } @EventHandler public void onPlayerChat(AsyncPlayerChatEvent event) { Player player = event.getPlayer(); + String originalMessage = event.getMessage().trim(); - // 检查玩家是否处于禁言状态 - if (muteEndTime.containsKey(player) && System.currentTimeMillis() < muteEndTime.get(player)) { + // 1. 优先检查禁言状态(所有玩家通用) + if (isMuted(player)) { long remainingSeconds = (muteEndTime.get(player) - System.currentTimeMillis()) / 1000; player.sendMessage( - ChatColor.RED + "禁言 |" + ChatColor.WHITE + " " + - ChatColor.GRAY + "请不要重复发送相同消息 (你需要等待: " + - ChatColor.DARK_RED + remainingSeconds + "s" + - ChatColor.GRAY + "才能发言)" + ChatColor.RED + "禁言提示 |" + ChatColor.WHITE + " " + + ChatColor.GRAY + "你因违规发言被禁言,剩余时间: " + + ChatColor.DARK_RED + remainingSeconds + "s" ); event.setCancelled(true); return; } - Scoreboard scoreboard = player.getScoreboard(); - boolean isAdmin = false; - Objective adminObj = scoreboard.getObjective(ADMIN_OBJECTIVE); - if (adminObj != null && adminObj.getScore(player.getName()).getScore() == 1) { - isAdmin = true; + // 2. 严格违禁词检测(管理员也需检查,无豁免) + if (containsForbiddenContent(originalMessage)) { + handleForbiddenViolation(player); + // 广播屏蔽后的消息 + String maskedMessage = ChatColor.GRAY + player.getPlayerListName() + ": " + ChatColor.RED + "***"; + player.getServer().broadcastMessage(maskedMessage); + event.setCancelled(true); + return; } - // 仅非管理员受消息长度限制(管理员忽略此检查) - String message = event.getMessage(); - if (!isAdmin && message.length() > MAX_MESSAGE_LENGTH) { - event.setCancelled(true); + // 3. 管理员判断(后续规则仅普通玩家受限) + boolean isAdmin = isAdministrator(player); + + // 4. 非管理员 - 消息长度限制 + if (!isAdmin && originalMessage.length() > MAX_MESSAGE_LENGTH) { player.sendMessage( - ChatColor.AQUA + "无法发言 |" + ChatColor.WHITE + ": " + - ChatColor.RED + "消息过长(" + - ChatColor.YELLOW + "非管理员仅可发送 30 字符以内的消息(String.message.length=" + message.length() + ")" + - ChatColor.RED + ")" + ChatColor.AQUA + "发言限制 |" + ChatColor.WHITE + ": " + + ChatColor.RED + "消息过长(非管理员仅可发送" + MAX_MESSAGE_LENGTH + "字符以内)" + + ChatColor.GRAY + "(当前长度: " + originalMessage.length() + ")" ); + event.setCancelled(true); return; } - // 检查发言间隔和发言内容 + // 5. 非管理员 - 发言间隔和重复消息检测 if (!isAdmin) { long currentTime = System.currentTimeMillis(); + // 发言速度检测 if (lastChatTime.containsKey(player)) { long interval = currentTime - lastChatTime.get(player); if (interval < SHORT_INTERVAL) { muteEndTime.put(player, currentTime + SHORT_INTERVAL_MUTE_TIME); player.sendMessage( - ChatColor.RED + "无法发言 |" + ChatColor.WHITE + " " + - ChatColor.GRAY + "你的发言太快了,你需要等待一会儿才能继续发言" - ); - event.setCancelled(true); - return; - } - if (lastMessage.containsKey(player) && lastMessage.get(player).equals(message)) { - muteEndTime.put(player, currentTime + SAME_MESSAGE_MUTE_TIME); - player.sendMessage( - ChatColor.RED + "无法发言 |" + ChatColor.WHITE + " " + - ChatColor.RED + "请不要发送相同的消息,你需要等待一会儿才能继续发言" + ChatColor.RED + "发言限制 |" + ChatColor.WHITE + " " + + ChatColor.GRAY + "发言速度过快,请等待1秒后再试" ); event.setCancelled(true); return; } } + // 重复消息检测 + if (lastMessage.containsKey(player) && lastMessage.get(player).equals(originalMessage)) { + muteEndTime.put(player, currentTime + SAME_MESSAGE_MUTE_TIME); + player.sendMessage( + ChatColor.RED + "发言限制 |" + ChatColor.WHITE + " " + + ChatColor.GRAY + "禁止发送重复消息,请等待15秒后再试" + ); + event.setCancelled(true); + return; + } + // 更新历史记录 lastChatTime.put(player, currentTime); - lastMessage.put(player, message); + lastMessage.put(player, originalMessage); } - String formattedMessage = player.getPlayerListName() + ":" + message; + + // 6. 正常消息格式化发送 + String formattedMessage = ChatColor.WHITE + player.getPlayerListName() + ": " + ChatColor.GRAY + originalMessage; event.setCancelled(true); player.getServer().broadcastMessage(formattedMessage); } -} + + /** + * 严格违禁词检测核心方法(覆盖多种规避手段) + * 处理流程:1. 清理文本 → 2. 谐音替换 → 3. 违禁词匹配 + */ + private boolean containsForbiddenContent(String text) { + if (text == null || text.isEmpty()) return false; + + // 步骤1:清理文本(移除所有分隔符、数字、符号,保留中英文) + String cleanedText = SEPARATOR_PATTERN.matcher(text).replaceAll(""); + if (cleanedText.isEmpty()) return false; + + // 步骤2:谐音/变体替换(将拼音、符号变体转为核心违禁词) + String convertedText = cleanedText.toLowerCase(); + for (Map.Entry entry : HOMOPHONE_MAP.entrySet()) { + convertedText = convertedText.replace(entry.getKey(), entry.getValue()); + } + + // 步骤3:检测是否包含任何违禁词(子串匹配,覆盖组合词) + for (String forbiddenWord : FORBIDDEN_WORDS) { + if (convertedText.contains(forbiddenWord)) { + return true; + } + } + + return false; + } + + /** + * 处理违禁词违规:扣除信用分 + 30秒禁言 + 提示 + */ + private void handleForbiddenViolation(Player player) { + long currentTime = System.currentTimeMillis(); + // 1. 设置30秒禁言 + muteEndTime.put(player, currentTime + FORBIDDEN_MUTE_TIME); + + // 2. 扣除5点信用分(同步执行命令,避免异步线程问题) + BukkitRunnable runnable = new BukkitRunnable() { + @Override + public void run() { + String command = "point " + player.getName() + " remove " + CREDIT_DEDUCTION; + // 用控制台执行命令,确保权限足够 + plugin.getServer().dispatchCommand(plugin.getServer().getConsoleSender(), command); + } + }; + runnable.runTask(plugin); // 同步到主线程执行 + } + + /** + * 检查玩家是否处于禁言状态 + */ + private boolean isMuted(Player player) { + if (!muteEndTime.containsKey(player)) return false; + return System.currentTimeMillis() < muteEndTime.get(player); + } + + /** + * 检查玩家是否为管理员(通过计分板标记) + */ + private boolean isAdministrator(Player player) { + Scoreboard scoreboard = player.getScoreboard(); + Objective adminObj = scoreboard.getObjective(ADMIN_OBJECTIVE); + if (adminObj == null) return false; + // 计分板分数为1表示管理员 + return adminObj.getScore(player.getName()).getScore() == 1; + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/event/ForceSurvival.java b/src/main/java/org/xgqy/survival/event/ForceSurvival.java new file mode 100644 index 0000000..f934101 --- /dev/null +++ b/src/main/java/org/xgqy/survival/event/ForceSurvival.java @@ -0,0 +1,17 @@ +package org.xgqy.survival.event; + +import org.bukkit.ChatColor; +import org.bukkit.GameMode; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerGameModeChangeEvent; + +public class ForceSurvival implements Listener { + @EventHandler + private void gamemodechange(PlayerGameModeChangeEvent event){ + if(event.getNewGameMode() != GameMode.SURVIVAL){ + event.getPlayer().setGameMode(GameMode.SURVIVAL); + event.getPlayer().sendMessage(ChatColor.AQUA+"星阁钱语 "+ChatColor.WHITE+"| "+ChatColor.RED+"您不被允许切换游戏模式"); + } + } +} diff --git a/src/main/java/org/xgqy/survival/event/JoinEvent.java b/src/main/java/org/xgqy/survival/event/JoinEvent.java index 6598a44..203a6c6 100644 --- a/src/main/java/org/xgqy/survival/event/JoinEvent.java +++ b/src/main/java/org/xgqy/survival/event/JoinEvent.java @@ -1,6 +1,7 @@ package org.xgqy.survival.event; import org.bukkit.ChatColor; +import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.Player; @@ -10,60 +11,125 @@ import org.bukkit.event.player.PlayerJoinEvent; import org.xgqy.survival.PlayerTags; import org.xgqy.survival.Survival; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; public class JoinEvent implements Listener { private Survival plugin; + // 名称违禁词库(覆盖常见违规词汇,含中文、英文、缩写,统一小写存储) + private static final Set NAME_FORBIDDEN_WORDS = new HashSet<>(Arrays.asList( + "傻逼", "煞笔", "傻屌", "屌", "操", "草", "艹", "妈蛋", "你妈", "他妈", + "cnm", "nmd", "tmd", "mlgb", "sb", "shabi", "cao", "gan", "ri", + "鸡巴", "jb", "j8", "嫖娼", "卖淫", "毒品", "大麻", "邪教", "反动", + "分裂", "台独", "港独", "藏独", "法西斯", "恐怖", "炸弹", "杀人", + "抢劫", "强奸", "赌博", "诈骗", "传销", "淫", "毒", "邪", "反", "裂", "独" + )); + public JoinEvent(Survival plugin) { this.plugin = plugin; } @EventHandler private void join(PlayerJoinEvent e) { - if(plugin.ppoint.getOrDefault(e.getPlayer(),100) <= 0){ - e.getPlayer().kickPlayer(ChatColor.RED+"您好! 您由于 信用分小于 0 ,我们决定对你的账号 "+ e.getPlayer().getName() +" 采取\n"+ChatColor.RED+ChatColor.BOLD+"永久封禁\n"+ChatColor.RED+"措施,如果您想解封您的账号,请到QQ群 717903781 申诉\n\n"+ChatColor.RED+"如果您对本次处罚不满,请采取以下措施:\n"+ChatColor.YELLOW+"1.如果您对自己的行为"+ChatColor.BOLD+" 问心无愧 "+ChatColor.YELLOW+"请立即向QQ 2213866559 发送解封申请\n"+ChatColor.YELLOW+"2.如果您属实有违规行为,请"+ChatColor.BOLD+"手写"+ChatColor.YELLOW+"一篇 "+ChatColor.BOLD+"100字以上且AIGC合格"+ChatColor.YELLOW+" 的检讨发送到申诉QQ群,态度诚恳我们将会对你进行解封"); - } - if(plugin.ppoint.getOrDefault(e.getPlayer(),100) == 100){ - plugin.ppoint.put(e.getPlayer(),100); - } - PlayerTags playertags = plugin.getPlayerTags(); - List tags = playertags.getTags(e.getPlayer()); - if (!tags.isEmpty() && playertags.getCurrentTag(e.getPlayer()) != -1) { - e.getPlayer().setDisplayName(ChatColor.WHITE + "[" + tags.get(playertags.getCurrentTag(e.getPlayer())) + ChatColor.WHITE + "]" + e.getPlayer().getName()); - e.getPlayer().setPlayerListName(ChatColor.WHITE + "[" + tags.get(playertags.getCurrentTag(e.getPlayer())) + ChatColor.WHITE + "]" + e.getPlayer().getName()); - } else { - if (!tags.isEmpty()) - e.getPlayer().sendMessage(ChatColor.RED + "你还没有选择任何称号!已自动设置为第一个称号。可输入 /tag 进行切换"); - } Player player = e.getPlayer(); - if(player.getScoreboard().getObjective("logged").getScore(player).getScore() == 0){ + + // ########## 新增:名称违禁词检测(优先执行,违规直接踢除)########## + if (containsForbiddenWord(player.getName())) { + String kickMessage = ChatColor.RED + "=====================================\n" + + ChatColor.RED+ "登录失败: 违规用户名\n"+ + ChatColor.RED + "====================================="; + player.kickPlayer(kickMessage); + e.setJoinMessage(null); // 取消违规名称的加入广播 + return; // 终止后续所有逻辑执行 + } + + // 原有逻辑:强制生存模式 + if (player.getGameMode() != GameMode.SURVIVAL) { + player.setGameMode(GameMode.SURVIVAL); + player.sendMessage(ChatColor.AQUA + "星阁钱语 " + ChatColor.WHITE + "| " + ChatColor.RED + "您的游戏模式已经被强制切换为 生存模式"); + } + + // 原有逻辑:信用分<=0 永久封禁 + if (plugin.ppoint.getOrDefault(player, 100) <= 0) { + player.kickPlayer(ChatColor.RED + "您好! 您由于 信用分小于 0 ,我们决定对你的账号 " + player.getName() + " 采取\n" + ChatColor.RED + ChatColor.BOLD + "永久封禁\n" + ChatColor.RED + "措施,如果您想解封您的账号,请到QQ群 717903781 申诉\n\n" + ChatColor.RED + "如果您对本次处罚不满,请采取以下措施:\n" + ChatColor.YELLOW + "1.如果您对自己的行为" + ChatColor.BOLD + " 问心无愧 " + ChatColor.YELLOW + "请立即向QQ 2213866559 发送解封申请\n" + ChatColor.YELLOW + "2.如果您属实有违规行为,请" + ChatColor.BOLD + "手写" + ChatColor.YELLOW + "一篇 " + ChatColor.BOLD + "100字以上且AIGC合格" + ChatColor.YELLOW + " 的检讨发送到申诉QQ群,态度诚恳我们将会对你进行解封"); + return; + } + + // 原有逻辑:初始化信用分(默认100) + if (plugin.ppoint.getOrDefault(player, 100) == 100) { + plugin.ppoint.put(player, 100); + } + + // 原有逻辑:称号设置 + PlayerTags playertags = plugin.getPlayerTags(); + List tags = playertags.getTags(player); + if (!tags.isEmpty()) { + if (playertags.getCurrentTag(player) != -1) { + player.setDisplayName(ChatColor.WHITE + "[" + tags.get(playertags.getCurrentTag(player)) + ChatColor.WHITE + "]" + player.getName()); + player.setPlayerListName(ChatColor.WHITE + "[" + tags.get(playertags.getCurrentTag(player)) + ChatColor.WHITE + "]" + player.getName()); + } else { + player.sendMessage(ChatColor.RED + "你还没有选择任何称号!已自动设置为第一个称号。可输入 /tag 进行切换"); + // 补充:自动设置第一个称号(原逻辑只提示未设置,此处优化体验) + playertags.setSelectedTag(player,0); + player.setDisplayName(ChatColor.WHITE + "[" + tags.get(0) + ChatColor.WHITE + "]" + player.getName()); + player.setPlayerListName(ChatColor.WHITE + "[" + tags.get(0) + ChatColor.WHITE + "]" + player.getName()); + } + } + + // 原有逻辑:首次登录随机传送 + if (player.getScoreboard().getObjective("logged") != null && player.getScoreboard().getObjective("logged").getScore(player).getScore() == 0) { double x = Math.random() * 10000; double z = Math.random() * 10000; int bx = (int) x, bz = (int) z; - while(true) { - if((x <= 150 && x >= -100) || (z <= 150 && z >= -180)){ + + // 修正:原循环逻辑存在死循环风险,优化条件判断 + while (true) { + // 避开出生点区域(-100~150 X/Z范围) + boolean inSpawnArea = (x >= -100 && x <= 150) || (z >= -180 && z <= 150); + // 确保最高方块不是水/ lava + Material highestBlock = player.getWorld().getHighestBlockAt(bx, bz).getType(); + boolean isDangerous = highestBlock == Material.WATER || highestBlock == Material.LAVA; + + if (inSpawnArea || isDangerous) { x = Math.random() * 10000; z = Math.random() * 10000; bx = (int) x; bz = (int) z; - }else if(player.getWorld().getHighestBlockAt(bx,bz).getType() != Material.WATER && - player.getWorld().getHighestBlockAt(bx,bz).getType() != Material.LAVA){ - x = Math.random() * 10000; - z = Math.random() * 10000; - bx = (int) x; - bz = (int) z; - }else{ + } else { break; } } - player.teleport(new Location(player.getWorld(),x,player.getWorld().getHighestBlockYAt(bx,bz),z)); - //player.sendMessage(ChatColor.GREEN+"你好,欢迎!"); + + int highestY = player.getWorld().getHighestBlockYAt(bx, bz); + Location spawnLoc = new Location(player.getWorld(), x, highestY + 1, z); // +1避免卡在方块里 + player.teleport(spawnLoc); player.getScoreboard().getObjective("logged").getScore(player).setScore(1); + player.setRespawnLocation(spawnLoc); } - e.setJoinMessage(e.getPlayer().getPlayerListName() + " 加入了 生存1区"); - e.getPlayer().sendMessage(ChatColor.YELLOW + "欢迎来到 星阁钱语 生存服!"); - e.getPlayer().sendMessage(ChatColor.YELLOW + "你可以输入 /help 来查看帮助"); - e.getPlayer().sendTitle(ChatColor.YELLOW + "欢迎来到 -生存1区-", ChatColor.AQUA + "星阁钱语", 20, 80, 20); + + // 原有逻辑:加入消息和欢迎提示 + e.setJoinMessage(player.getPlayerListName() + " 加入了 生存1区"); + player.sendMessage(ChatColor.YELLOW + "欢迎来到 星阁钱语 生存服!"); + player.sendMessage(ChatColor.YELLOW + "你可以输入 /help 来查看帮助"); + player.sendTitle(ChatColor.YELLOW + "欢迎来到 -生存1区-", ChatColor.AQUA + "星阁钱语", 20, 80, 20); } -} + + /** + * 核心检测:判断玩家名称是否包含违禁词 + * 特性:大小写不敏感、忽略空格、子串匹配(覆盖变体规避) + */ + private boolean containsForbiddenWord(String playerName) { + // 预处理:转为小写 + 移除所有空格(应对"傻 逼"这类分隔规避) + String processedName = playerName.toLowerCase().replaceAll("\\s+", ""); + // 遍历违禁词库,检测是否包含子串 + for (String forbiddenWord : NAME_FORBIDDEN_WORDS) { + if (processedName.contains(forbiddenWord)) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 73a3bf4..2021d25 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -80,7 +80,7 @@ commands: land: description: change point usage: / - selfkill: + home: description: self kill usage: / notice: