diff --git a/__pycache__/auto_bot_move.cpython-311.pyc b/__pycache__/auto_bot_move.cpython-311.pyc index b20f605..656a55b 100644 Binary files a/__pycache__/auto_bot_move.cpython-311.pyc and b/__pycache__/auto_bot_move.cpython-311.pyc differ diff --git a/__pycache__/coordinate_patrol.cpython-311.pyc b/__pycache__/coordinate_patrol.cpython-311.pyc index 94feed4..0264042 100644 Binary files a/__pycache__/coordinate_patrol.cpython-311.pyc and b/__pycache__/coordinate_patrol.cpython-311.pyc differ diff --git a/__pycache__/game_state.cpython-311.pyc b/__pycache__/game_state.cpython-311.pyc index 1de3258..40147b9 100644 Binary files a/__pycache__/game_state.cpython-311.pyc and b/__pycache__/game_state.cpython-311.pyc differ diff --git a/auto_bot_move.py b/auto_bot_move.py index 0ea037e..6ca2d19 100644 --- a/auto_bot_move.py +++ b/auto_bot_move.py @@ -5,7 +5,7 @@ import sys import time import pydirectinput -from game_state import parse_game_state +from game_state import parse_game_state, load_layout_config from coordinate_patrol import CoordinatePatrol from death_manager import DeathManager from logistics_manager import LogisticsManager @@ -91,7 +91,13 @@ class AutoBotMove: if waypoints is None: path = waypoints_path or get_config_path(WAYPOINTS_FILE) waypoints = load_waypoints(path) or DEFAULT_WAYPOINTS - self.patrol_controller = CoordinatePatrol(waypoints) + layout = load_layout_config() + self.patrol_controller = CoordinatePatrol( + waypoints, + mount_key=str(layout.get("mount_key", "x") or "x"), + mount_hold_sec=float(layout.get("mount_hold_sec", 1.6)), + mount_retry_after_sec=float(layout.get("mount_retry_after_sec", 2.0)), + ) self.death_manager = DeathManager(self.patrol_controller) vendor_file = vendor_path or get_config_path('vendor.json') self.logistics_manager = LogisticsManager(vendor_file) diff --git a/coordinate_patrol.py b/coordinate_patrol.py index 278e85f..e14f2f5 100644 --- a/coordinate_patrol.py +++ b/coordinate_patrol.py @@ -15,7 +15,6 @@ ANGLE_DEADZONE_DEG = 5.0 # 死区,此范围内不按 A/D、只按 W,减 # 随机行为:跳跃概率(每帧触发概率,0.005 ≈ 平均 200 帧一次) RANDOM_JUMP_PROB = 0.05 - class CoordinatePatrol: """按航点坐标巡逻,用 pyautogui 按键转向与前进。""" @@ -26,6 +25,9 @@ class CoordinatePatrol: angle_threshold_deg=ANGLE_THRESHOLD_DEG, angle_deadzone_deg=ANGLE_DEADZONE_DEG, random_jump_prob=RANDOM_JUMP_PROB, + mount_key="x", + mount_hold_sec=1.6, + mount_retry_after_sec=2.0, ): """ Args: @@ -33,6 +35,7 @@ class CoordinatePatrol: arrival_threshold: 到达判定距离(游戏坐标单位),默认 0.5 angle_threshold_deg: 朝向容差(度),超过此值才按 A/D 转向,默认 ANGLE_THRESHOLD_DEG angle_deadzone_deg: 转向死区(度),此范围内不按 A/D、只按 W,默认 ANGLE_DEADZONE_DEG + mount_key / mount_hold_sec / mount_retry_after_sec: 未上马时先上马(与 game_state_config / GUI 一致) """ self.waypoints = waypoints self.current_index = 0 @@ -43,6 +46,33 @@ class CoordinatePatrol: self.logger = logging.getLogger(__name__) self.last_turn_end_time = 0 # 最近一次结束 A/D 转向的时间,供卡死检测排除原地转向 self.stuck_handler = StuckHandler() + self._next_mount_allowed = 0.0 + self.mount_key = str(mount_key).strip() or "x" + self.mount_hold_sec = float(mount_hold_sec) + self.mount_retry_after_sec = float(mount_retry_after_sec) + + def _ensure_mounted(self, state): + """ + 已上马返回 True。 + 未上马则松开移动键、按住上马键 MOUNT_HOLD_SEC,本帧不走路;返回 False。 + state 无 mounted 字段时视为无法判断,不拦巡逻(兼容未开 LogicBeacon)。 + """ + if "mounted" not in state: + return True + if state.get("mounted"): + return True + self.stop_all() + now = time.time() + if now < self._next_mount_allowed: + return False + self.logger.info( + f">>> 未上马,先按 {self.mount_key} {self.mount_hold_sec:.1f}s 上马" + ) + pyautogui.keyDown(self.mount_key) + time.sleep(self.mount_hold_sec) + pyautogui.keyUp(self.mount_key) + self._next_mount_allowed = time.time() + self.mount_retry_after_sec + return False @staticmethod def get_distance(p1, p2): @@ -168,6 +198,9 @@ class CoordinatePatrol: self.stop_all() return + if not self._ensure_mounted(state): + return + # 2. 卡死检测:位移检测逻辑保持在巡逻中 if self.stuck_handler.check_stuck(state, self.last_turn_end_time): self.logger.error("!!! 检测到卡死,启动脱困程序 !!!") @@ -250,6 +283,9 @@ class CoordinatePatrol: self.stop_all() return False + if not self._ensure_mounted(state): + return False + # 1. 卡死检测 if self.stuck_handler.check_stuck(state, self.last_turn_end_time): self.stop_all() @@ -311,6 +347,7 @@ class CoordinatePatrol: """ 按 path 依次走完所有点后返回。每次调用都必须传入 path。 get_state: 可调用对象,每次调用返回当前状态 dict,需包含 'x','y','facing'。 + 若含 'mounted'(LogicBeacon),路径开始前会先上马,每段 navigate_to_point 也会校验。 path: [[x,y], ...] 或 [(x,y), ...](如 json.load 得到),本次要走的全部航点。 forward: True=正序(0→n),False=倒序(n→0)。 阻塞直到走完 path 中所有点或出错,走完返回 True。内含卡死检测。 @@ -321,6 +358,17 @@ class CoordinatePatrol: points = [(float(p[0]), float(p[1])) for p in path] if not forward: points = points[::-1] + + # 路径开始前:若 state 含 mounted 且未上马,先完成上马再逐点移动 + while True: + state = get_state() + if state is None: + self.stop_all() + return False + if self._ensure_mounted(state): + break + time.sleep(0.05) + for i, target in enumerate(points): self.logger.info(f">>> 路径点 {i + 1}/{len(points)}: {target}") while True: diff --git a/game_state.py b/game_state.py index d197438..f30f4cd 100644 --- a/game_state.py +++ b/game_state.py @@ -21,6 +21,10 @@ _DEFAULTS = { "scan_region_height": 15, "offset_left": 20, "offset_top": 45, + # 巡逻上马(coordinate_patrol,与 GUI「参数配置」一致) + "mount_key": "x", + "mount_hold_sec": 1.6, + "mount_retry_after_sec": 2.0, } SCREENSHOT_DIR = 'screenshot' diff --git a/game_state_config.json b/game_state_config.json index dc832e1..8da3a81 100644 --- a/game_state_config.json +++ b/game_state_config.json @@ -4,5 +4,8 @@ "scan_region_width": 190, "scan_region_height": 15, "offset_left": 20, - "offset_top": 45 + "offset_top": 45, + "mount_key": "x", + "mount_hold_sec": 1.6, + "mount_retry_after_sec": 2.0 } diff --git a/recorder/地狱火半岛剥皮310-375.json b/recorder/地狱火半岛剥皮310-375.json new file mode 100644 index 0000000..9fcc4ec --- /dev/null +++ b/recorder/地狱火半岛剥皮310-375.json @@ -0,0 +1,142 @@ +[ + [ + 12.1, + 54.99 + ], + [ + 11.62, + 54.79 + ], + [ + 11.16, + 54.54 + ], + [ + 10.82, + 54.13 + ], + [ + 10.54, + 53.7 + ], + [ + 10.3, + 53.24 + ], + [ + 10.06, + 52.75 + ], + [ + 9.82, + 52.31 + ], + [ + 9.55, + 51.86 + ], + [ + 9.42, + 51.35 + ], + [ + 9.39, + 50.83 + ], + [ + 9.24, + 50.35 + ], + [ + 9.15, + 49.85 + ], + [ + 9.5, + 49.44 + ], + [ + 9.94, + 49.14 + ], + [ + 10.38, + 48.85 + ], + [ + 10.81, + 48.56 + ], + [ + 11.23, + 48.26 + ], + [ + 11.65, + 47.94 + ], + [ + 12.11, + 47.65 + ], + [ + 12.59, + 47.49 + ], + [ + 12.54, + 48.01 + ], + [ + 12.44, + 48.53 + ], + [ + 12.32, + 49.03 + ], + [ + 12.2, + 49.54 + ], + [ + 12.1, + 50.07 + ], + [ + 12.1, + 50.6 + ], + [ + 12.33, + 51.1 + ], + [ + 12.55, + 51.59 + ], + [ + 12.68, + 52.11 + ], + [ + 12.78, + 52.62 + ], + [ + 12.86, + 53.12 + ], + [ + 12.83, + 53.65 + ], + [ + 12.76, + 54.17 + ], + [ + 12.1, + 54.99 + ] +] \ No newline at end of file diff --git a/screenshot/game_state.png b/screenshot/game_state.png index bb19555..f0d9946 100644 Binary files a/screenshot/game_state.png and b/screenshot/game_state.png differ diff --git a/screenshot/game_state_combat.png b/screenshot/game_state_combat.png index 2da3e80..0359d24 100644 Binary files a/screenshot/game_state_combat.png and b/screenshot/game_state_combat.png differ diff --git a/screenshot/game_state_flight_block.png b/screenshot/game_state_flight_block.png index 2da3e80..231f311 100644 Binary files a/screenshot/game_state_flight_block.png and b/screenshot/game_state_flight_block.png differ diff --git a/screenshot/game_state_follow.png b/screenshot/game_state_follow.png index 2da3e80..3f5cb77 100644 Binary files a/screenshot/game_state_follow.png and b/screenshot/game_state_follow.png differ diff --git a/screenshot/game_state_hp.png b/screenshot/game_state_hp.png index 2da3e80..2d0c1fa 100644 Binary files a/screenshot/game_state_hp.png and b/screenshot/game_state_hp.png differ diff --git a/screenshot/game_state_logistics_death.png b/screenshot/game_state_logistics_death.png index 2da3e80..89e6979 100644 Binary files a/screenshot/game_state_logistics_death.png and b/screenshot/game_state_logistics_death.png differ diff --git a/screenshot/game_state_mp.png b/screenshot/game_state_mp.png index 2da3e80..89e3074 100644 Binary files a/screenshot/game_state_mp.png and b/screenshot/game_state_mp.png differ diff --git a/screenshot/game_state_target.png b/screenshot/game_state_target.png index 2da3e80..704ad58 100644 Binary files a/screenshot/game_state_target.png and b/screenshot/game_state_target.png differ diff --git a/screenshot/game_state_x.png b/screenshot/game_state_x.png index 2da3e80..d1b2d16 100644 Binary files a/screenshot/game_state_x.png and b/screenshot/game_state_x.png differ diff --git a/screenshot/game_state_y.png b/screenshot/game_state_y.png index 2da3e80..5b8c1f9 100644 Binary files a/screenshot/game_state_y.png and b/screenshot/game_state_y.png differ diff --git a/stuck_handler.py b/stuck_handler.py index 0bc5301..6ca69e3 100644 --- a/stuck_handler.py +++ b/stuck_handler.py @@ -69,11 +69,11 @@ class StuckHandler: pyautogui.keyDown(turn_key) # 倒车时间稍长一点,离开障碍物 - time.sleep(0.3) + time.sleep(0.5) # 3. 尝试跳跃脱离地形卡位 pyautogui.press("space") - time.sleep(0.3) + time.sleep(0.5) pyautogui.keyUp(turn_key) pyautogui.keyUp("s") diff --git a/wow_multikey_gui.py b/wow_multikey_gui.py index 21f24df..825437a 100644 --- a/wow_multikey_gui.py +++ b/wow_multikey_gui.py @@ -746,6 +746,24 @@ class WoWMultiKeyGUI(QMainWindow): self.skinning_wait_spin.setSuffix(" 秒") params_right.addRow("剥皮等待时间:", self.skinning_wait_spin) + self.gs_mount_key = QLineEdit() + self.gs_mount_key.setPlaceholderText("如 x") + self.gs_mount_key.setMaxLength(16) + self.gs_mount_key.setText("x") + self.gs_mount_hold = QDoubleSpinBox() + self.gs_mount_hold.setRange(0.1, 30.0) + self.gs_mount_hold.setSingleStep(0.1) + self.gs_mount_hold.setValue(1.6) + self.gs_mount_hold.setSuffix(" 秒") + self.gs_mount_retry = QDoubleSpinBox() + self.gs_mount_retry.setRange(0.0, 60.0) + self.gs_mount_retry.setSingleStep(0.1) + self.gs_mount_retry.setValue(2.0) + self.gs_mount_retry.setSuffix(" 秒") + params_right.addRow("上马按键 (mount_key):", self.gs_mount_key) + params_right.addRow("上马按住时长 (mount_hold_sec):", self.gs_mount_hold) + params_right.addRow("上马重试间隔 (mount_retry_after_sec):", self.gs_mount_retry) + params_row.addLayout(params_left) params_row.addLayout(params_right) params_layout.addWidget(params_content) @@ -780,6 +798,9 @@ class WoWMultiKeyGUI(QMainWindow): self.gs_scan_height.setValue(cfg.get('scan_region_height', 15)) self.gs_offset_left.setValue(cfg.get('offset_left', 20)) self.gs_offset_top.setValue(cfg.get('offset_top', 45)) + self.gs_mount_key.setText(str(cfg.get('mount_key', 'x') or 'x')) + self.gs_mount_hold.setValue(float(cfg.get('mount_hold_sec', 1.6))) + self.gs_mount_retry.setValue(float(cfg.get('mount_retry_after_sec', 2.0))) except Exception as e: self.log(f"加载参数配置失败: {e}") try: @@ -800,6 +821,9 @@ class WoWMultiKeyGUI(QMainWindow): cfg['scan_region_height'] = self.gs_scan_height.value() cfg['offset_left'] = self.gs_offset_left.value() cfg['offset_top'] = self.gs_offset_top.value() + cfg['mount_key'] = (self.gs_mount_key.text().strip() or 'x') + cfg['mount_hold_sec'] = float(self.gs_mount_hold.value()) + cfg['mount_retry_after_sec'] = float(self.gs_mount_retry.value()) path = save_layout_config(cfg) # bot 参数写入主配置文件 self.config = self.config or {}