From 8787dd615078ec3592dfc969412009bdb30557c9 Mon Sep 17 00:00:00 2001 From: mumanMN Date: Sat, 6 Jun 2026 14:00:51 +0800 Subject: [PATCH 1/8] Add files via upload --- .../README.md" | 14 + .../__init__.py" | 736 ++++++++++++++++++ .../datas.json" | 14 + 3 files changed, 764 insertions(+) create mode 100644 "\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/README.md" create mode 100644 "\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" create mode 100644 "\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/datas.json" diff --git "a/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/README.md" "b/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/README.md" new file mode 100644 index 00000000..afc7429a --- /dev/null +++ "b/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/README.md" @@ -0,0 +1,14 @@ +# MUNAN ANTI CHEAT + +## 1.功能 +[ 1 ] Fly Check +[ 2 ] Speed Check + +## 2.注意事项 +添加 admin 标签可以查看加速度 速度值 +添加 bypass 标签可以豁免检测 + +当前版本: +0.0.1(beta) +如果可以的话求帮维护 +球球了qwq \ No newline at end of file diff --git "a/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" "b/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" new file mode 100644 index 00000000..589fac94 --- /dev/null +++ "b/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" @@ -0,0 +1,736 @@ +import time +import math +import json +from collections import deque +from tooldelta import Plugin, InternalBroadcast, fmts, plugin_entry, cfg as config + + +class SimpleAntiCheat(Plugin): + name = "反作弊-移动检测(MUNAN)" + author = "Assistant" + version = (0, 0, 1) + + def __init__(self, frame): + super().__init__(frame) + self.ListenInternalBroadcast("ggpp:publish_player_position", self.on_pos_data) + self.pos_history = {} + self.rollback_history = {} + self.last_pos = {} + self.air_pos_history = {} + self.last_print_time = 0 + self.last_admin_show_time = 0 + self.rollback_count = {} + + DEFAULT_CFG = { + #---- 所有检测开关 ---- + "飞行检测开关": True, + "平地飞检测开关": True, + "回弹飞检测开关": True, + "预测检测开关": True, + "控制台打印位置": True, + "加速度检测开关": True, + "速度检测开关": True, + "上升速度检测开关": True, + "管理员显示加速度": True, + #---- 飞行检测 ---- + "飞行最小水平速度": 12.0, + "飞行下落检测秒数": 3.0, + "飞行最大可通过下落": -0.8, + "飞行最小下落速度": -2.0, + "飞行跳跃上升阈值": 1.2, + "飞行最小悬空秒数": 0.5, + "飞行脚下检测距离": 1.0, + "飞行连续拉回次数阈值": 3, + #---- 平地飞检测 ---- + "平地飞水平速度阈值": 7.74545, + "平地飞Y轴标准差阈值": 0.35, + "平地飞水平加速度阈值": 6.54055, + #---- 回弹飞检测 ---- + "回弹飞最小持续秒数": 4.0, + "回弹飞最小波动次数": 3, + "回弹飞最小波幅": 0.4, + "回弹飞最大波幅": 2.5, + "回弹飞最低水平速度": 2.0, + #---- 预测检测 ---- + "预测时间_秒": 0.5, + "预测基础误差_米": 22.45173, + "预测速度误差因子": 0.3, + "预测速度不稳定放宽因子": 3.0, + "预测速度稳定性阈值": 0.45, + #---- 通用 ---- + "回拉秒数": 3.0, + "位置打印间隔_秒": 5.0, + #---- 加速度检测 ---- + "加速度异常水平阈值": 12.42526, + "加速度异常垂直阈值": 21.89734, + #---- 速度检测 ---- + "速度异常水平阈值": 8.0, + "骑马速度水平阈值": 18.0, + #---- 上升速度检测 ---- + "上升速度阈值": 4.5, + #---- 管理员显示 ---- + "管理员显示间隔_秒": 0.5, + #---- 豁免 ---- + "速度检测下落豁免": True + } + + STD_CFG = { + "飞行检测开关": bool, + "平地飞检测开关": bool, + "回弹飞检测开关": bool, + "预测检测开关": bool, + "控制台打印位置": bool, + "加速度检测开关": bool, + "速度检测开关": bool, + "上升速度检测开关": bool, + "管理员显示加速度": bool, + "飞行最小水平速度": (int, float), + "飞行下落检测秒数": (int, float), + "飞行最大可通过下落": (int, float), + "飞行最小下落速度": (int, float), + "飞行跳跃上升阈值": (int, float), + "飞行最小悬空秒数": (int, float), + "飞行脚下检测距离": (int, float), + "飞行连续拉回次数阈值": int, + "平地飞水平速度阈值": (int, float), + "平地飞Y轴标准差阈值": (int, float), + "平地飞水平加速度阈值": (int, float), + "回弹飞最小持续秒数": (int, float), + "回弹飞最小波动次数": int, + "回弹飞最小波幅": (int, float), + "回弹飞最大波幅": (int, float), + "回弹飞最低水平速度": (int, float), + "预测时间_秒": (int, float), + "预测基础误差_米": (int, float), + "预测速度误差因子": (int, float), + "预测速度不稳定放宽因子": (int, float), + "预测速度稳定性阈值": (int, float), + "回拉秒数": (int, float), + "位置打印间隔_秒": (int, float), + "加速度异常水平阈值": (int, float), + "加速度异常垂直阈值": (int, float), + "速度异常水平阈值": (int, float), + "骑马速度水平阈值": (int, float), + "上升速度阈值": (int, float), + "管理员显示间隔_秒": (int, float), + "速度检测下落豁免": bool + } + + self.cfg, _ = config.get_plugin_config_and_version( + self.name, STD_CFG, DEFAULT_CFG, self.version + ) + fmts.print_inf("[MUNAN ANTI CHEAT] 所有模块配置已加载") + + # ---------- 工具方法 ---------- + def is_bypass(self, name): + try: + resp = self.game_ctrl.sendwscmd_with_resp( + f"/execute as {name} if entity @s[tag=bypass]" + ) + return resp.SuccessCount > 0 + except: + return False + + def _is_riding(self, name): + try: + resp = self.game_ctrl.sendwscmd_with_resp( + f"/data get entity {name} RootVehicle" + ) + if resp.SuccessCount > 0: + return True + except: + pass + + try: + resp = self.game_ctrl.sendwscmd_with_resp( + f"/execute as {name} on vehicle" + ) + if resp.SuccessCount > 0: + return True + except: + pass + + return False + + def _is_unsupported(self, name): + distance = self.cfg["飞行脚下检测距离"] + try: + resp = self.game_ctrl.sendwscmd_with_resp( + f"/execute as {name} at @s if block ~ ~-{distance} ~ air" + ) + if resp.SuccessCount > 0: + return True + except: + pass + try: + resp = self.game_ctrl.sendwscmd_with_resp( + f"/execute as {name} at @s if block ~ ~-{distance} ~ #minecraft:climbable" + ) + if resp.SuccessCount > 0: + return True + except: + pass + return False + + def _update_history(self, name, pos, now): + if self._is_unsupported(name): + if name not in self.air_pos_history: + self.air_pos_history[name] = deque(maxlen=5) + self.air_pos_history[name].append((pos["x"], pos["y"], pos["z"], now)) + return + x, y, z = pos["x"], pos["y"], pos["z"] + self.last_pos[name] = (x, y, z, now) + if name not in self.pos_history: + self.pos_history[name] = deque(maxlen=20) + self.pos_history[name].append((x, y, z, now)) + if name in self.air_pos_history: + del self.air_pos_history[name] + + def _get_position_at(self, name, target_time, use_rollback=False): + history = self.rollback_history.get(name) if use_rollback else self.pos_history.get(name) + if not history: + return None + best = None + best_diff = float("inf") + for x, y, z, t in history: + diff = abs(t - target_time) + if diff < best_diff: + best_diff = diff + best = (x, y, z) + return best + + def _is_y_rising(self, name, count=3): + history = self.pos_history.get(name) + if not history or len(history) < count: + return False + points = list(history)[-count:] + for i in range(1, len(points)): + if points[i][1] <= points[i-1][1]: + return False + return True + + def _record_rollback_target(self, name, x, y, z, now): + if name not in self.rollback_history: + self.rollback_history[name] = deque(maxlen=50) + self.rollback_history[name].append((x, y, z, now)) + + def _rollback(self, name, smart=False, to_earliest=False): + now = time.time() + if smart: + current = self.last_pos.get(name) + if not current: + return + cx, cy, cz, _ = current + history = self.pos_history.get(name) + r_history = self.rollback_history.get(name) + + if to_earliest: + target = None + if history and len(history) > 0: + for x, y, z, t in history: + if math.sqrt((x - cx)**2 + (y - cy)**2 + (z - cz)**2) >= 1.0: + target = (x, y, z) + break + if not target and r_history and len(r_history) > 0: + x0, y0, z0, _ = r_history[0] + target = (x0, y0, z0) + elif not target and history and len(history) > 0: + x0, y0, z0, _ = history[0] + target = (x0, y0, z0) + if not target: + return + x, y, z = target + try: + self.game_ctrl.sendwscmd(f"/tp {name} {x:.5f} {y:.5f} {z:.5f}") + fmts.print_war(f"[反作弊] {name} 拉回至最早变化点 ({x:.5f}, {y:.5f}, {z:.5f})") + self._record_rollback_target(name, x, y, z, now) + except Exception as e: + fmts.print_err(f"[反作弊] 最早变化点拉回失败: {e}") + if name in self.pos_history: + self.pos_history[name].clear() + if name in self.rollback_count: + self.rollback_count[name] = [] + return + + if not history or len(history) < 2: + if r_history and len(r_history) > 0: + x, y, z, _ = r_history[-1] + try: + self.game_ctrl.sendwscmd(f"/tp {name} {x:.5f} {y:.5f} {z:.5f}") + fmts.print_war(f"[反作弊] {name} 拉回至最近安全位置 ({x:.5f}, {y:.5f}, {z:.5f})") + self._record_rollback_target(name, x, y, z, now) + except Exception as e: + fmts.print_err(f"[反作弊] 拉回 {name} 失败: {e}") + if name in self.pos_history: + self.pos_history[name].clear() + else: + fmts.print_war(f"[反作弊] {name} 无可用历史坐标,拉回失败") + return + + target = None + for x, y, z, t in reversed(list(history)[:-1]): + if math.sqrt((x - cx)**2 + (y - cy)**2 + (z - cz)**2) >= 1.0: + target = (x, y, z) + break + if not target: + x0, y0, z0, _ = history[0] + target = (x0, y0, z0) + + x, y, z = target + try: + self.game_ctrl.sendwscmd(f"/tp {name} {x:.5f} {y:.5f} {z:.5f}") + fmts.print_war(f"[反作弊] {name} 拉回至明显变化前位置 ({x:.5f}, {y:.5f}, {z:.5f})") + self._record_rollback_target(name, x, y, z, now) + except Exception as e: + fmts.print_err(f"[反作弊] 拉回 {name} 失败: {e}") + return + + if name in self.pos_history: + self.pos_history[name].clear() + + if name not in self.rollback_count: + self.rollback_count[name] = [] + self.rollback_count[name].append(now) + self.rollback_count[name] = [t for t in self.rollback_count[name] if now - t <= 10] + if len(self.rollback_count[name]) >= self.cfg["飞行连续拉回次数阈值"]: + if self._is_y_rising(name, count=3): + self._rollback(name, smart=True, to_earliest=True) + return + else: + rollback_times = [3, 5, 10, 15, 30, 60] + for sec in rollback_times: + target_time = now - sec + pos = self._get_position_at(name, target_time) + if pos is not None: + x, y, z = pos + try: + self.game_ctrl.sendwscmd(f"/tp {name} {x:.5f} {y:.5f} {z:.5f}") + fmts.print_war(f"[反作弊] 已将 {name} 拉回至 {sec}秒前 ({x:.5f}, {y:.5f}, {z:.5f})") + self._record_rollback_target(name, x, y, z, now) + except Exception as e: + fmts.print_err(f"[反作弊] 回拉 {name} 失败: {e}") + if name in self.pos_history: + self.pos_history[name].clear() + return + r_history = self.rollback_history.get(name) + if r_history and len(r_history) > 0: + x, y, z, _ = r_history[-1] + try: + self.game_ctrl.sendwscmd(f"/tp {name} {x:.5f} {y:.5f} {z:.5f}") + fmts.print_war(f"[反作弊] {name} 使用备用历史拉回 ({x:.5f}, {y:.5f}, {z:.5f})") + self._record_rollback_target(name, x, y, z, now) + except Exception as e: + fmts.print_err(f"[反作弊] 备用拉回 {name} 失败: {e}") + if name in self.pos_history: + self.pos_history[name].clear() + return + fmts.print_war(f"[反作弊] 无法找到 {name} 任何有效历史坐标,回退失败") + + def _calc_acceleration_from_points(self, p0, p1, p2, check_interval=True): + dt1 = p1[3] - p0[3] + dt2 = p2[3] - p1[3] + if check_interval: + if dt1 < 0.3 or dt2 < 0.3: + return None, None + else: + if dt1 <= 0 or dt2 <= 0: + return None, None + vx1 = (p1[0] - p0[0]) / dt1 + vy1 = (p1[1] - p0[1]) / dt1 + vz1 = (p1[2] - p0[2]) / dt1 + vx2 = (p2[0] - p1[0]) / dt2 + vy2 = (p2[1] - p1[1]) / dt2 + vz2 = (p2[2] - p1[2]) / dt2 + dv_h = math.sqrt((vx2 - vx1)**2 + (vz2 - vz1)**2) + if dv_h > 15.0: + return None, None + ax = (vx2 - vx1) / ((dt1 + dt2) / 2) + ay = (vy2 - vy1) / ((dt1 + dt2) / 2) + az = (vz2 - vz1) / ((dt1 + dt2) / 2) + h_acc = math.sqrt(ax**2 + az**2) + v_acc = abs(ay) + if h_acc > 50 or v_acc > 50: + return None, None + return h_acc, v_acc + + def _get_acceleration(self, name): + history = self.pos_history.get(name) + if history and len(history) >= 3: + p0, p1, p2 = list(history)[-3:] + return self._calc_acceleration_from_points(p0, p1, p2, check_interval=True) + air_hist = self.air_pos_history.get(name) + if air_hist and len(air_hist) >= 3: + p0, p1, p2 = list(air_hist)[-3:] + return self._calc_acceleration_from_points(p0, p1, p2, check_interval=False) + return None, None + + def _get_horizontal_speed(self, name, pos, now): + air_hist = self.air_pos_history.get(name) + if air_hist and len(air_hist) >= 2: + p1 = air_hist[-2] + p2 = air_hist[-1] + dt = p2[3] - p1[3] + if dt > 0: + dx = p2[0] - p1[0] + dz = p2[2] - p1[2] + return math.sqrt(dx**2 + dz**2) / dt + history = self.pos_history.get(name) + if history and len(history) >= 2: + p1 = history[-2] + p2 = history[-1] + dt = p2[3] - p1[3] + if dt > 0: + dx = p2[0] - p1[0] + dz = p2[2] - p1[2] + return math.sqrt(dx**2 + dz**2) / dt + last = self.last_pos.get(name) + if last: + lx, ly, lz, lt = last + dt = now - lt + if dt > 0: + dx = pos["x"] - lx + dz = pos["z"] - lz + return math.sqrt(dx**2 + dz**2) / dt + return None + + def _get_vertical_speed(self, name, pos, now): + air_hist = self.air_pos_history.get(name) + if air_hist and len(air_hist) >= 2: + p1 = air_hist[-2] + p2 = air_hist[-1] + dt = p2[3] - p1[3] + if dt > 0: + return (p2[1] - p1[1]) / dt + history = self.pos_history.get(name) + if history and len(history) >= 2: + p1 = history[-2] + p2 = history[-1] + dt = p2[3] - p1[3] + if dt > 0: + return (p2[1] - p1[1]) / dt + last = self.last_pos.get(name) + if last: + lx, ly, lz, lt = last + dt = now - lt + if dt > 0: + return (pos["y"] - ly) / dt + return 0.0 + + def _show_accel_to_admins(self, all_data, now): + info_parts = [f"§a==== {time.strftime('%H:%M:%S')} ==== "] + for name, pos in all_data.items(): + if self.is_bypass(name): + continue + h_acc, v_acc = self._get_acceleration(name) + show_h_acc = h_acc if h_acc is not None else 0.0 + show_v_acc = v_acc if v_acc is not None else 0.0 + + h_speed = self._get_horizontal_speed(name, pos, now) + if h_speed is None: + h_speed = 0.0 + + info_parts.append( + f"\n§e{name}§r: 速度§b{h_speed:.5f}§r, " + f"水平Acc§b{show_h_acc:.5f}§r, 垂直Acc§b{show_v_acc:.5f}" + ) + rawtext = [{"text": " ".join(info_parts)}] + cmd = f"""/titleraw @a[tag=admin] actionbar {{"rawtext":{json.dumps(rawtext)}}}""" + try: + self.game_ctrl.sendwocmd(cmd) + except: + pass + + # ---------- 主事件 ---------- + def on_pos_data(self, event: InternalBroadcast): + data = event.data + now = time.time() + + should_print = ( + self.cfg["控制台打印位置"] and + (now - self.last_print_time >= self.cfg["位置打印间隔_秒"]) + ) + if should_print: + self.last_print_time = now + fmts.print_inf(f"========== 玩家位置 ({time.strftime('%H:%M:%S')}) ==========") + + should_show_admin = ( + self.cfg["管理员显示加速度"] and + (now - self.last_admin_show_time >= self.cfg["管理员显示间隔_秒"]) + ) + if should_show_admin: + self.last_admin_show_time = now + self._show_accel_to_admins(data, now) + + for name, pos in data.items(): + if self.is_bypass(name): + continue + + self._update_history(name, pos, now) + + if should_print: + fmts.print_inf(f" {name}: ({pos['x']:.5f}, {pos['y']:.5f}, {pos['z']:.5f})") + + if self.cfg.get("上升速度检测开关", True): + self._check_rise(name, pos, now) + + if self.cfg["速度检测开关"]: + self._check_speed(name, pos, now) + + if self.cfg["飞行检测开关"]: + self._check_flight(name, pos, now) + if self.cfg["平地飞检测开关"]: + self._check_groundfly(name, pos, now) + if self.cfg["回弹飞检测开关"]: + self._check_bounce(name, pos, now) + if self.cfg["预测检测开关"]: + self._check_predict_move(name, pos, now) + if self.cfg["加速度检测开关"]: + self._check_acceleration(name, pos, now) + + # ---------- 各检测模块 ---------- + def _check_rise(self, name, pos, now): + vy = self._get_vertical_speed(name, pos, now) + if vy <= self.cfg["上升速度阈值"]: + return + if self._is_unsupported(name): + fmts.print_war(f"[反作弊] {name} 上升速度异常!垂直速度={vy:.5f} m/s(阈值{self.cfg['上升速度阈值']})") + self._rollback(name) + + def _check_speed(self, name, pos, now): + h_speed = self._get_horizontal_speed(name, pos, now) + if h_speed is None: + return + if self._is_riding(name): + threshold = self.cfg.get("骑马速度水平阈值", 25.0) + if h_speed > threshold: + fmts.print_war(f"[反作弊] {name} 骑马时速度异常!速度={h_speed:.5f} m/s(阈值{threshold})") + self._rollback(name) + else: + threshold = self.cfg["速度异常水平阈值"] + if h_speed > threshold: + fmts.print_war(f"[反作弊] {name} 水平速度异常!速度={h_speed:.5f} m/s(阈值{threshold})") + self._rollback(name) + + def _check_acceleration(self, name, pos, now): + h_acc, v_acc = self._get_acceleration(name) + h_threshold = self.cfg["加速度异常水平阈值"] + v_threshold = self.cfg["加速度异常垂直阈值"] + + if h_acc is not None: + if h_acc > h_threshold: + fmts.print_war(f"[反作弊] {name} 加速度异常!水平={h_acc:.5f} m/s²(阈值{h_threshold})") + self._rollback(name) + return + if v_acc > v_threshold: + if 18 < v_acc < 22: + vy = self._get_vertical_speed(name, pos, now) + if vy < 0: + return + fmts.print_war(f"[反作弊] {name} 加速度异常!垂直={v_acc:.5f} m/s²(阈值{v_threshold})") + self._rollback(name) + + def _check_flight(self, name, pos, now): + history = self.pos_history.get(name) + if not self._is_unsupported(name): + return + + min_air_time = self.cfg.get("飞行最小悬空秒数", 0.5) + last_ground_time = None + if history and len(history) > 0: + last_ground_time = history[-1][3] + elif name in self.last_pos: + last_ground_time = self.last_pos[name][3] + + if last_ground_time is not None: + air_duration = now - last_ground_time + if air_duration < min_air_time: + return + + h_speed = self._get_horizontal_speed(name, pos, now) or 0.0 + vy = self._get_vertical_speed(name, pos, now) + + if h_speed < 0.1 and abs(vy) < 0.1: + fmts.print_war(f"[反作弊] {name} 疑似静止悬停,高度={pos['y']:.5f}") + self._rollback(name, smart=True) + return + + if h_speed < 0.5 and vy < 0: + return + + if vy < self.cfg["飞行最小下落速度"]: + return + + lookback = self.cfg["飞行下落检测秒数"] + target_time = now - lookback + old_y = None + last = self.last_pos.get(name) + if last and (now - last[3]) <= lookback: + old_y = last[1] + else: + old_pos = self._get_position_at(name, target_time) + if old_pos is not None: + old_y = old_pos[1] + else: + old_pos_r = self._get_position_at(name, target_time, use_rollback=True) + if old_pos_r is not None: + old_y = old_pos_r[1] + + if old_y is not None: + dy = pos["y"] - old_y + if dy < self.cfg["飞行最大可通过下落"]: + return + if vy > 0 and dy > self.cfg["飞行跳跃上升阈值"]: + return + + if h_speed < self.cfg["飞行最小水平速度"]: + return + + dy_show = pos["y"] - old_y if old_y is not None else 0.0 + fmts.print_war( + f"[反作弊] {name} 疑似飞行(悬空),水平速度={h_speed:.5f}," + f"垂直速度={vy:.5f},高度={pos['y']:.5f},2秒下落={dy_show:.5f}" + ) + self._rollback(name, smart=True) + + def _check_groundfly(self, name, pos, now): + if self.cfg.get("速度检测下落豁免", True): + vy = self._get_vertical_speed(name, pos, now) + if vy < self.cfg["飞行最小下落速度"]: + return + + history = self.pos_history.get(name) + if not history or len(history) < 5: + return + recent = [p for p in history if now - p[3] <= 3.0] + if len(recent) < 3: + return + ys = [p[1] for p in recent] + mean_y = sum(ys) / len(ys) + std_y = (sum((y - mean_y)**2 for y in ys) / len(ys)) ** 0.5 + if std_y > self.cfg["平地飞Y轴标准差阈值"]: + return + last = self.last_pos.get(name) + if not last: + return + last_x, last_y, last_z, last_t = last + dt = now - last_t + if dt <= 0: + return + dx = pos["x"] - last_x + dz = pos["z"] - last_z + dist = (dx**2 + dz**2) ** 0.5 + h_speed = dist / dt + h_acc = 0 + if len(recent) >= 4: + mid = len(recent) // 2 + dt_half = (recent[-1][3] - recent[0][3]) / 2 + if dt_half > 0: + v1 = ((recent[mid][0] - recent[0][0])**2 + (recent[mid][2] - recent[0][2])**2) ** 0.5 / dt_half + v2 = ((recent[-1][0] - recent[mid][0])**2 + (recent[-1][2] - recent[mid][2])**2) ** 0.5 / dt_half + h_acc = (v2 - v1) / dt_half + if h_speed > self.cfg["平地飞水平速度阈值"]: + fmts.print_war(f"[反作弊] {name} 疑似平地飞,水平速度={h_speed:.5f},Y标准差={std_y:.5f}") + self._rollback(name) + elif h_acc > self.cfg["平地飞水平加速度阈值"] and h_speed > 5.0: + fmts.print_war(f"[反作弊] {name} 水平加速度异常={h_acc:.5f},水平速度={h_speed:.5f}") + self._rollback(name) + + def _check_bounce(self, name, pos, now): + history = self.pos_history.get(name) + if not history or len(history) < 5: + return + duration = self.cfg["回弹飞最小持续秒数"] + recent = [p for p in history if now - p[3] <= duration] + if len(recent) < 4: + return + ys = [p[1] for p in recent] + peaks = troughs = 0 + for i in range(1, len(ys) - 1): + if ys[i] > ys[i - 1] and ys[i] > ys[i + 1]: + peaks += 1 + elif ys[i] < ys[i - 1] and ys[i] < ys[i + 1]: + troughs += 1 + cycles = min(peaks, troughs) + if cycles < self.cfg["回弹飞最小波动次数"]: + return + amplitudes = [] + for i in range(1, len(ys) - 1): + if ys[i] > ys[i - 1] and ys[i] > ys[i + 1]: + left_trough = min(ys[i - 1], ys[i]) + right_trough = min(ys[i + 1], ys[i]) + amp = (ys[i] - left_trough + ys[i] - right_trough) / 2 + if amp > 0: + amplitudes.append(amp) + if not amplitudes: + return + avg_amplitude = sum(amplitudes) / len(amplitudes) + if not (self.cfg["回弹飞最小波幅"] <= avg_amplitude <= self.cfg["回弹飞最大波幅"]): + return + dx = recent[-1][0] - recent[0][0] + dz = recent[-1][2] - recent[0][2] + dt = recent[-1][3] - recent[0][3] + if dt <= 0: + return + h_speed = ((dx**2 + dz**2) ** 0.5) / dt + if h_speed < self.cfg["回弹飞最低水平速度"]: + return + fmts.print_war(f"[反作弊] {name} 疑似回弹飞行!波动次数={cycles},平均振幅={avg_amplitude:.5f},水平速度={h_speed:.5f}") + self._rollback(name) + + def _check_predict_move(self, name, pos, now): + if self.cfg.get("速度检测下落豁免", True): + vy_current = self._get_vertical_speed(name, pos, now) + if vy_current < self.cfg["飞行最小下落速度"]: + return + + history = self.pos_history.get(name) + if not history or len(history) < 4: + return + p0 = history[-4] + p1 = history[-3] + p2 = history[-2] + p3 = history[-1] + dt1 = p1[3] - p0[3] + dt2 = p2[3] - p1[3] + if dt1 <= 0 or dt2 <= 0: + return + vx1 = (p1[0] - p0[0]) / dt1 + vy1 = (p1[1] - p0[1]) / dt1 + vz1 = (p1[2] - p0[2]) / dt1 + vx2 = (p2[0] - p1[0]) / dt2 + vy2 = (p2[1] - p1[1]) / dt2 + vz2 = (p2[2] - p1[2]) / dt2 + avg_vx = (vx1 + vx2) / 2 + avg_vy = (vy1 + vy2) / 2 + avg_vz = (vz1 + vz2) / 2 + predict_dt = self.cfg["预测时间_秒"] + predicted_x = p2[0] + avg_vx * predict_dt + predicted_y = p2[1] + avg_vy * predict_dt + predicted_z = p2[2] + avg_vz * predict_dt + actual_x, actual_y, actual_z = pos["x"], pos["y"], pos["z"] + dist = math.sqrt((actual_x - predicted_x)**2 + (actual_y - predicted_y)**2 + (actual_z - predicted_z)**2) + speed = math.sqrt(avg_vx**2 + avg_vy**2 + avg_vz**2) + threshold = self.cfg["预测基础误差_米"] + speed * self.cfg["预测速度误差因子"] + speed1 = math.sqrt(vx1**2 + vy1**2 + vz1**2) + speed2 = math.sqrt(vx2**2 + vy2**2 + vz2**2) + max_speed = max(speed1, speed2) + if max_speed > 0: + speed_change_ratio = abs(speed2 - speed1) / max_speed + else: + speed_change_ratio = 0 + if speed_change_ratio > self.cfg["预测速度稳定性阈值"]: + threshold *= self.cfg["预测速度不稳定放宽因子"] + if speed > 0: + dot = vx1 * vx2 + vz1 * vz2 + mag = math.sqrt(vx1**2 + vz1**2) * math.sqrt(vx2**2 + vz2**2) + if mag > 0: + cos_angle = max(-1, min(1, dot / mag)) + angle = math.acos(cos_angle) + if angle > math.radians(90): + threshold *= 2.0 + if dist > threshold: + fmts.print_war(f"[反作弊] {name} 移动预测异常:偏差={dist:.5f} 米(阈值{threshold:.5f}),速度={speed:.5f} m/s") + self._rollback(name) + + +entry = plugin_entry(SimpleAntiCheat, "反作弊-移动检测") \ No newline at end of file diff --git "a/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/datas.json" "b/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/datas.json" new file mode 100644 index 00000000..dfa9ebb7 --- /dev/null +++ "b/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/datas.json" @@ -0,0 +1,14 @@ +{ + "author": "反作弊-移动检测", + "version": "0.0.1", + "plugin-type": "classic", + "description": "\n# MUNAN ANTI CHEAT\n## 1.功能\n[ 1 ] Fly Check\n[ 2 ] Speed Check\n## 2.注意事项\n添加 admin 标签可以查看加速度 速度值\n添加 bypass 标签可以豁免检测\n当前版本:\n0.0.1(beta)\n如果可以的话求帮维护\n球球了qwq\n", + "pre-plugins": { + "XUID获取": "0.0.7", + "聊天栏菜单": "0.3.2", + "循环获取玩家坐标": "0.0.5", + "pip": "0.0.6" + }, + "plugin-id": "MUNAN ANTI CHEAT", + "enabled": true +} \ No newline at end of file From 896b00f8131c4d6f20765f4d7ace38b4281be07b Mon Sep 17 00:00:00 2001 From: mumanMN Date: Sat, 6 Jun 2026 15:33:26 +0800 Subject: [PATCH 2/8] =?UTF-8?q?=E5=8F=8D=E4=BD=9C=E5=BC=8A-=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__init__.py" | 315 +++++++++++------- 1 file changed, 201 insertions(+), 114 deletions(-) diff --git "a/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" "b/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" index 589fac94..844efb43 100644 --- "a/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" +++ "b/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" @@ -1,3 +1,7 @@ +""" +反作弊-移动检测 (MUNAN) +包含飞行、平地飞、回弹飞、加速度、速度、上升速度等多维度检测。 +""" import time import math import json @@ -6,6 +10,8 @@ class SimpleAntiCheat(Plugin): + """多维移动反作弊插件,支持自定义阈值与骑马检测。""" + name = "反作弊-移动检测(MUNAN)" author = "Assistant" version = (0, 0, 1) @@ -13,16 +19,17 @@ class SimpleAntiCheat(Plugin): def __init__(self, frame): super().__init__(frame) self.ListenInternalBroadcast("ggpp:publish_player_position", self.on_pos_data) - self.pos_history = {} - self.rollback_history = {} - self.last_pos = {} - self.air_pos_history = {} + self.pos_history = {} # 玩家地面坐标历史 + self.rollback_history = {} # 拉回成功的目标坐标历史 + self.last_pos = {} # 最后一次地面坐标 + self.air_pos_history = {} # 空中坐标历史(用于加速度计算) self.last_print_time = 0 self.last_admin_show_time = 0 - self.rollback_count = {} + self.rollback_count = {} # 连续拉回计数 + # 配置项及默认值 DEFAULT_CFG = { - #---- 所有检测开关 ---- + # 开关 "飞行检测开关": True, "平地飞检测开关": True, "回弹飞检测开关": True, @@ -32,7 +39,7 @@ def __init__(self, frame): "速度检测开关": True, "上升速度检测开关": True, "管理员显示加速度": True, - #---- 飞行检测 ---- + # 飞行检测 "飞行最小水平速度": 12.0, "飞行下落检测秒数": 3.0, "飞行最大可通过下落": -0.8, @@ -41,37 +48,37 @@ def __init__(self, frame): "飞行最小悬空秒数": 0.5, "飞行脚下检测距离": 1.0, "飞行连续拉回次数阈值": 3, - #---- 平地飞检测 ---- + # 平地飞检测 "平地飞水平速度阈值": 7.74545, "平地飞Y轴标准差阈值": 0.35, "平地飞水平加速度阈值": 6.54055, - #---- 回弹飞检测 ---- + # 回弹飞检测 "回弹飞最小持续秒数": 4.0, "回弹飞最小波动次数": 3, "回弹飞最小波幅": 0.4, "回弹飞最大波幅": 2.5, "回弹飞最低水平速度": 2.0, - #---- 预测检测 ---- + # 预测检测 "预测时间_秒": 0.5, "预测基础误差_米": 22.45173, "预测速度误差因子": 0.3, "预测速度不稳定放宽因子": 3.0, "预测速度稳定性阈值": 0.45, - #---- 通用 ---- + # 通用 "回拉秒数": 3.0, "位置打印间隔_秒": 5.0, - #---- 加速度检测 ---- + # 加速度检测 "加速度异常水平阈值": 12.42526, "加速度异常垂直阈值": 21.89734, - #---- 速度检测 ---- + # 速度检测 "速度异常水平阈值": 8.0, "骑马速度水平阈值": 18.0, - #---- 上升速度检测 ---- + # 上升速度检测 "上升速度阈值": 4.5, - #---- 管理员显示 ---- + # 管理员显示 "管理员显示间隔_秒": 0.5, - #---- 豁免 ---- - "速度检测下落豁免": True + # 豁免 + "速度检测下落豁免": True, } STD_CFG = { @@ -113,7 +120,7 @@ def __init__(self, frame): "骑马速度水平阈值": (int, float), "上升速度阈值": (int, float), "管理员显示间隔_秒": (int, float), - "速度检测下落豁免": bool + "速度检测下落豁免": bool, } self.cfg, _ = config.get_plugin_config_and_version( @@ -122,23 +129,25 @@ def __init__(self, frame): fmts.print_inf("[MUNAN ANTI CHEAT] 所有模块配置已加载") # ---------- 工具方法 ---------- - def is_bypass(self, name): + def is_bypass(self, name: str) -> bool: + """检查玩家是否拥有 bypass 豁免标签。""" try: resp = self.game_ctrl.sendwscmd_with_resp( f"/execute as {name} if entity @s[tag=bypass]" ) return resp.SuccessCount > 0 - except: + except Exception: return False - def _is_riding(self, name): + def _is_riding(self, name: str) -> bool: + """检查玩家是否骑乘实体(通过 RootVehicle 或 on vehicle 命令)。""" try: resp = self.game_ctrl.sendwscmd_with_resp( f"/data get entity {name} RootVehicle" ) if resp.SuccessCount > 0: return True - except: + except Exception: pass try: @@ -147,12 +156,13 @@ def _is_riding(self, name): ) if resp.SuccessCount > 0: return True - except: + except Exception: pass return False - def _is_unsupported(self, name): + def _is_unsupported(self, name: str) -> bool: + """检查脚下是否没有固体支撑(空气或可攀爬方块)。""" distance = self.cfg["飞行脚下检测距离"] try: resp = self.game_ctrl.sendwscmd_with_resp( @@ -160,7 +170,7 @@ def _is_unsupported(self, name): ) if resp.SuccessCount > 0: return True - except: + except Exception: pass try: resp = self.game_ctrl.sendwscmd_with_resp( @@ -168,11 +178,12 @@ def _is_unsupported(self, name): ) if resp.SuccessCount > 0: return True - except: + except Exception: pass return False - def _update_history(self, name, pos, now): + def _update_history(self, name: str, pos: dict, now: float) -> None: + """更新玩家坐标历史,空中与地面分开存储。""" if self._is_unsupported(name): if name not in self.air_pos_history: self.air_pos_history[name] = deque(maxlen=5) @@ -186,7 +197,9 @@ def _update_history(self, name, pos, now): if name in self.air_pos_history: del self.air_pos_history[name] - def _get_position_at(self, name, target_time, use_rollback=False): + def _get_position_at(self, name: str, target_time: float, + use_rollback: bool = False): + """从历史中获取最接近 target_time 的坐标。""" history = self.rollback_history.get(name) if use_rollback else self.pos_history.get(name) if not history: return None @@ -199,22 +212,27 @@ def _get_position_at(self, name, target_time, use_rollback=False): best = (x, y, z) return best - def _is_y_rising(self, name, count=3): + def _is_y_rising(self, name: str, count: int = 3) -> bool: + """检查最近几次地面历史中 Y 坐标是否严格递增。""" history = self.pos_history.get(name) if not history or len(history) < count: return False points = list(history)[-count:] for i in range(1, len(points)): - if points[i][1] <= points[i-1][1]: + if points[i][1] <= points[i - 1][1]: return False return True - def _record_rollback_target(self, name, x, y, z, now): + def _record_rollback_target(self, name: str, x: float, y: float, z: float, + now: float) -> None: + """记录一次成功的拉回目标坐标。""" if name not in self.rollback_history: self.rollback_history[name] = deque(maxlen=50) self.rollback_history[name].append((x, y, z, now)) - def _rollback(self, name, smart=False, to_earliest=False): + def _rollback(self, name: str, smart: bool = False, + to_earliest: bool = False) -> None: + """执行拉回操作,支持智能拉回和渐进回溯。""" now = time.time() if smart: current = self.last_pos.get(name) @@ -225,24 +243,25 @@ def _rollback(self, name, smart=False, to_earliest=False): r_history = self.rollback_history.get(name) if to_earliest: + # 寻找最早的距离超过 1.0 的点 target = None if history and len(history) > 0: for x, y, z, t in history: - if math.sqrt((x - cx)**2 + (y - cy)**2 + (z - cz)**2) >= 1.0: + if math.sqrt((x - cx) ** 2 + (y - cy) ** 2 + (z - cz) ** 2) >= 1.0: target = (x, y, z) break if not target and r_history and len(r_history) > 0: - x0, y0, z0, _ = r_history[0] - target = (x0, y0, z0) + target = (r_history[0][0], r_history[0][1], r_history[0][2]) elif not target and history and len(history) > 0: - x0, y0, z0, _ = history[0] - target = (x0, y0, z0) + target = (history[0][0], history[0][1], history[0][2]) if not target: return x, y, z = target try: self.game_ctrl.sendwscmd(f"/tp {name} {x:.5f} {y:.5f} {z:.5f}") - fmts.print_war(f"[反作弊] {name} 拉回至最早变化点 ({x:.5f}, {y:.5f}, {z:.5f})") + fmts.print_war( + f"[反作弊] {name} 拉回至最早变化点 ({x:.5f}, {y:.5f}, {z:.5f})" + ) self._record_rollback_target(name, x, y, z, now) except Exception as e: fmts.print_err(f"[反作弊] 最早变化点拉回失败: {e}") @@ -257,7 +276,9 @@ def _rollback(self, name, smart=False, to_earliest=False): x, y, z, _ = r_history[-1] try: self.game_ctrl.sendwscmd(f"/tp {name} {x:.5f} {y:.5f} {z:.5f}") - fmts.print_war(f"[反作弊] {name} 拉回至最近安全位置 ({x:.5f}, {y:.5f}, {z:.5f})") + fmts.print_war( + f"[反作弊] {name} 拉回至最近安全位置 ({x:.5f}, {y:.5f}, {z:.5f})" + ) self._record_rollback_target(name, x, y, z, now) except Exception as e: fmts.print_err(f"[反作弊] 拉回 {name} 失败: {e}") @@ -267,19 +288,20 @@ def _rollback(self, name, smart=False, to_earliest=False): fmts.print_war(f"[反作弊] {name} 无可用历史坐标,拉回失败") return + # 普通智能拉回:从最近历史中找距离>=1.0 的点 target = None for x, y, z, t in reversed(list(history)[:-1]): - if math.sqrt((x - cx)**2 + (y - cy)**2 + (z - cz)**2) >= 1.0: + if math.sqrt((x - cx) ** 2 + (y - cy) ** 2 + (z - cz) ** 2) >= 1.0: target = (x, y, z) break if not target: - x0, y0, z0, _ = history[0] - target = (x0, y0, z0) - + target = (history[0][0], history[0][1], history[0][2]) x, y, z = target try: self.game_ctrl.sendwscmd(f"/tp {name} {x:.5f} {y:.5f} {z:.5f}") - fmts.print_war(f"[反作弊] {name} 拉回至明显变化前位置 ({x:.5f}, {y:.5f}, {z:.5f})") + fmts.print_war( + f"[反作弊] {name} 拉回至明显变化前位置 ({x:.5f}, {y:.5f}, {z:.5f})" + ) self._record_rollback_target(name, x, y, z, now) except Exception as e: fmts.print_err(f"[反作弊] 拉回 {name} 失败: {e}") @@ -288,45 +310,57 @@ def _rollback(self, name, smart=False, to_earliest=False): if name in self.pos_history: self.pos_history[name].clear() + # 连续拉回计数 if name not in self.rollback_count: self.rollback_count[name] = [] self.rollback_count[name].append(now) - self.rollback_count[name] = [t for t in self.rollback_count[name] if now - t <= 10] + self.rollback_count[name] = [ + t for t in self.rollback_count[name] if now - t <= 10 + ] if len(self.rollback_count[name]) >= self.cfg["飞行连续拉回次数阈值"]: if self._is_y_rising(name, count=3): self._rollback(name, smart=True, to_earliest=True) return - else: - rollback_times = [3, 5, 10, 15, 30, 60] - for sec in rollback_times: - target_time = now - sec - pos = self._get_position_at(name, target_time) - if pos is not None: - x, y, z = pos - try: - self.game_ctrl.sendwscmd(f"/tp {name} {x:.5f} {y:.5f} {z:.5f}") - fmts.print_war(f"[反作弊] 已将 {name} 拉回至 {sec}秒前 ({x:.5f}, {y:.5f}, {z:.5f})") - self._record_rollback_target(name, x, y, z, now) - except Exception as e: - fmts.print_err(f"[反作弊] 回拉 {name} 失败: {e}") - if name in self.pos_history: - self.pos_history[name].clear() - return - r_history = self.rollback_history.get(name) - if r_history and len(r_history) > 0: - x, y, z, _ = r_history[-1] + + # 普通拉回:渐进回溯 + rollback_times = [3, 5, 10, 15, 30, 60] + for sec in rollback_times: + target_time = now - sec + pos = self._get_position_at(name, target_time) + if pos is not None: + x, y, z = pos try: self.game_ctrl.sendwscmd(f"/tp {name} {x:.5f} {y:.5f} {z:.5f}") - fmts.print_war(f"[反作弊] {name} 使用备用历史拉回 ({x:.5f}, {y:.5f}, {z:.5f})") + fmts.print_war( + f"[反作弊] 已将 {name} 拉回至 {sec}秒前 ({x:.5f}, {y:.5f}, {z:.5f})" + ) self._record_rollback_target(name, x, y, z, now) except Exception as e: - fmts.print_err(f"[反作弊] 备用拉回 {name} 失败: {e}") + fmts.print_err(f"[反作弊] 回拉 {name} 失败: {e}") if name in self.pos_history: self.pos_history[name].clear() return - fmts.print_war(f"[反作弊] 无法找到 {name} 任何有效历史坐标,回退失败") + + # 所有回溯时间都找不到,尝试备用历史 + r_history = self.rollback_history.get(name) + if r_history and len(r_history) > 0: + x, y, z, _ = r_history[-1] + try: + self.game_ctrl.sendwscmd(f"/tp {name} {x:.5f} {y:.5f} {z:.5f}") + fmts.print_war( + f"[反作弊] {name} 使用备用历史拉回 ({x:.5f}, {y:.5f}, {z:.5f})" + ) + self._record_rollback_target(name, x, y, z, now) + except Exception as e: + fmts.print_err(f"[反作弊] 备用拉回 {name} 失败: {e}") + if name in self.pos_history: + self.pos_history[name].clear() + return + + fmts.print_war(f"[反作弊] 无法找到 {name} 任何有效历史坐标,回退失败") def _calc_acceleration_from_points(self, p0, p1, p2, check_interval=True): + """根据三个点计算水平和垂直加速度。""" dt1 = p1[3] - p0[3] dt2 = p2[3] - p1[3] if check_interval: @@ -341,19 +375,20 @@ def _calc_acceleration_from_points(self, p0, p1, p2, check_interval=True): vx2 = (p2[0] - p1[0]) / dt2 vy2 = (p2[1] - p1[1]) / dt2 vz2 = (p2[2] - p1[2]) / dt2 - dv_h = math.sqrt((vx2 - vx1)**2 + (vz2 - vz1)**2) + dv_h = math.sqrt((vx2 - vx1) ** 2 + (vz2 - vz1) ** 2) if dv_h > 15.0: return None, None ax = (vx2 - vx1) / ((dt1 + dt2) / 2) ay = (vy2 - vy1) / ((dt1 + dt2) / 2) az = (vz2 - vz1) / ((dt1 + dt2) / 2) - h_acc = math.sqrt(ax**2 + az**2) + h_acc = math.sqrt(ax ** 2 + az ** 2) v_acc = abs(ay) if h_acc > 50 or v_acc > 50: return None, None return h_acc, v_acc def _get_acceleration(self, name): + """获取玩家当前精确加速度(优先地面历史,其次空中历史)。""" history = self.pos_history.get(name) if history and len(history) >= 3: p0, p1, p2 = list(history)[-3:] @@ -364,7 +399,8 @@ def _get_acceleration(self, name): return self._calc_acceleration_from_points(p0, p1, p2, check_interval=False) return None, None - def _get_horizontal_speed(self, name, pos, now): + def _get_horizontal_speed(self, name: str, pos: dict, now: float): + """计算玩家水平速度,优先使用空中历史以保证实时性。""" air_hist = self.air_pos_history.get(name) if air_hist and len(air_hist) >= 2: p1 = air_hist[-2] @@ -373,7 +409,7 @@ def _get_horizontal_speed(self, name, pos, now): if dt > 0: dx = p2[0] - p1[0] dz = p2[2] - p1[2] - return math.sqrt(dx**2 + dz**2) / dt + return math.sqrt(dx ** 2 + dz ** 2) / dt history = self.pos_history.get(name) if history and len(history) >= 2: p1 = history[-2] @@ -382,7 +418,7 @@ def _get_horizontal_speed(self, name, pos, now): if dt > 0: dx = p2[0] - p1[0] dz = p2[2] - p1[2] - return math.sqrt(dx**2 + dz**2) / dt + return math.sqrt(dx ** 2 + dz ** 2) / dt last = self.last_pos.get(name) if last: lx, ly, lz, lt = last @@ -390,10 +426,11 @@ def _get_horizontal_speed(self, name, pos, now): if dt > 0: dx = pos["x"] - lx dz = pos["z"] - lz - return math.sqrt(dx**2 + dz**2) / dt + return math.sqrt(dx ** 2 + dz ** 2) / dt return None - def _get_vertical_speed(self, name, pos, now): + def _get_vertical_speed(self, name: str, pos: dict, now: float) -> float: + """计算玩家垂直速度。""" air_hist = self.air_pos_history.get(name) if air_hist and len(air_hist) >= 2: p1 = air_hist[-2] @@ -416,7 +453,8 @@ def _get_vertical_speed(self, name, pos, now): return (pos["y"] - ly) / dt return 0.0 - def _show_accel_to_admins(self, all_data, now): + def _show_accel_to_admins(self, all_data: dict, now: float) -> None: + """向 tag=admin 的玩家在 actionbar 显示速度和加速度信息。""" info_parts = [f"§a==== {time.strftime('%H:%M:%S')} ==== "] for name, pos in all_data.items(): if self.is_bypass(name): @@ -437,25 +475,28 @@ def _show_accel_to_admins(self, all_data, now): cmd = f"""/titleraw @a[tag=admin] actionbar {{"rawtext":{json.dumps(rawtext)}}}""" try: self.game_ctrl.sendwocmd(cmd) - except: + except Exception: pass # ---------- 主事件 ---------- - def on_pos_data(self, event: InternalBroadcast): + def on_pos_data(self, event: InternalBroadcast) -> None: + """接收前置插件广播的坐标,执行所有检测模块。""" data = event.data now = time.time() should_print = ( - self.cfg["控制台打印位置"] and - (now - self.last_print_time >= self.cfg["位置打印间隔_秒"]) + self.cfg["控制台打印位置"] + and (now - self.last_print_time >= self.cfg["位置打印间隔_秒"]) ) if should_print: self.last_print_time = now - fmts.print_inf(f"========== 玩家位置 ({time.strftime('%H:%M:%S')}) ==========") + fmts.print_inf( + f"========== 玩家位置 ({time.strftime('%H:%M:%S')}) ==========" + ) should_show_admin = ( - self.cfg["管理员显示加速度"] and - (now - self.last_admin_show_time >= self.cfg["管理员显示间隔_秒"]) + self.cfg["管理员显示加速度"] + and (now - self.last_admin_show_time >= self.cfg["管理员显示间隔_秒"]) ) if should_show_admin: self.last_admin_show_time = now @@ -468,7 +509,9 @@ def on_pos_data(self, event: InternalBroadcast): self._update_history(name, pos, now) if should_print: - fmts.print_inf(f" {name}: ({pos['x']:.5f}, {pos['y']:.5f}, {pos['z']:.5f})") + fmts.print_inf( + f" {name}: ({pos['x']:.5f}, {pos['y']:.5f}, {pos['z']:.5f})" + ) if self.cfg.get("上升速度检测开关", True): self._check_rise(name, pos, now) @@ -488,48 +531,68 @@ def on_pos_data(self, event: InternalBroadcast): self._check_acceleration(name, pos, now) # ---------- 各检测模块 ---------- - def _check_rise(self, name, pos, now): + def _check_rise(self, name: str, pos: dict, now: float) -> None: + """上升速度检测:无支撑且垂直速度超过阈值即拉回。""" vy = self._get_vertical_speed(name, pos, now) if vy <= self.cfg["上升速度阈值"]: return if self._is_unsupported(name): - fmts.print_war(f"[反作弊] {name} 上升速度异常!垂直速度={vy:.5f} m/s(阈值{self.cfg['上升速度阈值']})") + fmts.print_war( + f"[反作弊] {name} 上升速度异常!垂直速度={vy:.5f} m/s" + f"(阈值{self.cfg['上升速度阈值']})" + ) self._rollback(name) - def _check_speed(self, name, pos, now): + def _check_speed(self, name: str, pos: dict, now: float) -> None: + """水平速度检测:区分骑马与普通移动,超阈值即拉回。""" h_speed = self._get_horizontal_speed(name, pos, now) if h_speed is None: return if self._is_riding(name): threshold = self.cfg.get("骑马速度水平阈值", 25.0) if h_speed > threshold: - fmts.print_war(f"[反作弊] {name} 骑马时速度异常!速度={h_speed:.5f} m/s(阈值{threshold})") + fmts.print_war( + f"[反作弊] {name} 骑马时速度异常!速度={h_speed:.5f} m/s" + f"(阈值{threshold})" + ) self._rollback(name) else: threshold = self.cfg["速度异常水平阈值"] if h_speed > threshold: - fmts.print_war(f"[反作弊] {name} 水平速度异常!速度={h_speed:.5f} m/s(阈值{threshold})") + fmts.print_war( + f"[反作弊] {name} 水平速度异常!速度={h_speed:.5f} m/s" + f"(阈值{threshold})" + ) self._rollback(name) - def _check_acceleration(self, name, pos, now): + def _check_acceleration(self, name: str, pos: dict, now: float) -> None: + """加速度检测:水平或垂直加速度超阈值(且非正常下落)即拉回。""" h_acc, v_acc = self._get_acceleration(name) h_threshold = self.cfg["加速度异常水平阈值"] v_threshold = self.cfg["加速度异常垂直阈值"] if h_acc is not None: if h_acc > h_threshold: - fmts.print_war(f"[反作弊] {name} 加速度异常!水平={h_acc:.5f} m/s²(阈值{h_threshold})") + fmts.print_war( + f"[反作弊] {name} 加速度异常!水平={h_acc:.5f} m/s²" + f"(阈值{h_threshold})" + ) self._rollback(name) return if v_acc > v_threshold: + # 下落区间放行(18~22 m/s² 且垂直速度向下) if 18 < v_acc < 22: vy = self._get_vertical_speed(name, pos, now) if vy < 0: return - fmts.print_war(f"[反作弊] {name} 加速度异常!垂直={v_acc:.5f} m/s²(阈值{v_threshold})") + fmts.print_war( + f"[反作弊] {name} 加速度异常!垂直={v_acc:.5f} m/s²" + f"(阈值{v_threshold})" + ) self._rollback(name) - def _check_flight(self, name, pos, now): + def _check_flight(self, name: str, pos: dict, now: float) -> None: + """飞行检测:无支撑且无有效下落/跳跃趋势时判定为悬空飞行。""" history = self.pos_history.get(name) if not self._is_unsupported(name): return @@ -549,17 +612,23 @@ def _check_flight(self, name, pos, now): h_speed = self._get_horizontal_speed(name, pos, now) or 0.0 vy = self._get_vertical_speed(name, pos, now) + # 静止悬停 if h_speed < 0.1 and abs(vy) < 0.1: - fmts.print_war(f"[反作弊] {name} 疑似静止悬停,高度={pos['y']:.5f}") + fmts.print_war( + f"[反作弊] {name} 疑似静止悬停,高度={pos['y']:.5f}" + ) self._rollback(name, smart=True) return + # 缓降豁免 if h_speed < 0.5 and vy < 0: return + # 快速下落豁免 if vy < self.cfg["飞行最小下落速度"]: return + # 下落/跳跃趋势检查 lookback = self.cfg["飞行下落检测秒数"] target_time = now - lookback old_y = None @@ -592,7 +661,8 @@ def _check_flight(self, name, pos, now): ) self._rollback(name, smart=True) - def _check_groundfly(self, name, pos, now): + def _check_groundfly(self, name: str, pos: dict, now: float) -> None: + """平地飞检测:地面高速移动或异常水平加速度。""" if self.cfg.get("速度检测下落豁免", True): vy = self._get_vertical_speed(name, pos, now) if vy < self.cfg["飞行最小下落速度"]: @@ -606,7 +676,7 @@ def _check_groundfly(self, name, pos, now): return ys = [p[1] for p in recent] mean_y = sum(ys) / len(ys) - std_y = (sum((y - mean_y)**2 for y in ys) / len(ys)) ** 0.5 + std_y = (sum((y - mean_y) ** 2 for y in ys) / len(ys)) ** 0.5 if std_y > self.cfg["平地飞Y轴标准差阈值"]: return last = self.last_pos.get(name) @@ -618,24 +688,33 @@ def _check_groundfly(self, name, pos, now): return dx = pos["x"] - last_x dz = pos["z"] - last_z - dist = (dx**2 + dz**2) ** 0.5 + dist = (dx ** 2 + dz ** 2) ** 0.5 h_speed = dist / dt h_acc = 0 if len(recent) >= 4: mid = len(recent) // 2 dt_half = (recent[-1][3] - recent[0][3]) / 2 if dt_half > 0: - v1 = ((recent[mid][0] - recent[0][0])**2 + (recent[mid][2] - recent[0][2])**2) ** 0.5 / dt_half - v2 = ((recent[-1][0] - recent[mid][0])**2 + (recent[-1][2] - recent[mid][2])**2) ** 0.5 / dt_half + v1 = ((recent[mid][0] - recent[0][0]) ** 2 + + (recent[mid][2] - recent[0][2]) ** 2) ** 0.5 / dt_half + v2 = ((recent[-1][0] - recent[mid][0]) ** 2 + + (recent[-1][2] - recent[mid][2]) ** 2) ** 0.5 / dt_half h_acc = (v2 - v1) / dt_half if h_speed > self.cfg["平地飞水平速度阈值"]: - fmts.print_war(f"[反作弊] {name} 疑似平地飞,水平速度={h_speed:.5f},Y标准差={std_y:.5f}") + fmts.print_war( + f"[反作弊] {name} 疑似平地飞,水平速度={h_speed:.5f}," + f"Y标准差={std_y:.5f}" + ) self._rollback(name) elif h_acc > self.cfg["平地飞水平加速度阈值"] and h_speed > 5.0: - fmts.print_war(f"[反作弊] {name} 水平加速度异常={h_acc:.5f},水平速度={h_speed:.5f}") + fmts.print_war( + f"[反作弊] {name} 水平加速度异常={h_acc:.5f}," + f"水平速度={h_speed:.5f}" + ) self._rollback(name) - def _check_bounce(self, name, pos, now): + def _check_bounce(self, name: str, pos: dict, now: float) -> None: + """回弹飞行检测:周期性上下波动且水平移动。""" history = self.pos_history.get(name) if not history or len(history) < 5: return @@ -671,13 +750,17 @@ def _check_bounce(self, name, pos, now): dt = recent[-1][3] - recent[0][3] if dt <= 0: return - h_speed = ((dx**2 + dz**2) ** 0.5) / dt + h_speed = ((dx ** 2 + dz ** 2) ** 0.5) / dt if h_speed < self.cfg["回弹飞最低水平速度"]: return - fmts.print_war(f"[反作弊] {name} 疑似回弹飞行!波动次数={cycles},平均振幅={avg_amplitude:.5f},水平速度={h_speed:.5f}") + fmts.print_war( + f"[反作弊] {name} 疑似回弹飞行!波动次数={cycles}," + f"平均振幅={avg_amplitude:.5f},水平速度={h_speed:.5f}" + ) self._rollback(name) - def _check_predict_move(self, name, pos, now): + def _check_predict_move(self, name: str, pos: dict, now: float) -> None: + """移动预测检测:基于历史轨迹预测位置,偏差过大即拉回。""" if self.cfg.get("速度检测下落豁免", True): vy_current = self._get_vertical_speed(name, pos, now) if vy_current < self.cfg["飞行最小下落速度"]: @@ -686,10 +769,7 @@ def _check_predict_move(self, name, pos, now): history = self.pos_history.get(name) if not history or len(history) < 4: return - p0 = history[-4] - p1 = history[-3] - p2 = history[-2] - p3 = history[-1] + p0, p1, p2, p3 = list(history)[-4:] dt1 = p1[3] - p0[3] dt2 = p2[3] - p1[3] if dt1 <= 0 or dt2 <= 0: @@ -708,11 +788,15 @@ def _check_predict_move(self, name, pos, now): predicted_y = p2[1] + avg_vy * predict_dt predicted_z = p2[2] + avg_vz * predict_dt actual_x, actual_y, actual_z = pos["x"], pos["y"], pos["z"] - dist = math.sqrt((actual_x - predicted_x)**2 + (actual_y - predicted_y)**2 + (actual_z - predicted_z)**2) - speed = math.sqrt(avg_vx**2 + avg_vy**2 + avg_vz**2) + dist = math.sqrt( + (actual_x - predicted_x) ** 2 + + (actual_y - predicted_y) ** 2 + + (actual_z - predicted_z) ** 2 + ) + speed = math.sqrt(avg_vx ** 2 + avg_vy ** 2 + avg_vz ** 2) threshold = self.cfg["预测基础误差_米"] + speed * self.cfg["预测速度误差因子"] - speed1 = math.sqrt(vx1**2 + vy1**2 + vz1**2) - speed2 = math.sqrt(vx2**2 + vy2**2 + vz2**2) + speed1 = math.sqrt(vx1 ** 2 + vy1 ** 2 + vz1 ** 2) + speed2 = math.sqrt(vx2 ** 2 + vy2 ** 2 + vz2 ** 2) max_speed = max(speed1, speed2) if max_speed > 0: speed_change_ratio = abs(speed2 - speed1) / max_speed @@ -722,14 +806,17 @@ def _check_predict_move(self, name, pos, now): threshold *= self.cfg["预测速度不稳定放宽因子"] if speed > 0: dot = vx1 * vx2 + vz1 * vz2 - mag = math.sqrt(vx1**2 + vz1**2) * math.sqrt(vx2**2 + vz2**2) + mag = math.sqrt(vx1 ** 2 + vz1 ** 2) * math.sqrt(vx2 ** 2 + vz2 ** 2) if mag > 0: cos_angle = max(-1, min(1, dot / mag)) angle = math.acos(cos_angle) if angle > math.radians(90): threshold *= 2.0 if dist > threshold: - fmts.print_war(f"[反作弊] {name} 移动预测异常:偏差={dist:.5f} 米(阈值{threshold:.5f}),速度={speed:.5f} m/s") + fmts.print_war( + f"[反作弊] {name} 移动预测异常:偏差={dist:.5f} 米" + f"(阈值{threshold:.5f}),速度={speed:.5f} m/s" + ) self._rollback(name) From 2f89f9f6010e7e3eae5f3e844cabef701ea803ac Mon Sep 17 00:00:00 2001 From: mumanMN Date: Sat, 6 Jun 2026 15:41:32 +0800 Subject: [PATCH 3/8] =?UTF-8?q?=E5=8F=8D=E4=BD=9C=E5=BC=8A-=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 骑马检测不知道怎么搞qwq --- .../__init__.py" | 422 +++++++++--------- 1 file changed, 217 insertions(+), 205 deletions(-) diff --git "a/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" "b/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" index 844efb43..fe309a5f 100644 --- "a/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" +++ "b/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" @@ -14,7 +14,7 @@ class SimpleAntiCheat(Plugin): name = "反作弊-移动检测(MUNAN)" author = "Assistant" - version = (0, 0, 1) + version = (0, 0, 2) def __init__(self, frame): super().__init__(frame) @@ -27,7 +27,6 @@ def __init__(self, frame): self.last_admin_show_time = 0 self.rollback_count = {} # 连续拉回计数 - # 配置项及默认值 DEFAULT_CFG = { # 开关 "飞行检测开关": True, @@ -235,124 +234,109 @@ def _rollback(self, name: str, smart: bool = False, """执行拉回操作,支持智能拉回和渐进回溯。""" now = time.time() if smart: - current = self.last_pos.get(name) - if not current: - return - cx, cy, cz, _ = current - history = self.pos_history.get(name) - r_history = self.rollback_history.get(name) - - if to_earliest: - # 寻找最早的距离超过 1.0 的点 - target = None - if history and len(history) > 0: - for x, y, z, t in history: - if math.sqrt((x - cx) ** 2 + (y - cy) ** 2 + (z - cz) ** 2) >= 1.0: - target = (x, y, z) - break - if not target and r_history and len(r_history) > 0: - target = (r_history[0][0], r_history[0][1], r_history[0][2]) - elif not target and history and len(history) > 0: - target = (history[0][0], history[0][1], history[0][2]) - if not target: - return - x, y, z = target - try: - self.game_ctrl.sendwscmd(f"/tp {name} {x:.5f} {y:.5f} {z:.5f}") - fmts.print_war( - f"[反作弊] {name} 拉回至最早变化点 ({x:.5f}, {y:.5f}, {z:.5f})" - ) - self._record_rollback_target(name, x, y, z, now) - except Exception as e: - fmts.print_err(f"[反作弊] 最早变化点拉回失败: {e}") - if name in self.pos_history: - self.pos_history[name].clear() - if name in self.rollback_count: - self.rollback_count[name] = [] - return + self._smart_rollback(name, now, to_earliest) + else: + self._normal_rollback(name, now) - if not history or len(history) < 2: - if r_history and len(r_history) > 0: - x, y, z, _ = r_history[-1] - try: - self.game_ctrl.sendwscmd(f"/tp {name} {x:.5f} {y:.5f} {z:.5f}") - fmts.print_war( - f"[反作弊] {name} 拉回至最近安全位置 ({x:.5f}, {y:.5f}, {z:.5f})" - ) - self._record_rollback_target(name, x, y, z, now) - except Exception as e: - fmts.print_err(f"[反作弊] 拉回 {name} 失败: {e}") - if name in self.pos_history: - self.pos_history[name].clear() - else: - fmts.print_war(f"[反作弊] {name} 无可用历史坐标,拉回失败") - return + def _smart_rollback(self, name: str, now: float, to_earliest: bool) -> None: + """智能拉回:利用历史坐标或备用历史。""" + current = self.last_pos.get(name) + if not current: + return + cx, cy, cz, _ = current + history = self.pos_history.get(name) + r_history = self.rollback_history.get(name) - # 普通智能拉回:从最近历史中找距离>=1.0 的点 - target = None - for x, y, z, t in reversed(list(history)[:-1]): - if math.sqrt((x - cx) ** 2 + (y - cy) ** 2 + (z - cz) ** 2) >= 1.0: - target = (x, y, z) - break - if not target: - target = (history[0][0], history[0][1], history[0][2]) - x, y, z = target - try: - self.game_ctrl.sendwscmd(f"/tp {name} {x:.5f} {y:.5f} {z:.5f}") - fmts.print_war( - f"[反作弊] {name} 拉回至明显变化前位置 ({x:.5f}, {y:.5f}, {z:.5f})" - ) - self._record_rollback_target(name, x, y, z, now) - except Exception as e: - fmts.print_err(f"[反作弊] 拉回 {name} 失败: {e}") - return + if to_earliest: + self._earliest_rollback(name, cx, cy, cz, history, r_history, now) + return + # 普通智能拉回 + if not history or len(history) < 2: + if r_history and len(r_history) > 0: + x, y, z, _ = r_history[-1] + self._tp_and_record(name, x, y, z, "最近安全位置", now) + else: + fmts.print_war(f"[反作弊] {name} 无可用历史坐标,拉回失败") if name in self.pos_history: self.pos_history[name].clear() - - # 连续拉回计数 - if name not in self.rollback_count: - self.rollback_count[name] = [] - self.rollback_count[name].append(now) - self.rollback_count[name] = [ - t for t in self.rollback_count[name] if now - t <= 10 - ] - if len(self.rollback_count[name]) >= self.cfg["飞行连续拉回次数阈值"]: - if self._is_y_rising(name, count=3): - self._rollback(name, smart=True, to_earliest=True) return - # 普通拉回:渐进回溯 + # 寻找距离>=1.0 的变化点 + target = self._find_change_point(history, cx, cy, cz) + self._tp_and_record(name, target[0], target[1], target[2], "明显变化前位置", now) + + if name in self.pos_history: + self.pos_history[name].clear() + self._update_rollback_count(name, now) + + def _earliest_rollback(self, name: str, cx: float, cy: float, cz: float, + history, r_history, now: float) -> None: + """寻找最早的变化点进行拉回。""" + target = None + if history and len(history) > 0: + for x, y, z, t in history: + if math.hypot(x - cx, y - cy, z - cz) >= 1.0: + target = (x, y, z) + break + if not target and r_history and len(r_history) > 0: + target = (r_history[0][0], r_history[0][1], r_history[0][2]) + elif not target and history and len(history) > 0: + target = (history[0][0], history[0][1], history[0][2]) + if not target: + return + self._tp_and_record(name, target[0], target[1], target[2], "最早变化点", now) + if name in self.pos_history: + self.pos_history[name].clear() + if name in self.rollback_count: + self.rollback_count[name] = [] + + def _find_change_point(self, history, cx: float, cy: float, cz: float): + """从历史中找出与当前位置距离>=1.0 的点。""" + for x, y, z, t in reversed(list(history)[:-1]): + if math.hypot(x - cx, y - cy, z - cz) >= 1.0: + return (x, y, z) + return (history[0][0], history[0][1], history[0][2]) + + def _tp_and_record(self, name: str, x: float, y: float, z: float, + reason: str, now: float) -> None: + """传送玩家并记录拉回目标。""" + try: + self.game_ctrl.sendwscmd(f"/tp {name} {x:.5f} {y:.5f} {z:.5f}") + fmts.print_war(f"[反作弊] {name} 拉回至{reason} ({x:.5f}, {y:.5f}, {z:.5f})") + self._record_rollback_target(name, x, y, z, now) + except Exception as e: + fmts.print_err(f"[反作弊] 拉回 {name} 失败: {e}") + + def _update_rollback_count(self, name: str, now: float) -> None: + """更新连续拉回计数,若超阈值且Y轴持续上升,触发最早变化点拉回。""" + if name not in self.rollback_count: + self.rollback_count[name] = [] + self.rollback_count[name].append(now) + self.rollback_count[name] = [ + t for t in self.rollback_count[name] if now - t <= 10 + ] + if len(self.rollback_count[name]) >= self.cfg["飞行连续拉回次数阈值"]: + if self._is_y_rising(name, count=3): + self._rollback(name, smart=True, to_earliest=True) + + def _normal_rollback(self, name: str, now: float) -> None: + """普通拉回:渐进回溯。""" rollback_times = [3, 5, 10, 15, 30, 60] for sec in rollback_times: target_time = now - sec pos = self._get_position_at(name, target_time) if pos is not None: x, y, z = pos - try: - self.game_ctrl.sendwscmd(f"/tp {name} {x:.5f} {y:.5f} {z:.5f}") - fmts.print_war( - f"[反作弊] 已将 {name} 拉回至 {sec}秒前 ({x:.5f}, {y:.5f}, {z:.5f})" - ) - self._record_rollback_target(name, x, y, z, now) - except Exception as e: - fmts.print_err(f"[反作弊] 回拉 {name} 失败: {e}") + self._tp_and_record(name, x, y, z, f"{sec}秒前", now) if name in self.pos_history: self.pos_history[name].clear() return - # 所有回溯时间都找不到,尝试备用历史 r_history = self.rollback_history.get(name) if r_history and len(r_history) > 0: x, y, z, _ = r_history[-1] - try: - self.game_ctrl.sendwscmd(f"/tp {name} {x:.5f} {y:.5f} {z:.5f}") - fmts.print_war( - f"[反作弊] {name} 使用备用历史拉回 ({x:.5f}, {y:.5f}, {z:.5f})" - ) - self._record_rollback_target(name, x, y, z, now) - except Exception as e: - fmts.print_err(f"[反作弊] 备用拉回 {name} 失败: {e}") + self._tp_and_record(name, x, y, z, "备用历史", now) if name in self.pos_history: self.pos_history[name].clear() return @@ -375,13 +359,13 @@ def _calc_acceleration_from_points(self, p0, p1, p2, check_interval=True): vx2 = (p2[0] - p1[0]) / dt2 vy2 = (p2[1] - p1[1]) / dt2 vz2 = (p2[2] - p1[2]) / dt2 - dv_h = math.sqrt((vx2 - vx1) ** 2 + (vz2 - vz1) ** 2) + dv_h = math.hypot(vx2 - vx1, vz2 - vz1) if dv_h > 15.0: return None, None ax = (vx2 - vx1) / ((dt1 + dt2) / 2) ay = (vy2 - vy1) / ((dt1 + dt2) / 2) az = (vz2 - vz1) / ((dt1 + dt2) / 2) - h_acc = math.sqrt(ax ** 2 + az ** 2) + h_acc = math.hypot(ax, az) v_acc = abs(ay) if h_acc > 50 or v_acc > 50: return None, None @@ -407,26 +391,20 @@ def _get_horizontal_speed(self, name: str, pos: dict, now: float): p2 = air_hist[-1] dt = p2[3] - p1[3] if dt > 0: - dx = p2[0] - p1[0] - dz = p2[2] - p1[2] - return math.sqrt(dx ** 2 + dz ** 2) / dt + return math.hypot(p2[0] - p1[0], p2[2] - p1[2]) / dt history = self.pos_history.get(name) if history and len(history) >= 2: p1 = history[-2] p2 = history[-1] dt = p2[3] - p1[3] if dt > 0: - dx = p2[0] - p1[0] - dz = p2[2] - p1[2] - return math.sqrt(dx ** 2 + dz ** 2) / dt + return math.hypot(p2[0] - p1[0], p2[2] - p1[2]) / dt last = self.last_pos.get(name) if last: - lx, ly, lz, lt = last + lx, _, lz, lt = last dt = now - lt if dt > 0: - dx = pos["x"] - lx - dz = pos["z"] - lz - return math.sqrt(dx ** 2 + dz ** 2) / dt + return math.hypot(pos["x"] - lx, pos["z"] - lz) / dt return None def _get_vertical_speed(self, name: str, pos: dict, now: float) -> float: @@ -447,7 +425,7 @@ def _get_vertical_speed(self, name: str, pos: dict, now: float) -> float: return (p2[1] - p1[1]) / dt last = self.last_pos.get(name) if last: - lx, ly, lz, lt = last + _, ly, _, lt = last dt = now - lt if dt > 0: return (pos["y"] - ly) / dt @@ -593,57 +571,26 @@ def _check_acceleration(self, name: str, pos: dict, now: float) -> None: def _check_flight(self, name: str, pos: dict, now: float) -> None: """飞行检测:无支撑且无有效下落/跳跃趋势时判定为悬空飞行。""" - history = self.pos_history.get(name) if not self._is_unsupported(name): return - min_air_time = self.cfg.get("飞行最小悬空秒数", 0.5) - last_ground_time = None - if history and len(history) > 0: - last_ground_time = history[-1][3] - elif name in self.last_pos: - last_ground_time = self.last_pos[name][3] - - if last_ground_time is not None: - air_duration = now - last_ground_time - if air_duration < min_air_time: - return + if not self._has_enough_air_time(name, now): + return h_speed = self._get_horizontal_speed(name, pos, now) or 0.0 vy = self._get_vertical_speed(name, pos, now) # 静止悬停 if h_speed < 0.1 and abs(vy) < 0.1: - fmts.print_war( - f"[反作弊] {name} 疑似静止悬停,高度={pos['y']:.5f}" - ) + fmts.print_war(f"[反作弊] {name} 疑似静止悬停,高度={pos['y']:.5f}") self._rollback(name, smart=True) return - # 缓降豁免 - if h_speed < 0.5 and vy < 0: + # 缓降或快速下落豁免 + if (h_speed < 0.5 and vy < 0) or vy < self.cfg["飞行最小下落速度"]: return - # 快速下落豁免 - if vy < self.cfg["飞行最小下落速度"]: - return - - # 下落/跳跃趋势检查 - lookback = self.cfg["飞行下落检测秒数"] - target_time = now - lookback - old_y = None - last = self.last_pos.get(name) - if last and (now - last[3]) <= lookback: - old_y = last[1] - else: - old_pos = self._get_position_at(name, target_time) - if old_pos is not None: - old_y = old_pos[1] - else: - old_pos_r = self._get_position_at(name, target_time, use_rollback=True) - if old_pos_r is not None: - old_y = old_pos_r[1] - + old_y = self._get_old_y(name, now) if old_y is not None: dy = pos["y"] - old_y if dy < self.cfg["飞行最大可通过下落"]: @@ -661,6 +608,36 @@ def _check_flight(self, name: str, pos: dict, now: float) -> None: ) self._rollback(name, smart=True) + def _has_enough_air_time(self, name: str, now: float) -> bool: + """检查悬空时间是否超过阈值。""" + min_air_time = self.cfg.get("飞行最小悬空秒数", 0.5) + history = self.pos_history.get(name) + last_ground_time = None + if history and len(history) > 0: + last_ground_time = history[-1][3] + elif name in self.last_pos: + last_ground_time = self.last_pos[name][3] + + if last_ground_time is None: + return True + return (now - last_ground_time) >= min_air_time + + def _get_old_y(self, name: str, now: float): + """获取过去一段时间前的 Y 坐标(用于判断下落/跳跃趋势)。""" + lookback = self.cfg["飞行下落检测秒数"] + target_time = now - lookback + last = self.last_pos.get(name) + if last and (now - last[3]) <= lookback: + return last[1] + + old_pos = self._get_position_at(name, target_time) + if old_pos is not None: + return old_pos[1] + old_pos_r = self._get_position_at(name, target_time, use_rollback=True) + if old_pos_r is not None: + return old_pos_r[1] + return None + def _check_groundfly(self, name: str, pos: dict, now: float) -> None: """平地飞检测:地面高速移动或异常水平加速度。""" if self.cfg.get("速度检测下落豁免", True): @@ -671,47 +648,62 @@ def _check_groundfly(self, name: str, pos: dict, now: float) -> None: history = self.pos_history.get(name) if not history or len(history) < 5: return + recent = [p for p in history if now - p[3] <= 3.0] if len(recent) < 3: return - ys = [p[1] for p in recent] - mean_y = sum(ys) / len(ys) - std_y = (sum((y - mean_y) ** 2 for y in ys) / len(ys)) ** 0.5 - if std_y > self.cfg["平地飞Y轴标准差阈值"]: + + if self._has_large_y_variation(recent): return + last = self.last_pos.get(name) if not last: return - last_x, last_y, last_z, last_t = last + last_x, _, last_z, last_t = last dt = now - last_t if dt <= 0: return - dx = pos["x"] - last_x - dz = pos["z"] - last_z - dist = (dx ** 2 + dz ** 2) ** 0.5 - h_speed = dist / dt - h_acc = 0 - if len(recent) >= 4: - mid = len(recent) // 2 - dt_half = (recent[-1][3] - recent[0][3]) / 2 - if dt_half > 0: - v1 = ((recent[mid][0] - recent[0][0]) ** 2 + - (recent[mid][2] - recent[0][2]) ** 2) ** 0.5 / dt_half - v2 = ((recent[-1][0] - recent[mid][0]) ** 2 + - (recent[-1][2] - recent[mid][2]) ** 2) ** 0.5 / dt_half - h_acc = (v2 - v1) / dt_half + h_speed = math.hypot(pos["x"] - last_x, pos["z"] - last_z) / dt + if h_speed > self.cfg["平地飞水平速度阈值"]: fmts.print_war( - f"[反作弊] {name} 疑似平地飞,水平速度={h_speed:.5f}," - f"Y标准差={std_y:.5f}" - ) - self._rollback(name) - elif h_acc > self.cfg["平地飞水平加速度阈值"] and h_speed > 5.0: - fmts.print_war( - f"[反作弊] {name} 水平加速度异常={h_acc:.5f}," - f"水平速度={h_speed:.5f}" + f"[反作弊] {name} 疑似平地飞,水平速度={h_speed:.5f}" ) self._rollback(name) + return + + # 加速度检测 + if h_speed > 5.0 and len(recent) >= 4: + h_acc = self._calc_horizontal_acceleration(recent) + if h_acc > self.cfg["平地飞水平加速度阈值"]: + fmts.print_war( + f"[反作弊] {name} 水平加速度异常={h_acc:.5f}," + f"水平速度={h_speed:.5f}" + ) + self._rollback(name) + + def _has_large_y_variation(self, recent_points: list) -> bool: + """检查最近坐标的 Y 标准差是否过大(跳跃/地形)。""" + ys = [p[1] for p in recent_points] + mean_y = sum(ys) / len(ys) + variance = sum((y - mean_y) ** 2 for y in ys) / len(ys) + return variance ** 0.5 > self.cfg["平地飞Y轴标准差阈值"] + + def _calc_horizontal_acceleration(self, recent_points: list) -> float: + """根据最近四个点计算水平加速度。""" + mid = len(recent_points) // 2 + dt_half = (recent_points[-1][3] - recent_points[0][3]) / 2 + if dt_half <= 0: + return 0.0 + v1 = math.hypot( + recent_points[mid][0] - recent_points[0][0], + recent_points[mid][2] - recent_points[0][2] + ) / dt_half + v2 = math.hypot( + recent_points[-1][0] - recent_points[mid][0], + recent_points[-1][2] - recent_points[mid][2] + ) / dt_half + return (v2 - v1) / dt_half def _check_bounce(self, name: str, pos: dict, now: float) -> None: """回弹飞行检测:周期性上下波动且水平移动。""" @@ -722,16 +714,34 @@ def _check_bounce(self, name: str, pos: dict, now: float) -> None: recent = [p for p in history if now - p[3] <= duration] if len(recent) < 4: return - ys = [p[1] for p in recent] + + cycles, avg_amplitude = self._analyze_bounce(recent) + if cycles < self.cfg["回弹飞最小波动次数"]: + return + if not self.cfg["回弹飞最小波幅"] <= avg_amplitude <= self.cfg["回弹飞最大波幅"]: + return + + h_speed = self._calc_horizontal_speed_from_recent(recent) + if h_speed < self.cfg["回弹飞最低水平速度"]: + return + + fmts.print_war( + f"[反作弊] {name} 疑似回弹飞行!波动次数={cycles}," + f"平均振幅={avg_amplitude:.5f},水平速度={h_speed:.5f}" + ) + self._rollback(name) + + def _analyze_bounce(self, points: list) -> tuple: + """分析坐标序列的波峰波谷,返回(完整周期数, 平均振幅)。""" + ys = [p[1] for p in points] peaks = troughs = 0 for i in range(1, len(ys) - 1): if ys[i] > ys[i - 1] and ys[i] > ys[i + 1]: peaks += 1 elif ys[i] < ys[i - 1] and ys[i] < ys[i + 1]: troughs += 1 + cycles = min(peaks, troughs) - if cycles < self.cfg["回弹飞最小波动次数"]: - return amplitudes = [] for i in range(1, len(ys) - 1): if ys[i] > ys[i - 1] and ys[i] > ys[i + 1]: @@ -740,24 +750,19 @@ def _check_bounce(self, name: str, pos: dict, now: float) -> None: amp = (ys[i] - left_trough + ys[i] - right_trough) / 2 if amp > 0: amplitudes.append(amp) + if not amplitudes: - return - avg_amplitude = sum(amplitudes) / len(amplitudes) - if not (self.cfg["回弹飞最小波幅"] <= avg_amplitude <= self.cfg["回弹飞最大波幅"]): - return - dx = recent[-1][0] - recent[0][0] - dz = recent[-1][2] - recent[0][2] - dt = recent[-1][3] - recent[0][3] + return 0, 0.0 + return cycles, sum(amplitudes) / len(amplitudes) + + def _calc_horizontal_speed_from_recent(self, points: list) -> float: + """从最近的两个点计算水平速度。""" + dx = points[-1][0] - points[0][0] + dz = points[-1][2] - points[0][2] + dt = points[-1][3] - points[0][3] if dt <= 0: - return - h_speed = ((dx ** 2 + dz ** 2) ** 0.5) / dt - if h_speed < self.cfg["回弹飞最低水平速度"]: - return - fmts.print_war( - f"[反作弊] {name} 疑似回弹飞行!波动次数={cycles}," - f"平均振幅={avg_amplitude:.5f},水平速度={h_speed:.5f}" - ) - self._rollback(name) + return 0.0 + return math.hypot(dx, dz) / dt def _check_predict_move(self, name: str, pos: dict, now: float) -> None: """移动预测检测:基于历史轨迹预测位置,偏差过大即拉回。""" @@ -769,34 +774,39 @@ def _check_predict_move(self, name: str, pos: dict, now: float) -> None: history = self.pos_history.get(name) if not history or len(history) < 4: return - p0, p1, p2, p3 = list(history)[-4:] + p0, p1, p2, _ = list(history)[-4:] # 只取前三个点 dt1 = p1[3] - p0[3] dt2 = p2[3] - p1[3] if dt1 <= 0 or dt2 <= 0: return + vx1 = (p1[0] - p0[0]) / dt1 vy1 = (p1[1] - p0[1]) / dt1 vz1 = (p1[2] - p0[2]) / dt1 vx2 = (p2[0] - p1[0]) / dt2 vy2 = (p2[1] - p1[1]) / dt2 vz2 = (p2[2] - p1[2]) / dt2 + avg_vx = (vx1 + vx2) / 2 avg_vy = (vy1 + vy2) / 2 avg_vz = (vz1 + vz2) / 2 + predict_dt = self.cfg["预测时间_秒"] predicted_x = p2[0] + avg_vx * predict_dt predicted_y = p2[1] + avg_vy * predict_dt predicted_z = p2[2] + avg_vz * predict_dt + actual_x, actual_y, actual_z = pos["x"], pos["y"], pos["z"] - dist = math.sqrt( - (actual_x - predicted_x) ** 2 + - (actual_y - predicted_y) ** 2 + - (actual_z - predicted_z) ** 2 + dist = math.hypot( + actual_x - predicted_x, + actual_y - predicted_y, + actual_z - predicted_z ) - speed = math.sqrt(avg_vx ** 2 + avg_vy ** 2 + avg_vz ** 2) + speed = math.hypot(avg_vx, avg_vy, avg_vz) + threshold = self.cfg["预测基础误差_米"] + speed * self.cfg["预测速度误差因子"] - speed1 = math.sqrt(vx1 ** 2 + vy1 ** 2 + vz1 ** 2) - speed2 = math.sqrt(vx2 ** 2 + vy2 ** 2 + vz2 ** 2) + speed1 = math.hypot(vx1, vy1, vz1) + speed2 = math.hypot(vx2, vy2, vz2) max_speed = max(speed1, speed2) if max_speed > 0: speed_change_ratio = abs(speed2 - speed1) / max_speed @@ -804,14 +814,16 @@ def _check_predict_move(self, name: str, pos: dict, now: float) -> None: speed_change_ratio = 0 if speed_change_ratio > self.cfg["预测速度稳定性阈值"]: threshold *= self.cfg["预测速度不稳定放宽因子"] + if speed > 0: dot = vx1 * vx2 + vz1 * vz2 - mag = math.sqrt(vx1 ** 2 + vz1 ** 2) * math.sqrt(vx2 ** 2 + vz2 ** 2) + mag = math.hypot(vx1, vz1) * math.hypot(vx2, vz2) if mag > 0: cos_angle = max(-1, min(1, dot / mag)) angle = math.acos(cos_angle) if angle > math.radians(90): threshold *= 2.0 + if dist > threshold: fmts.print_war( f"[反作弊] {name} 移动预测异常:偏差={dist:.5f} 米" From 3508bd1c943764ef86998f9095e8058fd9b349ba Mon Sep 17 00:00:00 2001 From: mumanMN Date: Sat, 6 Jun 2026 15:42:50 +0800 Subject: [PATCH 4/8] =?UTF-8?q?=E5=8F=8D=E4=BD=9C=E5=BC=8A-=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 不会修骑马检测qwq From e22b9c4ebdc2c76edd8bc43378a1900a6911f5ce Mon Sep 17 00:00:00 2001 From: mumanMN Date: Sat, 6 Jun 2026 15:47:57 +0800 Subject: [PATCH 5/8] =?UTF-8?q?=E5=8F=8D=E4=BD=9C=E5=BC=8A-=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 不知道怎么修骑马检测qwq --- .../__init__.py" | 111 +++++++++++------- 1 file changed, 70 insertions(+), 41 deletions(-) diff --git "a/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" "b/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" index fe309a5f..94ec4c70 100644 --- "a/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" +++ "b/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" @@ -14,18 +14,18 @@ class SimpleAntiCheat(Plugin): name = "反作弊-移动检测(MUNAN)" author = "Assistant" - version = (0, 0, 2) + version = (0, 0, 3) def __init__(self, frame): super().__init__(frame) self.ListenInternalBroadcast("ggpp:publish_player_position", self.on_pos_data) - self.pos_history = {} # 玩家地面坐标历史 - self.rollback_history = {} # 拉回成功的目标坐标历史 - self.last_pos = {} # 最后一次地面坐标 - self.air_pos_history = {} # 空中坐标历史(用于加速度计算) + self.pos_history = {} + self.rollback_history = {} + self.last_pos = {} + self.air_pos_history = {} self.last_print_time = 0 self.last_admin_show_time = 0 - self.rollback_count = {} # 连续拉回计数 + self.rollback_count = {} DEFAULT_CFG = { # 开关 @@ -148,7 +148,6 @@ def _is_riding(self, name: str) -> bool: return True except Exception: pass - try: resp = self.game_ctrl.sendwscmd_with_resp( f"/execute as {name} on vehicle" @@ -157,7 +156,6 @@ def _is_riding(self, name: str) -> bool: return True except Exception: pass - return False def _is_unsupported(self, name: str) -> bool: @@ -173,7 +171,8 @@ def _is_unsupported(self, name: str) -> bool: pass try: resp = self.game_ctrl.sendwscmd_with_resp( - f"/execute as {name} at @s if block ~ ~-{distance} ~ #minecraft:climbable" + f"/execute as {name} at @s if block ~ ~-{distance} ~ " + f"#minecraft:climbable" ) if resp.SuccessCount > 0: return True @@ -186,7 +185,9 @@ def _update_history(self, name: str, pos: dict, now: float) -> None: if self._is_unsupported(name): if name not in self.air_pos_history: self.air_pos_history[name] = deque(maxlen=5) - self.air_pos_history[name].append((pos["x"], pos["y"], pos["z"], now)) + self.air_pos_history[name].append( + (pos["x"], pos["y"], pos["z"], now) + ) return x, y, z = pos["x"], pos["y"], pos["z"] self.last_pos[name] = (x, y, z, now) @@ -199,7 +200,11 @@ def _update_history(self, name: str, pos: dict, now: float) -> None: def _get_position_at(self, name: str, target_time: float, use_rollback: bool = False): """从历史中获取最接近 target_time 的坐标。""" - history = self.rollback_history.get(name) if use_rollback else self.pos_history.get(name) + history = ( + self.rollback_history.get(name) + if use_rollback + else self.pos_history.get(name) + ) if not history: return None best = None @@ -222,8 +227,8 @@ def _is_y_rising(self, name: str, count: int = 3) -> bool: return False return True - def _record_rollback_target(self, name: str, x: float, y: float, z: float, - now: float) -> None: + def _record_rollback_target(self, name: str, x: float, y: float, + z: float, now: float) -> None: """记录一次成功的拉回目标坐标。""" if name not in self.rollback_history: self.rollback_history[name] = deque(maxlen=50) @@ -238,7 +243,8 @@ def _rollback(self, name: str, smart: bool = False, else: self._normal_rollback(name, now) - def _smart_rollback(self, name: str, now: float, to_earliest: bool) -> None: + def _smart_rollback(self, name: str, now: float, + to_earliest: bool) -> None: """智能拉回:利用历史坐标或备用历史。""" current = self.last_pos.get(name) if not current: @@ -251,7 +257,6 @@ def _smart_rollback(self, name: str, now: float, to_earliest: bool) -> None: self._earliest_rollback(name, cx, cy, cz, history, r_history, now) return - # 普通智能拉回 if not history or len(history) < 2: if r_history and len(r_history) > 0: x, y, z, _ = r_history[-1] @@ -262,20 +267,22 @@ def _smart_rollback(self, name: str, now: float, to_earliest: bool) -> None: self.pos_history[name].clear() return - # 寻找距离>=1.0 的变化点 target = self._find_change_point(history, cx, cy, cz) - self._tp_and_record(name, target[0], target[1], target[2], "明显变化前位置", now) + self._tp_and_record( + name, target[0], target[1], target[2], "明显变化前位置", now + ) if name in self.pos_history: self.pos_history[name].clear() self._update_rollback_count(name, now) - def _earliest_rollback(self, name: str, cx: float, cy: float, cz: float, - history, r_history, now: float) -> None: + def _earliest_rollback(self, name: str, cx: float, cy: float, + cz: float, history, r_history, + now: float) -> None: """寻找最早的变化点进行拉回。""" target = None if history and len(history) > 0: - for x, y, z, t in history: + for x, y, z, _ in history: if math.hypot(x - cx, y - cy, z - cz) >= 1.0: target = (x, y, z) break @@ -285,15 +292,18 @@ def _earliest_rollback(self, name: str, cx: float, cy: float, cz: float, target = (history[0][0], history[0][1], history[0][2]) if not target: return - self._tp_and_record(name, target[0], target[1], target[2], "最早变化点", now) + self._tp_and_record( + name, target[0], target[1], target[2], "最早变化点", now + ) if name in self.pos_history: self.pos_history[name].clear() if name in self.rollback_count: self.rollback_count[name] = [] - def _find_change_point(self, history, cx: float, cy: float, cz: float): + @staticmethod + def _find_change_point(history, cx: float, cy: float, cz: float): """从历史中找出与当前位置距离>=1.0 的点。""" - for x, y, z, t in reversed(list(history)[:-1]): + for x, y, z, _ in reversed(list(history)[:-1]): if math.hypot(x - cx, y - cy, z - cz) >= 1.0: return (x, y, z) return (history[0][0], history[0][1], history[0][2]) @@ -303,7 +313,9 @@ def _tp_and_record(self, name: str, x: float, y: float, z: float, """传送玩家并记录拉回目标。""" try: self.game_ctrl.sendwscmd(f"/tp {name} {x:.5f} {y:.5f} {z:.5f}") - fmts.print_war(f"[反作弊] {name} 拉回至{reason} ({x:.5f}, {y:.5f}, {z:.5f})") + fmts.print_war( + f"[反作弊] {name} 拉回至{reason} ({x:.5f}, {y:.5f}, {z:.5f})" + ) self._record_rollback_target(name, x, y, z, now) except Exception as e: fmts.print_err(f"[反作弊] 拉回 {name} 失败: {e}") @@ -316,9 +328,10 @@ def _update_rollback_count(self, name: str, now: float) -> None: self.rollback_count[name] = [ t for t in self.rollback_count[name] if now - t <= 10 ] - if len(self.rollback_count[name]) >= self.cfg["飞行连续拉回次数阈值"]: - if self._is_y_rising(name, count=3): - self._rollback(name, smart=True, to_earliest=True) + if (len(self.rollback_count[name]) + >= self.cfg["飞行连续拉回次数阈值"] + and self._is_y_rising(name, count=3)): + self._rollback(name, smart=True, to_earliest=True) def _normal_rollback(self, name: str, now: float) -> None: """普通拉回:渐进回溯。""" @@ -343,7 +356,8 @@ def _normal_rollback(self, name: str, now: float) -> None: fmts.print_war(f"[反作弊] 无法找到 {name} 任何有效历史坐标,回退失败") - def _calc_acceleration_from_points(self, p0, p1, p2, check_interval=True): + @staticmethod + def _calc_acceleration_from_points(p0, p1, p2, check_interval=True): """根据三个点计算水平和垂直加速度。""" dt1 = p1[3] - p0[3] dt2 = p2[3] - p1[3] @@ -376,11 +390,15 @@ def _get_acceleration(self, name): history = self.pos_history.get(name) if history and len(history) >= 3: p0, p1, p2 = list(history)[-3:] - return self._calc_acceleration_from_points(p0, p1, p2, check_interval=True) + return self._calc_acceleration_from_points( + p0, p1, p2, check_interval=True + ) air_hist = self.air_pos_history.get(name) if air_hist and len(air_hist) >= 3: p0, p1, p2 = list(air_hist)[-3:] - return self._calc_acceleration_from_points(p0, p1, p2, check_interval=False) + return self._calc_acceleration_from_points( + p0, p1, p2, check_interval=False + ) return None, None def _get_horizontal_speed(self, name: str, pos: dict, now: float): @@ -450,7 +468,10 @@ def _show_accel_to_admins(self, all_data: dict, now: float) -> None: f"水平Acc§b{show_h_acc:.5f}§r, 垂直Acc§b{show_v_acc:.5f}" ) rawtext = [{"text": " ".join(info_parts)}] - cmd = f"""/titleraw @a[tag=admin] actionbar {{"rawtext":{json.dumps(rawtext)}}}""" + cmd = ( + f"/titleraw @a[tag=admin] actionbar " + f'{{"rawtext":{json.dumps(rawtext)}}}' + ) try: self.game_ctrl.sendwocmd(cmd) except Exception: @@ -474,7 +495,8 @@ def on_pos_data(self, event: InternalBroadcast) -> None: should_show_admin = ( self.cfg["管理员显示加速度"] - and (now - self.last_admin_show_time >= self.cfg["管理员显示间隔_秒"]) + and (now - self.last_admin_show_time + >= self.cfg["管理员显示间隔_秒"]) ) if should_show_admin: self.last_admin_show_time = now @@ -488,7 +510,8 @@ def on_pos_data(self, event: InternalBroadcast) -> None: if should_print: fmts.print_inf( - f" {name}: ({pos['x']:.5f}, {pos['y']:.5f}, {pos['z']:.5f})" + f" {name}: ({pos['x']:.5f}, {pos['y']:.5f}, " + f"{pos['z']:.5f})" ) if self.cfg.get("上升速度检测开关", True): @@ -558,7 +581,6 @@ def _check_acceleration(self, name: str, pos: dict, now: float) -> None: self._rollback(name) return if v_acc > v_threshold: - # 下落区间放行(18~22 m/s² 且垂直速度向下) if 18 < v_acc < 22: vy = self._get_vertical_speed(name, pos, now) if vy < 0: @@ -672,7 +694,6 @@ def _check_groundfly(self, name: str, pos: dict, now: float) -> None: self._rollback(name) return - # 加速度检测 if h_speed > 5.0 and len(recent) >= 4: h_acc = self._calc_horizontal_acceleration(recent) if h_acc > self.cfg["平地飞水平加速度阈值"]: @@ -689,7 +710,8 @@ def _has_large_y_variation(self, recent_points: list) -> bool: variance = sum((y - mean_y) ** 2 for y in ys) / len(ys) return variance ** 0.5 > self.cfg["平地飞Y轴标准差阈值"] - def _calc_horizontal_acceleration(self, recent_points: list) -> float: + @staticmethod + def _calc_horizontal_acceleration(recent_points: list) -> float: """根据最近四个点计算水平加速度。""" mid = len(recent_points) // 2 dt_half = (recent_points[-1][3] - recent_points[0][3]) / 2 @@ -718,7 +740,9 @@ def _check_bounce(self, name: str, pos: dict, now: float) -> None: cycles, avg_amplitude = self._analyze_bounce(recent) if cycles < self.cfg["回弹飞最小波动次数"]: return - if not self.cfg["回弹飞最小波幅"] <= avg_amplitude <= self.cfg["回弹飞最大波幅"]: + if not (self.cfg["回弹飞最小波幅"] + <= avg_amplitude + <= self.cfg["回弹飞最大波幅"]): return h_speed = self._calc_horizontal_speed_from_recent(recent) @@ -731,7 +755,8 @@ def _check_bounce(self, name: str, pos: dict, now: float) -> None: ) self._rollback(name) - def _analyze_bounce(self, points: list) -> tuple: + @staticmethod + def _analyze_bounce(points: list) -> tuple: """分析坐标序列的波峰波谷,返回(完整周期数, 平均振幅)。""" ys = [p[1] for p in points] peaks = troughs = 0 @@ -755,7 +780,8 @@ def _analyze_bounce(self, points: list) -> tuple: return 0, 0.0 return cycles, sum(amplitudes) / len(amplitudes) - def _calc_horizontal_speed_from_recent(self, points: list) -> float: + @staticmethod + def _calc_horizontal_speed_from_recent(points: list) -> float: """从最近的两个点计算水平速度。""" dx = points[-1][0] - points[0][0] dz = points[-1][2] - points[0][2] @@ -774,7 +800,7 @@ def _check_predict_move(self, name: str, pos: dict, now: float) -> None: history = self.pos_history.get(name) if not history or len(history) < 4: return - p0, p1, p2, _ = list(history)[-4:] # 只取前三个点 + p0, p1, p2, _ = list(history)[-4:] dt1 = p1[3] - p0[3] dt2 = p2[3] - p1[3] if dt1 <= 0 or dt2 <= 0: @@ -804,7 +830,10 @@ def _check_predict_move(self, name: str, pos: dict, now: float) -> None: ) speed = math.hypot(avg_vx, avg_vy, avg_vz) - threshold = self.cfg["预测基础误差_米"] + speed * self.cfg["预测速度误差因子"] + threshold = ( + self.cfg["预测基础误差_米"] + + speed * self.cfg["预测速度误差因子"] + ) speed1 = math.hypot(vx1, vy1, vz1) speed2 = math.hypot(vx2, vy2, vz2) max_speed = max(speed1, speed2) From 631bb213102eae4db73afcba50a0dcb314000176 Mon Sep 17 00:00:00 2001 From: mumanMN Date: Sat, 6 Jun 2026 15:54:59 +0800 Subject: [PATCH 6/8] =?UTF-8?q?=E5=8F=8D=E4=BD=9C=E5=BC=8A-=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 不知道怎么修骑马的移动检测qwq --- .../__init__.py" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" "b/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" index 94ec4c70..bd8759e0 100644 --- "a/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" +++ "b/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" @@ -14,7 +14,7 @@ class SimpleAntiCheat(Plugin): name = "反作弊-移动检测(MUNAN)" author = "Assistant" - version = (0, 0, 3) + version = (0, 0, 4) def __init__(self, frame): super().__init__(frame) From e33b4049abd5af9d1661790fad085d7e3a5b2e5d Mon Sep 17 00:00:00 2001 From: mumanMN Date: Sat, 6 Jun 2026 16:08:02 +0800 Subject: [PATCH 7/8] =?UTF-8?q?=E5=8F=8D=E4=BD=9C=E5=BC=8A-=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 骑马会误判不知道怎么修复 --- .../__init__.py" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" "b/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" index bd8759e0..8f515f4c 100644 --- "a/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" +++ "b/\345\217\215\344\275\234\345\274\212-\347\247\273\345\212\250\346\243\200\346\265\213/__init__.py" @@ -861,4 +861,4 @@ def _check_predict_move(self, name: str, pos: dict, now: float) -> None: self._rollback(name) -entry = plugin_entry(SimpleAntiCheat, "反作弊-移动检测") \ No newline at end of file +entry = plugin_entry(SimpleAntiCheat, "反作弊-移动检测") From ed257ee39d1b5ee8d688b120764cfbd9330ba0eb Mon Sep 17 00:00:00 2001 From: mumanMN Date: Sat, 6 Jun 2026 16:08:31 +0800 Subject: [PATCH 8/8] =?UTF-8?q?=E5=8F=8D=E4=BD=9C=E5=BC=8A-=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 骑马会误判不知道怎么修复