From a375365d29b641c3a9d71951c9944f66364cd7c2 Mon Sep 17 00:00:00 2001 From: ch2012enyc Date: Sun, 2 Nov 2025 19:29:09 +0800 Subject: [PATCH] update all --- .../java/org/xgqy/survival/FriendData.java | 31 ++ .../command/FriendCommandExecutor.java | 486 ++++++++++++++++++ .../command/InventoryCommandExecutor.java | 114 ++++ .../survival/command/KillCommandExecutor.java | 29 ++ .../survival/command/LandCommandExecutor.java | 237 +++++++++ .../command/LoginCommandExecutor.java | 54 ++ .../command/PartyCommandExecutor.java | 386 ++++++++++++++ .../survival/command/PcCommandExecutor.java | 65 +++ .../command/PointCommandExecutor.java | 54 ++ .../survival/command/QdCommandExecutor.java | 237 +++++++++ .../survival/command/RegCommandExecutor.java | 69 +++ .../survival/command/msgCommandExecutor.java | 86 ++++ .../org/xgqy/survival/event/AntiExploit.java | 88 ++++ .../xgqy/survival/event/CommandLimiter.java | 128 +++++ .../org/xgqy/survival/event/LandEvent.java | 266 ++++++++++ .../org/xgqy/survival/event/LoginEvent.java | 264 ++++++++++ .../org/xgqy/survival/event/QuitEvent.java | 71 +++ 17 files changed, 2665 insertions(+) create mode 100644 src/main/java/org/xgqy/survival/FriendData.java create mode 100644 src/main/java/org/xgqy/survival/command/FriendCommandExecutor.java create mode 100644 src/main/java/org/xgqy/survival/command/InventoryCommandExecutor.java create mode 100644 src/main/java/org/xgqy/survival/command/KillCommandExecutor.java create mode 100644 src/main/java/org/xgqy/survival/command/LandCommandExecutor.java create mode 100644 src/main/java/org/xgqy/survival/command/LoginCommandExecutor.java create mode 100644 src/main/java/org/xgqy/survival/command/PartyCommandExecutor.java create mode 100644 src/main/java/org/xgqy/survival/command/PcCommandExecutor.java create mode 100644 src/main/java/org/xgqy/survival/command/PointCommandExecutor.java create mode 100644 src/main/java/org/xgqy/survival/command/QdCommandExecutor.java create mode 100644 src/main/java/org/xgqy/survival/command/RegCommandExecutor.java create mode 100644 src/main/java/org/xgqy/survival/command/msgCommandExecutor.java create mode 100644 src/main/java/org/xgqy/survival/event/AntiExploit.java create mode 100644 src/main/java/org/xgqy/survival/event/CommandLimiter.java create mode 100644 src/main/java/org/xgqy/survival/event/LandEvent.java create mode 100644 src/main/java/org/xgqy/survival/event/LoginEvent.java create mode 100644 src/main/java/org/xgqy/survival/event/QuitEvent.java diff --git a/src/main/java/org/xgqy/survival/FriendData.java b/src/main/java/org/xgqy/survival/FriendData.java new file mode 100644 index 0000000..0552e47 --- /dev/null +++ b/src/main/java/org/xgqy/survival/FriendData.java @@ -0,0 +1,31 @@ +package org.xgqy.survival; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +// 实现Serializable接口并添加版本号(关键修复) +public class FriendData implements Serializable { + private static final long serialVersionUID = 1L; // 序列化版本号 + + private Map> friends; + private Map> waiting; + + // getter和setter + public Map> getFriends() { + return friends; + } + + public void setFriends(Map> friends) { + this.friends = friends; + } + + public Map> getWaiting() { + return waiting; + } + + public void setWaiting(Map> waiting) { + this.waiting = waiting; + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/command/FriendCommandExecutor.java b/src/main/java/org/xgqy/survival/command/FriendCommandExecutor.java new file mode 100644 index 0000000..b2f8b2a --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/FriendCommandExecutor.java @@ -0,0 +1,486 @@ +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 org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +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.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +public class FriendCommandExecutor implements CommandExecutor { + + 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; + + public FriendCommandExecutor(Survival plugin) { + this.plugin = plugin; + this.friendDataFile = new File(plugin.getDataFolder(), "friend_data.ser"); + loadFriendData(); // 加载数据 + } + + // 提供外部访问任务的方法(用于插件禁用时清理) + public Map getTimeoutTasks() { + return timeoutTasks; + } + + public Map getDeleteTimeoutTasks() { + return deleteTimeoutTasks; + } + + @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 + "❌ 无法对非玩家类使用该命令!"); + 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); + return true; + } + + // 基础参数校验 + if (args.length < 1) { + sendUsage(player); + return true; + } + + 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("好友数据文件不存在,初始化空数据!"); + return; + } + + try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(friendDataFile))) { + FriendData data = (FriendData) ois.readObject(); + Map> uuidFriends = data.getFriends(); + Map> uuidWaiting = data.getWaiting(); + + // 转换好友数据: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()); + } + } + + /** + * 保存好友数据(使用副本遍历避免并发异常) + */ + 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 handleList(Player player) { + plugin.friends.computeIfAbsent(player, k -> new ArrayList<>()); + List friends = plugin.friends.get(player); + + if (friends.isEmpty()) { + player.sendMessage(ChatColor.RED + "📭 你的好友列表为空!使用 /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(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 + "❌ 该玩家不在你的好友列表中!"); + return; + } + + if (pendingDelete.containsKey(player)) { + player.sendMessage(ChatColor.RED + "❌ 你有未完成的删除确认请求,请先处理!"); + return; + } + + pendingDelete.put(player, target); + player.sendMessage(ChatColor.YELLOW + "⚠️ 确认要删除好友 " + target.getName() + " 吗?(10秒内有效)"); + + 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); + + // 设置超时任务 + 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); + } + + /** + * 处理删除确认/取消操作 + */ + 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 + "❌ 无对应的删除确认请求!"); + return; + } + + if (deleteTimeoutTasks.containsKey(player)) { + deleteTimeoutTasks.get(player).cancel(); + deleteTimeoutTasks.remove(player); + } + + if (action.equalsIgnoreCase("yes")) { + handleRemove(player, targetName); + } else if (action.equalsIgnoreCase("no")) { + player.sendMessage(ChatColor.GREEN + "✅ 已取消删除好友 " + target.getName() + "!"); + } + + pendingDelete.remove(player); + } + + /** + * 处理添加好友命令(添加异常捕获) + */ + private void handleAdd(Player requester, Player target) { + try { + if (requester.equals(target)) { + requester.sendMessage(ChatColor.RED + "❌ 无法添加自己为好友!"); + return; + } + + plugin.friends.computeIfAbsent(requester, k -> new ArrayList<>()); + plugin.friends.computeIfAbsent(target, k -> new ArrayList<>()); + plugin.waiting.computeIfAbsent(target, k -> new ArrayList<>()); + + if (plugin.friends.get(requester).contains(target)) { + requester.sendMessage(ChatColor.RED + "❌ " + target.getName() + " 已是你的好友!"); + return; + } + + if (plugin.waiting.get(target).contains(requester)) { + requester.sendMessage(ChatColor.RED + "⌛ 你已向 " + target.getName() + " 发送过好友请求,等待对方确认中!"); + return; + } + + if (plugin.waiting.get(requester).contains(target)) { + requester.sendMessage(ChatColor.YELLOW + "📩 " + target.getName() + " 已向你发送好友请求,请使用 /friend allow " + target.getName() + " 同意!"); + return; + } + + // 发送请求 + 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() + " 拒绝"); + + // 生成按钮 + 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); + + // 超时任务(使用唯一键精准管理) + 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); + + saveFriendData(); + } catch (Exception e) { + requester.sendMessage(ChatColor.RED + "❌ 添加好友失败!请联系管理员。"); + plugin.getLogger().severe("添加好友异常(" + requester.getName() + " -> " + target.getName() + "):" + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * 处理删除好友命令 + */ + private void handleRemove(Player player, String targetName) { + Player target = Bukkit.getPlayerExact(targetName); + plugin.friends.computeIfAbsent(player, k -> new ArrayList<>()); + + 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; + } + } + 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]); + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/command/InventoryCommandExecutor.java b/src/main/java/org/xgqy/survival/command/InventoryCommandExecutor.java new file mode 100644 index 0000000..f56e0c6 --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/InventoryCommandExecutor.java @@ -0,0 +1,114 @@ +package org.xgqy.survival.command; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.jetbrains.annotations.NotNull; +import org.xgqy.survival.Survival; + +public class InventoryCommandExecutor implements CommandExecutor { + + private final Survival plugin; + + public InventoryCommandExecutor(Survival plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + // 检查命令发送者是否为玩家 + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "此命令只能由玩家执行!"); + return true; + } + Player senderPlayer = (Player) sender; + + // 检查权限(OP) + if (!senderPlayer.isOp()) { + sender.sendMessage(ChatColor.RED + "你没有权限使用此命令!"); + return true; + } + + // 检查参数是否正确 + if (args.length < 1) { + sender.sendMessage(ChatColor.RED + "用法: /inventory <玩家名>"); + return true; + } + + // 获取目标玩家 + Player targetPlayer = Bukkit.getPlayer(args[0]); + if (targetPlayer == null || !targetPlayer.isOnline()) { + sender.sendMessage(ChatColor.RED + "目标玩家不存在或未在线!"); + return true; + } + + // 创建显示用的GUI(54格=6行×9列) + Inventory gui = Bukkit.createInventory( + null, + 54, + targetPlayer.getDisplayName() + ChatColor.WHITE + " 的背包" + ); + + PlayerInventory targetInv = targetPlayer.getInventory(); + + // 1. 填充主背包(上3行:0-26格) + for (int i = 0; i < 27; i++) { + ItemStack item = targetInv.getItem(i); + gui.setItem(i, getItemOrBarrier(item)); + } + + // 2. 第4行:分割线(淡灰色玻璃板,27-35格) + ItemStack divider = new ItemStack(Material.LIGHT_GRAY_STAINED_GLASS_PANE); + for (int i = 27; i < 36; i++) { + gui.setItem(i, divider); + } + + // 3. 第5行:快捷栏(36-44格,对应玩家快捷栏27-35格) + for (int i = 27; i < 36; i++) { + ItemStack item = targetInv.getItem(i); + // 快捷栏在GUI中偏移9格(因为第4行占了9格) + gui.setItem(i + 9, getItemOrBarrier(item)); + } + + // 4. 第6行:装备和副手(45-53格) + // 装备槽对应关系:靴子(36)、护腿(37)、胸甲(38)、头盔(39)、副手(40) + ItemStack boots = targetInv.getItem(36); + ItemStack leggings = targetInv.getItem(37); + ItemStack chestplate = targetInv.getItem(38); + ItemStack helmet = targetInv.getItem(39); + ItemStack offhand = targetInv.getItem(40); + + // 放置装备(按常规装备顺序排列) + gui.setItem(45, getItemOrBarrier(boots)); // 靴子 + gui.setItem(46, getItemOrBarrier(leggings)); // 护腿 + gui.setItem(47, getItemOrBarrier(chestplate)); // 胸甲 + gui.setItem(48, getItemOrBarrier(helmet)); // 头盔 + gui.setItem(52, getItemOrBarrier(offhand)); // 副手 + + // 第6行剩余位置用屏障填充 + for (int i = 49; i < 52; i++) { + gui.setItem(i, new ItemStack(Material.BARRIER)); + } + gui.setItem(53, new ItemStack(Material.BARRIER)); + + // 打开GUI给命令发送者 + senderPlayer.openInventory(gui); + return true; + } + + /** + * 工具方法:如果物品为空则返回屏障,否则返回原物品 + */ + private ItemStack getItemOrBarrier(ItemStack item) { + return (item == null || item.getType() == Material.AIR) + ? new ItemStack(Material.AIR) + : item; + } +} \ 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 new file mode 100644 index 0000000..fd79236 --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/KillCommandExecutor.java @@ -0,0 +1,29 @@ +package org.xgqy.survival.command; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.xgqy.survival.Survival; + +public class KillCommandExecutor implements CommandExecutor { + + private Survival plugin; + + public KillCommandExecutor(Survival plugin) { + this.plugin = plugin; + } + + @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 + "无法对非玩家类使用"); + return true; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/command/LandCommandExecutor.java b/src/main/java/org/xgqy/survival/command/LandCommandExecutor.java new file mode 100644 index 0000000..b9023e9 --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/LandCommandExecutor.java @@ -0,0 +1,237 @@ +package org.xgqy.survival.command; + +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.xgqy.survival.Survival; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class LandCommandExecutor implements CommandExecutor { + + private final Survival plugin; + // 临时存储玩家正在创建的领地 + private final Map tempLands = new HashMap<>(); + + public LandCommandExecutor(Survival plugin) { + this.plugin = plugin; + } + + // 判断位置是否在x-z区域内 + public static boolean isLocationBetween(Location target, Location a, Location b) { + if (target == null || a == null || b == null) return false; + if (!Objects.equals(target.getWorld(), a.getWorld()) || !Objects.equals(target.getWorld(), b.getWorld())) { + return false; + } + double minX = Math.min(a.getX(), b.getX()); + double maxX = Math.max(a.getX(), b.getX()); + double minZ = Math.min(a.getZ(), b.getZ()); + double maxZ = Math.max(a.getZ(), b.getZ()); + return target.getX() >= minX && target.getX() <= maxX && target.getZ() >= minZ && target.getZ() <= maxZ; + } + + // 判断两个x-z区域是否重叠 + public static boolean isRegionsTouching(Location loc1, Location loc2, Location loc3, Location loc4) { + if (loc1 == null || loc2 == null || loc3 == null || loc4 == null) return false; + if (!Objects.equals(loc1.getWorld(), loc3.getWorld())) return false; + + double minX1 = Math.min(loc1.getX(), loc2.getX()); + double maxX1 = Math.max(loc1.getX(), loc2.getX()); + double minZ1 = Math.min(loc1.getZ(), loc2.getZ()); + double maxZ1 = Math.max(loc1.getZ(), loc2.getZ()); + + double minX2 = Math.min(loc3.getX(), loc4.getX()); + double maxX2 = Math.max(loc3.getX(), loc4.getX()); + double minZ2 = Math.min(loc3.getZ(), loc4.getZ()); + double maxZ2 = Math.max(loc3.getZ(), loc4.getZ()); + + return maxX1 >= minX2 && maxX2 >= minX1 && maxZ1 >= minZ2 && maxZ2 >= minZ1; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "仅玩家可使用领地命令!"); + return true; + } + Player player = (Player) sender; + + if (args.length == 0) { + sendHelp(player); + return true; + } + + switch (args[0].toLowerCase()) { + case "set1": + handleSet1(player); + break; + case "set2": + handleSet2(player); + break; + case "allow": + handleAllowDisallow(player, args, true); + break; + case "disallow": + handleAllowDisallow(player, args, false); + break; + case "reset": + handleReset(player); + break; + case "protect": + handleProtect(player); + break; + default: + sender.sendMessage(ChatColor.RED + "未知命令!输入 /land 查看帮助。"); + } + + return true; + } + + // 设置领地起点 + private void handleSet1(Player player) { + if (tempLands.containsKey(player)) { + Survival.Land temp = tempLands.get(player); + if (temp.getStart() != null) { + player.sendMessage(ChatColor.RED + "已设置过起点!使用 /land reset 重置。"); + return; + } + } else { + tempLands.put(player, new Survival.Land(null, null, new ArrayList<>(), player.getName(), new HashMap<>())); + } + + Survival.Land temp = tempLands.get(player); + temp.setStart(player.getLocation()); + player.sendMessage(ChatColor.GREEN + "已设置领地起点!使用 /land set2 设置终点。"); + } + + // 设置领地终点(完成创建) + private void handleSet2(Player player) { + if (!tempLands.containsKey(player)) { + player.sendMessage(ChatColor.RED + "请先使用 /land set1 设置起点!"); + return; + } + Survival.Land temp = tempLands.get(player); + if (temp.getStart() == null) { + player.sendMessage(ChatColor.RED + "请先设置起点!"); + return; + } + if (temp.getEnd() != null) { + player.sendMessage(ChatColor.RED + "已设置过终点!使用 /land reset 重置。"); + return; + } + + Location end = player.getLocation(); + temp.setEnd(end); + Location start = temp.getStart(); + + // 检查是否包含出生点 + Location spawn = player.getRespawnLocation() != null ? player.getRespawnLocation() : player.getWorld().getSpawnLocation(); + if (!isLocationBetween(spawn, start, end)) { + tempLands.remove(player); + player.sendMessage(ChatColor.RED + "领地必须包含你的出生点!"); + return; + } + + // 检查与其他领地重叠 + for (Survival.Land land : plugin.land) { + if (land.getOwner() == null) continue; + if (land.getOwner().equals(player.getName())) continue; // 跳过自己的领地 + if (isRegionsTouching(start, end, land.getStart(), land.getEnd())) { + tempLands.remove(player); + player.sendMessage(ChatColor.RED + "与其他玩家领地重叠!"); + return; + } + } + + // 初始化默认权限(7项:破坏、放置、飞行、交互、使用方块、TNT爆炸、玩家伤害) + List defaultPerm = new ArrayList<>(); + for (int i = 0; i < 7; i++) { + defaultPerm.add(false); // 默认禁止所有权限 + } + temp.setDefaultperm(defaultPerm); + + // 添加到正式列表 + plugin.land.add(temp); + tempLands.remove(player); + player.sendMessage(ChatColor.GREEN + "领地创建成功!"); + } + + // 允许/禁止玩家权限 + private void handleAllowDisallow(Player player, String[] args, boolean allow) { + if (args.length < 3) { + player.sendMessage(ChatColor.RED + "用法:/land " + (allow ? "allow" : "disallow") + " <玩家名> <1-7>"); + player.sendMessage(ChatColor.GRAY + "权限索引:1=破坏 2=放置 3=飞行 4=交互 5=使用方块 6=TNT爆炸 7=玩家伤害"); + return; + } + + String targetName = args[1]; + int permIndex; + try { + permIndex = Integer.parseInt(args[2]) - 1; // 转为0基索引 + if (permIndex < 0 || permIndex > 6) throw new NumberFormatException(); + } catch (NumberFormatException e) { + player.sendMessage(ChatColor.RED + "权限索引必须是1-7!"); + return; + } + + // 获取玩家自己的领地 + Survival.Land land = getPlayerLand(player); + if (land == null) { + player.sendMessage(ChatColor.RED + "你没有领地!"); + return; + } + + // 设置权限 + Map> permMap = land.perm; + List targetPerm = permMap.getOrDefault(targetName, new ArrayList<>(land.getDefaultperm())); + while (targetPerm.size() < 7) targetPerm.add(false); // 确保长度 + targetPerm.set(permIndex, allow); + permMap.put(targetName, targetPerm); + + player.sendMessage(ChatColor.GREEN + "已" + (allow ? "允许" : "禁止") + "玩家 " + targetName + " 的 " + getPermName(permIndex) + " 权限!"); + } + + // 重置领地设置 + private void handleReset(Player player) { + tempLands.remove(player); + plugin.land.remove(player); + player.sendMessage(ChatColor.GREEN + "领地设置已重置!"); + } + + // 开关领地保护(示例:控制TNT爆炸) + private void handleProtect(Player player) { + } + private Survival.Land getPlayerLand(Player player) { + for (Survival.Land land : plugin.land) { + if (land.getOwner() != null && land.getOwner().equals(player.getName())) { + return land; + } + } + return null; + } + + // 辅助:获取权限名称 + private String getPermName(int index) { + String[] names = {"破坏", "放置", "飞行", "交互", "使用方块", "TNT爆炸", "玩家伤害"}; + return names[index]; + } + + // 发送帮助信息 + private void sendHelp(Player player) { + player.sendMessage(ChatColor.YELLOW + "===== 领地命令 ====="); + player.sendMessage(ChatColor.GOLD + "/land set1" + ChatColor.WHITE + " - 设置领地起点"); + player.sendMessage(ChatColor.GOLD + "/land set2" + ChatColor.WHITE + " - 设置领地终点(完成创建)"); + player.sendMessage(ChatColor.GOLD + "/land allow <玩家> <1-7>" + ChatColor.WHITE + " - 允许玩家权限"); + player.sendMessage(ChatColor.GOLD + "/land disallow <玩家> <1-7>" + ChatColor.WHITE + " - 禁止玩家权限"); + player.sendMessage(ChatColor.GOLD + "/land reset" + ChatColor.WHITE + " - 重置领地设置"); + player.sendMessage(ChatColor.GOLD + "/land protect" + ChatColor.WHITE + " - 开关超级保护(测试中)"); + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/command/LoginCommandExecutor.java b/src/main/java/org/xgqy/survival/command/LoginCommandExecutor.java new file mode 100644 index 0000000..1dafdf0 --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/LoginCommandExecutor.java @@ -0,0 +1,54 @@ +package org.xgqy.survival.command; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.xgqy.survival.Survival; + +public class LoginCommandExecutor implements CommandExecutor { + private final Survival plugin; + + public LoginCommandExecutor(Survival plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "只有玩家可以使用此命令!"); + return true; + } + + Player player = (Player) sender; + + // 检查玩家是否已登录 + if (plugin.getAccountManager().isLoggedIn(player)) { + player.sendMessage(ChatColor.RED + "你已经登录了!"); + return true; + } + + // 检查参数是否正确 + if (args.length != 1) { + player.sendMessage(ChatColor.RED + "用法: /login <密码>"); + return true; + } + + // 检查玩家是否已注册 + if (!plugin.getAccountManager().isRegistered(player.getUniqueId())) { + player.sendMessage(ChatColor.RED + "你还没有注册! 请使用 /reg <密码> <确认密码> 注册"); + return true; + } + + // 尝试登录 + if (plugin.getAccountManager().login(player, args[0])) { + player.sendMessage(ChatColor.GREEN + "登录成功! 欢迎回来," + player.getName() + "!"); + return true; + } else { + player.sendMessage(ChatColor.RED + "登录失败! 密码不正确"); + player.kickPlayer(ChatColor.RED+"密码错误!"); + return false; + } + } +} diff --git a/src/main/java/org/xgqy/survival/command/PartyCommandExecutor.java b/src/main/java/org/xgqy/survival/command/PartyCommandExecutor.java new file mode 100644 index 0000000..d499cbe --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/PartyCommandExecutor.java @@ -0,0 +1,386 @@ +package org.xgqy.survival.command; + +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.xgqy.survival.Survival; + +import java.util.ArrayList; +import java.util.List; + +public class PartyCommandExecutor implements CommandExecutor { + + private final Survival plugin; + + public PartyCommandExecutor(Survival plugin) { + this.plugin = plugin; + } + + private void colorfulline(Player player) { + player.sendMessage( + ChatColor.DARK_RED + "---" + + ChatColor.RED + "---" + + ChatColor.GREEN + "---" + + ChatColor.BLUE + "---" + + ChatColor.YELLOW + "---" + + ChatColor.LIGHT_PURPLE + "---" + + ChatColor.AQUA + "---" + + ChatColor.LIGHT_PURPLE + "---" + + ChatColor.DARK_PURPLE + "---" + ); + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "无法对非玩家类使用"); + return true; + } + + Player send = (Player) sender; + if (args.length < 1) { + sender.sendMessage(ChatColor.RED + "参数不足!请输入 /party help 查看命令用法"); + return true; + } + + switch (args[0].toLowerCase()) { + case "create": + handleCreate(send); + break; + case "join": + handleJoin(send, args); + break; + case "quit": + handleQuit(send); + break; + case "list": + handleList(send); + break; + case "kick": + handleKick(send, args); + break; + case "ban": + handleBan(send, args); + break; + case "disband": + handleDisband(send); + break; + case "help": + handleHelp(send); + break; + case "check": + send.sendMessage(ChatColor.RED + "无权使用该命令!"); + break; + default: + sender.sendMessage(ChatColor.RED + "参数错误!请输入 /party help 查看命令用法"); + } + return true; + } + + // 创建队伍 + private void handleCreate(Player player) { + if (plugin.party.getOrDefault(player, null) != null) { + player.sendMessage(ChatColor.RED + "您已经加入了一个队伍!"); + return; + } + colorfulline(player); + int pid = (int) (Math.random() * 89999 + 10000); + player.sendMessage(ChatColor.GREEN + "队伍创建成功,队伍编号: " + pid); + plugin.party.put(player, pid); + List playerList = new ArrayList<>(); + playerList.add(player); + plugin.partyp.put(pid, playerList); + plugin.owner.put(pid, player); + colorfulline(player); + } + + // 加入队伍 + private void handleJoin(Player player, String[] args) { + // 检查参数长度 + if (args.length < 2) { + player.sendMessage(ChatColor.RED + "用法错误!正确格式:/party join <队伍号>"); + return; + } + + // 检查是否已加入队伍 + if (plugin.party.getOrDefault(player, null) != null) { + player.sendMessage(ChatColor.RED + "您已经加入了一个队伍!"); + return; + } + + // 解析队伍ID(处理非数字输入) + int pid; + try { + pid = Integer.parseInt(args[1]); + } catch (NumberFormatException e) { + player.sendMessage(ChatColor.RED + "队伍编号必须是数字!"); + return; + } + + // 检查队伍是否存在 + if (plugin.partyp.getOrDefault(pid, null) == null) { + player.sendMessage(ChatColor.RED + "队伍不存在!"); + return; + } + + // 检查是否被ban + List bannedPlayers = plugin.ban.getOrDefault(pid, new ArrayList<>()); + if (bannedPlayers.contains(player)) { + player.sendMessage(ChatColor.RED + "你已被该队伍禁止加入!"); + return; + } + + // 执行加入逻辑 + colorfulline(player); + player.sendMessage(ChatColor.GREEN + "加入队伍成功!"); + colorfulline(player); + + List teamPlayers = plugin.partyp.get(pid); + // 通知队伍成员 + for (Player member : teamPlayers) { + colorfulline(member); + member.sendMessage(player.getDisplayName() + ChatColor.GREEN + " 加入了队伍"); + colorfulline(member); + } + // 添加到队伍 + teamPlayers.add(player); + plugin.partyp.put(pid, teamPlayers); + plugin.party.put(player, pid); + } + + // 退出队伍 + private void handleQuit(Player player) { + Integer pid = plugin.party.getOrDefault(player, null); + if (pid == null) { + player.sendMessage(ChatColor.RED + "您尚未加入队伍!"); + return; + } + + colorfulline(player); + player.sendMessage(ChatColor.RED + "退出队伍成功!"); + colorfulline(player); + + List teamPlayers = plugin.partyp.get(pid); + // 通知队伍成员 + for (Player member : teamPlayers) { + colorfulline(member); + member.sendMessage(player.getDisplayName() + ChatColor.RED + " 退出了队伍"); + colorfulline(member); + } + + // 移除玩家 + teamPlayers.remove(player); + // 如果是队长退出,解散队伍 + if (plugin.owner.getOrDefault(pid, null).equals(player)) { + for (Player member : teamPlayers) { + colorfulline(member); + member.sendMessage(ChatColor.RED + "队伍已解散"); + colorfulline(member); + plugin.party.remove(member); + } + plugin.owner.remove(pid); + plugin.partyp.remove(pid); + plugin.ban.remove(pid); + } else { + plugin.partyp.put(pid, teamPlayers); + } + plugin.party.remove(player); + } + + // 队伍列表 + private void handleList(Player player) { + Integer pid = plugin.party.getOrDefault(player, null); + if (pid == null) { + player.sendMessage(ChatColor.RED + "您还未加入队伍"); + return; + } + + colorfulline(player); + List teamPlayers = plugin.partyp.get(pid); + boolean isOwner = plugin.owner.getOrDefault(pid, null).equals(player); + + for (Player member : teamPlayers) { + ComponentBuilder builder = new ComponentBuilder(member.getDisplayName()); + // 公共传送按钮 + builder.append(ChatColor.GREEN + " [传送到] ") + .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/teleport " + member.getName())); + builder.append(ChatColor.GREEN + "[传送来] ") + .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/teleport " + member.getName() + " me")); + // 队长额外的踢出按钮 + if (isOwner) { + builder.append(ChatColor.RED + "[踢出]") + .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/party kick " + member.getName())); + } + player.spigot().sendMessage(builder.create()); + } + colorfulline(player); + } + + // 踢出玩家 + private void handleKick(Player sender, String[] args) { + // 检查参数长度 + if (args.length < 2) { + sender.sendMessage(ChatColor.RED + "用法错误!正确格式:/party kick <玩家名>"); + return; + } + + Integer pid = plugin.party.getOrDefault(sender, null); + if (pid == null) { + sender.sendMessage(ChatColor.RED + "您还未加入队伍"); + return; + } + + // 检查是否为队长 + if (!plugin.owner.getOrDefault(pid, null).equals(sender)) { + sender.sendMessage(ChatColor.RED + "你无权使用该命令!"); + return; + } + + // 检查目标玩家 + Player target = Bukkit.getPlayer(args[1]); + if (target == null || !target.isOnline()) { + sender.sendMessage(ChatColor.RED + "玩家不在线或不存在!"); + return; + } + + List teamPlayers = plugin.partyp.get(pid); + // 修正:判断目标是否在队伍内(原逻辑颠倒) + if (!teamPlayers.contains(target)) { + sender.sendMessage(ChatColor.RED + "队伍内无该玩家!"); + return; + } + + // 执行踢出逻辑 + colorfulline(sender); + // 通知被踢出玩家 + colorfulline(target); + target.sendMessage(ChatColor.RED + "您已被踢出队伍!"); + colorfulline(target); + + // 更新数据 + plugin.party.remove(target); + teamPlayers.remove(target); + plugin.partyp.put(pid, teamPlayers); + + // 通知其他成员 + for (Player member : teamPlayers) { + colorfulline(member); + member.sendMessage(target.getDisplayName() + ChatColor.RED + " 已被踢出队伍"); + colorfulline(member); + } + colorfulline(sender); + } + + // 禁止玩家加入 + private void handleBan(Player sender, String[] args) { + // 检查参数长度 + if (args.length < 2) { + sender.sendMessage(ChatColor.RED + "用法错误!正确格式:/party ban <玩家名>"); + return; + } + + Integer pid = plugin.party.getOrDefault(sender, null); + if (pid == null) { + sender.sendMessage(ChatColor.RED + "您还未加入队伍"); + return; + } + + // 检查是否为队长 + if (!plugin.owner.getOrDefault(pid, null).equals(sender)) { + sender.sendMessage(ChatColor.RED + "你无权使用该命令!"); + return; + } + + // 检查目标玩家 + Player target = Bukkit.getPlayer(args[1]); + if (target == null || !target.isOnline()) { + sender.sendMessage(ChatColor.RED + "玩家不在线或不存在!"); + return; + } + + List teamPlayers = plugin.partyp.get(pid); + // 修正:判断目标是否在队伍内(原逻辑颠倒) + if (teamPlayers.contains(target)) { + sender.sendMessage(ChatColor.RED + "无法禁止队伍内的玩家!请先使用kick命令踢出"); + return; + } + + // 初始化ban列表(避免空指针) + List bannedPlayers = plugin.ban.getOrDefault(pid, new ArrayList<>()); + if (bannedPlayers.contains(target)) { + sender.sendMessage(ChatColor.RED + "该玩家已被禁止加入!"); + return; + } + + // 执行ban逻辑 + colorfulline(sender); + sender.sendMessage(ChatColor.GREEN + "成功禁止 " + target.getDisplayName() + " 加入队伍!"); + colorfulline(sender); + + // 通知目标玩家 + colorfulline(target); + target.sendMessage(ChatColor.RED + "您已被 " + sender.getDisplayName() + " 的队伍禁止加入!"); + colorfulline(target); + + // 更新ban列表 + bannedPlayers.add(target); + plugin.ban.put(pid, bannedPlayers); + } + + // 解散队伍 + private void handleDisband(Player sender) { + Integer pid = plugin.party.getOrDefault(sender, null); + if (pid == null) { + sender.sendMessage(ChatColor.RED + "您还未加入队伍"); + return; + } + + // 检查是否为队长 + if (!plugin.owner.getOrDefault(pid, null).equals(sender)) { + sender.sendMessage(ChatColor.RED + "你无权使用该命令!"); + return; + } + + List teamPlayers = plugin.partyp.get(pid); + // 先通知所有成员,再移除数据(原逻辑先移除导致通知不全) + colorfulline(sender); + sender.sendMessage(ChatColor.RED + "队伍解散成功"); + colorfulline(sender); + + for (Player member : teamPlayers) { + if (!member.equals(sender)) { + colorfulline(member); + member.sendMessage(ChatColor.RED + "队伍已解散"); + colorfulline(member); + plugin.party.remove(member); + } + } + + // 清理队伍数据 + plugin.owner.remove(pid); + plugin.party.remove(sender); + plugin.partyp.remove(pid); + plugin.ban.remove(pid); + } + + // 帮助信息 + private void handleHelp(Player player) { + colorfulline(player); + player.sendMessage(ChatColor.GREEN + "/party join <队伍号> - 加入指定队伍"); + player.sendMessage(ChatColor.GREEN + "/party quit - 退出当前队伍"); + player.sendMessage(ChatColor.GREEN + "/party disband - 解散当前队伍(仅队长)"); + player.sendMessage(ChatColor.GREEN + "/party create - 创建新队伍"); + player.sendMessage(ChatColor.GREEN + "/party ban <玩家名> - 禁止指定玩家加入(仅队长)"); + player.sendMessage(ChatColor.GREEN + "/party kick <玩家名> - 踢出队伍成员(仅队长)"); + player.sendMessage(ChatColor.GREEN + "/party list - 查看队伍成员及快捷功能"); + player.sendMessage(ChatColor.GREEN + "/party help - 查看命令帮助"); + player.sendMessage(ChatColor.GREEN + "/pc <消息> - 队伍内讲话"); + colorfulline(player); + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/command/PcCommandExecutor.java b/src/main/java/org/xgqy/survival/command/PcCommandExecutor.java new file mode 100644 index 0000000..13046f5 --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/PcCommandExecutor.java @@ -0,0 +1,65 @@ +package org.xgqy.survival.command; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.xgqy.survival.Survival; + +import java.util.List; + +public class PcCommandExecutor implements CommandExecutor { + + private final Survival plugin; + + public PcCommandExecutor(Survival plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "无法对非玩家类使用"); + return true; + } + + Player send = (Player) sender; + // 检查是否加入队伍 + Integer partyId = plugin.party.get(send); + if (partyId == null) { + send.sendMessage(ChatColor.RED + "您尚未加入队伍!"); + return true; + } + + // 拼接消息内容(处理空格分割的参数) + String originalMessage = String.join(" ", args); + + // 检查空消息(去除首尾空格后为空) + if (originalMessage.trim().isEmpty()) { + send.sendMessage(ChatColor.RED + "不允许发送空消息!"); + return true; + } + + // 限制消息长度为32字符内(中文算1个字符) + String processedMessage; + if (originalMessage.length() > 32) { + processedMessage = originalMessage.substring(0, 32); // 截取前32个字符 + } else { + processedMessage = originalMessage; + } + + // 发送给队伍所有成员 + List teamMembers = plugin.partyp.get(partyId); + for (Player member : teamMembers) { + member.sendMessage( + ChatColor.BLUE + "队伍" + + ChatColor.WHITE + send.getName() + " > " + + processedMessage + ); + } + + return true; + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/command/PointCommandExecutor.java b/src/main/java/org/xgqy/survival/command/PointCommandExecutor.java new file mode 100644 index 0000000..2296e74 --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/PointCommandExecutor.java @@ -0,0 +1,54 @@ +package org.xgqy.survival.command; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.xgqy.survival.Survival; + +public class PointCommandExecutor implements CommandExecutor { + + private Survival plugin; + + public PointCommandExecutor(Survival plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (sender instanceof Player) { + if(args.length != 4){ + sender.sendMessage(ChatColor.RED+"参数不足或参数过多!\n/point "); + return true; + } + Player player = Bukkit.getPlayer(args[0]); + if(!player.isOnline()){ + sender.sendMessage(ChatColor.RED+"玩家不存在!"); + return true; + } + if(args[1].equals("add")){ + plugin.ppoint.put(player,plugin.ppoint.get(player)+Integer.parseInt(args[2])); + plugin.breason.put(player,args[3]); + player.getScoreboard().getObjective("handled").getScore(player).setScore(Integer.parseInt(args[2])); + }else if(args[1].equals("set")){ + plugin.breason.put(player,args[3]); + if(Integer.parseInt(args[2]) < plugin.ppoint.get(player))player.getScoreboard().getObjective("handled").getScore(player).setScore(Integer.parseInt(args[2])); + plugin.ppoint.put(player,Integer.parseInt(args[2])); + }else if(args[1].equals("remove")){ + plugin.ppoint.put(player,plugin.ppoint.get(player)-Integer.parseInt(args[2])); + plugin.breason.put(player,args[3]); + player.getScoreboard().getObjective("handled").getScore(player).setScore(Integer.parseInt(args[2])); + }else{ + sender.sendMessage(ChatColor.RED+"参数错误!\n/point "); + return true; + } + return true; + } else { + sender.sendMessage(ChatColor.RED + "无法对非玩家类使用"); + return true; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/command/QdCommandExecutor.java b/src/main/java/org/xgqy/survival/command/QdCommandExecutor.java new file mode 100644 index 0000000..d0dd651 --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/QdCommandExecutor.java @@ -0,0 +1,237 @@ +package org.xgqy.survival.command; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.EnchantmentStorageMeta; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; +import org.xgqy.survival.Survival; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class QdCommandExecutor implements CommandExecutor { + + private final Survival plugin; + private final Random random = new Random(); + + public QdCommandExecutor(Survival plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "此命令仅玩家可使用!"); + return true; + } + + Player player = (Player) sender; + // 创建54格签到界面(6行×9列) + Inventory signInGui = Bukkit.createInventory(null, 54, ChatColor.GOLD + "每日签到奖励"); + + // 初始化背景物品(灰色玻璃 pane) + ItemStack background = createBackgroundItem(); + fillBackground(signInGui, background); + + // 设置金币奖励(1,3,5,7,9,11,13位置 - 对应索引0,2,4,6,8,10,12) + setupGoldRewards(signInGui); + + // 设置礼包奖励(8,14位置 - 对应索引7,13) + setupGiftRewards(signInGui); + + // 设置矿石奖励(2,12位置 - 对应索引1,11) + setupOreRewards(signInGui); + + // 设置点券奖励(4,10位置 - 对应索引3,9) + setupPointRewards(signInGui); + + // 设置高级附魔书奖励(6位置 - 对应索引5) + setupEnchantBookReward(signInGui); + + // 打开签到界面 + player.openInventory(signInGui); + return true; + } + + /** + * 创建背景装饰物品 + */ + private ItemStack createBackgroundItem() { + ItemStack background = new ItemStack(Material.GRAY_STAINED_GLASS_PANE); + ItemMeta meta = background.getItemMeta(); + if (meta != null) { + meta.setDisplayName(" "); // 空白名称 + background.setItemMeta(meta); + } + return background; + } + + /** + * 填充背景物品 + */ + private void fillBackground(Inventory inventory, ItemStack background) { + for (int i = 0; i < inventory.getSize(); i++) { + inventory.setItem(i, background); + } + } + + /** + * 设置金币奖励物品 + */ + private void setupGoldRewards(Inventory gui) { + // 金币奖励位置(索引)与对应金额 + int[] slots = {0, 2, 4, 6, 8, 10, 12}; + int[] amounts = {200, 500, 1000, 1500, 2000, 5000, 10000}; + String[] days = {"第1天", "第2天", "第3天", "第4天", "第5天", "第6天", "第7天(累计)"}; + + for (int i = 0; i < slots.length; i++) { + ItemStack gold = new ItemStack(Material.GOLD_INGOT); + ItemMeta meta = gold.getItemMeta(); + if (meta != null) { + meta.setDisplayName(ChatColor.YELLOW + "金币奖励 " + days[i]); + List lore = new ArrayList<>(); + lore.add(ChatColor.GRAY + "签到可获得 " + ChatColor.YELLOW + amounts[i] + " 金币"); + lore.add(ChatColor.GRAY + "点击领取奖励"); + meta.setLore(lore); + gold.setItemMeta(meta); + } + gui.setItem(slots[i], gold); + } + } + + /** + * 设置礼包奖励物品 + */ + private void setupGiftRewards(Inventory gui) { + int[] slots = {7, 13}; + String[] giftNames = {"普通签到礼包", "高级签到礼包"}; + + for (int i = 0; i < slots.length; i++) { + ItemStack gift = new ItemStack(Material.ENDER_CHEST); + ItemMeta meta = gift.getItemMeta(); + if (meta != null) { + meta.setDisplayName(ChatColor.GREEN + giftNames[i]); + List lore = new ArrayList<>(); + lore.add(ChatColor.GRAY + "包含随机珍贵物品"); + lore.add(ChatColor.GRAY + "内含材料、工具或特殊道具"); + lore.add(ChatColor.GRAY + "点击领取"); + meta.setLore(lore); + gift.setItemMeta(meta); + } + gui.setItem(slots[i], gift); + } + } + + /** + * 设置矿石奖励物品 + */ + private void setupOreRewards(Inventory gui) { + int[] slots = {1, 11}; + Material[] ores = {Material.DIAMOND, Material.IRON_BLOCK}; + String[] oreNames = {"钻石矿石", "铁矿块"}; + + for (int i = 0; i < slots.length; i++) { + ItemStack ore = new ItemStack(ores[i]); + ItemMeta meta = ore.getItemMeta(); + if (meta != null) { + meta.setDisplayName(ChatColor.AQUA + oreNames[i] + "奖励"); + List lore = new ArrayList<>(); + lore.add(ChatColor.GRAY + "签到可获得 " + ChatColor.AQUA + oreNames[i] + " x5"); + lore.add(ChatColor.GRAY + "点击领取"); + meta.setLore(lore); + ore.setItemMeta(meta); + } + gui.setItem(slots[i], ore); + } + } + + /** + * 设置点券奖励物品 + */ + private void setupPointRewards(Inventory gui) { + int[] slots = {3, 9}; + int[] amounts = {50, 100}; + + for (int i = 0; i < slots.length; i++) { + ItemStack point = new ItemStack(Material.EMERALD); + ItemMeta meta = point.getItemMeta(); + if (meta != null) { + meta.setDisplayName(ChatColor.GREEN + "点券奖励"); + List lore = new ArrayList<>(); + lore.add(ChatColor.GRAY + "签到可获得 " + ChatColor.GREEN + amounts[i] + " 点券"); + lore.add(ChatColor.GRAY + "可用于兑换特殊道具"); + lore.add(ChatColor.GRAY + "点击领取"); + meta.setLore(lore); + point.setItemMeta(meta); + } + gui.setItem(slots[i], point); + } + } + + /** + * 设置高级附魔书奖励 + */ + private void setupEnchantBookReward(Inventory gui) { + ItemStack enchBook = new ItemStack(Material.ENCHANTED_BOOK); + EnchantmentStorageMeta bookMeta = (EnchantmentStorageMeta) enchBook.getItemMeta(); + + if (bookMeta != null) { + bookMeta.setDisplayName(ChatColor.LIGHT_PURPLE + "高级附魔书"); + List lore = new ArrayList<>(); + lore.add(ChatColor.GRAY + "包含以下高级附魔:"); + + // 添加指定高级附魔(生存最大等级+2~5) + addEnchantment(bookMeta, lore, Enchantment.SHARPNESS); // 锋利 + addEnchantment(bookMeta, lore, Enchantment.PROTECTION); // 保护 + addEnchantment(bookMeta, lore, Enchantment.DEPTH_STRIDER); // 亡灵杀手 + addEnchantment(bookMeta, lore, Enchantment.BANE_OF_ARTHROPODS);// 截肢杀手 + addEnchantment(bookMeta, lore, Enchantment.THORNS); // 荆棘 + addEnchantment(bookMeta, lore, Enchantment.EFFICIENCY); // 耐久 + + lore.add(""); + lore.add(ChatColor.GRAY + "点击领取此附魔书"); + bookMeta.setLore(lore); + enchBook.setItemMeta(bookMeta); + } + + gui.setItem(5, enchBook); + } + + /** + * 为附魔书添加指定附魔并设置等级 + */ + private void addEnchantment(EnchantmentStorageMeta meta, List lore, Enchantment enchantment) { + if (enchantment == null) return; + + int maxLevel = enchantment.getMaxLevel(); + int customLevel = maxLevel + random.nextInt(4) + 2; // +2到5级 + meta.addStoredEnchant(enchantment, customLevel, true); + + // 获取附魔显示名称 + String enchName = getEnchantmentName(enchantment); + lore.add(ChatColor.GRAY + "- " + enchName + " " + customLevel + "级"); + } + + /** + * 获取附魔的中文显示名称 + */ + private String getEnchantmentName(Enchantment enchantment) { + if (enchantment.equals(Enchantment.SHARPNESS)) return "锋利"; + if (enchantment.equals(Enchantment.PROTECTION)) return "保护"; + if (enchantment.equals(Enchantment.DEPTH_STRIDER)) return "亡灵杀手"; + if (enchantment.equals(Enchantment.BANE_OF_ARTHROPODS)) return "截肢杀手"; + if (enchantment.equals(Enchantment.THORNS)) return "荆棘"; + if (enchantment.equals(Enchantment.EFFICIENCY)) return "耐久"; + return enchantment.getKey().getKey(); + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/command/RegCommandExecutor.java b/src/main/java/org/xgqy/survival/command/RegCommandExecutor.java new file mode 100644 index 0000000..a739be2 --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/RegCommandExecutor.java @@ -0,0 +1,69 @@ +package org.xgqy.survival.command; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.xgqy.survival.Survival; + +public class RegCommandExecutor implements CommandExecutor { + private final Survival plugin; + + public RegCommandExecutor(Survival plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "只有玩家可以使用此命令!"); + return true; + } + + Player player = (Player) sender; + + // 检查玩家是否已登录 + if (plugin.getAccountManager().isLoggedIn(player)) { + player.sendMessage(ChatColor.RED + "你已经登录了!"); + return true; + } + + // 检查参数是否正确 + if (args.length != 3) { + player.sendMessage(ChatColor.RED + "用法: /reg <密码> <确认密码> <确认码>"); + return true; + } + + // 检查玩家是否已注册 + if (plugin.getAccountManager().isRegistered(player.getUniqueId())) { + player.sendMessage(ChatColor.RED + "你已经注册过了! 请使用 /login 命令登录"); + return true; + } + + // 检查两次输入的密码是否一致 + if (!args[0].equals(args[1])) { + player.sendMessage(ChatColor.RED + "两次输入的密码不一致!"); + return true; + } + + // 检查密码长度 + if (args[0].length() < 6) { + player.sendMessage(ChatColor.RED + "密码长度不能少于6个字符!"); + return true; + } + if(!args[2].equals("3C11CFE63ED00BD6BC50729CD3D5F393F4AA84B0")){ + player.kickPlayer(ChatColor.RED+"确认码错误,请仔细阅读!"); + return true; + } + // 注册账号 + if (plugin.getAccountManager().registerAccount(player, args[0])) { + player.sendMessage(ChatColor.GREEN + "注册成功! 请使用 /login <密码> 登录"); + player.kickPlayer(ChatColor.GREEN+"注册成功,请重进登录!"); + return true; + } else { + player.sendMessage(ChatColor.RED + "注册失败! 请稍后再试"); + return false; + } + } +} diff --git a/src/main/java/org/xgqy/survival/command/msgCommandExecutor.java b/src/main/java/org/xgqy/survival/command/msgCommandExecutor.java new file mode 100644 index 0000000..cb08ac2 --- /dev/null +++ b/src/main/java/org/xgqy/survival/command/msgCommandExecutor.java @@ -0,0 +1,86 @@ +package org.xgqy.survival.command; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.xgqy.survival.Survival; + +public class msgCommandExecutor implements CommandExecutor { + + private final Survival plugin; + + public msgCommandExecutor(Survival plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + // 仅允许玩家使用 + if (!(sender instanceof Player sendPlayer)) { + sender.sendMessage(ChatColor.RED + "无法对非玩家类使用"); + return true; + } + + // 校验参数长度(至少需要 目标玩家名 + 消息内容) + if (args.length < 2) { + sendPlayer.sendMessage(ChatColor.RED + "用法错误!正确格式:/msg <玩家名> <消息内容>"); + return true; + } + + // 处理目标玩家 + String targetName = args[0]; + Player targetPlayer = Bukkit.getPlayer(targetName); + // 校验目标玩家是否存在且在线(避免空指针) + if (targetPlayer == null || !targetPlayer.isOnline()) { + sendPlayer.sendMessage(ChatColor.RED + "目标玩家不存在或不在线!"); + return true; + } + + // 拼接消息内容(从第2个参数开始,处理带空格的消息) + StringBuilder messageBuilder = new StringBuilder(); + for (int i = 1; i < args.length; i++) { + messageBuilder.append(args[i]).append(" "); + } + String originalMessage = messageBuilder.toString().trim(); // 去除首尾空格 + + // 拦截空消息 + if (originalMessage.isEmpty()) { + sendPlayer.sendMessage(ChatColor.RED + "不允许发送空消息!"); + return true; + } + + // 限制消息长度为32个字符(中文算1个) + String finalMessage = originalMessage; + if (originalMessage.length() > 32) { + finalMessage = originalMessage.substring(0, 32); + } + + // 校验好友关系 + if (plugin.friends.getOrDefault(sendPlayer, null) == null) { + sendPlayer.sendMessage(ChatColor.RED + "您还没有好友哦!输入 /friend add <玩家名> 添加好友吧"); + return true; + } + if (!plugin.friends.get(sendPlayer).contains(targetPlayer)) { + sendPlayer.sendMessage(ChatColor.RED + "该玩家不是您的好友!输入 /friend add <玩家名> 添加好友吧"); + return true; + } + + // 发送私聊消息(双方都能看到,提升体验) + // 接收方消息 + targetPlayer.sendMessage( + ChatColor.LIGHT_PURPLE + "" + ChatColor.BOLD + "私聊 " + + ChatColor.WHITE + sendPlayer.getName() + " > " + finalMessage + ); + // 发送方回执 + sendPlayer.sendMessage( + ChatColor.LIGHT_PURPLE + "" + ChatColor.BOLD + "私聊 " + + ChatColor.WHITE + "你 > " + targetPlayer.getName() + ":" + finalMessage + ); + + return true; + } +} \ 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 new file mode 100644 index 0000000..0c3474c --- /dev/null +++ b/src/main/java/org/xgqy/survival/event/AntiExploit.java @@ -0,0 +1,88 @@ +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/CommandLimiter.java b/src/main/java/org/xgqy/survival/event/CommandLimiter.java new file mode 100644 index 0000000..435d86b --- /dev/null +++ b/src/main/java/org/xgqy/survival/event/CommandLimiter.java @@ -0,0 +1,128 @@ +package org.xgqy.survival.event; + +import org.bukkit.BanList; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.scheduler.BukkitRunnable; +import org.xgqy.survival.Survival; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class CommandLimiter implements Listener { + // 核心配置常量 + private static final int TEN_SEC_LIMIT = 5; // 10秒内最大命令数 + private static final int ONE_MIN_LIMIT = 15; // 1分钟内最大命令数 + private static final long TEN_SEC_MS = 10000; // 10秒(毫秒) + private static final long ONE_MIN_MS = 60000; // 1分钟(毫秒) + private static final long BAN_DURATION = 60; // 封禁时长(秒) + private static final String BAN_REASON = "DDoS攻击"; // 封禁理由 + + private final Survival plugin; + // 存储玩家命令时间戳:key=玩家UUID,value=命令发送时间戳列表 + private final Map> playerCommandTimes = new HashMap<>(); + // 存储已封禁玩家,避免重复封禁 + private final Map bannedPlayers = new HashMap<>(); + + public CommandLimiter(Survival plugin) { + this.plugin = plugin; + // 注册事件监听器 + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + /** + * 监听玩家命令发送事件 + */ + @EventHandler + public void onPlayerCommand(PlayerCommandPreprocessEvent event) { + Player player = event.getPlayer(); + UUID playerUuid = player.getUniqueId(); + + // 跳过已封禁玩家的命令处理 + if (bannedPlayers.getOrDefault(playerUuid, false) || isPlayerBanned(player)) { + event.setCancelled(true); + player.sendMessage("§c你因" + BAN_REASON + "已被封禁" + BAN_DURATION + "秒!"); + return; + } + + // 初始化玩家命令时间列表 + playerCommandTimes.computeIfAbsent(playerUuid, k -> new ArrayList<>()); + List commandTimes = playerCommandTimes.get(playerUuid); + + // 添加当前命令时间戳 + long currentTime = System.currentTimeMillis(); + commandTimes.add(currentTime); + + // 清理过期时间戳(只保留1分钟内的,节省内存) + commandTimes.removeIf(time -> time < currentTime - ONE_MIN_MS); + + // 统计10秒内和1分钟内的命令数 + long tenSecCount = commandTimes.stream() + .filter(time -> time >= currentTime - TEN_SEC_MS) + .count(); + long oneMinCount = commandTimes.size(); + + // 检查是否触发封禁条件 + if (tenSecCount > TEN_SEC_LIMIT || oneMinCount > ONE_MIN_LIMIT) { + banPlayer(player); + event.setCancelled(true); + } + } + + /** + * 监听玩家退出事件,清理数据(避免内存泄漏) + */ + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + UUID playerUuid = event.getPlayer().getUniqueId(); + playerCommandTimes.remove(playerUuid); + bannedPlayers.remove(playerUuid); + } + + /** + * 封禁玩家指定时长 + */ + private void banPlayer(Player player) { + UUID playerUuid = player.getUniqueId(); + // 标记为已封禁,避免重复处理 + bannedPlayers.put(playerUuid, true); + + // 执行封禁操作 + Bukkit.getBanList(BanList.Type.NAME).addBan( + player.getName(), + BAN_REASON, + new java.util.Date(System.currentTimeMillis() + BAN_DURATION * 1000), + "系统" + ); + + // 踢出玩家 + player.kickPlayer("§c你因" + BAN_REASON + "被封禁" + BAN_DURATION + "秒!"); + + // 封禁到期后移除标记 + new BukkitRunnable() { + @Override + public void run() { + bannedPlayers.remove(playerUuid); + // 可选:发送解封通知(如果玩家已重新加入) + Player onlinePlayer = Bukkit.getPlayer(playerUuid); + if (onlinePlayer != null && onlinePlayer.isOnline()) { + //onlinePlayer.sendMessage("§a你的" + BAN_DURATION + "秒封禁已到期,可正常使用命令!"); + } + } + }.runTaskLater(plugin, BAN_DURATION * 20); // 20tick=1秒 + } + + /** + * 检查玩家是否处于封禁状态 + */ + private boolean isPlayerBanned(Player player) { + return Bukkit.getBanList(BanList.Type.NAME).isBanned(player.getName()); + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/event/LandEvent.java b/src/main/java/org/xgqy/survival/event/LandEvent.java new file mode 100644 index 0000000..afc3217 --- /dev/null +++ b/src/main/java/org/xgqy/survival/event/LandEvent.java @@ -0,0 +1,266 @@ +package org.xgqy.survival.event; + +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.xgqy.survival.Survival; + +import java.util.List; +import java.util.Objects; +import java.util.WeakHashMap; + +public class LandEvent implements Listener { + private final Survival plugin; + // 缓存玩家当前所在的领地所有者(避免重复发送消息) + private final WeakHashMap currentLandOwner = new WeakHashMap<>(); + + public LandEvent(Survival plugin) { + this.plugin = plugin; + } + + /** + * 工具方法:计算两个坐标的最小值和最大值 + * @return 长度为2的数组,[0]是最小值,[1]是最大值 + */ + private int[] getMinMax(int a, int b) { + return new int[]{Math.min(a, b), Math.max(a, b)}; + } + + /** + * 检查位置是否在领地的x-z范围内 + */ + private boolean isInLand(Location loc, Survival.Land land) { + if (loc == null || land == null) return false; + Location start = land.getStart(); + Location end = land.getEnd(); + if (start == null || end == null) return false; // 领地坐标无效 + // 检查是否在同一个世界 + if (!loc.getWorld().equals(start.getWorld())) return false; + + int[] xRange = getMinMax(start.getBlockX(), end.getBlockX()); + int[] zRange = getMinMax(start.getBlockZ(), end.getBlockZ()); + + int locX = loc.getBlockX(); + int locZ = loc.getBlockZ(); + + return locX >= xRange[0] && locX <= xRange[1] && locZ >= zRange[0] && locZ <= zRange[1]; + } + + @EventHandler + private void onPlayerMove(PlayerMoveEvent event) { + Player player = event.getPlayer(); + // 仅当玩家跨方块移动时才处理(减少触发频率) + Location from = event.getFrom(); + Location to = event.getTo(); + if (to == null || (from.getBlockX() == to.getBlockX() && from.getBlockZ() == to.getBlockZ())) { + return; + } + + List lands = plugin.land; + if (lands == null || lands.isEmpty()) return; // 无领地数据时直接返回 + + String previousOwner = currentLandOwner.getOrDefault(player, null); + String newOwner = null; + Survival.Land enteredLand = null; + + // 查找玩家当前所在的领地 + for (Survival.Land land : lands) { + if (isInLand(to, land)) { + newOwner = land.getOwner(); + enteredLand = land; + break; // 找到第一个包含该位置的领地(若有重叠需额外处理) + } + } + + // 情况1:进入新领地(与之前不同) + if (newOwner != null && !Objects.equals(newOwner, previousOwner) && enteredLand != null) { + currentLandOwner.put(player, newOwner); + plugin.inland.put(player, newOwner); // 更新缓存 + + // 不是自己的领地才显示权限 + if (!Objects.equals(newOwner, player.getName())) { + player.sendMessage(ChatColor.YELLOW + "[领地]" + ChatColor.GOLD + "您已进入玩家 " + ChatColor.WHITE + newOwner + ChatColor.GOLD + " 的领地"); + + // 获取当前领地的权限(使用找到的enteredLand,而非lands列表) + List perm = enteredLand.getPerm(player); + List defaultPerm = enteredLand.getDefaultperm(); + + // 权限为空时使用默认权限,并设置到领地中 + if (perm == null) { + // 确保默认权限不为null,避免后续get(i)报错 + defaultPerm = (defaultPerm != null) ? defaultPerm : List.of(false, false, false, false, false, false, false); + perm = defaultPerm; + enteredLand.setPerm(player, perm); // 存储权限(Land类setPerm会用玩家名作为键) + } + + // 发送权限信息(确保权限列表长度足够,避免索引越界) + player.sendMessage(ChatColor.GREEN + "你在本领地里的权限为:"); + sendPermissionMessage(player, perm, 0, "破坏"); + sendPermissionMessage(player, perm, 1, "放置"); + sendPermissionMessage(player, perm, 2, "飞行"); + sendPermissionMessage(player, perm, 3, "交互"); + sendPermissionMessage(player, perm, 4, "使用方块"); + sendPermissionMessage(player, perm, 5, "TNT爆炸"); + sendPermissionMessage(player, perm, 6, "玩家间伤害"); + + // 处理飞行权限 + if (isValidPermIndex(perm, 2) && perm.get(2)) { + player.setFlySpeed(0.1f); // 恢复默认飞行速度 + player.setFlying(true); + } else { + player.setFlying(false); + } + } else { + // 进入自己的领地 + player.sendMessage(ChatColor.YELLOW + "[领地]" + ChatColor.GOLD + "您已进入自己的领地"); + player.setFlying(false); // 自己领地不强制飞行 + } + } + + // 情况2:离开所有领地(之前在领地,现在不在) + if (newOwner == null && previousOwner != null) { + player.sendMessage(ChatColor.YELLOW + "[领地]" + ChatColor.GOLD + "您已离开玩家 " + previousOwner + ChatColor.GOLD + " 的领地"); + currentLandOwner.remove(player); + plugin.inland.put(player, null); + player.setFlying(false); // 离开领地关闭飞行 + } + } + + /** + * 工具方法:发送权限信息(避免索引越界) + */ + private void sendPermissionMessage(Player player, List perm, int index, String action) { + if (isValidPermIndex(perm, index)) { + player.sendMessage(ChatColor.YELLOW + action + ":" + (perm.get(index) ? "允许" : "不允许")); + } else { + player.sendMessage(ChatColor.YELLOW + action + ":" + "未设置"); + } + } + + /** + * 工具方法:检查权限索引是否有效 + */ + private boolean isValidPermIndex(List perm, int index) { + return perm != null && index >= 0 && index < perm.size(); + } + + @EventHandler + private void onBlockBreak(BlockBreakEvent event) { + Player player = event.getPlayer(); + checkActionPermission(player, event.getBlock().getLocation(), 0, () -> { + event.setCancelled(true); + player.sendMessage(ChatColor.YELLOW + "[领地]" + ChatColor.GOLD + "你没有 " + ChatColor.RED + "破坏" + ChatColor.GOLD + " 权限"); + }); + } + + @EventHandler + private void onBlockPlace(BlockPlaceEvent event) { + Player player = event.getPlayer(); + checkActionPermission(player, event.getBlock().getLocation(), 1, () -> { + event.setCancelled(true); + player.sendMessage(ChatColor.YELLOW + "[领地]" + ChatColor.GOLD + "你没有 " + ChatColor.RED + "放置" + ChatColor.GOLD + " 权限"); + }); + } + + @EventHandler + private void onPlayerInteract(PlayerInteractEvent event) { + Player player = event.getPlayer(); + // 仅处理方块点击动作 + if (event.getAction() != Action.LEFT_CLICK_BLOCK && event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + if (event.getClickedBlock() == null) return; // 点击的方块为空时不处理 + + // 交互权限(索引3)和使用方块权限(索引4)分别判断 + Location loc = event.getClickedBlock().getLocation(); + checkActionPermission(player, loc, 3, () -> { + event.setCancelled(true); + player.sendMessage(ChatColor.YELLOW + "[领地]" + ChatColor.GOLD + "你没有 " + ChatColor.RED + "交互" + ChatColor.GOLD + " 权限"); + }); + checkActionPermission(player, loc, 4, () -> { + event.setCancelled(true); + player.sendMessage(ChatColor.YELLOW + "[领地]" + ChatColor.GOLD + "你没有 " + ChatColor.RED + "使用方块" + ChatColor.GOLD + " 权限"); + }); + } + + @EventHandler + private void onEntityExplode(EntityExplodeEvent event) { + Location explodeLoc = event.getLocation(); + List lands = plugin.land; + if (lands == null || lands.isEmpty()) return; + + // 检查爆炸位置是否在某个领地内,且TNT爆炸权限关闭 + for (Survival.Land land : lands) { + if (isInLand(explodeLoc, land)) { + // 获取爆炸的触发玩家(如TNT的放置者,这里简化处理) + Player causer = null; + Entity entity = event.getEntity(); + if (entity != null && entity.getCustomName() != null) { + // 假设TNT的自定义名称存储了放置者(实际需根据插件逻辑获取) + String playerName = entity.getCustomName(); + causer = plugin.getServer().getPlayer(playerName); + } + + // 获取权限(无触发者则用默认权限) + List perm = (causer != null) ? land.getPerm(causer) : land.getDefaultperm(); + if (perm == null) { + perm = List.of(false, false, false, false, false, false, false); + } + + // TNT爆炸权限(索引5)为false时取消爆炸 + if (isValidPermIndex(perm, 5) && !perm.get(5)) { + event.setCancelled(true); + if (causer != null) { + causer.sendMessage(ChatColor.YELLOW + "[领地]" + ChatColor.GOLD + "此领地禁止TNT爆炸"); + } + return; // 一个领地禁止即可取消 + } + } + } + } + + /** + * 通用方法:检查玩家在指定位置的动作权限 + */ + private void checkActionPermission(Player player, Location loc, int permIndex, Runnable onDenied) { + if (player == null || loc == null || onDenied == null) return; + + List lands = plugin.land; + if (lands == null || lands.isEmpty()) return; + + // 查找位置所在的领地 + for (Survival.Land land : lands) { + if (isInLand(loc, land)) { + String owner = land.getOwner(); + if (owner == null) continue; // 领地所有者为空时不处理 + + // 自己的领地不限制 + if (Objects.equals(owner, player.getName())) { + return; + } + + // 检查权限 + List perm = land.getPerm(player); + List defaultPerm = land.getDefaultperm(); + if (perm == null) { + perm = (defaultPerm != null) ? defaultPerm : List.of(false, false, false, false, false, false, false); + } + + // 权限不足时执行操作 + if (isValidPermIndex(perm, permIndex) && !perm.get(permIndex)) { + onDenied.run(); + return; + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/event/LoginEvent.java b/src/main/java/org/xgqy/survival/event/LoginEvent.java new file mode 100644 index 0000000..5d8d2b0 --- /dev/null +++ b/src/main/java/org/xgqy/survival/event/LoginEvent.java @@ -0,0 +1,264 @@ +package org.xgqy.survival.event; + +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 net.md_5.bungee.api.chat.HoverEvent; +import org.bukkit.ChatColor; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +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.inventory.InventoryOpenEvent; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerPickupItemEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scheduler.BukkitRunnable; +import org.xgqy.survival.Survival; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class LoginEvent implements Listener { + private final Survival plugin; + // 存储未登录玩家的初始位置(登录后恢复用) + private final Map initialLocations = new HashMap<>(); + + public LoginEvent(Survival plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + Location initialLoc = player.getLocation().clone(); + initialLocations.put(player, initialLoc); // 记录初始位置 + // 初始化未登录隔离状态:隐身+高处传送+致盲 + setupUnloggedState(player); + + // 登录提示定时器(每秒发送一次,登录后停止) + new BukkitRunnable() { + @Override + public void run() { + if (plugin.getAccountManager().isLoggedIn(player)) { + // 登录成功,恢复正常状态 + restoreLoggedState(player); + initialLocations.remove(player); // 移除缓存的初始位置 + this.cancel(); + return; + } + // 发送登录/注册提示 + if (plugin.getAccountManager().isRegistered(player.getUniqueId())) { + player.sendMessage("§e欢迎回来! 请使用 /login <密码> 登录"); + player.sendTitle("§b星阁钱语", "§e请输入 /login <密码> 登录", 0, 100, 0); + } else { + BaseComponent[] message = new ComponentBuilder() + .append(new ComponentBuilder(ChatColor.GREEN + "请先同意 《星阁钱语服务条款》(点击复制链接)") + // 设置点击事件:复制链接到剪贴板 + .event(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, "https://starpavilion.xyz/terms.html")) + // 设置悬浮事件:鼠标悬停时显示的文字 + .event(new HoverEvent( + HoverEvent.Action.SHOW_TEXT, // 悬浮动作:显示文本 + // 悬浮文字内容(可带颜色) + new ComponentBuilder() + .append(ChatColor.GRAY+"https://starpavilion.xyz/terms.html") + .create() + )) + .create()) + .create(); + player.spigot().sendMessage(message); + player.sendMessage("§e欢迎来到服务器! 请使用 /reg <密码> <确认密码> 注册"); + player.sendTitle("§b星阁钱语", "§e请输入 /reg <密码> <密码> <确认码> 注册", 0, 100, 0); + } + } + }.runTaskTimer(plugin, 60, 60); // 1秒发送一次 + + // 登录超时踢人(60秒) + new BukkitRunnable() { + @Override + public void run() { + if (!plugin.getAccountManager().isLoggedIn(player)) { + initialLocations.remove(player); + player.kickPlayer("§c登录超时"); + } + } + }.runTaskLater(plugin, 20 * 60); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + // 玩家退出时登出账号并清理缓存 + plugin.getAccountManager().logout(player); + initialLocations.remove(player); + } + + // ------------------------------ 核心拦截:阻止所有外界交互 ------------------------------ + @EventHandler + public void onPlayerMove(PlayerMoveEvent event) { + if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) { + // 完全阻止移动(包括位置和视角旋转) + event.setCancelled(true); + } + } + + @EventHandler + public void onPlayerChat(AsyncPlayerChatEvent event) { + if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) { + event.setCancelled(true); + event.getPlayer().sendMessage("§c请先登录或注册"); + } + } + + @EventHandler + public void onPlayerCommand(PlayerCommandPreprocessEvent event) { + String command = event.getMessage().toLowerCase(); + // 仅允许登录/注册命令 + if (command.startsWith("/reg") || command.startsWith("/login")) { + return; + } + if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) { + event.setCancelled(true); + event.getPlayer().sendMessage("§c请先登录或注册"); + } + } + + // 阻止方块破坏 + @EventHandler + public void onBlockBreak(BlockBreakEvent event) { + if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) { + event.setCancelled(true); + } + } + + // 阻止方块放置 + @EventHandler + public void onBlockPlace(BlockPlaceEvent event) { + if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) { + event.setCancelled(true); + } + } + + // 阻止物品/方块交互 + @EventHandler + public void onPlayerInteract(PlayerInteractEvent event) { + if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) { + event.setCancelled(true); + } + } + + // 阻止实体交互(如骑乘、点击实体) + @EventHandler + public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { + if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) { + event.setCancelled(true); + } + } + + // 阻止拾取物品 + @EventHandler + public void onPlayerPickupItem(PlayerPickupItemEvent event) { + if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) { + event.setCancelled(true); + } + } + + // 阻止丢弃物品 + @EventHandler + public void onPlayerDropItem(PlayerDropItemEvent event) { + if (!plugin.getAccountManager().isLoggedIn(event.getPlayer())) { + event.setCancelled(true); + } + } + + // 阻止攻击行为 + @EventHandler + public void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + if (event.getDamager() instanceof Player player && !plugin.getAccountManager().isLoggedIn(player)) { + event.setCancelled(true); + } + } + + // 阻止打开容器(箱子、熔炉等) + @EventHandler + public void onInventoryOpen(InventoryOpenEvent event) { + if (event.getPlayer() instanceof Player player && !plugin.getAccountManager().isLoggedIn(player)) { + event.setCancelled(true); + } + } + + // ------------------------------ 状态管理工具方法 ------------------------------ + /** + * 设置未登录玩家的隔离状态:隐身+高处传送+致盲+交互阻断 + */ + private void setupUnloggedState(Player player) { + // 1. 隐身(对其他玩家不可见) + player.setInvisible(true); + // 2. 隐藏玩家列表名称(不在Tab列表显示) + player.setPlayerListName(""); + // 3. 禁用飞行控制(防止移动) + player.setAllowFlight(false); + // 4. 传送至当前世界的安全高处(Y=255,避免掉落后受伤) + Location highLoc = new Location( + player.getWorld(), + player.getLocation().getX(), + 255, + player.getLocation().getZ() + ); + player.teleport(highLoc); + // 6. 移除所有可能的干扰效果(保留致盲) + player.getActivePotionEffects().forEach(effect -> { + if (effect.getType() != PotionEffectType.BLINDNESS) { + player.removePotionEffect(effect.getType()); + } + }); + } + /** + * 恢复登录玩家的正常状态 + */ + /** + * 恢复登录玩家的正常状态 + */ + public void restoreLoggedState(Player player) { + // 1. 先设置更长时间的无敌(10秒=200tick),确保覆盖坠落过程 + player.setNoDamageTicks(200); + + // 2. 恢复可见性 + player.setInvisible(false); + + // 3. 恢复玩家列表名称 + List tags = plugin.getPlayerTags().getTags(player); + int currentTagIndex = plugin.getPlayerTags().getCurrentTag(player); + int validIndex = Math.max(0, Math.min(currentTagIndex, tags.size() - 1)); + player.setPlayerListName(ChatColor.WHITE + "[" + tags.get(validIndex) + ChatColor.WHITE + "]" + player.getName()); + + // 4. 恢复飞行权限 + player.setAllowFlight(false); + + // 5. 移除致盲效果 + player.removePotionEffect(PotionEffectType.BLINDNESS); + + // 6. 传送回初始位置(优先使用安全的重生点) + Location initialLoc = initialLocations.getOrDefault(player, player.getWorld().getSpawnLocation()); + // 额外检查:如果初始位置Y坐标过低(比如高空),强制使用世界重生点 + if (initialLoc.getY() > 200) { // 假设Y>200为危险高空 + initialLoc = player.getWorld().getSpawnLocation(); + } + player.teleport(initialLoc); + + // 7. 恢复游戏模式 + player.setGameMode(GameMode.SURVIVAL); + } +} \ No newline at end of file diff --git a/src/main/java/org/xgqy/survival/event/QuitEvent.java b/src/main/java/org/xgqy/survival/event/QuitEvent.java new file mode 100644 index 0000000..23169a6 --- /dev/null +++ b/src/main/java/org/xgqy/survival/event/QuitEvent.java @@ -0,0 +1,71 @@ +package org.xgqy.survival.event; + +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.scoreboard.Objective; +import org.xgqy.survival.Survival; + +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +public class QuitEvent implements Listener { + public Survival plugin; + public QuitEvent(Survival plugin){ + this.plugin=plugin; + } + @EventHandler + private void PlayerQuit(PlayerQuitEvent event){ + Player player = event.getPlayer(); + plugin.inland.put(player,null); + if(event.getPlayer().getScoreboard().getObjective("handled").getScore(event.getPlayer()).getScore() != 0){ + Objective handledObjective = player.getScoreboard().getObjective("handled"); + if (handledObjective != null) { + int handledScore = handledObjective.getScore(player.getName()).getScore(); + + if (handledScore == 1) { + // 2. 安全遍历举报者列表 + Set reporters = plugin.reportlist.get(player); + if (reporters != null) { + for (Player repo : reporters) { + if (repo.isOnline()) { + repo.sendMessage(ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | " + + ChatColor.GREEN + "\n 您举报的玩家 " + ChatColor.RED + ChatColor.BOLD + + player.getName() + ChatColor.GREEN + " 已经被封禁\n"+ChatColor.LIGHT_PURPLE + "举报系统" + ChatColor.WHITE + " | \n"+ChatColor.GREEN +" 感谢您为维护游戏平衡做贡献!"); + + } + } + } + + // 3. 清理数据 + plugin.reportlist.remove(player); // 直接移除条目 + plugin.banreason.remove(player); + + // 4. 安全删除banlist中的条目 + Iterator> iterator = plugin.banlist.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (player.equals(entry.getValue())) { + iterator.remove(); + } + } + + event.setQuitMessage(ChatColor.AQUA + "StarPavilion" + + ChatColor.DARK_AQUA + "NetWork" + + ChatColor.WHITE + " | \n" + + ChatColor.YELLOW + " 一名违规玩家 " + + ChatColor.RED + ChatColor.BOLD + player.getName()+ChatColor.YELLOW + + " 被净化了\n"); + } else { + event.setQuitMessage(ChatColor.RED + "玩家 " + player.getName() + " 离开了游戏!"); + } + } else { + // 当handled目标不存在时的默认处理 + event.setQuitMessage(ChatColor.RED + "玩家 " + player.getName() + " 离开了游戏!"); + } + } + } +}