diff --git a/auto_bot_move.py b/auto_bot_move.py index b0850ed..b442257 100644 --- a/auto_bot_move.py +++ b/auto_bot_move.py @@ -239,6 +239,7 @@ class AutoBotMove: self, waypoints=None, waypoints_path=None, + prepare_route_path=None, vendor_path=None, attack_loop_path=None, skinning_wait_sec=None, @@ -290,6 +291,19 @@ class AutoBotMove: if waypoints is None: path = waypoints_path or get_config_path(WAYPOINTS_FILE) waypoints = load_waypoints(path) or DEFAULT_WAYPOINTS + self.prepare_route_waypoints = [] + if prepare_route_path: + try: + self.prepare_route_waypoints = [ + (float(point[0]), float(point[1])) + for point in (load_waypoints(prepare_route_path) or []) + ] + except Exception: + self.prepare_route_waypoints = [] + print(f">>> [准备路线] 加载失败,忽略配置: {prepare_route_path}") + self.prepare_route_index = 0 + self.prepare_route_completed = not bool(self.prepare_route_waypoints) + self.is_running_prepare_route = bool(self.prepare_route_waypoints) layout = load_layout_config() self.patrol_controller = CoordinatePatrol( waypoints, @@ -311,6 +325,48 @@ class AutoBotMove: self.logistics_manager.bag_full_hearthstone = bool(layout.get("bag_full_hearthstone", False)) self.logistics_manager.hearthstone_key = str(layout.get("hearthstone_key", "b") or "b") + def _has_prepare_route(self) -> bool: + return bool(self.prepare_route_waypoints) + + def _run_prepare_route_step(self, state): + if self.prepare_route_completed or not self._has_prepare_route(): + self.prepare_route_completed = True + self.is_running_prepare_route = False + return + + if self.prepare_route_index >= len(self.prepare_route_waypoints): + self.prepare_route_completed = True + self.is_running_prepare_route = False + self.is_moving = False + self.patrol_controller.stop_all() + self.patrol_controller.reset_stuck() + print(">>> [准备路线] 准备路线完成,开始正式巡逻...") + return + + self.is_running_prepare_route = True + target_pos = self.prepare_route_waypoints[self.prepare_route_index] + is_arrived = self.patrol_controller.navigate_to_point( + state, + target_pos, + stop_on_arrival=False, + ) + self.is_moving = True + if not is_arrived: + return + + print( + f">>> [准备路线] 到达准备路线点 {self.prepare_route_index + 1}/" + f"{len(self.prepare_route_waypoints)}: {target_pos}" + ) + self.prepare_route_index += 1 + if self.prepare_route_index >= len(self.prepare_route_waypoints): + self.prepare_route_completed = True + self.is_running_prepare_route = False + self.is_moving = False + self.patrol_controller.stop_all() + self.patrol_controller.reset_stuck() + print(">>> [准备路线] 准备路线完成,开始正式巡逻...") + def _is_effective_target(self, state) -> bool: """ 判断当前 `state['target']` 是否是“可攻击的有效目标”。 @@ -772,7 +828,7 @@ class AutoBotMove: current_pos = None if state.get('x') is not None and state.get('y') is not None: current_pos = (state.get('x'), state.get('y')) - if current_pos is not None: + if current_pos is not None and self.prepare_route_completed: self.patrol_controller.snap_to_forward_waypoint(current_pos) # 扫尾动作执行完后,本 tick 强制结束,防止立即按下 Tab @@ -803,12 +859,15 @@ class AutoBotMove: # 进食期间不巡逻、不主动找目标(不按 Tab) return - # 4. 没战斗没目标:巡逻(卡死检测在 patrol_controller.navigate 内) - self.is_moving = True - self.patrol_controller.navigate(state) + # 4. 没战斗没目标:优先执行准备路线,完成后再正式巡逻 + if not self.prepare_route_completed: + self._run_prepare_route_step(state) + else: + self.is_moving = True + self.patrol_controller.navigate(state) # 5. 顺便每隔几秒按一下 Tab(主动找怪) - if not effective_target and (time.time() - self.last_tab_time > 2.0): + if self.prepare_route_completed and not effective_target and (time.time() - self.last_tab_time > 2.0): hw_ctrl.press(KEY_TAB) self.last_tab_time = time.time() diff --git a/coordinate_patrol.py b/coordinate_patrol.py index 1ff9277..02d81e0 100644 --- a/coordinate_patrol.py +++ b/coordinate_patrol.py @@ -278,7 +278,7 @@ class CoordinatePatrol: return - def navigate_to_point(self, state, target_pos, arrival_threshold=None): + def navigate_to_point(self, state, target_pos, arrival_threshold=None, stop_on_arrival=True): """ 优化版:平滑导航,支持边走边转。 """ @@ -309,11 +309,12 @@ class CoordinatePatrol: # 2. 距离判断 dist = self.get_distance(current_pos, target_pos) if dist < threshold: - # 到点后停止前进:松开 W,避免到达后仍持续向前移动 - # (该函数被 death/logistics 等模块用于“到点就交互/停止”。) + # 到点后默认停止前进,供 death/logistics 等“到点就交互/停止”的流程复用。 + # 准备路线可传 stop_on_arrival=False,实现平滑过点。 pydirectinput.keyUp("a") pydirectinput.keyUp("d") - pydirectinput.keyUp("w") + if stop_on_arrival: + pydirectinput.keyUp("w") self.reset_stuck() return True diff --git a/wow_multikey_gui.py b/wow_multikey_gui.py index 05eb0c7..488087f 100644 --- a/wow_multikey_gui.py +++ b/wow_multikey_gui.py @@ -303,6 +303,7 @@ class GameLoopWorker(QThread): self, mode, waypoints_path=None, + prepare_route_path=None, vendor_path=None, record_filename=None, record_min_distance=None, @@ -339,6 +340,7 @@ class GameLoopWorker(QThread): self.flight_bot = None self.recorder = None self.waypoints_path = waypoints_path + self.prepare_route_path = prepare_route_path self.vendor_path = vendor_path self.record_filename = record_filename or 'waypoints' self.record_min_distance = record_min_distance @@ -403,6 +405,7 @@ class GameLoopWorker(QThread): from auto_bot_move import AutoBotMove, load_waypoints self.bot_move = AutoBotMove( waypoints_path=self.waypoints_path, + prepare_route_path=self.prepare_route_path, vendor_path=self.vendor_path, attack_loop_path=self.attack_loop_path, skinning_wait_sec=self.skinning_wait_sec, @@ -722,15 +725,19 @@ class WoWMultiKeyGUI(QMainWindow): self.vendor_combo.setMinimumWidth(200) self.patrol_attack_loop_combo = QComboBox() self.patrol_attack_loop_combo.setMinimumWidth(200) + self.prepare_route_combo = QComboBox() + self.prepare_route_combo.setMinimumWidth(200) self._refresh_recorder_combos() refresh_btn = QPushButton("🔄 刷新列表") refresh_btn.clicked.connect(self._refresh_recorder_combos) + patrol_layout.addRow("准备路线 JSON:", self.prepare_route_combo) patrol_layout.addRow("巡逻点 JSON:", self.waypoints_combo) patrol_layout.addRow("修理商 JSON:", self.vendor_combo) self.resurrection_route_a_combo = QComboBox() self.resurrection_route_a_combo.setMinimumWidth(200) self.resurrection_route_b_combo = QComboBox() self.resurrection_route_b_combo.setMinimumWidth(200) + self._refresh_recorder_combos() patrol_layout.addRow("复活路线 A JSON:", self.resurrection_route_a_combo) patrol_layout.addRow("复活路线 B JSON:", self.resurrection_route_b_combo) patrol_layout.addRow("攻击循环:", self.patrol_attack_loop_combo) @@ -1477,12 +1484,17 @@ class WoWMultiKeyGUI(QMainWindow): combo.setCurrentIndex(1) combo.blockSignals(False) # 复活路线下拉(无默认值,可置空) - for combo_name in ("resurrection_route_a_combo", "resurrection_route_b_combo"): + for combo_name in ( + "prepare_route_combo", + "resurrection_route_a_combo", + "resurrection_route_b_combo", + ): if hasattr(self, combo_name): combo = getattr(self, combo_name) combo.blockSignals(True) combo.clear() - combo.addItem("-- 置空(直接跑尸体) --", "") + empty_text = "-- 置空(直接巡逻) --" if combo_name == "prepare_route_combo" else "-- 置空(直接跑尸体) --" + combo.addItem(empty_text, "") for name, path in items: combo.addItem(name, path) combo.blockSignals(False) @@ -1621,11 +1633,13 @@ class WoWMultiKeyGUI(QMainWindow): QMessageBox.warning(self, "提示", "请先选择模式(状态监控 / 巡逻打怪 / 自动打怪 / 任务跟随 / 飞行模式 / 回城修理)") return waypoints_path = None + prepare_route_path = None vendor_path = None flight_json_path = None resurrection_route_a_path = None resurrection_route_b_path = None if mode == 'patrol': + prep = self.prepare_route_combo.currentData() or "" wp = self.waypoints_combo.currentData() or "" vp = self.vendor_combo.currentData() or "" route_a = self.resurrection_route_a_combo.currentData() or "" @@ -1642,6 +1656,9 @@ class WoWMultiKeyGUI(QMainWindow): if not os.path.exists(vp): QMessageBox.warning(self, "提示", f"修理商文件不存在: {vp}") return + if prep and not os.path.exists(prep): + QMessageBox.warning(self, "提示", f"准备路线文件不存在: {prep}") + return if route_a and not os.path.exists(route_a): QMessageBox.warning(self, "提示", f"复活路线 A 文件不存在: {route_a}") return @@ -1649,6 +1666,7 @@ class WoWMultiKeyGUI(QMainWindow): QMessageBox.warning(self, "提示", f"复活路线 B 文件不存在: {route_b}") return waypoints_path = wp + prepare_route_path = prep or None vendor_path = vp resurrection_route_a_path = route_a or None resurrection_route_b_path = route_b or None @@ -1734,7 +1752,7 @@ class WoWMultiKeyGUI(QMainWindow): use_hardware_input = self.gs_use_hardware_input.isChecked() self.game_worker = GameLoopWorker( - mode, waypoints_path=waypoints_path, vendor_path=vendor_path, + mode, waypoints_path=waypoints_path, prepare_route_path=prepare_route_path, vendor_path=vendor_path, attack_loop_path=attack_loop_path, skinning_wait_sec=skinning_wait_sec, food_key=food_key,