From 12ce30d2dfca1701ad69cea76d173f22760ad802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E9=B9=8F?= Date: Wed, 29 Apr 2026 21:50:33 +0800 Subject: [PATCH] Support nearest resurrection route selection --- auto_bot_move.py | 7 ++- death_manager.py | 144 ++++++++++++++++++++++++++++++++++---------- wow_multikey_gui.py | 51 +++++++++++----- 3 files changed, 154 insertions(+), 48 deletions(-) diff --git a/auto_bot_move.py b/auto_bot_move.py index 9ce9ed9..b0850ed 100644 --- a/auto_bot_move.py +++ b/auto_bot_move.py @@ -247,6 +247,8 @@ class AutoBotMove: eat_max_wait_sec=None, stop_check=None, resurrection_waypoints_path=None, + resurrection_route_a_path=None, + resurrection_route_b_path=None, release_spirit_key=None, resurrect_key=None, enable_mouse_loot=True, @@ -297,7 +299,10 @@ class AutoBotMove: enable_mount=bool(layout.get("enable_mount", True)), ) self.death_manager = DeathManager( - self.patrol_controller, resurrection_waypoints_path, + self.patrol_controller, + resurrection_waypoints_path=resurrection_waypoints_path, + resurrection_route_a_path=resurrection_route_a_path, + resurrection_route_b_path=resurrection_route_b_path, release_spirit_key=layout.get('release_spirit_key', '9') if release_spirit_key is None else release_spirit_key, resurrect_key=layout.get('resurrect_key', '0') if resurrect_key is None else resurrect_key, ) diff --git a/death_manager.py b/death_manager.py index a754c2f..a6c51fa 100644 --- a/death_manager.py +++ b/death_manager.py @@ -1,82 +1,162 @@ -import time import math +import time + from hardware_control import hw_ctrl class DeathManager: - def __init__(self, patrol_system, resurrection_waypoints_path=None, - release_spirit_key='9', resurrect_key='0'): - self.corpse_pos = None + ROUTE_SELECTION_EPSILON = 0.3 + + def __init__( + self, + patrol_system, + resurrection_waypoints_path=None, + resurrection_route_a_path=None, + resurrection_route_b_path=None, + release_spirit_key='9', + resurrect_key='0', + ): self.patrol_system = patrol_system - self.is_running_to_corpse = False - self._spirit_release_sent = False - self.resurrection_waypoints = self._load_waypoints(resurrection_waypoints_path) - self._resurrection_completed = False self.release_spirit_key = str(release_spirit_key).strip() or '9' self.resurrect_key = str(resurrect_key).strip() or '0' + self.resurrection_route_a = self._load_waypoints( + resurrection_route_a_path or resurrection_waypoints_path + ) + self.resurrection_route_b = self._load_waypoints(resurrection_route_b_path) + + self.corpse_pos = None + self.graveyard_pos = None + self.is_running_to_corpse = False + self.selected_resurrection_route = None + self.selected_resurrection_route_name = None + + self._spirit_release_sent = False + self._route_selected = False + self._resurrection_completed = False def _load_waypoints(self, path): - """加载复活点路线 JSON,可置空。""" + """Load a resurrection route JSON file, allowing empty configuration.""" if not path: return None - import json, os + + import json + import os + if not os.path.exists(path): return None try: with open(path, 'r', encoding='utf-8') as f: data = json.load(f) - if isinstance(data, list) and len(data) > 0: - return [(float(p[0]), float(p[1])) for p in data] + if isinstance(data, list) and data: + return [(float(point[0]), float(point[1])) for point in data] except Exception: pass return None + def _clear_death_run_state(self): + self.graveyard_pos = None + self.selected_resurrection_route = None + self.selected_resurrection_route_name = None + self._route_selected = False + self._resurrection_completed = False + + def _build_resurrection_route_candidates(self): + candidates = [] + if self.resurrection_route_a: + candidates.append(("A", self.resurrection_route_a)) + if self.resurrection_route_b: + candidates.append(("B", self.resurrection_route_b)) + return candidates + + def _record_graveyard_pos(self, state): + if self.graveyard_pos is not None: + return True + + x = state.get('x') + y = state.get('y') + if x is None or y is None: + return False + + self.graveyard_pos = (float(x), float(y)) + print(f">>> [系统] 记录墓地坐标: {self.graveyard_pos}") + return True + + def _choose_resurrection_route(self): + if self.graveyard_pos is None: + return None, None + + candidates = self._build_resurrection_route_candidates() + if not candidates: + return None, None + + best_name = None + best_points = None + best_dist = None + for name, points in candidates: + start_pos = points[0] + dist = math.dist(self.graveyard_pos, start_pos) + print(f">>> [系统] 复活路线{name} 起点距离墓地: {dist:.3f} | 起点: {start_pos}") + if best_dist is None or dist + self.ROUTE_SELECTION_EPSILON < best_dist: + best_name = name + best_points = points + best_dist = dist + return best_name, best_points + def reset_when_alive(self): - """存活时清标志,避免下次死亡无法再次释放灵魂/记录尸体坐标。""" + """Clear death-related state after the character is alive again.""" self._spirit_release_sent = False self.corpse_pos = None self.is_running_to_corpse = False - self._resurrection_completed = False + self._clear_death_run_state() def on_death(self, state): - """1. 死亡瞬间调用:从 player_position 获取坐标并记录""" + """Record corpse coordinates once, then release spirit.""" if self._spirit_release_sent: return + self._spirit_release_sent = True + self._clear_death_run_state() self.corpse_pos = (state['x'], state['y']) self.is_running_to_corpse = True print(f">>> [系统] 记录死亡坐标: {self.corpse_pos},准备释放灵魂...") hw_ctrl.press(self.release_spirit_key) - time.sleep(5) # 等待加载界面 + time.sleep(5) def run_to_corpse(self, state, get_state=None): """ - 2. 跑尸寻路逻辑:坐标与朝向从 player_position 获取。 - - 两阶段逻辑: - - 阶段1:若配置了复活点路线,先跑完该路线 - - 阶段2:路线跑完后(或无路线),跑向尸体 + Run the configured resurrection route first, then navigate back to the corpse. """ if not self.corpse_pos: return - # 阶段1:复活路线未走完 → navigate_path 跑完路线 - if self.resurrection_waypoints and not self._resurrection_completed: - if get_state is None: - return - if self.patrol_system.navigate_path(get_state, self.resurrection_waypoints): - self._resurrection_completed = True - print(">>> 复活路线跑完,开始跑向尸体...") + if not self._record_graveyard_pos(state): + return + + if not self._route_selected: + route_name, route_points = self._choose_resurrection_route() + self.selected_resurrection_route_name = route_name + self.selected_resurrection_route = route_points + self._route_selected = True + if route_name: + print(f">>> [系统] 本次死亡选择复活路线: {route_name}") + else: + print(">>> [系统] 未配置可用复活路线,直接前往尸体...") + + if self.selected_resurrection_route and not self._resurrection_completed: + if get_state is None: + return + if self.patrol_system.navigate_path(get_state, self.selected_resurrection_route): + self._resurrection_completed = True + print( + f">>> [系统] 复活路线{self.selected_resurrection_route_name}跑完,开始前往尸体..." + ) return - # 阶段2:复活路线走完(或无) → 直接跑向尸体 is_arrived = self.patrol_system.navigate_to_point(state, self.corpse_pos) - # 如果距离尸体很近(0.005 约等于 10-20 码) if is_arrived: print(">>> 已到达尸体附近,尝试复活...") hw_ctrl.press(self.resurrect_key) time.sleep(5) - # 检查是否还是灵魂状态,如果是则再按一次复活键 if get_state: new_state = get_state() if new_state and new_state.get('death_state') == 2: @@ -85,4 +165,4 @@ class DeathManager: time.sleep(5) self.is_running_to_corpse = False self.corpse_pos = None - return + self._clear_death_run_state() diff --git a/wow_multikey_gui.py b/wow_multikey_gui.py index 9178674..05eb0c7 100644 --- a/wow_multikey_gui.py +++ b/wow_multikey_gui.py @@ -320,6 +320,8 @@ class GameLoopWorker(QThread): flight_land_key=None, flight_land_hold_sec=None, resurrection_waypoints_path=None, + resurrection_route_a_path=None, + resurrection_route_b_path=None, release_spirit_key=None, resurrect_key=None, enable_mouse_loot=True, @@ -363,6 +365,8 @@ class GameLoopWorker(QThread): self.flight_land_key = flight_land_key self.flight_land_hold_sec = flight_land_hold_sec self.resurrection_waypoints_path = resurrection_waypoints_path + self.resurrection_route_a_path = resurrection_route_a_path or resurrection_waypoints_path + self.resurrection_route_b_path = resurrection_route_b_path self.release_spirit_key = release_spirit_key self.resurrect_key = resurrect_key self.enable_mouse_loot = enable_mouse_loot @@ -407,6 +411,8 @@ class GameLoopWorker(QThread): eat_max_wait_sec=self.eat_max_wait_sec, stop_check=lambda: not self.running, resurrection_waypoints_path=self.resurrection_waypoints_path, + resurrection_route_a_path=self.resurrection_route_a_path, + resurrection_route_b_path=self.resurrection_route_b_path, release_spirit_key=self.release_spirit_key, resurrect_key=self.resurrect_key, enable_mouse_loot=self.enable_mouse_loot, @@ -721,9 +727,12 @@ class WoWMultiKeyGUI(QMainWindow): refresh_btn.clicked.connect(self._refresh_recorder_combos) patrol_layout.addRow("巡逻点 JSON:", self.waypoints_combo) patrol_layout.addRow("修理商 JSON:", self.vendor_combo) - self.resurrection_waypoints_combo = QComboBox() - self.resurrection_waypoints_combo.setMinimumWidth(200) - patrol_layout.addRow("复活点路线 JSON:", self.resurrection_waypoints_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) + 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) patrol_layout.addRow("", refresh_btn) self.patrol_group.setVisible(False) @@ -1467,14 +1476,16 @@ class WoWMultiKeyGUI(QMainWindow): elif combo.count() > 1: combo.setCurrentIndex(1) combo.blockSignals(False) - # 复活点路线下拉(无默认值,可置空) - if hasattr(self, "resurrection_waypoints_combo"): - self.resurrection_waypoints_combo.blockSignals(True) - self.resurrection_waypoints_combo.clear() - self.resurrection_waypoints_combo.addItem("-- 置空(直接跑尸体) --", "") - for name, path in items: - self.resurrection_waypoints_combo.addItem(name, path) - self.resurrection_waypoints_combo.blockSignals(False) + # 复活路线下拉(无默认值,可置空) + for combo_name in ("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("-- 置空(直接跑尸体) --", "") + for name, path in items: + combo.addItem(name, path) + combo.blockSignals(False) def _refresh_repair_vendor_json_combo(self): """刷新“回城修理配置”的修理商下拉框。""" @@ -1612,11 +1623,13 @@ class WoWMultiKeyGUI(QMainWindow): waypoints_path = None vendor_path = None flight_json_path = None - resurrection_waypoints_path = None + resurrection_route_a_path = None + resurrection_route_b_path = None if mode == 'patrol': wp = self.waypoints_combo.currentData() or "" vp = self.vendor_combo.currentData() or "" - rwp = self.resurrection_waypoints_combo.currentData() or "" + route_a = self.resurrection_route_a_combo.currentData() or "" + route_b = self.resurrection_route_b_combo.currentData() or "" if not wp: QMessageBox.warning(self, "提示", "巡逻打怪模式需选择巡逻点 JSON 文件") return @@ -1629,9 +1642,16 @@ class WoWMultiKeyGUI(QMainWindow): if not os.path.exists(vp): QMessageBox.warning(self, "提示", f"修理商文件不存在: {vp}") return + if route_a and not os.path.exists(route_a): + QMessageBox.warning(self, "提示", f"复活路线 A 文件不存在: {route_a}") + return + if route_b and not os.path.exists(route_b): + QMessageBox.warning(self, "提示", f"复活路线 B 文件不存在: {route_b}") + return waypoints_path = wp vendor_path = vp - resurrection_waypoints_path = rwp if rwp and os.path.exists(rwp) else None + resurrection_route_a_path = route_a or None + resurrection_route_b_path = route_b or None attack_loop_path = None if mode == 'patrol': attack_loop_path = self.patrol_attack_loop_combo.currentData() or None @@ -1728,7 +1748,8 @@ class WoWMultiKeyGUI(QMainWindow): flight_takeoff_hold_sec=self.flight_takeoff_hold_spin.value(), flight_land_key=self.flight_land_key_edit.text(), flight_land_hold_sec=self.flight_land_hold_spin.value(), - resurrection_waypoints_path=resurrection_waypoints_path, + resurrection_route_a_path=resurrection_route_a_path, + resurrection_route_b_path=resurrection_route_b_path, release_spirit_key=release_spirit_key, resurrect_key=resurrect_key, enable_mouse_loot=enable_mouse_loot,