修复三项问题:1)大箱子跨领地边界绕过容器权限(双箱改为左右两侧均收集定位并逐侧判定denyContainer,交互事件改用被点击方块定位并补查相邻半箱)2)tpaall语义文案修正(通知/按钮/GUI统一为全服玩家一起传送到发起者身边)3)新人验证码超时不放行改为重新出题,题库引入九九乘法表加减乘除+GUI五选一随机抽题;另注释移动豁免诊断日志
This commit is contained in:
@@ -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<Location> 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<Location> 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();
|
||||
|
||||
|
||||
@@ -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<Random> 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();
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user