From f008c49e84baaff8fb0b874b20948dfdb6963efb Mon Sep 17 00:00:00 2001 From: youpaishidifu <1@ypshidifu.cn> Date: Sun, 27 Sep 2026 12:11:41 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=89=E9=A1=B9=E9=97=AE?= =?UTF-8?q?=E9=A2=98=EF=BC=9A1)=E5=A4=A7=E7=AE=B1=E5=AD=90=E8=B7=A8?= =?UTF-8?q?=E9=A2=86=E5=9C=B0=E8=BE=B9=E7=95=8C=E7=BB=95=E8=BF=87=E5=AE=B9?= =?UTF-8?q?=E5=99=A8=E6=9D=83=E9=99=90=EF=BC=88=E5=8F=8C=E7=AE=B1=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E5=B7=A6=E5=8F=B3=E4=B8=A4=E4=BE=A7=E5=9D=87=E6=94=B6?= =?UTF-8?q?=E9=9B=86=E5=AE=9A=E4=BD=8D=E5=B9=B6=E9=80=90=E4=BE=A7=E5=88=A4?= =?UTF-8?q?=E5=AE=9AdenyContainer=EF=BC=8C=E4=BA=A4=E4=BA=92=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E6=94=B9=E7=94=A8=E8=A2=AB=E7=82=B9=E5=87=BB=E6=96=B9?= =?UTF-8?q?=E5=9D=97=E5=AE=9A=E4=BD=8D=E5=B9=B6=E8=A1=A5=E6=9F=A5=E7=9B=B8?= =?UTF-8?q?=E9=82=BB=E5=8D=8A=E7=AE=B1=EF=BC=892)tpaall=E8=AF=AD=E4=B9=89?= =?UTF-8?q?=E6=96=87=E6=A1=88=E4=BF=AE=E6=AD=A3=EF=BC=88=E9=80=9A=E7=9F=A5?= =?UTF-8?q?/=E6=8C=89=E9=92=AE/GUI=E7=BB=9F=E4=B8=80=E4=B8=BA=E5=85=A8?= =?UTF-8?q?=E6=9C=8D=E7=8E=A9=E5=AE=B6=E4=B8=80=E8=B5=B7=E4=BC=A0=E9=80=81?= =?UTF-8?q?=E5=88=B0=E5=8F=91=E8=B5=B7=E8=80=85=E8=BA=AB=E8=BE=B9=EF=BC=89?= =?UTF-8?q?3)=E6=96=B0=E4=BA=BA=E9=AA=8C=E8=AF=81=E7=A0=81=E8=B6=85?= =?UTF-8?q?=E6=97=B6=E4=B8=8D=E6=94=BE=E8=A1=8C=E6=94=B9=E4=B8=BA=E9=87=8D?= =?UTF-8?q?=E6=96=B0=E5=87=BA=E9=A2=98=EF=BC=8C=E9=A2=98=E5=BA=93=E5=BC=95?= =?UTF-8?q?=E5=85=A5=E4=B9=9D=E4=B9=9D=E4=B9=98=E6=B3=95=E8=A1=A8=E5=8A=A0?= =?UTF-8?q?=E5=87=8F=E4=B9=98=E9=99=A4+GUI=E4=BA=94=E9=80=89=E4=B8=80?= =?UTF-8?q?=E9=9A=8F=E6=9C=BA=E6=8A=BD=E9=A2=98=EF=BC=9B=E5=8F=A6=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=E7=A7=BB=E5=8A=A8=E8=B1=81=E5=85=8D=E8=AF=8A=E6=96=AD?= =?UTF-8?q?=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/Sdf1_login/AreaProtection.java | 148 ++++++++++++------ .../java/Sdf1_login/ChatFilterManager.java | 111 +++++++++---- src/main/java/Sdf1_login/Main.java | 15 ++ src/main/java/Sdf1_login/TeleportManager.java | 46 +++++- 4 files changed, 232 insertions(+), 88 deletions(-) diff --git a/src/main/java/Sdf1_login/AreaProtection.java b/src/main/java/Sdf1_login/AreaProtection.java index 8a9bb57..349708e 100644 --- a/src/main/java/Sdf1_login/AreaProtection.java +++ b/src/main/java/Sdf1_login/AreaProtection.java @@ -3580,7 +3580,7 @@ public class AreaProtection implements Listener { int landId = getLandIdFromDb(ac.name); PermRow row = landId > 0 ? findPermRow(landId, p.getName()) : null; String name = p.getName(); - plugin.getLogger().info("[防护][移动豁免诊断] 场景=" + scene + /* plugin.getLogger().info("[防护][移动豁免诊断] 场景=" + scene + " 领地=" + ac.name + " 玩家=" + name + " 码点=" + java.util.Arrays.toString(name.codePoints().toArray()) @@ -3591,7 +3591,7 @@ public class AreaProtection implements Listener { + " 白名单=" + isPlayerWhitelisted(name, ac) + " 访客行=" + (row == null ? "无" : (row.playerName + "/" + row.role + "/exp=" + row.expiresAt + "/" + row.permissions)) - + " 领地默认denyMove=" + ac.denyMove); + + " 领地默认denyMove=" + ac.denyMove);*/ } catch (Exception ignored) { } } @@ -5376,6 +5376,38 @@ public class AreaProtection implements Listener { } + // ===== 容器/UI方块交互:以【被点击方块】定位领地 ===== + // 玩家站在领地外右键领地内容器/UI方块同样受控(旧逻辑用玩家脚下定位, + // 站领地外 ac==null 直接放行);双箱被点侧在领地外时补查相邻同材质半边 + if (e.getAction() == Action.RIGHT_CLICK_BLOCK) { + Block clicked = e.getClickedBlock(); + if (clicked != null && isUIBlock(clicked.getType())) { + AreaConfig cac = getArea(clicked.getWorld().getName(), + clicked.getX(), clicked.getY(), clicked.getZ()); + if (cac == null && (clicked.getType() == Material.CHEST + || clicked.getType() == Material.TRAPPED_CHEST)) { + org.bukkit.block.BlockFace[] horiz = { + org.bukkit.block.BlockFace.EAST, org.bukkit.block.BlockFace.WEST, + org.bukkit.block.BlockFace.NORTH, org.bukkit.block.BlockFace.SOUTH}; + for (org.bukkit.block.BlockFace bf : horiz) { + Block nb = clicked.getRelative(bf); + if (nb.getType() == clicked.getType()) { + cac = getArea(nb.getWorld().getName(), + nb.getX(), nb.getY(), nb.getZ()); + if (cac != null) break; + } + } + } + // 公共设施连控:公共设施启用时访客自动获得容器交互权限 + if (cac != null && !cac.isPublicBuilding + && getEffectiveDeny(p, cac, "denyContainer")) { + e.setCancelled(true); + p.sendMessage("§c§l[区域防护] §f此领地禁止访问容器"); + return; + } + } + } + // ===== 区域规则检查 ===== AreaConfig ac = getArea( p.getWorld().getName(), @@ -5532,88 +5564,106 @@ public class AreaProtection implements Listener { Player p = (Player) e.getPlayer(); InventoryHolder holder = e.getInventory().getHolder(); - Location loc = null; + java.util.List locs = new java.util.ArrayList<>(); - // ★ 双箱特殊处理:DoubleChest的holder不是BlockState,需要从left/right获取 - if (holder != null && holder.getClass().getSimpleName().contains("DoubleChest")) { - try { - // DoubleChest通过反射获取left/right - Object dc = holder; - java.lang.reflect.Method getLeft = dc.getClass().getMethod("getLeft"); - Object left = getLeft.invoke(dc); - if (left instanceof org.bukkit.block.BlockState) { - loc = ((org.bukkit.block.BlockState) left).getLocation(); - } else if (left != null) { - java.lang.reflect.Method getBlock = left.getClass().getMethod("getBlock"); - Object block = getBlock.invoke(left); - if (block instanceof org.bukkit.block.Block) { - loc = ((org.bukkit.block.Block) block).getLocation(); - } - } - } catch (Exception ignored) {} + // ★ 双箱特殊处理:DoubleChest 的 holder 不是 BlockState, + // 必须同时收集左右两侧位置——任一侧在领地内即受 denyContainer 管控。 + // 旧实现只反射取 left 且 getLeft() 返回 Inventory 无 getBlock(), + // 反射异常被吞后 loc=null 直接放行,导致大箱子完全绕过领地判定。 + if (e.getInventory() instanceof org.bukkit.inventory.DoubleChestInventory) { + org.bukkit.inventory.DoubleChestInventory dci = + (org.bukkit.inventory.DoubleChestInventory) e.getInventory(); + addChestSideLocation(locs, dci.getLeftSide()); + addChestSideLocation(locs, dci.getRightSide()); + } else if (holder instanceof org.bukkit.block.DoubleChest) { + org.bukkit.block.DoubleChest dc = (org.bukkit.block.DoubleChest) holder; + addChestSideLocation(locs, dc.getLeftSide()); + addChestSideLocation(locs, dc.getRightSide()); } // ★ 检测所有容器类型:箱子、熔炉、高炉、潜影盒、末影箱、讲台、铁砧、信标、漏斗、发射器、投掷器、酿造台、工作台等 else if (holder instanceof BlockState) { BlockState bs = (BlockState) holder; - loc = bs.getLocation(); + locs.add(bs.getLocation()); } else if (holder instanceof Container) { // Container接口覆盖所有方块容器 if (holder instanceof org.bukkit.block.Block) { org.bukkit.block.Block block = (org.bukkit.block.Block) holder; - loc = block.getLocation(); + locs.add(block.getLocation()); } } // ★ Issue 9: 矿车容器(漏斗矿车、运输矿车) - if (holder != null && loc == null) { + if (holder != null && locs.isEmpty()) { String className = holder.getClass().getSimpleName(); if (className.contains("Minecart")) { // 矿车容器使用实体位置 if (holder instanceof org.bukkit.entity.Entity) { org.bukkit.entity.Entity entity = (org.bukkit.entity.Entity) holder; - loc = entity.getLocation(); + locs.add(entity.getLocation()); } } } - // 末影箱特殊处理:检查末影箱所在位置而非玩家位置 - if (holder != null) { - String typeName = holder.getClass().getSimpleName(); - if (typeName.contains("EnderChest") || typeName.contains("Ender")) { - // 末影箱也是BlockState,已经在上面处理 - } + if (locs.isEmpty()) return; + + // ★ 跨边界大箱子:任一侧在领地内即判定;先取第一个命中领地做打开记录 + AreaConfig ac = null; + Location hitLoc = null; + for (Location loc : locs) { + if (loc == null || loc.getWorld() == null) continue; + AreaConfig a = getArea(loc.getWorld().getName(), + loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); + if (a != null) { ac = a; hitLoc = loc; break; } } - - if (loc == null) return; - - AreaConfig ac = getArea(loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); - if (ac == null) return; + if (ac == null || hitLoc == null) return; // ★ 领地记录(所有容器打开都记录,含领地主/管理员/公共设施) String containerType; - if (loc.getBlock().getType() != org.bukkit.Material.AIR) { - containerType = loc.getBlock().getType().name(); + if (hitLoc.getBlock().getType() != org.bukkit.Material.AIR) { + containerType = hitLoc.getBlock().getType().name(); } else { containerType = holder.getClass().getSimpleName(); } - plugin.landRecordManager.recordContainerOpen(ac, p, loc, containerType, e.getInventory()); + plugin.landRecordManager.recordContainerOpen(ac, p, hitLoc, containerType, e.getInventory()); - if (hasPermission(p, ac, PermissionLevel.OWNER)) return; + // ★ 逐侧判定:任一侧所在领地禁止容器访问即拦截(跨边界大箱子不再漏网) + for (Location loc : locs) { + if (loc == null || loc.getWorld() == null) continue; + AreaConfig a = getArea(loc.getWorld().getName(), + loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); + if (a == null) continue; + if (hasPermission(p, a, PermissionLevel.OWNER)) continue; - // ★ 公共设施连控:公共设施启用时,访客自动获得容器交互权限(跳过禁止检查) - if (ac.isPublicBuilding) return; + // ★ 公共设施连控:公共设施启用时,访客自动获得容器交互权限(跳过禁止检查) + if (a.isPublicBuilding) continue; - // ★ 容器管理权限:denyContainer控制访客能否访问容器 - // getEffectiveDeny 已内置 ADMIN/OWNER/VISITOR 豁免, - // 对无权限玩家则走领地默认 denyContainer 配置 - if (getEffectiveDeny(p, ac, "denyContainer")) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f此领地禁止访问容器"); - return; + // ★ 容器管理权限:denyContainer控制访客能否访问容器 + // getEffectiveDeny 已内置 ADMIN/OWNER/VISITOR 豁免, + // 对无权限玩家则走领地默认 denyContainer 配置 + if (getEffectiveDeny(p, a, "denyContainer")) { + e.setCancelled(true); + p.sendMessage("§c§l[区域防护] §f此领地禁止访问容器"); + return; + } } } - public void loadWhitelists() { + // ★ 双箱侧边定位:从半边(Inventory 或 InventoryHolder)的 BlockState holder 取其方块位置 + private void addChestSideLocation(java.util.List locs, Object side) { + if (side == null) return; + Object sh = side; + if (side instanceof org.bukkit.inventory.Inventory) { + sh = ((org.bukkit.inventory.Inventory) side).getHolder(); + } + if (sh instanceof BlockState) { + Location l = ((BlockState) sh).getLocation(); + if (l != null) locs.add(l); + } else if (sh instanceof org.bukkit.block.Block) { + locs.add(((org.bukkit.block.Block) sh).getLocation()); + } + } + + public void loadWhitelists() { globalPlayerWhitelist.clear(); areaPlayerWhitelist.clear(); diff --git a/src/main/java/Sdf1_login/ChatFilterManager.java b/src/main/java/Sdf1_login/ChatFilterManager.java index 3c4da62..c3b1f66 100644 --- a/src/main/java/Sdf1_login/ChatFilterManager.java +++ b/src/main/java/Sdf1_login/ChatFilterManager.java @@ -109,7 +109,7 @@ public class ChatFilterManager { static class VerificationData { long createTime; - int a, b, op; // 0=加法, 1=减法, 2=乘法 + int a, b, op; // 0=加法, 1=减法, 2=乘法(九九表), 3=除法(九九表) String guiTargets; // GUI模式下需要选择的物品名列表("|"分隔) VerificationType type; // 验证码类型 boolean completed; @@ -120,6 +120,7 @@ public class ChatFilterManager { this.type = type; } + /** 30秒未作答 = 超时;超时【不放行】,由上层清掉旧题重新出题 */ boolean isExpired() { return System.currentTimeMillis() - createTime > 30000; } @@ -130,7 +131,8 @@ public class ChatFilterManager { NEED_VERIFICATION, // 需要验证码 PENDING, // 验证码进行中 VERIFIED, // 已验证 - FAILED // 验证失败(消息吞噬) + FAILED, // 验证失败(消息吞噬) + TIMEOUT // 超时未作答(不放行,自动重新出题) } /** 验证码答案缓存,供外部判断是否为答案 */ @@ -161,11 +163,12 @@ public class ChatFilterManager { verificationData.remove(name); return VerificationResult.VERIFIED; } - // 验证码是否过期 — 过期后自动放行,不无限卡玩家 + // ★ 30秒未作答 = 超时:不放行,清掉旧题并重新出题(绝不加入 verifiedPlayers) if (vd.isExpired()) { verificationData.remove(name); - verifiedPlayers.add(name); - return VerificationResult.VERIFIED; + p.sendMessage("§c§l[验证码] §c上一题超时,已为你换新题"); + generateMathVerification(name); + return VerificationResult.TIMEOUT; } return VerificationResult.PENDING; } @@ -184,43 +187,76 @@ public class ChatFilterManager { private static final ThreadLocal sharedRandom = ThreadLocal.withInitial(Random::new); /** - * 生成随机验证码:数学题或GUI随机50%概率 + * 生成随机验证码:加 / 减 / 乘 / 除 / GUI 五种每次随机抽一种。 + * 乘法与除法严格取自九九乘法表。 + * + * ★ 出题前必须先关掉可能还开着的旧验证码GUI:旧GUI 的 InventoryCloseEvent + * 会清 verificationData,让它先跑完,否则刚生成的新题会被清掉(点哪儿都没用)。 */ public void generateMathVerification(String playerName) { Random rand = sharedRandom.get(); - // 50% 概率数学题,50% 概率GUI - if (rand.nextBoolean()) { - generateMathChallenge(playerName, rand); - } else { + Player online = org.bukkit.Bukkit.getPlayer(playerName); + if (online != null) { + closeVerificationGuiIfOpen(online); + } + + int kind = rand.nextInt(5); // 0加 1减 2乘 3除 4GUI + if (kind == 4) { generateGUIChallenge(playerName, rand); + } else { + generateMathChallenge(playerName, kind, rand); + } + } + + /** 关掉还开着的验证码GUI(标题匹配才关,避免误关玩家自己的箱子/菜单) */ + private void closeVerificationGuiIfOpen(Player p) { + try { + String title = p.getOpenInventory().getTitle(); + if (title != null && title.contains("点击") && title.contains("完成验证")) { + p.closeInventory(); + } + } catch (Exception ignored) { } } - /** 生成数学题验证码 */ - private void generateMathChallenge(String playerName, Random rand) { - int op = rand.nextInt(3); // 0=加, 1=减, 2=乘 + /** 生成数学题验证码(op: 0加 1减 2乘 3除;乘/除每次从九九乘法表随机抽一题) */ + private void generateMathChallenge(String playerName, int op, Random rand) { int a, b; switch (op) { - case 0: a = rand.nextInt(10) + 1; b = rand.nextInt(10) + 1; break; - case 1: a = rand.nextInt(10) + 2; b = rand.nextInt(a) + 1; break; - default: a = rand.nextInt(10) + 1; b = rand.nextInt(10) + 1; break; + case 0: // 两位数加法 + a = 10 + rand.nextInt(90); + b = 10 + rand.nextInt(90); + break; + case 1: // 两位数减法(保证结果仍是两位数,不出现负数) + a = 20 + rand.nextInt(80); + b = 10 + rand.nextInt(a - 19); // b ∈ [10, a-10],结果 ≥ 10 + break; + case 2: // 九九乘法表:1×1 ~ 9×9 + a = 1 + rand.nextInt(9); + b = 1 + rand.nextInt(9); + break; + default: // 九九乘法表除法(整除):(i×j) ÷ j = i + b = 1 + rand.nextInt(9); + a = (1 + rand.nextInt(9)) * b; + break; } - + VerificationData vd = new VerificationData(a, b, op, VerificationType.MATH); verificationData.put(playerName, vd); String opSymbol = switch (op) { case 0 -> "+"; case 1 -> "-"; - default -> "×"; + case 2 -> "×"; + default -> "÷"; }; - + org.bukkit.Bukkit.getScheduler().runTask(plugin, () -> { Player p = org.bukkit.Bukkit.getPlayer(playerName); if (p != null && p.isOnline()) { p.sendMessage("§e§l[验证码] §f请回答: §e" + a + " " + opSymbol + " " + b + " = ?"); - p.sendMessage("§7(30秒内输入答案)"); + p.sendMessage("§7(30秒内输入答案,超时会自动换新题)"); } }); } @@ -366,8 +402,8 @@ public class ChatFilterManager { p.openInventory(inv); } catch (Exception ex) { plugin.getLogger().warning("[验证码] GUI挑战生成失败: " + ex.getMessage()); - // 降级为数学题 - generateMathChallenge(playerName, rand); + // 降级为数学题(随机抽一种运算) + generateMathChallenge(playerName, rand.nextInt(4), rand); } } }); @@ -391,10 +427,11 @@ public class ChatFilterManager { VerificationData vd = verificationData.get(playerName); if (vd == null) return VerificationResult.VERIFIED; + // ★ 超时不放行:清旧题并重新出题 if (vd.isExpired()) { verificationData.remove(playerName); - verifiedPlayers.add(playerName); - return VerificationResult.VERIFIED; + generateMathVerification(playerName); + return VerificationResult.TIMEOUT; } if (vd.type == VerificationType.MATH) { @@ -414,10 +451,11 @@ public class ChatFilterManager { VerificationData vd = verificationData.get(playerName); if (vd == null) return VerificationResult.VERIFIED; // 无验证码 → 放行 + // ★ 超时不放行:清旧题并重新出题 if (vd.isExpired()) { verificationData.remove(playerName); - verifiedPlayers.add(playerName); // 过期后不再要求 - return VerificationResult.VERIFIED; + generateMathVerification(playerName); + return VerificationResult.TIMEOUT; } if (vd.type == VerificationType.MATH) { @@ -427,7 +465,8 @@ public class ChatFilterManager { switch (vd.op) { case 0 -> expected = vd.a + vd.b; case 1 -> expected = vd.a - vd.b; - default -> expected = vd.a * vd.b; + case 2 -> expected = vd.a * vd.b; + default -> expected = vd.a / vd.b; // 九九表整除 } if (ans == expected) { @@ -462,13 +501,20 @@ public class ChatFilterManager { public VerificationResult checkGUIClick(String playerName, String clickedItemName) { String name = playerName; VerificationData vd = verificationData.get(name); - if (vd == null) return VerificationResult.VERIFIED; + // ★ 没有题目(旧题已被5秒定时器清掉)时绝不能算通过, + // 否则超时后随便点一下就绕过验证;已通过的老玩家仍按通过处理(防连点二次出题) + if (vd == null) { + return verifiedPlayers.contains(name) + ? VerificationResult.VERIFIED + : VerificationResult.TIMEOUT; + } if (!VerificationType.GUI.equals(vd.type)) return VerificationResult.PENDING; - + + // ★ 30秒未点选 = 超时:不放行,重新出题 if (vd.isExpired()) { verificationData.remove(name); - verifiedPlayers.add(name); - return VerificationResult.VERIFIED; + generateMathVerification(name); + return VerificationResult.TIMEOUT; } // ★ 关键修复:点击的物品名必须等于目标物品名才通过 @@ -507,7 +553,8 @@ public class ChatFilterManager { } /** - * 清理过期的验证码 + * 清理过期的验证码(只做内存回收,绝不把玩家标记为已验证; + * 玩家下一次发言/点击会走 NEED_VERIFICATION 或 TIMEOUT 分支重新出题) */ public void cleanupExpiredVerifications() { long now = System.currentTimeMillis(); diff --git a/src/main/java/Sdf1_login/Main.java b/src/main/java/Sdf1_login/Main.java index 469b0a1..32e09c9 100644 --- a/src/main/java/Sdf1_login/Main.java +++ b/src/main/java/Sdf1_login/Main.java @@ -3428,6 +3428,16 @@ public class Main extends JavaPlugin e.setCancelled(true); return; } + + if (vr == ChatFilterManager.VerificationResult.TIMEOUT) { + // ★ 30秒超时:不放行(已在 ChatFilterManager 里清旧题并换新题) + // 这条消息:纯数字按"超时的答案"吞掉,其它照样缓存,避免玩家打的字丢失 + if (!msg.trim().matches("-?\\d+")) { + chatFilter.cachePendingMessage(p.getName(), msg); + } + e.setCancelled(true); + return; + } if (vr == ChatFilterManager.VerificationResult.PENDING) { // 验证进行中:当前聊天框输入就是答案 @@ -4078,6 +4088,11 @@ public class Main extends JavaPlugin } chatFilter.pendingMessages.remove(p.getName()); } + } else if (vr == ChatFilterManager.VerificationResult.TIMEOUT) { + // ★ 超时/旧题已被清理:不放行、不清缓存消息,直接换新题 + p.closeInventory(); + p.sendMessage("§c§l[验证码] §c上一题超时,已为你换新题"); + chatFilter.generateMathVerification(p.getName()); } else { p.closeInventory(); chatFilter.pendingMessages.remove(p.getName()); diff --git a/src/main/java/Sdf1_login/TeleportManager.java b/src/main/java/Sdf1_login/TeleportManager.java index 30acf74..3fe9b52 100644 --- a/src/main/java/Sdf1_login/TeleportManager.java +++ b/src/main/java/Sdf1_login/TeleportManager.java @@ -416,6 +416,33 @@ public class TeleportManager implements Listener { } } + /** + * 发送带可点击按钮的 tpaall(全服传送)通知 + * ★ 语义:A 请求"全服玩家一起传送到 A 身边",接受者会被传到发起者那里, + * 绝不能复用 tpa 的"请求传送到你身边"文案(那是 A 想来到你这边)。 + */ + private void sendClickableTPAllNotice(Player target, String senderName) { + if (isBedrockPlayer(target)) { + target.sendMessage("§e[传送] §f" + senderName + " §e请求全服玩家一起传送到他身边"); + target.sendMessage("§7接受后,§f你 §7会被传送到 §f" + senderName + " §7身边"); + target.sendMessage("§7────────────────────"); + target.sendMessage("§a使用 §e/tpaccept §a接受,§e/tpdeny §a拒绝"); + } else { + target.sendMessage(Component.text("§e[传送] §f" + senderName + " §e请求全服玩家一起传送到他身边")); + target.sendMessage(Component.text("§7(接受后,§f你 §7会被传送到 §f" + senderName + " §7身边)")); + target.sendMessage(Component.text("§7────────────────────")); + target.sendMessage(Component.empty() + .append(Component.text("§a[✔ 接受] ") + .clickEvent(ClickEvent.runCommand("/tpaccept " + senderName)) + .hoverEvent(HoverEvent.showText(Component.text("接受 " + senderName + " 的全服传送请求")))) + .append(Component.text(" ")) + .append(Component.text("§c[✘ 拒绝] ") + .clickEvent(ClickEvent.runCommand("/tpdeny " + senderName)) + .hoverEvent(HoverEvent.showText(Component.text("拒绝 " + senderName + " 的全服传送请求"))))); + target.sendMessage(Component.text("§7或输入 §e/tpaccept §7查看全部待处理请求")); + } + } + // ==================== CLI 交互菜单 ==================== /** @@ -509,9 +536,9 @@ public class TeleportManager implements Listener { // 全服传送 player.sendMessage(Component.empty() .append(Component.text(" §e/tpaall ")) - .append(Component.text("§b[请求全服传送到你身边] ") + .append(Component.text("§b[请求全服玩家一起传送到你身边] ") .clickEvent(ClickEvent.runCommand("/tpaall")) - .hoverEvent(HoverEvent.showText(Component.text("向所有在线玩家发送传送请求"))))); + .hoverEvent(HoverEvent.showText(Component.text("向全服在线玩家发送传送请求,接受后他们一起传送到你所在位置"))))); // 自动接受开关 String autoStatus = autoAccept ? "§a已开启" : "§c已关闭"; @@ -764,8 +791,13 @@ public class TeleportManager implements Listener { removeIncomingRequest(player.getName(), targetName); removeOutgoingRequest(targetName, player.getName()); - player.sendMessage("§a[传送] 已传送到 §f" + targetName + " §a身边"); - actualSender.sendMessage("§a[传送] §f" + player.getName() + " §a已接受请求"); + if ("tpaall".equals(requestType)) { + player.sendMessage("§a[传送] §a已接受全服传送,你被传送到 §f" + targetName + " §a身边"); + actualSender.sendMessage("§a[传送] §f" + player.getName() + " §a已接受全服传送,来到你身边"); + } else { + player.sendMessage("§a[传送] 已传送到 §f" + targetName + " §a身边"); + actualSender.sendMessage("§a[传送] §f" + player.getName() + " §a已接受请求"); + } teleportCooldown.remove(actualSender.getName()); return true; @@ -1106,8 +1138,8 @@ public class TeleportManager implements Listener { teleportRequestTimes.put(sender + ":" + name, now); teleportRequestTypes.put(sender + ":" + name, "tpaall"); - // 可点击通知 - sendClickableRequestNotice(p, sender); + // 可点击通知(tpaall 专用文案:全服传送到发起者身边) + sendClickableTPAllNotice(p, sender); count++; } @@ -1231,7 +1263,7 @@ public class TeleportManager implements Listener { ItemMeta tpaAllMeta = tpaAllItem.getItemMeta(); if (tpaAllMeta != null) { tpaAllMeta.setDisplayName("§b全服传送"); - tpaAllMeta.setLore(Arrays.asList("§7请求所有玩家传送到你身边")); + tpaAllMeta.setLore(Arrays.asList("§7全服玩家接受后一起传送到你身边", "§7(A 请求全服玩家一起传送到 A 身边)")); tpaAllItem.setItemMeta(tpaAllMeta); } inv.setItem(4, tpaAllItem);