Add route profile patrol selection
This commit is contained in:
@@ -14,6 +14,7 @@ from game_state import parse_game_state, load_layout_config
|
|||||||
from coordinate_patrol import CoordinatePatrol
|
from coordinate_patrol import CoordinatePatrol
|
||||||
from death_manager import DeathManager
|
from death_manager import DeathManager
|
||||||
from logistics_manager import LogisticsManager
|
from logistics_manager import LogisticsManager
|
||||||
|
from route_profile import load_route_profile
|
||||||
|
|
||||||
# 定义按键常量
|
# 定义按键常量
|
||||||
KEY_TAB = '3'
|
KEY_TAB = '3'
|
||||||
@@ -239,6 +240,7 @@ class AutoBotMove:
|
|||||||
self,
|
self,
|
||||||
waypoints=None,
|
waypoints=None,
|
||||||
waypoints_path=None,
|
waypoints_path=None,
|
||||||
|
route_profile_path=None,
|
||||||
prepare_route_path=None,
|
prepare_route_path=None,
|
||||||
vendor_path=None,
|
vendor_path=None,
|
||||||
attack_loop_path=None,
|
attack_loop_path=None,
|
||||||
@@ -289,11 +291,35 @@ class AutoBotMove:
|
|||||||
self.logistics_resume_cooldown_until = 0.0
|
self.logistics_resume_cooldown_until = 0.0
|
||||||
# stop_check: 返回 True 表示需要立即停止(用于中断阻塞中的后勤/路线导航)
|
# stop_check: 返回 True 表示需要立即停止(用于中断阻塞中的后勤/路线导航)
|
||||||
self._stop_check = stop_check if callable(stop_check) else (lambda: False)
|
self._stop_check = stop_check if callable(stop_check) else (lambda: False)
|
||||||
if waypoints is None:
|
|
||||||
|
self.route_profile = None
|
||||||
|
self.route_profile_name = ""
|
||||||
|
self.patrol_routes = []
|
||||||
|
self.current_patrol_route_name = ""
|
||||||
|
|
||||||
|
if route_profile_path:
|
||||||
|
self.route_profile = load_route_profile(route_profile_path)
|
||||||
|
self.route_profile_name = self.route_profile.get("name") or ""
|
||||||
|
self.patrol_routes = list(self.route_profile.get("patrol_routes") or [])
|
||||||
|
if self.patrol_routes:
|
||||||
|
waypoints = self.patrol_routes[0]["points"]
|
||||||
|
prepare_route = self.route_profile.get("prepare_route")
|
||||||
|
if prepare_route:
|
||||||
|
self.prepare_route_waypoints = list(prepare_route["points"])
|
||||||
|
else:
|
||||||
|
self.prepare_route_waypoints = []
|
||||||
|
print(
|
||||||
|
f">>> [路线方案] 已加载: {self.route_profile_name},"
|
||||||
|
f"巡逻路线 {len(self.patrol_routes)} 条"
|
||||||
|
)
|
||||||
|
elif 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 = []
|
self.prepare_route_waypoints = []
|
||||||
if prepare_route_path:
|
else:
|
||||||
|
self.prepare_route_waypoints = []
|
||||||
|
|
||||||
|
if (not route_profile_path) and prepare_route_path:
|
||||||
try:
|
try:
|
||||||
self.prepare_route_waypoints = [
|
self.prepare_route_waypoints = [
|
||||||
(float(point[0]), float(point[1]))
|
(float(point[0]), float(point[1]))
|
||||||
@@ -320,11 +346,17 @@ class AutoBotMove:
|
|||||||
resurrection_waypoints_path=resurrection_waypoints_path,
|
resurrection_waypoints_path=resurrection_waypoints_path,
|
||||||
resurrection_route_a_path=resurrection_route_a_path,
|
resurrection_route_a_path=resurrection_route_a_path,
|
||||||
resurrection_route_b_path=resurrection_route_b_path,
|
resurrection_route_b_path=resurrection_route_b_path,
|
||||||
|
resurrection_routes=(
|
||||||
|
self.route_profile.get("resurrection_routes") if self.route_profile else None
|
||||||
|
),
|
||||||
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,
|
||||||
)
|
)
|
||||||
vendor_file = vendor_path or get_config_path('vendor.json')
|
vendor_file = vendor_path or get_config_path('vendor.json')
|
||||||
self.logistics_manager = LogisticsManager(vendor_file)
|
self.logistics_manager = LogisticsManager(vendor_file)
|
||||||
|
if self.route_profile:
|
||||||
|
self.logistics_manager.set_repair_route(self.route_profile.get("vendor_route"))
|
||||||
|
self.logistics_manager.set_mailbox_route(self.route_profile.get("mailbox_route"))
|
||||||
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")
|
||||||
self.logistics_manager.hearthstone_cast_sec = float(layout.get("hearthstone_wait_sec", 12.0))
|
self.logistics_manager.hearthstone_cast_sec = float(layout.get("hearthstone_wait_sec", 12.0))
|
||||||
@@ -345,6 +377,31 @@ class AutoBotMove:
|
|||||||
self.logistics_manager.mailbox_open_wait_sec = float(layout.get("mailbox_open_wait_sec", 2.0))
|
self.logistics_manager.mailbox_open_wait_sec = float(layout.get("mailbox_open_wait_sec", 2.0))
|
||||||
self.logistics_manager.mail_send_wait_sec = float(layout.get("mail_send_wait_sec", 60.0))
|
self.logistics_manager.mail_send_wait_sec = float(layout.get("mail_send_wait_sec", 60.0))
|
||||||
|
|
||||||
|
if self.patrol_routes and self.prepare_route_completed:
|
||||||
|
self._select_random_patrol_route("启动")
|
||||||
|
|
||||||
|
def _select_random_patrol_route(self, reason=""):
|
||||||
|
if not self.patrol_routes:
|
||||||
|
return False
|
||||||
|
route = random.choice(self.patrol_routes)
|
||||||
|
points = list(route.get("points") or [])
|
||||||
|
if not points:
|
||||||
|
return False
|
||||||
|
self.current_patrol_route_name = str(route.get("name") or "未命名巡逻路线")
|
||||||
|
reverse_route = random.choice((False, True))
|
||||||
|
direction_name = "逆向" if reverse_route else "正向"
|
||||||
|
if reverse_route:
|
||||||
|
points = list(reversed(points))
|
||||||
|
self.patrol_controller.waypoints = points
|
||||||
|
self.patrol_controller.current_index = 0
|
||||||
|
self.patrol_controller.reset_stuck()
|
||||||
|
prefix = f"{reason}," if reason else ""
|
||||||
|
print(
|
||||||
|
f">>> [巡逻路线] {prefix}随机选择: "
|
||||||
|
f"{self.current_patrol_route_name},方向: {direction_name}({len(points)} 点)"
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
def _has_prepare_route(self) -> bool:
|
def _has_prepare_route(self) -> bool:
|
||||||
return bool(self.prepare_route_waypoints)
|
return bool(self.prepare_route_waypoints)
|
||||||
|
|
||||||
@@ -364,6 +421,7 @@ class AutoBotMove:
|
|||||||
if self.prepare_route_waypoints:
|
if self.prepare_route_waypoints:
|
||||||
print(">>> [后勤] 全自动续跑:重置准备路线,准备返回巡逻。")
|
print(">>> [后勤] 全自动续跑:重置准备路线,准备返回巡逻。")
|
||||||
else:
|
else:
|
||||||
|
self._select_random_patrol_route("后勤返回")
|
||||||
print(">>> [后勤] 全自动续跑:未配置准备路线,直接恢复巡逻。")
|
print(">>> [后勤] 全自动续跑:未配置准备路线,直接恢复巡逻。")
|
||||||
|
|
||||||
def _snap_prepare_route_to_nearest(self, state):
|
def _snap_prepare_route_to_nearest(self, state):
|
||||||
@@ -418,6 +476,7 @@ class AutoBotMove:
|
|||||||
self.is_moving = False
|
self.is_moving = False
|
||||||
self.patrol_controller.stop_all()
|
self.patrol_controller.stop_all()
|
||||||
self.patrol_controller.reset_stuck()
|
self.patrol_controller.reset_stuck()
|
||||||
|
self._select_random_patrol_route("准备路线完成")
|
||||||
print(">>> [准备路线] 准备路线完成,开始正式巡逻...")
|
print(">>> [准备路线] 准备路线完成,开始正式巡逻...")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -443,6 +502,7 @@ class AutoBotMove:
|
|||||||
self.is_moving = False
|
self.is_moving = False
|
||||||
self.patrol_controller.stop_all()
|
self.patrol_controller.stop_all()
|
||||||
self.patrol_controller.reset_stuck()
|
self.patrol_controller.reset_stuck()
|
||||||
|
self._select_random_patrol_route("准备路线完成")
|
||||||
print(">>> [准备路线] 准备路线完成,开始正式巡逻...")
|
print(">>> [准备路线] 准备路线完成,开始正式巡逻...")
|
||||||
|
|
||||||
def _is_effective_target(self, state) -> bool:
|
def _is_effective_target(self, state) -> bool:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import math
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from hardware_control import hw_ctrl
|
from hardware_control import hw_ctrl
|
||||||
|
from route_profile import normalize_route_list
|
||||||
|
|
||||||
|
|
||||||
class DeathManager:
|
class DeathManager:
|
||||||
@@ -13,16 +14,19 @@ class DeathManager:
|
|||||||
resurrection_waypoints_path=None,
|
resurrection_waypoints_path=None,
|
||||||
resurrection_route_a_path=None,
|
resurrection_route_a_path=None,
|
||||||
resurrection_route_b_path=None,
|
resurrection_route_b_path=None,
|
||||||
|
resurrection_routes=None,
|
||||||
release_spirit_key='9',
|
release_spirit_key='9',
|
||||||
resurrect_key='0',
|
resurrect_key='0',
|
||||||
):
|
):
|
||||||
self.patrol_system = patrol_system
|
self.patrol_system = patrol_system
|
||||||
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(
|
self.resurrection_routes = self._load_resurrection_routes(
|
||||||
resurrection_route_a_path or resurrection_waypoints_path
|
resurrection_routes,
|
||||||
|
resurrection_waypoints_path=resurrection_waypoints_path,
|
||||||
|
resurrection_route_a_path=resurrection_route_a_path,
|
||||||
|
resurrection_route_b_path=resurrection_route_b_path,
|
||||||
)
|
)
|
||||||
self.resurrection_route_b = self._load_waypoints(resurrection_route_b_path)
|
|
||||||
|
|
||||||
self.corpse_pos = None
|
self.corpse_pos = None
|
||||||
self.graveyard_pos = None
|
self.graveyard_pos = None
|
||||||
@@ -53,6 +57,29 @@ class DeathManager:
|
|||||||
pass
|
pass
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _load_resurrection_routes(
|
||||||
|
self,
|
||||||
|
resurrection_routes=None,
|
||||||
|
resurrection_waypoints_path=None,
|
||||||
|
resurrection_route_a_path=None,
|
||||||
|
resurrection_route_b_path=None,
|
||||||
|
):
|
||||||
|
routes = []
|
||||||
|
for route in normalize_route_list(resurrection_routes, "复活路线"):
|
||||||
|
routes.append((route["name"], route["points"]))
|
||||||
|
if routes:
|
||||||
|
return routes
|
||||||
|
|
||||||
|
legacy_routes = [
|
||||||
|
("A", resurrection_route_a_path or resurrection_waypoints_path),
|
||||||
|
("B", resurrection_route_b_path),
|
||||||
|
]
|
||||||
|
for name, path in legacy_routes:
|
||||||
|
points = self._load_waypoints(path)
|
||||||
|
if points:
|
||||||
|
routes.append((name, points))
|
||||||
|
return routes
|
||||||
|
|
||||||
def _clear_death_run_state(self):
|
def _clear_death_run_state(self):
|
||||||
self.graveyard_pos = None
|
self.graveyard_pos = None
|
||||||
self.selected_resurrection_route = None
|
self.selected_resurrection_route = None
|
||||||
@@ -61,12 +88,7 @@ class DeathManager:
|
|||||||
self._resurrection_completed = False
|
self._resurrection_completed = False
|
||||||
|
|
||||||
def _build_resurrection_route_candidates(self):
|
def _build_resurrection_route_candidates(self):
|
||||||
candidates = []
|
return list(self.resurrection_routes)
|
||||||
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):
|
def _record_graveyard_pos(self, state):
|
||||||
if self.graveyard_pos is not None:
|
if self.graveyard_pos is not None:
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import math
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from hardware_control import hw_ctrl
|
from hardware_control import hw_ctrl
|
||||||
|
from route_profile import normalize_points
|
||||||
|
|
||||||
# 修理商所在位置(游戏坐标),按实际位置修改
|
# 修理商所在位置(游戏坐标),按实际位置修改
|
||||||
VENDOR_POS = (30.08, 71.51)
|
VENDOR_POS = (30.08, 71.51)
|
||||||
@@ -18,6 +19,8 @@ class LogisticsManager:
|
|||||||
self.bag_full = False
|
self.bag_full = False
|
||||||
self.is_returning = False
|
self.is_returning = False
|
||||||
self.route_file = route_file or VENDOR_FILE
|
self.route_file = route_file or VENDOR_FILE
|
||||||
|
self.route_points = []
|
||||||
|
self.route_name = ""
|
||||||
self.bag_full_hearthstone = False # 包满时用炉石回城而非走路修理
|
self.bag_full_hearthstone = False # 包满时用炉石回城而非走路修理
|
||||||
self.hearthstone_key = "b" # 炉石按键
|
self.hearthstone_key = "b" # 炉石按键
|
||||||
self.hearthstone_cast_sec = 12.0 # 炉石施法等待秒数
|
self.hearthstone_cast_sec = 12.0 # 炉石施法等待秒数
|
||||||
@@ -26,6 +29,8 @@ class LogisticsManager:
|
|||||||
self.bag_slot_threshold = 2
|
self.bag_slot_threshold = 2
|
||||||
self.durability_threshold = 0.2
|
self.durability_threshold = 0.2
|
||||||
self.mailbox_route_file = os.path.join("recorder", "mailbox.json")
|
self.mailbox_route_file = os.path.join("recorder", "mailbox.json")
|
||||||
|
self.mailbox_route_points = []
|
||||||
|
self.mailbox_route_name = ""
|
||||||
self.mailbox_interact_key = "8"
|
self.mailbox_interact_key = "8"
|
||||||
self.mail_recipient_key = ""
|
self.mail_recipient_key = ""
|
||||||
self.mail_send_key = "f8"
|
self.mail_send_key = "f8"
|
||||||
@@ -46,6 +51,35 @@ class LogisticsManager:
|
|||||||
return cwd_path
|
return cwd_path
|
||||||
return os.path.join(os.path.dirname(os.path.abspath(__file__)), path)
|
return os.path.join(os.path.dirname(os.path.abspath(__file__)), path)
|
||||||
|
|
||||||
|
def set_repair_route(self, route):
|
||||||
|
route = route or {}
|
||||||
|
self.route_name = str(route.get("name") or "修理商路线").strip()
|
||||||
|
self.route_points = normalize_points(route.get("points") or [])
|
||||||
|
|
||||||
|
def set_mailbox_route(self, route):
|
||||||
|
route = route or {}
|
||||||
|
self.mailbox_route_name = str(route.get("name") or "邮箱路线").strip()
|
||||||
|
self.mailbox_route_points = normalize_points(route.get("points") or [])
|
||||||
|
|
||||||
|
def _load_configured_route(self, configured_points, route_file, source_name, label):
|
||||||
|
points = normalize_points(configured_points or [])
|
||||||
|
if points:
|
||||||
|
return points, source_name or label
|
||||||
|
|
||||||
|
resolved_file = self._resolve_path(route_file)
|
||||||
|
if not resolved_file or not os.path.exists(resolved_file):
|
||||||
|
return None, resolved_file
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(resolved_file, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
except Exception as exc:
|
||||||
|
print(f">>> [{label}] 路线读取失败: {exc}")
|
||||||
|
return None, resolved_file
|
||||||
|
|
||||||
|
points = normalize_points(data)
|
||||||
|
return points, resolved_file
|
||||||
|
|
||||||
def _sleep_with_stop(self, seconds, stop_check=None):
|
def _sleep_with_stop(self, seconds, stop_check=None):
|
||||||
end_at = time.time() + max(0.0, float(seconds))
|
end_at = time.time() + max(0.0, float(seconds))
|
||||||
while time.time() < end_at:
|
while time.time() < end_at:
|
||||||
@@ -199,9 +233,14 @@ class LogisticsManager:
|
|||||||
炉石 -> 跑到邮箱路线终点 -> 交互打开邮箱 -> 按 MailboxCourier 宏键 -> 等待后停止。
|
炉石 -> 跑到邮箱路线终点 -> 交互打开邮箱 -> 按 MailboxCourier 宏键 -> 等待后停止。
|
||||||
Python 不判断邮件是否发完,发送细节交给游戏内插件。
|
Python 不判断邮件是否发完,发送细节交给游戏内插件。
|
||||||
"""
|
"""
|
||||||
route_file = self._resolve_path(self.mailbox_route_file)
|
path, route_source = self._load_configured_route(
|
||||||
if not route_file or not os.path.exists(route_file):
|
self.mailbox_route_points,
|
||||||
print(f">>> [后勤-邮箱] 邮箱路线不存在,已停止: {route_file}")
|
self.mailbox_route_file,
|
||||||
|
self.mailbox_route_name,
|
||||||
|
"后勤-邮箱",
|
||||||
|
)
|
||||||
|
if not path:
|
||||||
|
print(f">>> [后勤-邮箱] 邮箱路线不存在或为空,已停止: {route_source}")
|
||||||
self.is_returning = False
|
self.is_returning = False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -216,20 +255,7 @@ class LogisticsManager:
|
|||||||
self.is_returning = False
|
self.is_returning = False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
print(f">>> [后勤-邮箱] 开始跑邮箱路线: {route_source}")
|
||||||
with open(route_file, "r", encoding="utf-8") as f:
|
|
||||||
path = json.load(f)
|
|
||||||
except Exception as exc:
|
|
||||||
print(f">>> [后勤-邮箱] 邮箱路线读取失败: {exc}")
|
|
||||||
self.is_returning = False
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not path:
|
|
||||||
print(f">>> [后勤-邮箱] 邮箱路线为空,已停止: {route_file}")
|
|
||||||
self.is_returning = False
|
|
||||||
return False
|
|
||||||
|
|
||||||
print(f">>> [后勤-邮箱] 开始跑邮箱路线: {route_file}")
|
|
||||||
old_enable_mount = getattr(patrol, "enable_mount", None)
|
old_enable_mount = getattr(patrol, "enable_mount", None)
|
||||||
if old_enable_mount is not None:
|
if old_enable_mount is not None:
|
||||||
patrol.enable_mount = False
|
patrol.enable_mount = False
|
||||||
@@ -282,9 +308,14 @@ class LogisticsManager:
|
|||||||
耐久低修理流程:
|
耐久低修理流程:
|
||||||
炉石 -> 从修理路线最近点接入 -> 到达 NPC -> 按目标键 -> 按交互/修理键。
|
炉石 -> 从修理路线最近点接入 -> 到达 NPC -> 按目标键 -> 按交互/修理键。
|
||||||
"""
|
"""
|
||||||
route_file = self._resolve_path(self.route_file)
|
path, route_source = self._load_configured_route(
|
||||||
if not route_file or not os.path.exists(route_file):
|
self.route_points,
|
||||||
print(f">>> [后勤-修理] 修理路线不存在,已停止: {route_file}")
|
self.route_file,
|
||||||
|
self.route_name,
|
||||||
|
"后勤-修理",
|
||||||
|
)
|
||||||
|
if not path:
|
||||||
|
print(f">>> [后勤-修理] 修理路线不存在或为空,已停止: {route_source}")
|
||||||
self.is_returning = False
|
self.is_returning = False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -299,20 +330,7 @@ class LogisticsManager:
|
|||||||
self.is_returning = False
|
self.is_returning = False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
print(f">>> [后勤-修理] 开始跑修理路线: {route_source}")
|
||||||
with open(route_file, "r", encoding="utf-8") as f:
|
|
||||||
path = json.load(f)
|
|
||||||
except Exception as exc:
|
|
||||||
print(f">>> [后勤-修理] 修理路线读取失败: {exc}")
|
|
||||||
self.is_returning = False
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not path:
|
|
||||||
print(f">>> [后勤-修理] 修理路线为空,已停止: {route_file}")
|
|
||||||
self.is_returning = False
|
|
||||||
return False
|
|
||||||
|
|
||||||
print(f">>> [后勤-修理] 开始跑修理路线: {route_file}")
|
|
||||||
ok = patrol.navigate_path(
|
ok = patrol.navigate_path(
|
||||||
get_state,
|
get_state,
|
||||||
path,
|
path,
|
||||||
|
|||||||
1079
recorder/祖达克刷怪方案.json
Normal file
1079
recorder/祖达克刷怪方案.json
Normal file
File diff suppressed because it is too large
Load Diff
103
route_profile.py
Normal file
103
route_profile.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_points(raw_points):
|
||||||
|
"""Return route points as [(x, y), ...], dropping malformed entries."""
|
||||||
|
points = []
|
||||||
|
if not isinstance(raw_points, list):
|
||||||
|
return points
|
||||||
|
|
||||||
|
for point in raw_points:
|
||||||
|
if not isinstance(point, (list, tuple)) or len(point) < 2:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
points.append((float(point[0]), float(point[1])))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
continue
|
||||||
|
return points
|
||||||
|
|
||||||
|
|
||||||
|
def _looks_like_points(raw_value):
|
||||||
|
if not isinstance(raw_value, list) or not raw_value:
|
||||||
|
return False
|
||||||
|
first = raw_value[0]
|
||||||
|
if not isinstance(first, (list, tuple)) or len(first) < 2:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
float(first[0])
|
||||||
|
float(first[1])
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_route(raw_route, fallback_name=""):
|
||||||
|
"""Normalize {"name": ..., "points": ...} into a route dict."""
|
||||||
|
if raw_route is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if isinstance(raw_route, dict):
|
||||||
|
name = str(raw_route.get("name") or fallback_name or "未命名路线").strip()
|
||||||
|
raw_points = (
|
||||||
|
raw_route.get("points")
|
||||||
|
or raw_route.get("waypoints")
|
||||||
|
or raw_route.get("path")
|
||||||
|
or []
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
name = str(fallback_name or "未命名路线").strip()
|
||||||
|
raw_points = raw_route
|
||||||
|
|
||||||
|
points = normalize_points(raw_points)
|
||||||
|
if not points:
|
||||||
|
return None
|
||||||
|
return {"name": name or fallback_name or "未命名路线", "points": points}
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_route_list(raw_routes, fallback_prefix):
|
||||||
|
routes = []
|
||||||
|
if raw_routes is None:
|
||||||
|
return routes
|
||||||
|
|
||||||
|
if _looks_like_points(raw_routes):
|
||||||
|
route = normalize_route(raw_routes, fallback_prefix)
|
||||||
|
return [route] if route else []
|
||||||
|
|
||||||
|
if isinstance(raw_routes, dict):
|
||||||
|
raw_routes = raw_routes.get("routes") or raw_routes.get("items") or []
|
||||||
|
|
||||||
|
if not isinstance(raw_routes, list):
|
||||||
|
return routes
|
||||||
|
|
||||||
|
for idx, raw_route in enumerate(raw_routes, start=1):
|
||||||
|
route = normalize_route(raw_route, f"{fallback_prefix}{idx}")
|
||||||
|
if route:
|
||||||
|
routes.append(route)
|
||||||
|
return routes
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_route_profile(data, fallback_name=""):
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
raise ValueError("路线方案 JSON 必须是对象格式")
|
||||||
|
|
||||||
|
name = str(data.get("name") or fallback_name or "未命名方案").strip()
|
||||||
|
patrol_routes = normalize_route_list(data.get("patrol_routes"), "巡逻路线")
|
||||||
|
if not patrol_routes:
|
||||||
|
raise ValueError("路线方案必须包含至少一条 patrol_routes")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"name": name or fallback_name or "未命名方案",
|
||||||
|
"prepare_route": normalize_route(data.get("prepare_route"), "准备路线"),
|
||||||
|
"patrol_routes": patrol_routes,
|
||||||
|
"resurrection_routes": normalize_route_list(data.get("resurrection_routes"), "复活路线"),
|
||||||
|
"vendor_route": normalize_route(data.get("vendor_route"), "修理商路线"),
|
||||||
|
"mailbox_route": normalize_route(data.get("mailbox_route"), "邮箱路线"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def load_route_profile(path):
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
fallback_name = os.path.splitext(os.path.basename(path))[0]
|
||||||
|
return normalize_route_profile(data, fallback_name=fallback_name)
|
||||||
@@ -23,6 +23,7 @@ import win32gui
|
|||||||
import win32api
|
import win32api
|
||||||
import win32con
|
import win32con
|
||||||
from pynput import keyboard
|
from pynput import keyboard
|
||||||
|
from route_profile import load_route_profile
|
||||||
|
|
||||||
|
|
||||||
def _config_base():
|
def _config_base():
|
||||||
@@ -67,6 +68,19 @@ def list_recorder_json():
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def list_route_profile_json():
|
||||||
|
"""列出 recorder 下的大路线方案 JSON,显示名使用 JSON 内的 name。"""
|
||||||
|
result = []
|
||||||
|
for filename, path in list_recorder_json():
|
||||||
|
try:
|
||||||
|
profile = load_route_profile(path)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
display_name = str(profile.get("name") or os.path.splitext(filename)[0]).strip()
|
||||||
|
result.append((display_name or filename, path))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_combat_loops_dir():
|
def get_combat_loops_dir():
|
||||||
"""combat_loops 文件夹路径,不存在则创建"""
|
"""combat_loops 文件夹路径,不存在则创建"""
|
||||||
path = os.path.join(_config_base(), COMBAT_LOOPS_DIR)
|
path = os.path.join(_config_base(), COMBAT_LOOPS_DIR)
|
||||||
@@ -93,10 +107,20 @@ class WaypointPreviewCanvas(QWidget):
|
|||||||
ZOOM_MIN = 0.2
|
ZOOM_MIN = 0.2
|
||||||
ZOOM_MAX = 10.0
|
ZOOM_MAX = 10.0
|
||||||
ZOOM_STEP = 1.15
|
ZOOM_STEP = 1.15
|
||||||
|
ROUTE_COLORS = [
|
||||||
|
QColor(0, 200, 255),
|
||||||
|
QColor(255, 214, 0),
|
||||||
|
QColor(70, 220, 120),
|
||||||
|
QColor(255, 105, 180),
|
||||||
|
QColor(255, 150, 60),
|
||||||
|
QColor(160, 130, 255),
|
||||||
|
QColor(90, 220, 210),
|
||||||
|
QColor(255, 100, 100),
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.points = [] # [(x,y), ...],游戏坐标
|
self.routes = [] # [{"name": str, "points": [(x,y), ...]}, ...]
|
||||||
self.zoom_scale = 1.0 # 1.0 = 0~100 刚好铺满画布
|
self.zoom_scale = 1.0 # 1.0 = 0~100 刚好铺满画布
|
||||||
self.pan_x = 0.0
|
self.pan_x = 0.0
|
||||||
self.pan_y = 0.0
|
self.pan_y = 0.0
|
||||||
@@ -106,7 +130,24 @@ class WaypointPreviewCanvas(QWidget):
|
|||||||
|
|
||||||
def set_points(self, points):
|
def set_points(self, points):
|
||||||
"""设置要绘制的路径点列表。"""
|
"""设置要绘制的路径点列表。"""
|
||||||
self.points = [(float(p[0]), float(p[1])) for p in points] if points else []
|
self.set_routes([{"name": "巡逻路线", "points": points or []}])
|
||||||
|
|
||||||
|
def set_routes(self, routes):
|
||||||
|
"""设置多条巡逻路线,并用不同颜色绘制。"""
|
||||||
|
normalized = []
|
||||||
|
for idx, route in enumerate(routes or [], start=1):
|
||||||
|
raw_points = route.get("points") if isinstance(route, dict) else []
|
||||||
|
points = []
|
||||||
|
for point in raw_points or []:
|
||||||
|
try:
|
||||||
|
points.append((float(point[0]), float(point[1])))
|
||||||
|
except (TypeError, ValueError, IndexError):
|
||||||
|
continue
|
||||||
|
if not points:
|
||||||
|
continue
|
||||||
|
name = str(route.get("name") or f"巡逻路线{idx}").strip()
|
||||||
|
normalized.append({"name": name or f"巡逻路线{idx}", "points": points})
|
||||||
|
self.routes = normalized
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def reset_view(self):
|
def reset_view(self):
|
||||||
@@ -171,35 +212,51 @@ class WaypointPreviewCanvas(QWidget):
|
|||||||
x1, y1 = to_canvas((100, 100))
|
x1, y1 = to_canvas((100, 100))
|
||||||
painter.drawRect(int(x0), int(y0), int(x1 - x0), int(y1 - y0))
|
painter.drawRect(int(x0), int(y0), int(x1 - x0), int(y1 - y0))
|
||||||
|
|
||||||
if not self.points:
|
all_points = []
|
||||||
|
for route in self.routes:
|
||||||
|
all_points.extend(route["points"])
|
||||||
|
|
||||||
|
if not all_points:
|
||||||
painter.setPen(QPen(QColor(200, 200, 200), 1))
|
painter.setPen(QPen(QColor(200, 200, 200), 1))
|
||||||
painter.drawText(rect, Qt.AlignmentFlag.AlignCenter, "无巡逻点数据")
|
painter.drawText(rect, Qt.AlignmentFlag.AlignCenter, "无巡逻点数据")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 绘制路径连线
|
for idx, route in enumerate(self.routes):
|
||||||
painter.setPen(QPen(QColor(0, 200, 255), 2))
|
color = self.ROUTE_COLORS[idx % len(self.ROUTE_COLORS)]
|
||||||
for i in range(len(self.points) - 1):
|
points = route["points"]
|
||||||
x1, y1 = to_canvas(self.points[i])
|
|
||||||
x2, y2 = to_canvas(self.points[i + 1])
|
painter.setPen(QPen(color, 2))
|
||||||
|
for i in range(len(points) - 1):
|
||||||
|
x1, y1 = to_canvas(points[i])
|
||||||
|
x2, y2 = to_canvas(points[i + 1])
|
||||||
painter.drawLine(int(x1), int(y1), int(x2), int(y2))
|
painter.drawLine(int(x1), int(y1), int(x2), int(y2))
|
||||||
|
|
||||||
# 绘制所有巡逻点
|
painter.setPen(QPen(color, 1))
|
||||||
painter.setPen(QPen(QColor(255, 255, 0), 1))
|
painter.setBrush(color)
|
||||||
painter.setBrush(QColor(255, 255, 0))
|
for p in points:
|
||||||
for p in self.points:
|
|
||||||
px, py = to_canvas(p)
|
px, py = to_canvas(p)
|
||||||
painter.drawEllipse(int(px) - 2, int(py) - 2, 4, 4)
|
painter.drawEllipse(int(px) - 2, int(py) - 2, 4, 4)
|
||||||
|
|
||||||
# 起点与终点标记(覆盖在普通点之上)
|
start_x, start_y = to_canvas(points[0])
|
||||||
start_x, start_y = to_canvas(self.points[0])
|
end_x, end_y = to_canvas(points[-1])
|
||||||
end_x, end_y = to_canvas(self.points[-1])
|
painter.setPen(QPen(QColor(245, 245, 245), 2))
|
||||||
painter.setPen(QPen(QColor(0, 255, 0), 2))
|
painter.setBrush(color)
|
||||||
painter.setBrush(QColor(0, 255, 0))
|
painter.drawEllipse(int(start_x) - 5, int(start_y) - 5, 10, 10)
|
||||||
painter.drawEllipse(int(start_x) - 4, int(start_y) - 4, 8, 8)
|
painter.setPen(QPen(QColor(30, 30, 30), 2))
|
||||||
|
painter.setBrush(color)
|
||||||
|
painter.drawEllipse(int(end_x) - 5, int(end_y) - 5, 10, 10)
|
||||||
|
|
||||||
painter.setPen(QPen(QColor(255, 80, 80), 2))
|
legend_x = rect.left() + 12
|
||||||
painter.setBrush(QColor(255, 80, 80))
|
legend_y = rect.top() + 12
|
||||||
painter.drawEllipse(int(end_x) - 4, int(end_y) - 4, 8, 8)
|
painter.setPen(QPen(QColor(230, 230, 230), 1))
|
||||||
|
for idx, route in enumerate(self.routes[:12]):
|
||||||
|
color = self.ROUTE_COLORS[idx % len(self.ROUTE_COLORS)]
|
||||||
|
y = legend_y + idx * 18
|
||||||
|
painter.setBrush(color)
|
||||||
|
painter.setPen(QPen(color, 1))
|
||||||
|
painter.drawRect(legend_x, y + 4, 10, 10)
|
||||||
|
painter.setPen(QPen(QColor(230, 230, 230), 1))
|
||||||
|
painter.drawText(legend_x + 16, y + 14, route["name"])
|
||||||
|
|
||||||
|
|
||||||
# ============ 多键控制 ============
|
# ============ 多键控制 ============
|
||||||
@@ -302,6 +359,7 @@ class GameLoopWorker(QThread):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
mode,
|
mode,
|
||||||
|
route_profile_path=None,
|
||||||
waypoints_path=None,
|
waypoints_path=None,
|
||||||
prepare_route_path=None,
|
prepare_route_path=None,
|
||||||
vendor_path=None,
|
vendor_path=None,
|
||||||
@@ -342,6 +400,7 @@ class GameLoopWorker(QThread):
|
|||||||
self.quest_follow_bot = None
|
self.quest_follow_bot = None
|
||||||
self.flight_bot = None
|
self.flight_bot = None
|
||||||
self.recorder = None
|
self.recorder = None
|
||||||
|
self.route_profile_path = route_profile_path
|
||||||
self.waypoints_path = waypoints_path
|
self.waypoints_path = waypoints_path
|
||||||
self.prepare_route_path = prepare_route_path
|
self.prepare_route_path = prepare_route_path
|
||||||
self.vendor_path = vendor_path
|
self.vendor_path = vendor_path
|
||||||
@@ -417,6 +476,7 @@ class GameLoopWorker(QThread):
|
|||||||
try:
|
try:
|
||||||
from auto_bot_move import AutoBotMove, load_waypoints
|
from auto_bot_move import AutoBotMove, load_waypoints
|
||||||
self.bot_move = AutoBotMove(
|
self.bot_move = AutoBotMove(
|
||||||
|
route_profile_path=self.route_profile_path,
|
||||||
waypoints_path=self.waypoints_path,
|
waypoints_path=self.waypoints_path,
|
||||||
prepare_route_path=self.prepare_route_path,
|
prepare_route_path=self.prepare_route_path,
|
||||||
vendor_path=self.vendor_path,
|
vendor_path=self.vendor_path,
|
||||||
@@ -441,6 +501,9 @@ class GameLoopWorker(QThread):
|
|||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
self.log_signal.emit(f"❌ 巡逻打怪依赖加载失败: {e}")
|
self.log_signal.emit(f"❌ 巡逻打怪依赖加载失败: {e}")
|
||||||
return
|
return
|
||||||
|
except Exception as e:
|
||||||
|
self.log_signal.emit(f"❌ 路线方案加载失败: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
if self.mode == 'combat':
|
if self.mode == 'combat':
|
||||||
try:
|
try:
|
||||||
@@ -738,30 +801,14 @@ class WoWMultiKeyGUI(QMainWindow):
|
|||||||
# 巡逻打怪配置(仅选择巡逻打怪模式时显示)
|
# 巡逻打怪配置(仅选择巡逻打怪模式时显示)
|
||||||
self.patrol_group = QGroupBox("巡逻打怪配置")
|
self.patrol_group = QGroupBox("巡逻打怪配置")
|
||||||
patrol_layout = QFormLayout(self.patrol_group)
|
patrol_layout = QFormLayout(self.patrol_group)
|
||||||
self.waypoints_combo = QComboBox()
|
self.route_profile_combo = QComboBox()
|
||||||
self.waypoints_combo.setMinimumWidth(200)
|
self.route_profile_combo.setMinimumWidth(240)
|
||||||
self.vendor_combo = QComboBox()
|
|
||||||
self.vendor_combo.setMinimumWidth(200)
|
|
||||||
self.mailbox_route_combo = QComboBox()
|
|
||||||
self.mailbox_route_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.route_profile_combo)
|
||||||
patrol_layout.addRow("巡逻点 JSON:", self.waypoints_combo)
|
|
||||||
patrol_layout.addRow("修理商 JSON:", self.vendor_combo)
|
|
||||||
patrol_layout.addRow("邮箱路线 JSON:", self.mailbox_route_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)
|
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)
|
||||||
@@ -930,7 +977,7 @@ class WoWMultiKeyGUI(QMainWindow):
|
|||||||
self.preview_waypoints_combo.setMinimumWidth(220)
|
self.preview_waypoints_combo.setMinimumWidth(220)
|
||||||
self._refresh_preview_waypoints()
|
self._refresh_preview_waypoints()
|
||||||
self.preview_waypoints_combo.currentIndexChanged.connect(self._on_preview_waypoints_changed)
|
self.preview_waypoints_combo.currentIndexChanged.connect(self._on_preview_waypoints_changed)
|
||||||
preview_file_layout.addRow("选择 JSON:", self.preview_waypoints_combo)
|
preview_file_layout.addRow("路线方案 JSON:", self.preview_waypoints_combo)
|
||||||
preview_layout.addWidget(preview_file_group)
|
preview_layout.addWidget(preview_file_group)
|
||||||
|
|
||||||
self.preview_canvas = WaypointPreviewCanvas()
|
self.preview_canvas = WaypointPreviewCanvas()
|
||||||
@@ -1570,35 +1617,34 @@ class WoWMultiKeyGUI(QMainWindow):
|
|||||||
self._load_params_config()
|
self._load_params_config()
|
||||||
|
|
||||||
def _refresh_preview_waypoints(self):
|
def _refresh_preview_waypoints(self):
|
||||||
"""刷新巡逻点预览下拉列表"""
|
"""刷新路线方案预览下拉列表"""
|
||||||
self.preview_waypoints_combo.blockSignals(True)
|
self.preview_waypoints_combo.blockSignals(True)
|
||||||
self.preview_waypoints_combo.clear()
|
self.preview_waypoints_combo.clear()
|
||||||
self.preview_waypoints_combo.addItem("-- 请选择 --", "")
|
self.preview_waypoints_combo.addItem("-- 请选择 --", "")
|
||||||
for name, path in list_recorder_json():
|
for name, path in list_route_profile_json():
|
||||||
self.preview_waypoints_combo.addItem(name, path)
|
self.preview_waypoints_combo.addItem(name, path)
|
||||||
self.preview_waypoints_combo.blockSignals(False)
|
self.preview_waypoints_combo.blockSignals(False)
|
||||||
if self.preview_waypoints_combo.count() > 1:
|
if self.preview_waypoints_combo.count() > 1:
|
||||||
self.preview_waypoints_combo.setCurrentIndex(1)
|
self.preview_waypoints_combo.setCurrentIndex(1)
|
||||||
|
|
||||||
def _on_preview_waypoints_changed(self, index):
|
def _on_preview_waypoints_changed(self, index):
|
||||||
"""选择巡逻点 JSON 时加载并绘制在画布上"""
|
"""选择路线方案 JSON 时加载并绘制所有巡逻路线。"""
|
||||||
path = self.preview_waypoints_combo.currentData()
|
path = self.preview_waypoints_combo.currentData()
|
||||||
if not path or not os.path.exists(path):
|
if not path or not os.path.exists(path):
|
||||||
self.preview_canvas.set_points([])
|
self.preview_canvas.set_routes([])
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
profile = load_route_profile(path)
|
||||||
data = json.load(f)
|
routes = profile.get("patrol_routes") or []
|
||||||
# 期望格式:[[x,y], ...] 或 [[x,y, ...], ...]
|
total_points = sum(len(route.get("points") or []) for route in routes)
|
||||||
points = []
|
self.preview_canvas.set_routes(routes)
|
||||||
for item in data:
|
self.log(
|
||||||
if isinstance(item, (list, tuple)) and len(item) >= 2:
|
f"已加载路线方案预览: {profile.get('name')} "
|
||||||
points.append((float(item[0]), float(item[1])))
|
f"({len(routes)} 条巡逻路线,共 {total_points} 点)"
|
||||||
self.preview_canvas.set_points(points)
|
)
|
||||||
self.log(f"已加载巡逻点预览: {path} (共 {len(points)} 点)")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log(f"加载巡逻点预览失败: {e}")
|
self.log(f"加载路线方案预览失败: {e}")
|
||||||
self.preview_canvas.set_points([])
|
self.preview_canvas.set_routes([])
|
||||||
|
|
||||||
def _reset_preview_canvas(self):
|
def _reset_preview_canvas(self):
|
||||||
"""重置巡逻点预览视图(缩放 + 平移)。"""
|
"""重置巡逻点预览视图(缩放 + 平移)。"""
|
||||||
@@ -1606,51 +1652,19 @@ class WoWMultiKeyGUI(QMainWindow):
|
|||||||
self.preview_canvas.reset_view()
|
self.preview_canvas.reset_view()
|
||||||
|
|
||||||
def _refresh_recorder_combos(self):
|
def _refresh_recorder_combos(self):
|
||||||
"""刷新巡逻点、修理商、复活点路线下拉列表"""
|
"""刷新路线方案和其他 recorder 下拉列表。"""
|
||||||
items = list_recorder_json()
|
if hasattr(self, "route_profile_combo"):
|
||||||
combos_with_default = [
|
self.route_profile_combo.blockSignals(True)
|
||||||
(self.waypoints_combo, 'waypoints.json'),
|
self.route_profile_combo.clear()
|
||||||
(self.vendor_combo, 'vendor.json'),
|
self.route_profile_combo.addItem("-- 请选择 --", "")
|
||||||
]
|
for name, path in list_route_profile_json():
|
||||||
|
self.route_profile_combo.addItem(name, path)
|
||||||
|
if self.route_profile_combo.count() > 1:
|
||||||
|
self.route_profile_combo.setCurrentIndex(1)
|
||||||
|
self.route_profile_combo.blockSignals(False)
|
||||||
|
|
||||||
if hasattr(self, "repair_vendor_combo"):
|
if hasattr(self, "repair_vendor_combo"):
|
||||||
combos_with_default.append((self.repair_vendor_combo, 'vendor.json'))
|
self._refresh_repair_vendor_json_combo()
|
||||||
if hasattr(self, "mailbox_route_combo"):
|
|
||||||
self.mailbox_route_combo.blockSignals(True)
|
|
||||||
self.mailbox_route_combo.clear()
|
|
||||||
self.mailbox_route_combo.addItem("-- 置空(不跑邮箱路线) --", "")
|
|
||||||
for name, path in items:
|
|
||||||
self.mailbox_route_combo.addItem(name, path)
|
|
||||||
idx = self.mailbox_route_combo.findData(os.path.join(get_recorder_dir(), 'mailbox.json'))
|
|
||||||
if idx >= 0:
|
|
||||||
self.mailbox_route_combo.setCurrentIndex(idx)
|
|
||||||
self.mailbox_route_combo.blockSignals(False)
|
|
||||||
for combo, default_name in combos_with_default:
|
|
||||||
combo.blockSignals(True)
|
|
||||||
combo.clear()
|
|
||||||
combo.addItem("-- 请选择 --", "")
|
|
||||||
for name, path in items:
|
|
||||||
combo.addItem(name, path)
|
|
||||||
idx = combo.findData(os.path.join(get_recorder_dir(), default_name))
|
|
||||||
if idx >= 0:
|
|
||||||
combo.setCurrentIndex(idx)
|
|
||||||
elif combo.count() > 1:
|
|
||||||
combo.setCurrentIndex(1)
|
|
||||||
combo.blockSignals(False)
|
|
||||||
# 复活路线下拉(无默认值,可置空)
|
|
||||||
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()
|
|
||||||
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)
|
|
||||||
|
|
||||||
def _refresh_repair_vendor_json_combo(self):
|
def _refresh_repair_vendor_json_combo(self):
|
||||||
"""刷新“回城修理配置”的修理商下拉框。"""
|
"""刷新“回城修理配置”的修理商下拉框。"""
|
||||||
@@ -1785,6 +1799,7 @@ class WoWMultiKeyGUI(QMainWindow):
|
|||||||
if mode is None:
|
if mode is None:
|
||||||
QMessageBox.warning(self, "提示", "请先选择模式(状态监控 / 巡逻打怪 / 自动打怪 / 任务跟随 / 飞行模式 / 回城修理)")
|
QMessageBox.warning(self, "提示", "请先选择模式(状态监控 / 巡逻打怪 / 自动打怪 / 任务跟随 / 飞行模式 / 回城修理)")
|
||||||
return
|
return
|
||||||
|
route_profile_path = None
|
||||||
waypoints_path = None
|
waypoints_path = None
|
||||||
prepare_route_path = None
|
prepare_route_path = None
|
||||||
vendor_path = None
|
vendor_path = None
|
||||||
@@ -1793,46 +1808,25 @@ class WoWMultiKeyGUI(QMainWindow):
|
|||||||
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 ""
|
profile_path = self.route_profile_combo.currentData() or ""
|
||||||
wp = self.waypoints_combo.currentData() or ""
|
if not profile_path:
|
||||||
vp = self.vendor_combo.currentData() or ""
|
QMessageBox.warning(self, "提示", "巡逻打怪模式需选择路线方案 JSON 文件")
|
||||||
mp = self.mailbox_route_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
|
return
|
||||||
if not vp:
|
if not os.path.exists(profile_path):
|
||||||
QMessageBox.warning(self, "提示", "巡逻打怪模式需选择修理商 JSON 文件")
|
QMessageBox.warning(self, "提示", f"路线方案文件不存在: {profile_path}")
|
||||||
return
|
return
|
||||||
if not os.path.exists(wp):
|
try:
|
||||||
QMessageBox.warning(self, "提示", f"巡逻点文件不存在: {wp}")
|
profile = load_route_profile(profile_path)
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.warning(self, "提示", f"路线方案 JSON 无效: {e}")
|
||||||
return
|
return
|
||||||
if not os.path.exists(vp):
|
if not profile.get("vendor_route"):
|
||||||
QMessageBox.warning(self, "提示", f"修理商文件不存在: {vp}")
|
QMessageBox.warning(self, "提示", "路线方案需包含 vendor_route 修理商路线")
|
||||||
return
|
return
|
||||||
if prep and not os.path.exists(prep):
|
if self.gs_enable_bag_full_mail.isChecked() and not profile.get("mailbox_route"):
|
||||||
QMessageBox.warning(self, "提示", f"准备路线文件不存在: {prep}")
|
QMessageBox.warning(self, "提示", "已启用包满邮寄,路线方案需包含 mailbox_route 邮箱路线")
|
||||||
return
|
return
|
||||||
if self.gs_enable_bag_full_mail.isChecked():
|
route_profile_path = profile_path
|
||||||
if not mp:
|
|
||||||
QMessageBox.warning(self, "提示", "已启用包满邮寄,请选择邮箱路线 JSON 文件")
|
|
||||||
return
|
|
||||||
if not os.path.exists(mp):
|
|
||||||
QMessageBox.warning(self, "提示", f"邮箱路线文件不存在: {mp}")
|
|
||||||
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
|
|
||||||
prepare_route_path = prep or None
|
|
||||||
vendor_path = vp
|
|
||||||
mailbox_route_path = mp or 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
|
||||||
@@ -1916,7 +1910,8 @@ class WoWMultiKeyGUI(QMainWindow):
|
|||||||
use_ghost_box = self.gs_use_ghost_box.isChecked()
|
use_ghost_box = self.gs_use_ghost_box.isChecked()
|
||||||
|
|
||||||
self.game_worker = GameLoopWorker(
|
self.game_worker = GameLoopWorker(
|
||||||
mode, waypoints_path=waypoints_path, prepare_route_path=prepare_route_path, vendor_path=vendor_path,
|
mode, route_profile_path=route_profile_path,
|
||||||
|
waypoints_path=waypoints_path, prepare_route_path=prepare_route_path, vendor_path=vendor_path,
|
||||||
mailbox_route_path=mailbox_route_path,
|
mailbox_route_path=mailbox_route_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,
|
||||||
|
|||||||
Reference in New Issue
Block a user