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