fix: 观赛退出绝不回创造模式、正版验证轮询fail-closed、验证码改为99以内小学加减乘除
- PVPArenaManager: 进观赛时创造/观察者一律归一为生存再备份,exitSpectator/forceLeaveArena 兜底还原绝不给 CREATIVE(防退出观赛变创造作弊) - WebManager: 正版验证轮询fail-closed——非JSON/缺字段视为无效响应仅记日志绝不放行,PHP明确失败即终止轮询,verified须凭证齐全且会话归属玩家一致才 autoLogin - ChatFilterManager: 验证码算术题改为小学水平99以内加减乘除(和/积≤99、减法不为负、除法整除)
This commit is contained in:
@@ -109,7 +109,7 @@ public class ChatFilterManager {
|
||||
|
||||
static class VerificationData {
|
||||
long createTime;
|
||||
int a, b, op; // 0=加法, 1=减法, 2=乘法(九九表), 3=除法(九九表)
|
||||
int a, b, op; // 0=加法, 1=减法, 2=乘法, 3=除法(均为99以内小学运算)
|
||||
String guiTargets; // GUI模式下需要选择的物品名列表("|"分隔)
|
||||
VerificationType type; // 验证码类型
|
||||
boolean completed;
|
||||
@@ -188,7 +188,7 @@ public class ChatFilterManager {
|
||||
|
||||
/**
|
||||
* 生成随机验证码:加 / 减 / 乘 / 除 / GUI 五种每次随机抽一种。
|
||||
* 乘法与除法严格取自九九乘法表。
|
||||
* ★ 算术题统一为小学水平「99以内加减乘除」(和/积≤99、减法不为负、除法整除)。
|
||||
*
|
||||
* ★ 出题前必须先关掉可能还开着的旧验证码GUI:旧GUI 的 InventoryCloseEvent
|
||||
* 会清 verificationData,让它先跑完,否则刚生成的新题会被清掉(点哪儿都没用)。
|
||||
@@ -237,26 +237,30 @@ public class ChatFilterManager {
|
||||
}
|
||||
}
|
||||
|
||||
/** 生成数学题验证码(op: 0加 1减 2乘 3除;乘/除每次从九九乘法表随机抽一题) */
|
||||
/**
|
||||
* 生成数学题验证码(op: 0加 1减 2乘 3除)
|
||||
* ★ 小学水平「99以内加减乘除」:加法和≤99、减法不出现负数、乘法积≤99、除法整除且被除数≤99
|
||||
*/
|
||||
private void generateMathChallenge(String playerName, int op, Random rand) {
|
||||
int a, b;
|
||||
|
||||
switch (op) {
|
||||
case 0: // 两位数加法
|
||||
a = 10 + rand.nextInt(90);
|
||||
b = 10 + rand.nextInt(90);
|
||||
case 0: // 加法:两数之和不超过99
|
||||
a = 1 + rand.nextInt(98); // a ∈ [1, 98]
|
||||
b = 1 + rand.nextInt(99 - a); // b ∈ [1, 99-a] → a+b ≤ 99
|
||||
break;
|
||||
case 1: // 两位数减法(保证结果仍是两位数,不出现负数)
|
||||
a = 20 + rand.nextInt(80);
|
||||
b = 10 + rand.nextInt(a - 19); // b ∈ [10, a-10],结果 ≥ 10
|
||||
case 1: // 减法:被减数不超过99,结果≥1(小学不出现负数/零)
|
||||
a = 2 + rand.nextInt(98); // a ∈ [2, 99]
|
||||
b = 1 + rand.nextInt(a - 1); // b ∈ [1, a-1] → 结果 ∈ [1, a-1]
|
||||
break;
|
||||
case 2: // 九九乘法表:1×1 ~ 9×9
|
||||
a = 1 + rand.nextInt(9);
|
||||
b = 1 + rand.nextInt(9);
|
||||
case 2: // 乘法:积不超过99(如 12×7=84、11×9=99)
|
||||
b = 2 + rand.nextInt(8); // b ∈ [2, 9]
|
||||
a = 2 + rand.nextInt((99 / b) - 1); // a ∈ [2, floor(99/b)] → a*b ≤ 99
|
||||
break;
|
||||
default: // 九九乘法表除法(整除):(i×j) ÷ j = i
|
||||
b = 1 + rand.nextInt(9);
|
||||
a = (1 + rand.nextInt(9)) * b;
|
||||
default: // 除法:整除且被除数不超过99(如 96÷8=12、99÷9=11)
|
||||
b = 2 + rand.nextInt(8); // b ∈ [2, 9]
|
||||
int q = 2 + rand.nextInt((99 / b) - 1); // 商 ∈ [2, floor(99/b)]
|
||||
a = b * q; // a ≤ 99
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -484,7 +488,7 @@ public class ChatFilterManager {
|
||||
case 0 -> expected = vd.a + vd.b;
|
||||
case 1 -> expected = vd.a - vd.b;
|
||||
case 2 -> expected = vd.a * vd.b;
|
||||
default -> expected = vd.a / vd.b; // 九九表整除
|
||||
default -> expected = vd.a / vd.b; // 生成时保证整除(a % b == 0)
|
||||
}
|
||||
|
||||
if (ans == expected) {
|
||||
|
||||
@@ -1235,7 +1235,10 @@ public class PVPArenaManager implements Listener {
|
||||
GameMode backMode = spectatorBackups.remove(playerName);
|
||||
if (backMode != null && !isDisconnect && player.isOnline()
|
||||
&& player.getGameMode() == GameMode.SPECTATOR) {
|
||||
player.setGameMode(backMode == GameMode.SPECTATOR ? GameMode.SURVIVAL : backMode);
|
||||
// ★ 兜底:还原目标为观察者或创造时一律回生存(退出观赛绝不回创造,防止作弊)
|
||||
GameMode restore = (backMode == GameMode.SPECTATOR
|
||||
|| backMode == GameMode.CREATIVE) ? GameMode.SURVIVAL : backMode;
|
||||
player.setGameMode(restore);
|
||||
}
|
||||
|
||||
// 清理所有状态(含取消超时定时器)
|
||||
@@ -2850,7 +2853,12 @@ public class PVPArenaManager implements Listener {
|
||||
|
||||
// 2) 记录原模式并进入 MC 原版观察者
|
||||
// ★ 必须【先记再切】:观察者模式下 addItem 会掉地上,故不再发放任何物品
|
||||
spectatorBackups.put(p.getName(), p.getGameMode());
|
||||
// ★ 绝不把【创造模式】当作还原目标:以创造进入观赛的,退出一律回生存(防止退出观赛变成创造作弊)
|
||||
GameMode before = p.getGameMode();
|
||||
if (before == GameMode.CREATIVE || before == GameMode.SPECTATOR) {
|
||||
before = GameMode.SURVIVAL;
|
||||
}
|
||||
spectatorBackups.put(p.getName(), before);
|
||||
p.setGameMode(GameMode.SPECTATOR);
|
||||
|
||||
p.sendMessage("§a§l[PVP观赛] §f已进入观察者模式,可穿墙自由观赛");
|
||||
@@ -2866,7 +2874,10 @@ public class PVPArenaManager implements Listener {
|
||||
if (p.getGameMode() != GameMode.SPECTATOR) return;
|
||||
|
||||
GameMode back = spectatorBackups.remove(p.getName());
|
||||
if (back == null || back == GameMode.SPECTATOR) back = GameMode.SURVIVAL;
|
||||
// ★ 兜底:备份缺失/观察者/创造 一律还原为生存(退出观赛绝不回创造,防止作弊)
|
||||
if (back == null || back == GameMode.SPECTATOR || back == GameMode.CREATIVE) {
|
||||
back = GameMode.SURVIVAL;
|
||||
}
|
||||
|
||||
// 观察者常悬在高空或钻进方块里 → 切回可玩模式前先挪到可站立的空气格
|
||||
safeRecoverPosition(p);
|
||||
|
||||
@@ -5215,6 +5215,8 @@ public class WebManager {
|
||||
}
|
||||
|
||||
final String[] pollData = {sessionId};
|
||||
// ★ 连续无效响应(非JSON/结构不符)计数:只用于日志与提示,任何情况下都不会据此放行
|
||||
final int[] invalidPolls = {0};
|
||||
final long startTime = System.currentTimeMillis();
|
||||
final long maxPollTime = 660000; // 11分钟(比会话过期多1分钟)
|
||||
|
||||
@@ -5262,16 +5264,69 @@ public class WebManager {
|
||||
String response = doGet(url);
|
||||
if (response == null) return;
|
||||
|
||||
com.google.gson.JsonObject json = com.google.gson.JsonParser.parseString(response).getAsJsonObject();
|
||||
if (!json.get("success").getAsBoolean()) return;
|
||||
|
||||
// ★ fail-closed:非JSON / 结构不符 一律视为"无效响应",绝不判为通过
|
||||
com.google.gson.JsonObject json;
|
||||
try {
|
||||
json = com.google.gson.JsonParser.parseString(response).getAsJsonObject();
|
||||
} catch (Exception parseEx) {
|
||||
invalidPolls[0]++;
|
||||
if (invalidPolls[0] == 1 || invalidPolls[0] % 15 == 0) {
|
||||
String preview = response.length() > 200 ? response.substring(0, 200) + "..." : response;
|
||||
plugin.getLogger().warning("[正版验证] 响应非JSON,忽略(第" + invalidPolls[0] + "次): " + preview);
|
||||
}
|
||||
if (invalidPolls[0] == 5) {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (player.isOnline()) {
|
||||
player.sendMessage("§c验证服务响应异常,正在自动重试…");
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!json.has("success") || !json.get("success").isJsonPrimitive()
|
||||
|| !json.get("success").getAsBoolean()) {
|
||||
// PHP明确返回失败(会话不存在/密钥错误等结构性错误)→ 终止轮询,不放行
|
||||
String err = (json.has("error") && json.get("error").isJsonPrimitive())
|
||||
? json.get("error").getAsString() : "未知错误";
|
||||
plugin.getLogger().warning("[正版验证] PHP返回失败,终止轮询: " + err);
|
||||
minecraftAuthSessions.remove(playerName);
|
||||
minecraftAuthPollers.remove(playerName);
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (player.isOnline()) {
|
||||
player.sendMessage("§c验证失败: " + err);
|
||||
}
|
||||
});
|
||||
this.cancel();
|
||||
return;
|
||||
}
|
||||
if (!json.has("data") || !json.get("data").isJsonObject()) {
|
||||
invalidPolls[0]++;
|
||||
plugin.getLogger().warning("[正版验证] 响应缺少data,忽略(第" + invalidPolls[0] + "次)");
|
||||
return;
|
||||
}
|
||||
com.google.gson.JsonObject data = json.getAsJsonObject("data");
|
||||
if (!data.has("status") || !data.get("status").isJsonPrimitive()) {
|
||||
invalidPolls[0]++;
|
||||
plugin.getLogger().warning("[正版验证] 响应缺少status,忽略(第" + invalidPolls[0] + "次)");
|
||||
return;
|
||||
}
|
||||
String status = data.get("status").getAsString();
|
||||
invalidPolls[0] = 0; // 收到有效响应 → 重置无效计数
|
||||
|
||||
if ("verified".equals(status)) {
|
||||
// 验证成功!
|
||||
String mcUuid = data.get("mc_uuid").getAsString();
|
||||
String mcUsername = data.get("mc_username").getAsString();
|
||||
String mcUuid = data.has("mc_uuid") ? data.get("mc_uuid").getAsString() : "";
|
||||
String mcUsername = data.has("mc_username") ? data.get("mc_username").getAsString() : "";
|
||||
String sessionPlayer = data.has("player_name") ? data.get("player_name").getAsString() : "";
|
||||
|
||||
// ★ fail-closed:凭证不全 / 会话归属玩家不符 → 一律不放行(继续轮询直到超时)
|
||||
if (mcUuid.isEmpty() || mcUsername.isEmpty()
|
||||
|| (sessionPlayer != null && !sessionPlayer.isEmpty()
|
||||
&& !sessionPlayer.equalsIgnoreCase(playerName))) {
|
||||
plugin.getLogger().warning("[正版验证] verified但校验不通过,拒绝放行: session.player="
|
||||
+ sessionPlayer + ", uuid=" + mcUuid + ", name=" + mcUsername);
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getLogger().info("[正版验证] 验证成功: " + playerName + " -> " + mcUsername + " (" + mcUuid + ")");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user