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