diff --git a/auto_bot_move.py b/auto_bot_move.py index fa25dd2..ffcdbf4 100644 --- a/auto_bot_move.py +++ b/auto_bot_move.py @@ -260,6 +260,8 @@ class AutoBotMove: distance_interact_pause_sec=None, mailbox_route_path=None, skip_prepare_route=False, + patrol_route_switch_laps=0, + patrol_point_offset_radius=0.15, ): self.last_tab_time = 0 self.last_interaction_time = 0 # 记录上一次按互动键的时间 @@ -291,6 +293,14 @@ class AutoBotMove: self._last_mouse_path_scale_signature = None self.logistics_resume_cooldown_until = 0.0 self.skip_prepare_route = bool(skip_prepare_route) + try: + self.patrol_route_switch_laps = max(0, int(patrol_route_switch_laps or 0)) + except (TypeError, ValueError): + self.patrol_route_switch_laps = 0 + try: + self.patrol_point_offset_radius = max(0.0, float(patrol_point_offset_radius or 0.0)) + except (TypeError, ValueError): + self.patrol_point_offset_radius = 0.0 # stop_check: 返回 True 表示需要立即停止(用于中断阻塞中的后勤/路线导航) self._stop_check = stop_check if callable(stop_check) else (lambda: False) @@ -298,6 +308,8 @@ class AutoBotMove: self.route_profile_name = "" self.patrol_routes = [] self.current_patrol_route_name = "" + self.current_patrol_route_index = None + self.current_patrol_route_laps = 0 if route_profile_path: self.route_profile = load_route_profile(route_profile_path) @@ -386,18 +398,27 @@ class AutoBotMove: if self.patrol_routes and self.prepare_route_completed: self._select_random_patrol_route("启动") - def _select_random_patrol_route(self, reason=""): + def _select_random_patrol_route(self, reason="", avoid_current=False): if not self.patrol_routes: return False - route = random.choice(self.patrol_routes) + candidates = list(enumerate(self.patrol_routes)) + if avoid_current and len(candidates) > 1 and self.current_patrol_route_index is not None: + candidates = [ + item for item in candidates + if item[0] != self.current_patrol_route_index + ] + route_index, route = random.choice(candidates) points = list(route.get("points") or []) if not points: return False self.current_patrol_route_name = str(route.get("name") or "未命名巡逻路线") + self.current_patrol_route_index = route_index + self.current_patrol_route_laps = 0 reverse_route = random.choice((False, True)) direction_name = "逆向" if reverse_route else "正向" if reverse_route: points = list(reversed(points)) + points = self._apply_patrol_point_offset(points) self.patrol_controller.waypoints = points self.patrol_controller.current_index = 0 self.patrol_controller.reset_stuck() @@ -408,6 +429,48 @@ class AutoBotMove: ) return True + def _apply_patrol_point_offset(self, points): + radius = self.patrol_point_offset_radius + if radius <= 0: + return [(float(point[0]), float(point[1])) for point in points] + + shifted = [] + for point in points: + x, y = float(point[0]), float(point[1]) + angle = random.uniform(0.0, math.tau) + distance = radius * math.sqrt(random.random()) + shifted.append(( + x + math.cos(angle) * distance, + y + math.sin(angle) * distance, + )) + print(f">>> [巡逻路线] 已为本次路线应用随机点位偏移,半径: {radius:.2f}") + return shifted + + def _maybe_switch_patrol_route_after_lap(self, before_index, after_index): + if self.patrol_route_switch_laps <= 0: + return + waypoints = getattr(self.patrol_controller, "waypoints", None) or [] + if not waypoints: + return + try: + before_index = int(before_index) + after_index = int(after_index) + except (TypeError, ValueError): + return + if before_index != len(waypoints) - 1 or after_index != 0: + return + + self.current_patrol_route_laps += 1 + print( + f">>> [巡逻路线] {self.current_patrol_route_name} " + f"已完成 {self.current_patrol_route_laps}/{self.patrol_route_switch_laps} 圈" + ) + if self.current_patrol_route_laps >= self.patrol_route_switch_laps: + self._select_random_patrol_route( + f"{self.current_patrol_route_name} 跑满 {self.patrol_route_switch_laps} 圈", + avoid_current=True, + ) + def _has_prepare_route(self) -> bool: return bool(self.prepare_route_waypoints) @@ -1021,7 +1084,10 @@ class AutoBotMove: self._run_prepare_route_step(state) else: self.is_moving = True + before_index = self.patrol_controller.current_index self.patrol_controller.navigate(state) + after_index = self.patrol_controller.current_index + self._maybe_switch_patrol_route_after_lap(before_index, after_index) # 5. 顺便每隔几秒按一下 Tab(主动找怪) if self.prepare_route_completed and not effective_target and (time.time() - self.last_tab_time > 2.0): diff --git a/wow_multikey_gui.py b/wow_multikey_gui.py index e97761f..bd35e54 100644 --- a/wow_multikey_gui.py +++ b/wow_multikey_gui.py @@ -392,6 +392,8 @@ class GameLoopWorker(QThread): turn_error_hold_sec=None, distance_interact_pause_sec=None, skip_prepare_route=False, + patrol_route_switch_laps=0, + patrol_point_offset_radius=0.15, ): super().__init__() self.mode = mode # 'monitor' | 'patrol' | 'combat' | 'quest_follow' | 'flight' | 'record' @@ -407,6 +409,14 @@ class GameLoopWorker(QThread): self.vendor_path = vendor_path self.mailbox_route_path = mailbox_route_path self.skip_prepare_route = bool(skip_prepare_route) + try: + self.patrol_route_switch_laps = max(0, int(patrol_route_switch_laps or 0)) + except (TypeError, ValueError): + self.patrol_route_switch_laps = 0 + try: + self.patrol_point_offset_radius = max(0.0, float(patrol_point_offset_radius or 0.0)) + except (TypeError, ValueError): + self.patrol_point_offset_radius = 0.0 self.record_filename = record_filename or 'waypoints' self.record_min_distance = record_min_distance self.attack_loop_path = attack_loop_path or None @@ -499,6 +509,8 @@ class GameLoopWorker(QThread): distance_interact_pause_sec=self.distance_interact_pause_sec, mailbox_route_path=self.mailbox_route_path, skip_prepare_route=self.skip_prepare_route, + patrol_route_switch_laps=self.patrol_route_switch_laps, + patrol_point_offset_radius=self.patrol_point_offset_radius, ) self.bot_move._on_hearthstone_stop = self.stop_signal.emit except ImportError as e: @@ -810,11 +822,25 @@ class WoWMultiKeyGUI(QMainWindow): self.patrol_attack_loop_combo.setMinimumWidth(200) self.skip_prepare_route_check = QCheckBox("跳过准备路线") self.skip_prepare_route_check.setChecked(False) + self.patrol_route_switch_laps_spin = QSpinBox() + self.patrol_route_switch_laps_spin.setRange(0, 999) + self.patrol_route_switch_laps_spin.setValue(0) + self.patrol_route_switch_laps_spin.setSuffix(" 圈") + self.patrol_route_switch_laps_spin.setSpecialValueText("不自动轮换") + self.patrol_point_offset_spin = QDoubleSpinBox() + self.patrol_point_offset_spin.setRange(0.0, 2.0) + self.patrol_point_offset_spin.setDecimals(2) + self.patrol_point_offset_spin.setSingleStep(0.05) + self.patrol_point_offset_spin.setValue(0.15) + self.patrol_point_offset_spin.setSuffix(" 坐标") + self.patrol_point_offset_spin.setSpecialValueText("不偏移") self._refresh_recorder_combos() refresh_btn = QPushButton("🔄 刷新列表") refresh_btn.clicked.connect(self._refresh_recorder_combos) patrol_layout.addRow("路线方案 JSON:", self.route_profile_combo) patrol_layout.addRow("", self.skip_prepare_route_check) + patrol_layout.addRow("巡逻路线轮换:", self.patrol_route_switch_laps_spin) + patrol_layout.addRow("巡逻点偏移半径:", self.patrol_point_offset_spin) patrol_layout.addRow("攻击循环:", self.patrol_attack_loop_combo) patrol_layout.addRow("", refresh_btn) self.patrol_group.setVisible(False) @@ -1814,6 +1840,8 @@ class WoWMultiKeyGUI(QMainWindow): resurrection_route_a_path = None resurrection_route_b_path = None skip_prepare_route = False + patrol_route_switch_laps = 0 + patrol_point_offset_radius = 0.15 if mode == 'patrol': profile_path = self.route_profile_combo.currentData() or "" if not profile_path: @@ -1835,6 +1863,8 @@ class WoWMultiKeyGUI(QMainWindow): return route_profile_path = profile_path skip_prepare_route = self.skip_prepare_route_check.isChecked() + patrol_route_switch_laps = int(self.patrol_route_switch_laps_spin.value()) + patrol_point_offset_radius = float(self.patrol_point_offset_spin.value()) attack_loop_path = None if mode == 'patrol': attack_loop_path = self.patrol_attack_loop_combo.currentData() or None @@ -1945,6 +1975,8 @@ class WoWMultiKeyGUI(QMainWindow): turn_error_hold_sec=turn_error_hold_sec, distance_interact_pause_sec=distance_interact_pause_sec, skip_prepare_route=skip_prepare_route, + patrol_route_switch_laps=patrol_route_switch_laps, + patrol_point_offset_radius=patrol_point_offset_radius, ) self.game_worker.state_signal.connect(self.state_label.setText) self.game_worker.log_signal.connect(self.log)