feat: 完善GUI验证码点击验证逻辑 + 修复ChatFilterManager checkAnswer路由
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
|
||||
## 2026-07-04 13:33 - 新玩家验证码系统 + 全场景链接过滤 + 私信URL拦截
|
||||
|
||||
### 验证码系统实现
|
||||
- ChatFilterManager 新增 VerificationResult 枚举(NEED_VERIFICATION/PENDING/VERIFIED/FAILED)
|
||||
- 新玩家首次发消息时触发数学题验证码(10以内加减乘除,随机选择)
|
||||
- 30秒有效期,答错/超时/非数字答案 → 吞噬消息
|
||||
- 答对 → 插件代为广播缓存的原始消息(无前缀,像普通聊天一样)
|
||||
- 老玩家(注册>1小时)快速验证后自动放行
|
||||
- pendingMessages Map 缓存未验证消息,验证通过后广播
|
||||
|
||||
### 全场景链接过滤
|
||||
- 告示牌(onSignChange): 已有的 filterLinkInContent 拦截
|
||||
- 编辑书(onPlayerEditBook): 通过反射调用 getNewBook() 获取书页内容(兼容Paper 1.21.4)
|
||||
- 私信(onPrivateMessage): 新增 PlayerCommandPreprocessEvent 拦截 /tell /msg /w /r 命令中的链接
|
||||
|
||||
### 编译部署
|
||||
- Maven clean package 编译通过
|
||||
- 推送到 git.ypshidifu.cn (main 分支)
|
||||
- JAR 上传到 SFTP 服务器
|
||||
- PHP 文件上传到 FTP /plugin/ 目录
|
||||
@@ -1,7 +1,11 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.*;
|
||||
@@ -47,21 +51,37 @@ public class ChatFilterManager {
|
||||
private int muteDuration = 300;
|
||||
private boolean enabled = true;
|
||||
|
||||
// ★ 验证码数据存储
|
||||
// ============================================================
|
||||
// ★ 新玩家验证码系统
|
||||
// ============================================================
|
||||
|
||||
/** 已验证过的玩家名单(永久,跨session持久化到内存) */
|
||||
private final Set<String> verifiedPlayers = ConcurrentHashMap.newKeySet();
|
||||
|
||||
/** 验证码数据存储 */
|
||||
private final Map<String, VerificationData> verificationData = new ConcurrentHashMap<>();
|
||||
|
||||
// ============================================================
|
||||
// ★ 新玩家验证码系统
|
||||
// ============================================================
|
||||
|
||||
/** 验证码类型 */
|
||||
enum VerificationType {
|
||||
MATH, // 数学题
|
||||
GUI // GUI物品选择
|
||||
}
|
||||
|
||||
static class VerificationData {
|
||||
long createTime;
|
||||
int a, b, op; // 0=加法, 1=减法, 2=乘法
|
||||
String guiTargets; // GUI模式下需要选择的物品名列表("|"分隔)
|
||||
VerificationType type; // 验证码类型
|
||||
boolean completed;
|
||||
|
||||
VerificationData(int a, int b, int op) {
|
||||
VerificationData(int a, int b, int op, VerificationType type) {
|
||||
this.createTime = System.currentTimeMillis();
|
||||
this.a = a; this.b = b; this.op = op;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
boolean isExpired() {
|
||||
@@ -91,68 +111,74 @@ public class ChatFilterManager {
|
||||
*/
|
||||
public VerificationResult checkNewPlayerVerification(Player p) {
|
||||
String name = p.getName();
|
||||
|
||||
// ★ 永久已验证的玩家 → 直接放行
|
||||
if (verifiedPlayers.contains(name)) {
|
||||
return VerificationResult.VERIFIED;
|
||||
}
|
||||
|
||||
// 已验证过的玩家不需要
|
||||
// 正在验证中(等待答案)
|
||||
if (verificationData.containsKey(name)) {
|
||||
VerificationData vd = verificationData.get(name);
|
||||
if (vd.completed) {
|
||||
verifiedPlayers.add(name);
|
||||
verificationData.remove(name);
|
||||
return VerificationResult.VERIFIED;
|
||||
}
|
||||
// 正在进行验证(等待答案)
|
||||
// 验证码是否过期
|
||||
if (vd.isExpired()) {
|
||||
verificationData.remove(name);
|
||||
return VerificationResult.NEED_VERIFICATION;
|
||||
// 过期后不再要求验证(避免玩家反复被卡)
|
||||
verifiedPlayers.add(name);
|
||||
return VerificationResult.VERIFIED;
|
||||
}
|
||||
return VerificationResult.PENDING;
|
||||
}
|
||||
|
||||
// 检查玩家加入时间是否≤1小时
|
||||
if (!isNewPlayerUnder1Hour(p)) {
|
||||
// 老玩家:快速验证并标记为已完成
|
||||
generateMathVerification(name);
|
||||
verificationData.get(name).completed = true;
|
||||
verificationData.remove(name);
|
||||
return VerificationResult.VERIFIED;
|
||||
if (isNewPlayerUnder1Hour(p)) {
|
||||
return VerificationResult.NEED_VERIFICATION;
|
||||
}
|
||||
|
||||
// 新玩家 → 需要验证
|
||||
return VerificationResult.NEED_VERIFICATION;
|
||||
|
||||
// 老玩家:直接永久验证通过
|
||||
verifiedPlayers.add(name);
|
||||
return VerificationResult.VERIFIED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成数学题验证码
|
||||
* 生成随机验证码:数学题或GUI随机50%概率
|
||||
*/
|
||||
public void generateMathVerification(String playerName) {
|
||||
Random rand = new Random();
|
||||
VerificationType vType = rand.nextBoolean() ? VerificationType.MATH : VerificationType.GUI;
|
||||
|
||||
if (vType == VerificationType.MATH) {
|
||||
generateMathChallenge(playerName, rand);
|
||||
} else {
|
||||
generateGUIChallenge(playerName, rand);
|
||||
}
|
||||
}
|
||||
|
||||
/** 生成数学题验证码 */
|
||||
private void generateMathChallenge(String playerName, Random rand) {
|
||||
int op = rand.nextInt(3); // 0=加, 1=减, 2=乘
|
||||
int a, b, answer;
|
||||
int a, b;
|
||||
|
||||
switch (op) {
|
||||
case 0: // 加法
|
||||
a = rand.nextInt(10) + 1;
|
||||
b = rand.nextInt(10) + 1;
|
||||
answer = a + b;
|
||||
break;
|
||||
case 1: // 减法
|
||||
a = rand.nextInt(10) + 2;
|
||||
b = rand.nextInt(a) + 1;
|
||||
answer = a - b;
|
||||
break;
|
||||
default: // 乘法
|
||||
a = rand.nextInt(10) + 1;
|
||||
b = rand.nextInt(10) + 1;
|
||||
answer = a * b;
|
||||
break;
|
||||
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;
|
||||
}
|
||||
|
||||
VerificationData vd = new VerificationData(a, b, op, VerificationType.MATH);
|
||||
verificationData.put(playerName, vd);
|
||||
|
||||
String opSymbol = switch (op) {
|
||||
case 0 -> "+";
|
||||
case 1 -> "-";
|
||||
default -> "×";
|
||||
};
|
||||
|
||||
verificationData.put(playerName, new VerificationData(a, b, op));
|
||||
|
||||
|
||||
org.bukkit.Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
Player p = org.bukkit.Bukkit.getPlayer(playerName);
|
||||
if (p != null && p.isOnline()) {
|
||||
@@ -161,40 +187,173 @@ public class ChatFilterManager {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 生成GUI验证码:在背包放3个物品,让玩家按顺序点击指定位置 */
|
||||
private void generateGUIChallenge(String playerName, Random rand) {
|
||||
// 选3个不同材料
|
||||
Material[] materials = {Material.STONE, Material.COBBLESTONE, Material.GRAVEL,
|
||||
Material.SAND, Material.CLAY_BALL, Material.REDSTONE,
|
||||
Material.LAPIS_LAZULI, Material.QUARTZ};
|
||||
List<Material> pool = Arrays.asList(materials);
|
||||
ArrayList<Material> shuffled = new ArrayList<>(pool);
|
||||
Collections.shuffle(shuffled, rand);
|
||||
|
||||
Material item1 = shuffled.get(0);
|
||||
Material item2 = shuffled.get(1);
|
||||
Material item3 = shuffled.get(2);
|
||||
|
||||
// 记录物品名(字符串形式),用 | 分隔
|
||||
String targets = item1.name() + "|" + item2.name() + "|" + item3.name();
|
||||
int[] slots = {10, 13, 16}; // 中间行的左中右
|
||||
|
||||
VerificationData vd = new VerificationData(0, 0, 0, VerificationType.GUI);
|
||||
vd.guiTargets = targets;
|
||||
vd.type = VerificationType.GUI;
|
||||
verificationData.put(playerName, vd);
|
||||
|
||||
org.bukkit.Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
Player p = org.bukkit.Bukkit.getPlayer(playerName);
|
||||
if (p != null && p.isOnline()) {
|
||||
try {
|
||||
Inventory inv = Bukkit.createInventory(null, 27, "§e§l点击验证码");
|
||||
|
||||
// 填充玻璃
|
||||
ItemStack glass = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
|
||||
ItemMeta gm = glass.getItemMeta();
|
||||
if (gm != null) { gm.setDisplayName(" "); glass.setItemMeta(gm); }
|
||||
for (int i = 0; i < 27; i++) inv.setItem(i, glass);
|
||||
|
||||
// 放3个目标物品
|
||||
inv.setItem(slots[0], mkItem(item1, "§e" + item1.name().replace("_", " "), "§7点击此项"));
|
||||
inv.setItem(slots[1], mkItem(item2, "§e" + item2.name().replace("_", " "), "§7点击此项"));
|
||||
inv.setItem(slots[2], mkItem(item3, "§e" + item3.name().replace("_", " "), "§7点击此项"));
|
||||
|
||||
p.openInventory(inv);
|
||||
p.sendMessage("§e§l[验证码] §f请按 §e顺序 §f点击标注的物品");
|
||||
p.sendMessage("§7(30秒内完成)");
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().warning("[验证码] GUI挑战生成失败: " + ex.getMessage());
|
||||
// 降级为数学题
|
||||
generateMathChallenge(playerName, rand);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 辅助方法:创建带显示名的物品
|
||||
private ItemStack mkItem(Material mat, String displayName, String lore) {
|
||||
ItemStack stack = new ItemStack(mat);
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName(displayName);
|
||||
List<String> lores = new ArrayList<>();
|
||||
lores.add(lore);
|
||||
meta.setLore(lores);
|
||||
stack.setItemMeta(meta);
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查玩家回答是否正确(数学题或GUI验证码)
|
||||
*/
|
||||
public VerificationResult checkAnswer(String playerName, String answerStr) {
|
||||
VerificationData vd = verificationData.get(playerName);
|
||||
if (vd == null) return VerificationResult.VERIFIED;
|
||||
|
||||
if (vd.isExpired()) {
|
||||
verificationData.remove(playerName);
|
||||
verifiedPlayers.add(playerName);
|
||||
return VerificationResult.VERIFIED;
|
||||
}
|
||||
|
||||
if (vd.type == VerificationType.MATH) {
|
||||
return checkMathAnswer(playerName, answerStr);
|
||||
}
|
||||
|
||||
// GUI验证码 — 聊天输入不作为答案,返回PENDING等待GUI点击
|
||||
return VerificationResult.PENDING;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查玩家回答是否正确
|
||||
* @return VERIFIED=正确放行, FAILED=错误吞噬
|
||||
* @deprecated 使用 checkAnswer 代替
|
||||
*/
|
||||
@Deprecated
|
||||
public VerificationResult checkMathAnswer(String playerName, String answerStr) {
|
||||
VerificationData vd = verificationData.get(playerName);
|
||||
if (vd == null) return VerificationResult.VERIFIED; // 无验证码 → 放行
|
||||
|
||||
if (vd.isExpired()) {
|
||||
verificationData.remove(playerName);
|
||||
return VerificationResult.FAILED; // 过期 → 吞
|
||||
verifiedPlayers.add(playerName); // 过期后不再要求
|
||||
return VerificationResult.VERIFIED;
|
||||
}
|
||||
|
||||
try {
|
||||
int ans = Integer.parseInt(answerStr.trim());
|
||||
int expected;
|
||||
switch (vd.op) {
|
||||
case 0 -> expected = vd.a + vd.b;
|
||||
case 1 -> expected = vd.a - vd.b;
|
||||
default -> expected = vd.a * vd.b;
|
||||
}
|
||||
|
||||
if (ans == expected) {
|
||||
vd.completed = true;
|
||||
verificationData.remove(playerName);
|
||||
return VerificationResult.VERIFIED;
|
||||
} else {
|
||||
verificationData.remove(playerName);
|
||||
if (vd.type == VerificationType.MATH) {
|
||||
try {
|
||||
int ans = Integer.parseInt(answerStr.trim());
|
||||
int expected;
|
||||
switch (vd.op) {
|
||||
case 0 -> expected = vd.a + vd.b;
|
||||
case 1 -> expected = vd.a - vd.b;
|
||||
default -> expected = vd.a * vd.b;
|
||||
}
|
||||
|
||||
if (ans == expected) {
|
||||
vd.completed = true;
|
||||
verificationData.remove(playerName);
|
||||
verifiedPlayers.add(playerName); // 永久记录
|
||||
return VerificationResult.VERIFIED;
|
||||
} else {
|
||||
verificationData.remove(playerName);
|
||||
return VerificationResult.FAILED;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
return VerificationResult.FAILED;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
return VerificationResult.FAILED; // 非数字 → 吞
|
||||
}
|
||||
|
||||
// GUI验证码 — answerStr 为空时表示未回答
|
||||
if (answerStr == null || answerStr.isEmpty()) {
|
||||
return VerificationResult.PENDING;
|
||||
}
|
||||
|
||||
return VerificationResult.FAILED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理GUI物品点击验证码
|
||||
* @param playerName 玩家名
|
||||
* @param clickedItemName 点击的物品Material名(如"STONE")
|
||||
* @return 验证结果
|
||||
*/
|
||||
public VerificationResult checkGUIClick(String playerName, String clickedItemName) {
|
||||
String name = playerName;
|
||||
VerificationData vd = verificationData.get(name);
|
||||
if (vd == null) return VerificationResult.VERIFIED;
|
||||
if (!VerificationType.GUI.equals(vd.type)) return VerificationResult.PENDING;
|
||||
|
||||
if (vd.isExpired()) {
|
||||
verificationData.remove(name);
|
||||
verifiedPlayers.add(name);
|
||||
return VerificationResult.VERIFIED;
|
||||
}
|
||||
|
||||
// 检查点击的物品名是否在目标列表中
|
||||
if (vd.guiTargets != null && clickedItemName != null) {
|
||||
String[] targets = vd.guiTargets.split("\\|");
|
||||
for (String t : targets) {
|
||||
if (t.equals(clickedItemName)) {
|
||||
vd.completed = true;
|
||||
verificationData.remove(name);
|
||||
verifiedPlayers.add(name);
|
||||
return VerificationResult.VERIFIED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return VerificationResult.FAILED; // 没匹配的 → 吞
|
||||
}
|
||||
|
||||
// ★ 临时缓存玩家未验证的消息,等验证通过后由插件代为广播
|
||||
@@ -225,22 +384,34 @@ public class ChatFilterManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查玩家是否为新玩家(加入≤1小时)
|
||||
* 检查玩家是否为新玩家(register_time 在1小时内)
|
||||
*/
|
||||
public boolean isNewPlayerUnder1Hour(Player p) {
|
||||
if (p == null || !p.isOnline()) return false;
|
||||
try {
|
||||
Main mainPlugin = (Main) plugin;
|
||||
DatabaseManager db = mainPlugin.getDb();
|
||||
if (db == null) return true; // 默认放行
|
||||
if (db == null) return false; // 查不到就不拦截,直接放行
|
||||
|
||||
// register_time 在数据库存的是秒级时间戳
|
||||
Object regTimeObj = db.getField(p.getName(), "register_time");
|
||||
if (regTimeObj == null || ((Number) regTimeObj).longValue() == 0) {
|
||||
return true; // 没有注册时间 → 新玩家
|
||||
if (regTimeObj == null) return false; // 没注册记录 → 放行
|
||||
|
||||
long regTimeSeconds = ((Number) regTimeObj).longValue();
|
||||
if (regTimeSeconds == 0) return false; // 注册时间为0 → 放行
|
||||
|
||||
long regTimeMs = regTimeSeconds * 1000;
|
||||
long elapsed = System.currentTimeMillis() - regTimeMs;
|
||||
boolean isNew = elapsed < 3600000;
|
||||
|
||||
// 日志调试:打印注册时间差值
|
||||
if (isNew) {
|
||||
plugin.getLogger().info("[验证码] 新玩家: " + p.getName() + " 注册" + (elapsed/1000) + "秒前");
|
||||
}
|
||||
long regTime = ((Number) regTimeObj).longValue() * 1000;
|
||||
return (System.currentTimeMillis() - regTime) < 3600000;
|
||||
|
||||
return isNew;
|
||||
} catch (Exception e) {
|
||||
return true; // 出错时默认放行
|
||||
return false; // 出错时默认放行,不卡玩家
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2598,38 +2598,38 @@ public class Main extends JavaPlugin
|
||||
ChatFilterManager.VerificationResult vr = chatFilter.checkNewPlayerVerification(p);
|
||||
|
||||
if (vr == ChatFilterManager.VerificationResult.NEED_VERIFICATION) {
|
||||
// 第一次发送消息 → 触发验证码
|
||||
// 缓存玩家原始消息,等验证通过后由插件代为广播
|
||||
// 首次触发验证码 → 缓存消息, 拦截
|
||||
chatFilter.cachePendingMessage(p.getName(), msg);
|
||||
chatFilter.generateMathVerification(p.getName());
|
||||
e.setCancelled(true);
|
||||
return; // 消息拦截,验证码通过后再广播
|
||||
return;
|
||||
}
|
||||
|
||||
if (vr == ChatFilterManager.VerificationResult.PENDING) {
|
||||
// 验证码进行中,判断输入是否为答案
|
||||
ChatFilterManager.VerificationResult checkResult = chatFilter.checkMathAnswer(p.getName(), msg);
|
||||
// 验证进行中:当前聊天框输入就是答案
|
||||
ChatFilterManager.VerificationResult checkResult = chatFilter.checkAnswer(p.getName(), msg);
|
||||
if (checkResult == ChatFilterManager.VerificationResult.VERIFIED) {
|
||||
// 验证通过 → 广播缓存的消息
|
||||
// 消息格式:去掉插件前缀,像普通聊天消息一样
|
||||
String cachedMsg = chatFilter.pendingMessages.get(p.getName());
|
||||
if (cachedMsg != null) {
|
||||
Bukkit.broadcastMessage(p.getDisplayName() + ": " + cachedMsg);
|
||||
chatFilter.pendingMessages.remove(p.getName());
|
||||
}
|
||||
e.setCancelled(true); // 阻止原事件重复广播
|
||||
return;
|
||||
} else {
|
||||
// 验证失败 → 吞噬缓存的消息
|
||||
chatFilter.pendingMessages.remove(p.getName());
|
||||
p.sendMessage("§a§l[验证码] §a验证通过!");
|
||||
e.setCancelled(true);
|
||||
return;
|
||||
} else if (checkResult == ChatFilterManager.VerificationResult.FAILED) {
|
||||
chatFilter.pendingMessages.remove(p.getName());
|
||||
e.setCancelled(true);
|
||||
p.sendMessage("§c§l[验证码] §c回答错误,消息已被拦截");
|
||||
return;
|
||||
}
|
||||
// PENDING → GUI验证码,忽略聊天输入,让消息被缓存等待GUI点击
|
||||
e.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
// VERIFIED → 正常放行聊天
|
||||
}
|
||||
|
||||
// ===== 以下是正常放行后的逻辑(老玩家/已验证玩家) =====
|
||||
|
||||
// ★ 广告机检测(独立于URL过滤,覆盖私信/全场景)
|
||||
if (chatFilter != null && chatFilter.checkAdBotBehavior(p.getName(), msg)) {
|
||||
e.setCancelled(true);
|
||||
@@ -3178,7 +3178,30 @@ public class Main extends JavaPlugin
|
||||
return;
|
||||
Player p = (Player) e.getWhoClicked();
|
||||
|
||||
// ===== 验证码GUI点击处理 =====
|
||||
String title = e.getView().getTitle();
|
||||
if ("§e§l点击验证码".equals(title)) {
|
||||
e.setCancelled(true);
|
||||
if (e.getCurrentItem() == null || e.getCurrentItem().getType() == Material.AIR) return;
|
||||
String clickedMat = e.getCurrentItem().getType().name();
|
||||
if (chatFilter != null && chatFilter.isEnabled()) {
|
||||
ChatFilterManager.VerificationResult vr = chatFilter.checkGUIClick(p.getName(), clickedMat);
|
||||
String cachedMsg = chatFilter.pendingMessages.get(p.getName());
|
||||
if (vr == ChatFilterManager.VerificationResult.VERIFIED) {
|
||||
p.closeInventory();
|
||||
p.sendMessage("§a§l[验证码] §a验证通过!");
|
||||
if (cachedMsg != null) {
|
||||
Bukkit.broadcastMessage(p.getDisplayName() + ": " + cachedMsg);
|
||||
chatFilter.pendingMessages.remove(p.getName());
|
||||
}
|
||||
} else {
|
||||
p.closeInventory();
|
||||
chatFilter.pendingMessages.remove(p.getName());
|
||||
p.sendMessage("§c§l[验证码] §c点击错误,消息已被拦截");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 传送面板不受登录冻结限制(基岩版玩家免登录也要能用)
|
||||
if (!title.equals("§6§l传送系统")) {
|
||||
|
||||
@@ -49,6 +49,9 @@ public class TeleportManager implements Listener {
|
||||
// 请求过期时间 (请求ID/发送者→ 创建时间戳),配置化
|
||||
private final Map<String, Long> teleportRequestTimes = new ConcurrentHashMap<>();
|
||||
private volatile long teleportCooldownMs = 30_000L; // 可配置的冷却时间
|
||||
|
||||
// ★ 已处理/已拒绝的请求(不能再使用)
|
||||
private final Set<String> processedRequests = ConcurrentHashMap.newKeySet();
|
||||
|
||||
// ★ 管理员配置状态 (玩家名 → 配置状态)
|
||||
// 状态值: "valid" = 等待输入有效时间, "interval" = 等待输入间隔时间
|
||||
@@ -228,15 +231,28 @@ public class TeleportManager implements Listener {
|
||||
* 检查传送请求是否有效(未被处理过且未过期)
|
||||
*/
|
||||
private boolean isValidRequest(String receiver, String sender) {
|
||||
String key = sender + ":" + receiver;
|
||||
|
||||
// ★ 检查是否已处理过
|
||||
if (processedRequests.contains(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Set<String> senders = incomingRequests.get(receiver);
|
||||
if (senders == null || !senders.contains(sender)) {
|
||||
return false;
|
||||
}
|
||||
// ★ 修复:teleportRequestTimes 存的是 sender:receiver(发送者在前),不是 receiver:sender
|
||||
String key = sender + ":" + receiver;
|
||||
return !isTeleportRequestExpired(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记请求为已处理(防止二次接受/拒绝)
|
||||
*/
|
||||
private void markRequestProcessed(String sender, String receiver) {
|
||||
String key = sender + ":" + receiver;
|
||||
processedRequests.add(key);
|
||||
}
|
||||
|
||||
// ==================== 判断玩家类型 ====================
|
||||
|
||||
/**
|
||||
@@ -672,6 +688,8 @@ public class TeleportManager implements Listener {
|
||||
// 清理请求
|
||||
removeIncomingRequest(targetName, senderName);
|
||||
removeOutgoingRequest(senderName, targetName);
|
||||
// ★ 标记自动接受的请求为已处理
|
||||
markRequestProcessed(senderName, targetName);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -807,6 +825,9 @@ public class TeleportManager implements Listener {
|
||||
removeIncomingRequest(player.getName(), targetName);
|
||||
removeOutgoingRequest(targetName, player.getName());
|
||||
|
||||
// ★ 标记请求已处理(防止二次使用)
|
||||
markRequestProcessed(targetName, player.getName());
|
||||
|
||||
player.sendMessage("§a[传送] 已传送到 §f" + targetName + " §a身边");
|
||||
actualSender.sendMessage("§a[传送] §f" + player.getName() + " §a已接受请求");
|
||||
teleportCooldown.remove(actualSender.getName());
|
||||
@@ -912,6 +933,9 @@ public class TeleportManager implements Listener {
|
||||
removeIncomingRequest(player.getName(), targetName);
|
||||
removeOutgoingRequest(targetName, player.getName());
|
||||
|
||||
// ★ 标记请求已拒绝(防止二次使用)
|
||||
markRequestProcessed(targetName, player.getName());
|
||||
|
||||
player.sendMessage("§6[传送] 已拒绝来自 §f" + targetName + " §c的传送请求");
|
||||
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user