From a48ed597b4bd243a374753cc5c8ce56a7e2d0112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E9=B9=8F?= Date: Thu, 7 May 2026 15:19:59 +0800 Subject: [PATCH] Add Ghostbox hardware and logistics automation --- Ghostbox_ddl/gbild64.dll | Bin 0 -> 88576 bytes Ghostbox_ddl/ghostbox.py | 260 ++++++++++++ Ghostbox_ddl/test_ghostbox.py | 31 ++ auto_bot_move.py | 80 ++-- build.spec | 1 + build_exe.ps1 | 4 + build_wow_multikey.spec | 1 + coordinate_patrol.py | 29 +- game_state.py | 6 + game_state_config.json | 15 +- hardware_control.py | 770 ++++++++++++++++++++++++---------- logistics_manager.py | 233 ++++++++-- wow_multikey_gui.py | 170 ++++++-- wow_multikey_qt.json | 10 +- 14 files changed, 1287 insertions(+), 323 deletions(-) create mode 100644 Ghostbox_ddl/gbild64.dll create mode 100644 Ghostbox_ddl/ghostbox.py create mode 100644 Ghostbox_ddl/test_ghostbox.py diff --git a/Ghostbox_ddl/gbild64.dll b/Ghostbox_ddl/gbild64.dll new file mode 100644 index 0000000000000000000000000000000000000000..3b12a92deef9a7504cbe23fa78324e1d3732a65b GIT binary patch literal 88576 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 bool: return bool(self.prepare_route_waypoints) + def _reset_prepare_route_for_logistics_resume(self): + 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) + self.prepare_route_snapped = False + self.target_acquired_time = None + self.last_turn_signal_time = 0.0 + self.last_interaction_time = 0.0 + self.distance_interact_brake_pending = False + self.distance_interact_pause_until = 0.0 + self.logistics_resume_cooldown_until = time.time() + 5.0 + self.last_tab_time = time.time() + 1.0 + self.patrol_controller.stop_all() + self.patrol_controller.reset_stuck() + if self.prepare_route_waypoints: + print(">>> [后勤] 全自动续跑:重置准备路线,准备返回巡逻。") + else: + print(">>> [后勤] 全自动续跑:未配置准备路线,直接恢复巡逻。") + def _snap_prepare_route_to_nearest(self, state): if self.prepare_route_snapped or self.prepare_route_completed or not self._has_prepare_route(): return @@ -768,40 +794,46 @@ class AutoBotMove: self.death_manager.run_to_corpse(state, get_state) return - # 2. 后勤检查(脱战时):空格或耐久不足则回城 - self.logistics_manager.check_logistics(state) + effective_target = self._is_effective_target(state) + in_combat_or_target = bool(state['combat'] or effective_target) + + # 2. 后勤检查:只在脱战且无有效目标时触发,避免战斗中突然回城/修理/邮寄。 + if not in_combat_or_target and time.time() >= self.logistics_resume_cooldown_until: + self.logistics_manager.check_logistics(state) + else: + self.logistics_manager.is_returning = False + if self.logistics_manager.is_returning: if self.is_moving: self.patrol_controller.stop_all() self.is_moving = False self.patrol_controller.reset_stuck() - bag_full_now = int(state.get('free_slots', 0) or 0) < 2 - if bag_full_now and self.logistics_manager.enable_bag_full_mail: - get_state_fn = (lambda: None if self._should_stop() else parse_game_state()) - self.logistics_manager.run_bag_full_mail_flow( - get_state_fn, - self.patrol_controller, - stop_check=self._should_stop, - ) - if callable(getattr(self, '_on_hearthstone_stop', None)): - self._on_hearthstone_stop() + + tasks = self.logistics_manager.get_pending_tasks(state) + get_state_fn = (lambda: None if self._should_stop() else parse_game_state()) + ok, completed_tasks = self.logistics_manager.run_pending_tasks( + tasks, + get_state_fn, + self.patrol_controller, + stop_check=self._should_stop, + ) + + can_resume = ( + ok + and self.logistics_manager.logistics_full_auto + and any(task in ("mail", "repair") for task in completed_tasks) + ) + if can_resume: + self._reset_prepare_route_for_logistics_resume() + self.is_moving = False return - # 勾选"包满炉石回城":只有真正包满时才炉石;耐久低仍走修理路线 - if bag_full_now and self.logistics_manager.bag_full_hearthstone: - get_state_fn = (lambda: None if self._should_stop() else parse_game_state()) - self.logistics_manager.use_hearthstone_and_stop(get_state=get_state_fn) - if callable(getattr(self, '_on_hearthstone_stop', None)): - self._on_hearthstone_stop() - return - # 中断策略:一旦 GUI 停止,后续 get_state 返回 None,使 navigate_path 立即退出。 - get_state = (lambda: None if self._should_stop() else parse_game_state()) - self.logistics_manager.run_route1_round(get_state, self.patrol_controller) + + if callable(getattr(self, '_on_hearthstone_stop', None)): + self._on_hearthstone_stop() return # 3. 战斗/有目标:停止移动,执行攻击逻辑;仅在「跑向怪」短窗口内做卡死检测 - effective_target = self._is_effective_target(state) # 核心修改:只要还在战斗中,就不算“完全脱战”,即使当前没目标(可能正在 Tab 找下一个) - in_combat_or_target = bool(state['combat'] or effective_target) if in_combat_or_target: # 被动进战时立即打断进食等待,转入正常战斗流程 diff --git a/build.spec b/build.spec index 2f1f24c..9d2c9f0 100644 --- a/build.spec +++ b/build.spec @@ -6,6 +6,7 @@ added_files = [ ('recorder\\*.json', 'recorder'), ('game_state_config.json', '.'), ('ddl', 'ddl'), + ('Ghostbox_ddl', 'Ghostbox_ddl'), ('images', 'images'), ('loot_path.json', '.'), ] diff --git a/build_exe.ps1 b/build_exe.ps1 index 0edee96..6d839fa 100644 --- a/build_exe.ps1 +++ b/build_exe.ps1 @@ -28,6 +28,10 @@ Write-Host "Copying ddl folder to dist..." -ForegroundColor Cyan if (Test-Path "dist\ddl") { Remove-Item "dist\ddl" -Recurse -Force -ErrorAction SilentlyContinue } if (Test-Path "ddl") { Copy-Item "ddl" "dist\ddl" -Recurse -Force } +Write-Host "Copying Ghostbox_ddl folder to dist..." -ForegroundColor Cyan +if (Test-Path "dist\Ghostbox_ddl") { Remove-Item "dist\Ghostbox_ddl" -Recurse -Force -ErrorAction SilentlyContinue } +if (Test-Path "Ghostbox_ddl") { Copy-Item "Ghostbox_ddl" "dist\Ghostbox_ddl" -Recurse -Force } + Write-Host "Copying images folder to dist..." -ForegroundColor Cyan if (Test-Path "dist\images") { Remove-Item "dist\images" -Recurse -Force -ErrorAction SilentlyContinue } if (Test-Path "images") { Copy-Item "images" "dist\images" -Recurse -Force } diff --git a/build_wow_multikey.spec b/build_wow_multikey.spec index bae1ad1..34d5788 100644 --- a/build_wow_multikey.spec +++ b/build_wow_multikey.spec @@ -6,6 +6,7 @@ added_files = [ ('recorder\\*.json', 'recorder'), ('game_state_config.json', '.'), ('ddl', 'ddl'), + ('Ghostbox_ddl', 'Ghostbox_ddl'), ('images', 'images'), ('loot_path.json', '.'), ] diff --git a/coordinate_patrol.py b/coordinate_patrol.py index 02d81e0..de4f766 100644 --- a/coordinate_patrol.py +++ b/coordinate_patrol.py @@ -490,7 +490,14 @@ class CoordinatePatrol: return False - def navigate_path(self, get_state, path, forward=True, arrival_threshold=None): + def navigate_path( + self, + get_state, + path, + forward=True, + arrival_threshold=None, + snap_to_nearest=False, + ): """ 按 path 依次走完所有点后返回。每次调用都必须传入 path。 get_state: 可调用对象,每次调用返回当前状态 dict,需包含 'x','y','facing'。 @@ -520,6 +527,26 @@ class CoordinatePatrol: break time.sleep(poll_sleep_sec) + if snap_to_nearest and points: + current_pos = None + try: + current_pos = (float(state.get("x")), float(state.get("y"))) + except Exception: + current_pos = None + if current_pos is not None: + best_idx = 0 + best_dist = None + for idx, point in enumerate(points): + dist = self.get_distance(current_pos, point) + if best_dist is None or dist < best_dist: + best_idx = idx + best_dist = dist + if best_idx > 0: + self.logger.info( + f">>> 路径智能接入最近点 {best_idx + 1}/{len(points)},距离 {best_dist:.2f}" + ) + points = points[best_idx:] + for i, target in enumerate(points): self.logger.info(f">>> 路径点 {i + 1}/{len(points)}: {target}") while True: diff --git a/game_state.py b/game_state.py index e3b9f89..ff9ca45 100644 --- a/game_state.py +++ b/game_state.py @@ -30,7 +30,13 @@ _DEFAULTS = { "mount_retry_after_sec": 2.0, # 炉石回城 "hearthstone_key": "b", + "hearthstone_wait_sec": 12.0, "bag_full_hearthstone": False, + "logistics_full_auto": False, + "bag_slot_threshold": 2, + "durability_threshold": 0.2, + "repair_target_key": "8", + "repair_interact_key": "4", # 包满炉石后跑邮箱,并触发 MailboxCourier 插件宏 "enable_bag_full_mail": False, "mailbox_route_json": "recorder/mailbox.json", diff --git a/game_state_config.json b/game_state_config.json index fa6bf87..119dc58 100644 --- a/game_state_config.json +++ b/game_state_config.json @@ -12,5 +12,18 @@ "mount_hold_sec": 2.0, "mount_retry_after_sec": 2.0, "hearthstone_key": "6", - "bag_full_hearthstone": true + "hearthstone_wait_sec": 12.0, + "bag_full_hearthstone": true, + "logistics_full_auto": false, + "bag_slot_threshold": 2, + "durability_threshold": 0.2, + "repair_target_key": "8", + "repair_interact_key": "4", + "enable_bag_full_mail": false, + "mailbox_route_json": "recorder/mailbox.json", + "mailbox_interact_key": "4", + "mail_recipient_key": "f7", + "mail_send_key": "f8", + "mailbox_open_wait_sec": 2.0, + "mail_send_wait_sec": 60.0 } diff --git a/hardware_control.py b/hardware_control.py index 2cc94af..e00bd69 100644 --- a/hardware_control.py +++ b/hardware_control.py @@ -1,3 +1,4 @@ +import importlib.util import json import os import random @@ -20,35 +21,489 @@ else: MAIN_CONFIG_FILE = "wow_multikey_qt.json" +BACKEND_TIANYA = "tianya" +BACKEND_GHOST = "ghost" +BACKEND_DIRECTINPUT = "directinput" +TIANYA_DLL_PATH = "ddl/wyhkm.dll" +GHOSTBOX_MODULE_PATH = "Ghostbox_ddl/ghostbox.py" + + +class _TianyaBackend: + name = BACKEND_TIANYA + + def __init__(self, controller, dll_path=TIANYA_DLL_PATH): + self._controller = controller + self._dll_path_setting = dll_path + self._wyhkm = None + self.dll_path = None + + def configure(self, dll_path=None): + if dll_path: + self._dll_path_setting = dll_path + + def label(self): + return self.name if self.is_available() else f"{self.name}_unavailable" + + def is_available(self): + if self._wyhkm: + try: + if self._wyhkm.IsOpen(0): + return True + except Exception: + self._wyhkm = None + + self.dll_path = self._controller._resolve_resource_path(self._dll_path_setting) + if not self.dll_path: + self._controller._log(f">>> [tianya_control] DLL not found: {self._dll_path_setting}") + self._wyhkm = None + return False + + try: + hkmdll = windll.LoadLibrary(self.dll_path) + hkmdll.DllInstall.argtypes = (c_long, c_longlong) + + if hkmdll.DllInstall(1, 2) < 0: + self._controller._log(">>> [tianya_control] DllInstall failed") + self._wyhkm = None + return False + + pythoncom.CoInitialize() + self._wyhkm = win32com.client.Dispatch("wyp.hkm") + + dev_id = self._wyhkm.SearchDevice(0x2612, 0x1701, 0) + if dev_id == -1: + self._controller._log(">>> [tianya_control] Device not found (Index 0)") + self._wyhkm = None + return False + + if not self._wyhkm.Open(dev_id, 0): + self._controller._log(">>> [tianya_control] Open device failed") + self._wyhkm = None + return False + + self._wyhkm.SetMode(1, 1) + self._wyhkm.SetMode(2, 1) + self._wyhkm.SetKeyInterval(30, 50) + self._wyhkm.SetMouseInterval(30, 50) + + self._controller._log(f">>> [tianya_control] Device opened and initialized: {dev_id}") + return True + except Exception as exc: + self._controller._log(f">>> [tianya_control] Init failed: {exc}") + self._wyhkm = None + return False + + def _normalize_key(self, key_str): + return str(key_str).strip().upper() + + def delay_rnd(self, min_ms, max_ms): + if not self.is_available(): + return False + try: + self._wyhkm.DelayRnd(int(min_ms), int(max_ms)) + return True + except Exception: + return False + + def key_down(self, key_str): + if not self.is_available(): + return False + try: + self._wyhkm.KeyDown(self._normalize_key(key_str)) + return True + except Exception as exc: + self._controller._log(f">>> [tianya_control] KeyDown failed ({key_str}): {exc}") + return False + + def key_up(self, key_str): + if not self.is_available(): + return False + try: + self._wyhkm.KeyUp(self._normalize_key(key_str)) + return True + except Exception as exc: + self._controller._log(f">>> [tianya_control] KeyUp failed ({key_str}): {exc}") + return False + + def key_press(self, key_str): + if not self.is_available(): + return False + try: + self._wyhkm.KeyPress(self._normalize_key(key_str)) + return True + except Exception as exc: + self._controller._log(f">>> [tianya_control] KeyPress failed ({key_str}): {exc}") + return False + + def move_to(self, x, y): + if not self.is_available(): + return False + try: + return bool(self._wyhkm.MoveTo(int(x), int(y))) + except Exception as exc: + self._controller._log(f">>> [tianya_control] MoveTo failed ({x}, {y}): {exc}") + return False + + def move_r(self, dx, dy): + if not self.is_available(): + return False + try: + return bool(self._wyhkm.MoveR(int(dx), int(dy))) + except Exception as exc: + self._controller._log(f">>> [tianya_control] MoveR failed ({dx}, {dy}): {exc}") + return False + + def left_click(self): + if not self.is_available(): + return False + try: + return bool(self._wyhkm.LeftClick()) + except Exception as exc: + self._controller._log(f">>> [tianya_control] LeftClick failed: {exc}") + return False + + def right_click(self): + if not self.is_available(): + return False + try: + return bool(self._wyhkm.RightClick()) + except Exception as exc: + self._controller._log(f">>> [tianya_control] RightClick failed: {exc}") + return False + + def left_down(self): + if not self.is_available(): + return False + try: + return bool(self._wyhkm.LeftDown()) + except Exception as exc: + self._controller._log(f">>> [tianya_control] LeftDown failed: {exc}") + return False + + def left_up(self): + if not self.is_available(): + return False + try: + return bool(self._wyhkm.LeftUp()) + except Exception as exc: + self._controller._log(f">>> [tianya_control] LeftUp failed: {exc}") + return False + + +class _GhostboxBackend: + name = BACKEND_GHOST + LEFT_BUTTON = 1 + RIGHT_BUTTON = 2 + + def __init__(self, controller, module_path=GHOSTBOX_MODULE_PATH): + self._controller = controller + self._module_path_setting = module_path + self._module = None + self.module_path = None + + def configure(self, module_path=None): + if module_path and module_path != self._module_path_setting: + self._module_path_setting = module_path + self._module = None + + def label(self): + return self.name if self.is_available() else f"{self.name}_unavailable" + + def _load_module(self): + if self._module is not None: + return self._module + + self.module_path = self._controller._resolve_resource_path(self._module_path_setting) + if not self.module_path: + self._controller._log(f">>> [ghost_control] module not found: {self._module_path_setting}") + return None + + try: + spec = importlib.util.spec_from_file_location("ghostbox_runtime", self.module_path) + if spec is None or spec.loader is None: + self._controller._log(f">>> [ghost_control] load spec failed: {self.module_path}") + return None + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + self._module = module + return module + except Exception as exc: + self._controller._log(f">>> [ghost_control] load failed: {exc}") + self._module = None + return None + + def is_available(self): + gb = self._load_module() + if gb is None: + return False + + try: + if gb.isconnected(): + return True + except Exception: + pass + + try: + result = gb.opendevice() + connected = bool(gb.isconnected()) + if not connected: + self._controller._log(f">>> [ghost_control] Open device failed: result={result}") + return False + + try: + gb.setpresskeydelay(30, 50) + gb.setpressmousebuttondelay(30, 50) + gb.setmousemovementdelay(3, 5) + except Exception: + pass + + self._controller._log(">>> [ghost_control] Device opened and initialized") + return True + except Exception as exc: + self._controller._log(f">>> [ghost_control] Init failed: {exc}") + return False + + def _normalize_key(self, key_str): + return str(key_str).strip().lower() + + def _call(self, action, func, *args): + if not self.is_available(): + return False + try: + func(*args) + return True + except Exception as exc: + self._controller._log(f">>> [ghost_control] {action} failed: {exc}") + return False + + def delay_rnd(self, min_ms, max_ms): + low = min(int(min_ms), int(max_ms)) + high = max(int(min_ms), int(max_ms)) + time.sleep(random.uniform(low, high) / 1000.0) + return True + + def key_down(self, key_str): + gb = self._load_module() + if gb is None: + return False + return self._call(f"KeyDown({key_str})", gb.presskeybyname, self._normalize_key(key_str)) + + def key_up(self, key_str): + gb = self._load_module() + if gb is None: + return False + return self._call(f"KeyUp({key_str})", gb.releasekeybyname, self._normalize_key(key_str)) + + def key_press(self, key_str): + gb = self._load_module() + if gb is None: + return False + return self._call(f"KeyPress({key_str})", gb.pressandreleasekeybyname, self._normalize_key(key_str)) + + def move_to(self, x, y): + gb = self._load_module() + if gb is None: + return False + return self._call(f"MoveTo({x}, {y})", gb.movemouseto, int(x), int(y)) + + def move_r(self, dx, dy): + gb = self._load_module() + if gb is None: + return False + return self._call(f"MoveR({dx}, {dy})", gb.movemouserelative, int(dx), int(dy)) + + def left_click(self): + gb = self._load_module() + if gb is None: + return False + return self._call("LeftClick", gb.pressandreleasemousebutton, self.LEFT_BUTTON) + + def right_click(self): + gb = self._load_module() + if gb is None: + return False + return self._call("RightClick", gb.pressandreleasemousebutton, self.RIGHT_BUTTON) + + def left_down(self): + gb = self._load_module() + if gb is None: + return False + return self._call("LeftDown", gb.pressmousebutton, self.LEFT_BUTTON) + + def left_up(self): + gb = self._load_module() + if gb is None: + return False + return self._call("LeftUp", gb.releasemousebutton, self.LEFT_BUTTON) + + +class _DirectInputBackend: + name = BACKEND_DIRECTINPUT + + def __init__(self, controller): + self._controller = controller + self._last_error = None + + def label(self): + return self.name if self.is_available() else f"{self.name}_unavailable" + + def is_available(self): + if pydirectinput is not None: + return True + if self._last_error != _PYDIRECTINPUT_IMPORT_ERROR: + self._last_error = _PYDIRECTINPUT_IMPORT_ERROR + self._controller._log(f">>> [input_control] pydirectinput unavailable: {_PYDIRECTINPUT_IMPORT_ERROR}") + return False + + def _normalize_key(self, key_str): + return str(key_str).strip().lower() + + def delay_rnd(self, min_ms, max_ms): + if not self.is_available(): + return False + low = min(int(min_ms), int(max_ms)) + high = max(int(min_ms), int(max_ms)) + time.sleep(random.uniform(low, high) / 1000.0) + return True + + def key_down(self, key_str): + if not self.is_available(): + return False + try: + pydirectinput.keyDown(self._normalize_key(key_str)) + return True + except Exception as exc: + self._controller._log(f">>> [directinput] keyDown failed ({key_str}): {exc}") + return False + + def key_up(self, key_str): + if not self.is_available(): + return False + try: + pydirectinput.keyUp(self._normalize_key(key_str)) + return True + except Exception as exc: + self._controller._log(f">>> [directinput] keyUp failed ({key_str}): {exc}") + return False + + def key_press(self, key_str): + if not self.is_available(): + return False + try: + pydirectinput.press(self._normalize_key(key_str)) + return True + except Exception as exc: + self._controller._log(f">>> [directinput] press failed ({key_str}): {exc}") + return False + + def move_to(self, x, y): + if not self.is_available(): + return False + try: + pydirectinput.moveTo(int(x), int(y)) + return True + except Exception as exc: + self._controller._log(f">>> [directinput] moveTo failed ({x}, {y}): {exc}") + return False + + def move_r(self, dx, dy): + if not self.is_available(): + return False + try: + pydirectinput.moveRel(int(dx), int(dy)) + return True + except Exception as exc: + self._controller._log(f">>> [directinput] moveRel failed ({dx}, {dy}): {exc}") + return False + + def left_click(self): + if not self.is_available(): + return False + try: + pydirectinput.click(button="left") + return True + except Exception as exc: + self._controller._log(f">>> [directinput] left click failed: {exc}") + return False + + def right_click(self): + if not self.is_available(): + return False + try: + pydirectinput.click(button="right") + return True + except Exception as exc: + self._controller._log(f">>> [directinput] right click failed: {exc}") + return False + + def left_down(self): + if not self.is_available(): + return False + try: + pydirectinput.mouseDown(button="left") + return True + except Exception as exc: + self._controller._log(f">>> [directinput] left mouseDown failed: {exc}") + return False + + def left_up(self): + if not self.is_available(): + return False + try: + pydirectinput.mouseUp(button="left") + return True + except Exception as exc: + self._controller._log(f">>> [directinput] left mouseUp failed: {exc}") + return False class HardwareController: _instance = None - _wyhkm = None def __new__(cls, *args, **kwargs): if not cls._instance: cls._instance = super(HardwareController, cls).__new__(cls) return cls._instance - def __init__(self, dll_path="ddl/wyhkm.dll", use_hardware_input=None): + def __init__( + self, + dll_path=TIANYA_DLL_PATH, + use_hardware_input=None, + use_tianya_box=None, + use_ghost_box=None, + ghostbox_module_path=GHOSTBOX_MODULE_PATH, + backend=None, + ): if getattr(self, "_bootstrapped", False): - if dll_path: - self._dll_path_setting = dll_path - if use_hardware_input is not None: - self.configure(use_hardware_input=use_hardware_input, dll_path=dll_path) + self._tianya_backend.configure(dll_path=dll_path) + self._ghost_backend.configure(module_path=ghostbox_module_path) + if backend is not None or use_hardware_input is not None or use_tianya_box is not None or use_ghost_box is not None: + self.configure( + use_hardware_input=use_hardware_input, + use_tianya_box=use_tianya_box, + use_ghost_box=use_ghost_box, + dll_path=dll_path, + ghostbox_module_path=ghostbox_module_path, + backend=backend, + ) return self._bootstrapped = True - self._dll_path_setting = dll_path - self._backend = "hardware" + self._backend = BACKEND_TIANYA self._last_backend_log = None - self._last_directinput_error = None - self.dll_path = None + self._tianya_backend = _TianyaBackend(self, dll_path) + self._ghost_backend = _GhostboxBackend(self, ghostbox_module_path) + self._directinput_backend = _DirectInputBackend(self) - if use_hardware_input is None: - use_hardware_input = self._load_backend_preference(default=True) - self.configure(use_hardware_input=use_hardware_input, dll_path=dll_path) + if backend is None and use_hardware_input is None and use_tianya_box is None and use_ghost_box is None: + use_tianya_box, use_ghost_box = self._load_backend_preference(default_tianya=True) + self.configure( + use_hardware_input=use_hardware_input, + use_tianya_box=use_tianya_box, + use_ghost_box=use_ghost_box, + backend=backend, + ) def _runtime_base_dir(self): if getattr(sys, "frozen", False): @@ -100,18 +555,21 @@ class HardwareController: return candidate return os.path.join(self._runtime_base_dir(), MAIN_CONFIG_FILE) - def _load_backend_preference(self, default=True): + def _load_backend_preference(self, default_tianya=True): config_path = self._config_path() if not os.path.exists(config_path): - return default + return default_tianya, False try: with open(config_path, "r", encoding="utf-8") as f: cfg = json.load(f) bot_cfg = (cfg or {}).get("bot") or {} - return bool(bot_cfg.get("use_hardware_input", default)) + has_new_flags = "use_tianya_box" in bot_cfg or "use_ghost_box" in bot_cfg + if has_new_flags: + return bool(bot_cfg.get("use_tianya_box", False)), bool(bot_cfg.get("use_ghost_box", False)) + return bool(bot_cfg.get("use_hardware_input", default_tianya)), False except Exception as exc: self._log(f">>> [input_control] load config failed: {exc}") - return default + return default_tianya, False def _log(self, message): print(message) @@ -122,68 +580,28 @@ class HardwareController: except Exception: pass - def _normalize_hardware_key(self, key_str): - return str(key_str).strip().upper() + def _normalize_backend(self, backend): + if backend in (BACKEND_TIANYA, "hardware"): + return BACKEND_TIANYA + if backend == BACKEND_GHOST: + return BACKEND_GHOST + if backend == BACKEND_DIRECTINPUT: + return BACKEND_DIRECTINPUT + return BACKEND_DIRECTINPUT - def _normalize_directinput_key(self, key_str): - return str(key_str).strip().lower() + def _select_backend(self, use_tianya_box, use_ghost_box): + if bool(use_tianya_box): + return BACKEND_TIANYA + if bool(use_ghost_box): + return BACKEND_GHOST + return BACKEND_DIRECTINPUT - def _directinput_available(self): - if pydirectinput is not None: - return True - if self._last_directinput_error != _PYDIRECTINPUT_IMPORT_ERROR: - self._last_directinput_error = _PYDIRECTINPUT_IMPORT_ERROR - self._log(f">>> [input_control] pydirectinput unavailable: {_PYDIRECTINPUT_IMPORT_ERROR}") - return False - - def _ensure_hardware_ready(self): - if self._wyhkm: - try: - if self._wyhkm.IsOpen(0): - return True - except Exception: - self._wyhkm = None - - self.dll_path = self._resolve_resource_path(self._dll_path_setting) - if not self.dll_path: - self._log(f">>> [hardware_control] DLL not found: {self._dll_path_setting}") - self._wyhkm = None - return False - - try: - hkmdll = windll.LoadLibrary(self.dll_path) - hkmdll.DllInstall.argtypes = (c_long, c_longlong) - - if hkmdll.DllInstall(1, 2) < 0: - self._log(">>> [hardware_control] DllInstall failed") - self._wyhkm = None - return False - - pythoncom.CoInitialize() - self._wyhkm = win32com.client.Dispatch("wyp.hkm") - - dev_id = self._wyhkm.SearchDevice(0x2612, 0x1701, 0) - if dev_id == -1: - self._log(">>> [hardware_control] Device not found (Index 0)") - self._wyhkm = None - return False - - if not self._wyhkm.Open(dev_id, 0): - self._log(">>> [hardware_control] Open device failed") - self._wyhkm = None - return False - - self._wyhkm.SetMode(1, 1) - self._wyhkm.SetMode(2, 1) - self._wyhkm.SetKeyInterval(30, 50) - self._wyhkm.SetMouseInterval(30, 50) - - self._log(f">>> [hardware_control] Device opened and initialized: {dev_id}") - return True - except Exception as exc: - self._log(f">>> [hardware_control] Init failed: {exc}") - self._wyhkm = None - return False + def _current_backend(self): + if self._backend == BACKEND_TIANYA: + return self._tianya_backend + if self._backend == BACKEND_GHOST: + return self._ghost_backend + return self._directinput_backend def _log_backend(self): label = self.backend_label() @@ -191,92 +609,62 @@ class HardwareController: self._last_backend_log = label self._log(f">>> [input_control] backend={label}") - def configure(self, use_hardware_input=True, dll_path=None): - if dll_path: - self._dll_path_setting = dll_path - self._backend = "hardware" if bool(use_hardware_input) else "directinput" - if self._backend == "hardware": - self._ensure_hardware_ready() + def configure( + self, + use_hardware_input=None, + use_tianya_box=None, + use_ghost_box=None, + dll_path=None, + ghostbox_module_path=None, + backend=None, + ): + self._tianya_backend.configure(dll_path=dll_path) + self._ghost_backend.configure(module_path=ghostbox_module_path) + + if backend is not None: + self._backend = self._normalize_backend(backend) else: - self._directinput_available() + if use_tianya_box is None and use_ghost_box is None: + if use_hardware_input is None: + use_tianya_box, use_ghost_box = self._load_backend_preference(default_tianya=True) + else: + use_tianya_box, use_ghost_box = bool(use_hardware_input), False + else: + use_tianya_box = bool(use_tianya_box) + use_ghost_box = bool(use_ghost_box) + if use_hardware_input is not None and not bool(use_hardware_input): + use_tianya_box, use_ghost_box = False, False + self._backend = self._select_backend(use_tianya_box, use_ghost_box) + self._log_backend() return self._backend def uses_hardware_input(self): - return self._backend == "hardware" + return self._backend in (BACKEND_TIANYA, BACKEND_GHOST) + + def uses_tianya_box(self): + return self._backend == BACKEND_TIANYA + + def uses_ghost_box(self): + return self._backend == BACKEND_GHOST def backend_label(self): - if self.uses_hardware_input(): - return "hardware" if self._ensure_hardware_ready() else "hardware_unavailable" - return "directinput" if self._directinput_available() else "directinput_unavailable" + return self._current_backend().label() def is_available(self): - if self.uses_hardware_input(): - return self._ensure_hardware_ready() - return self._directinput_available() + return self._current_backend().is_available() def delay_rnd(self, min_ms, max_ms): - if self.uses_hardware_input() and self.is_available(): - try: - self._wyhkm.DelayRnd(int(min_ms), int(max_ms)) - except Exception: - pass - return - if self._directinput_available(): - low = min(int(min_ms), int(max_ms)) - high = max(int(min_ms), int(max_ms)) - time.sleep(random.uniform(low, high) / 1000.0) + return self._current_backend().delay_rnd(min_ms, max_ms) def key_down(self, key_str): - if self.uses_hardware_input(): - if self.is_available(): - try: - self._wyhkm.KeyDown(self._normalize_hardware_key(key_str)) - return True - except Exception as exc: - self._log(f">>> [hardware_control] KeyDown failed ({key_str}): {exc}") - return False - if self._directinput_available(): - try: - pydirectinput.keyDown(self._normalize_directinput_key(key_str)) - return True - except Exception as exc: - self._log(f">>> [directinput] keyDown failed ({key_str}): {exc}") - return False + return self._current_backend().key_down(key_str) def key_up(self, key_str): - if self.uses_hardware_input(): - if self.is_available(): - try: - self._wyhkm.KeyUp(self._normalize_hardware_key(key_str)) - return True - except Exception as exc: - self._log(f">>> [hardware_control] KeyUp failed ({key_str}): {exc}") - return False - if self._directinput_available(): - try: - pydirectinput.keyUp(self._normalize_directinput_key(key_str)) - return True - except Exception as exc: - self._log(f">>> [directinput] keyUp failed ({key_str}): {exc}") - return False + return self._current_backend().key_up(key_str) def key_press(self, key_str): - if self.uses_hardware_input(): - if self.is_available(): - try: - self._wyhkm.KeyPress(self._normalize_hardware_key(key_str)) - return True - except Exception as exc: - self._log(f">>> [hardware_control] KeyPress failed ({key_str}): {exc}") - return False - if self._directinput_available(): - try: - pydirectinput.press(self._normalize_directinput_key(key_str)) - return True - except Exception as exc: - self._log(f">>> [directinput] press failed ({key_str}): {exc}") - return False + return self._current_backend().key_press(key_str) def keyDown(self, key_str): return self.key_down(key_str) @@ -288,103 +676,25 @@ class HardwareController: return self.key_press(key_str) def move_to(self, x, y): - if self.uses_hardware_input(): - if self.is_available(): - try: - return bool(self._wyhkm.MoveTo(int(x), int(y))) - except Exception as exc: - self._log(f">>> [hardware_control] MoveTo failed ({x}, {y}): {exc}") - return False - if self._directinput_available(): - try: - pydirectinput.moveTo(int(x), int(y)) - return True - except Exception as exc: - self._log(f">>> [directinput] moveTo failed ({x}, {y}): {exc}") - return False + return self._current_backend().move_to(x, y) def move_r(self, dx, dy): - if self.uses_hardware_input(): - if self.is_available(): - try: - return bool(self._wyhkm.MoveR(int(dx), int(dy))) - except Exception as exc: - self._log(f">>> [hardware_control] MoveR failed ({dx}, {dy}): {exc}") - return False - if self._directinput_available(): - try: - pydirectinput.moveRel(int(dx), int(dy)) - return True - except Exception as exc: - self._log(f">>> [directinput] moveRel failed ({dx}, {dy}): {exc}") - return False + return self._current_backend().move_r(dx, dy) def MoveR(self, dx, dy): return self.move_r(dx, dy) def left_click(self): - if self.uses_hardware_input(): - if self.is_available(): - try: - return bool(self._wyhkm.LeftClick()) - except Exception as exc: - self._log(f">>> [hardware_control] LeftClick failed: {exc}") - return False - if self._directinput_available(): - try: - pydirectinput.click(button="left") - return True - except Exception as exc: - self._log(f">>> [directinput] left click failed: {exc}") - return False + return self._current_backend().left_click() def right_click(self): - if self.uses_hardware_input(): - if self.is_available(): - try: - return bool(self._wyhkm.RightClick()) - except Exception as exc: - self._log(f">>> [hardware_control] RightClick failed: {exc}") - return False - if self._directinput_available(): - try: - pydirectinput.click(button="right") - return True - except Exception as exc: - self._log(f">>> [directinput] right click failed: {exc}") - return False + return self._current_backend().right_click() def left_down(self): - if self.uses_hardware_input(): - if self.is_available(): - try: - return bool(self._wyhkm.LeftDown()) - except Exception as exc: - self._log(f">>> [hardware_control] LeftDown failed: {exc}") - return False - if self._directinput_available(): - try: - pydirectinput.mouseDown(button="left") - return True - except Exception as exc: - self._log(f">>> [directinput] left mouseDown failed: {exc}") - return False + return self._current_backend().left_down() def left_up(self): - if self.uses_hardware_input(): - if self.is_available(): - try: - return bool(self._wyhkm.LeftUp()) - except Exception as exc: - self._log(f">>> [hardware_control] LeftUp failed: {exc}") - return False - if self._directinput_available(): - try: - pydirectinput.mouseUp(button="left") - return True - except Exception as exc: - self._log(f">>> [directinput] left mouseUp failed: {exc}") - return False + return self._current_backend().left_up() hw_ctrl = HardwareController() diff --git a/logistics_manager.py b/logistics_manager.py index dbd7764..89280ab 100644 --- a/logistics_manager.py +++ b/logistics_manager.py @@ -20,14 +20,20 @@ class LogisticsManager: self.route_file = route_file or VENDOR_FILE self.bag_full_hearthstone = False # 包满时用炉石回城而非走路修理 self.hearthstone_key = "b" # 炉石按键 - self.hearthstone_cast_sec = 10.0 # 炉石施法等待秒数 + self.hearthstone_cast_sec = 12.0 # 炉石施法等待秒数 self.enable_bag_full_mail = False + self.logistics_full_auto = False + self.bag_slot_threshold = 2 + self.durability_threshold = 0.2 self.mailbox_route_file = os.path.join("recorder", "mailbox.json") self.mailbox_interact_key = "8" self.mail_recipient_key = "" self.mail_send_key = "f8" self.mailbox_open_wait_sec = 2.0 self.mail_send_wait_sec = 60.0 + self.repair_target_key = "8" + self.repair_interact_key = "4" + self.repair_interact_wait_sec = 2.0 def _resolve_path(self, path): if not path: @@ -47,14 +53,60 @@ class LogisticsManager: return False time.sleep(min(0.1, end_at - time.time())) return True + + def _read_position(self, get_state): + if not callable(get_state): + return None + st = get_state() + if not st: + return None + try: + x = st.get("x") + y = st.get("y") + if x is None or y is None: + return None + return float(x), float(y) + except Exception: + return None + def _state_bag_full(self, state): + try: + return int(state.get("free_slots", 0) or 0) < int(self.bag_slot_threshold) + except Exception: + return False + + def _state_durability_low(self, state): + try: + threshold = float(self.durability_threshold) + if threshold > 1.0: + threshold = threshold / 100.0 + durability = state.get("durability", 1.0) + if durability is None: + return False + return float(durability) < threshold + except Exception: + return False + + def get_pending_tasks(self, state): + tasks = [] + bag_full = self._state_bag_full(state) + durability_low = self._state_durability_low(state) + + if bag_full and self.enable_bag_full_mail: + tasks.append("mail") + if durability_low: + tasks.append("repair") + if not tasks and bag_full and self.bag_full_hearthstone: + tasks.append("hearthstone_stop") + return tasks + def check_logistics(self, state): """ state['free_slots']: 剩余空格数量 state['durability']: 0.0 ~ 1.0 的耐久度 """ - # 触发阈值:空格少于 2 个,或耐久度低于 20% - if state['free_slots'] < 2 or state['durability'] < 0.2: + # 触发阈值:空格少于配置值,或耐久度低于配置值。 + if self.get_pending_tasks(state): if not self.is_returning: print(f">>> [后勤警告] 背包/耐久不足!触发回城程序。") self.is_returning = True @@ -67,11 +119,7 @@ class LogisticsManager: success = False for i in range(max_retries): - start_pos = None - if get_state: - st = get_state() - if st: - start_pos = (st.get('x'), st.get('y')) + start_pos = self._read_position(get_state) print(f">>> [后勤] 第 {i+1} 次尝试使用炉石(按键: {self.hearthstone_key})...") # 先按一下 S 确保停止移动,防止移动中按炉石失败 @@ -80,25 +128,34 @@ class LogisticsManager: hw_ctrl.press(self.hearthstone_key) # 等待施法过程 - print(f">>> [后勤] 正在等待施法 {self.hearthstone_cast_sec}s...") - time.sleep(self.hearthstone_cast_sec + 2.0) # 多等 2 秒保险 + print(f">>> [后勤] 正在等待炉石 {self.hearthstone_cast_sec}s...") + time.sleep(float(self.hearthstone_cast_sec)) - if get_state and start_pos and start_pos[0] is not None: - st_now = get_state() - if st_now: - end_pos = (st_now.get('x'), st_now.get('y')) + if get_state and start_pos is not None: + for _ in range(5): + end_pos = self._read_position(get_state) + if end_pos is None: + time.sleep(0.5) + continue dist = math.dist(start_pos, end_pos) # 如果坐标发生了明显跳变(大于 2.0),证明回城成功 if dist > 2.0: print(f">>> [后勤] 炉石回城成功!位置跳变距离: {dist:.2f}") success = True break - else: - print(f">>> [后勤] 炉石似乎失败(位置未变化),准备重试...") - else: + time.sleep(0.5) + if success: + break + print(f">>> [后勤] 炉石似乎失败(位置未变化),准备重试...") + elif get_state: + st_now = get_state() + if st_now is None: # 获取不到状态可能已经卡死或窗口关闭,默认成功以退出循环 success = True break + else: + success = True + break else: # 如果没法校验坐标,就只执行一次 success = True @@ -130,13 +187,13 @@ class LogisticsManager: self.is_returning = False def _do_vendor_interact(self): - """执行与修理商/背包的交互按键(8、4)。""" - hw_ctrl.press("8") + """执行与修理商/背包的交互按键。""" + hw_ctrl.press(self.repair_target_key) time.sleep(0.5) - hw_ctrl.press("4") - time.sleep(2) + hw_ctrl.press(self.repair_interact_key) + time.sleep(float(self.repair_interact_wait_sec)) - def run_bag_full_mail_flow(self, get_state, patrol, stop_check=None): + def run_bag_full_mail_flow(self, get_state, patrol, stop_check=None, use_hearthstone=True): """ 包满邮寄第一版流程: 炉石 -> 跑到邮箱路线终点 -> 交互打开邮箱 -> 按 MailboxCourier 宏键 -> 等待后停止。 @@ -152,11 +209,12 @@ class LogisticsManager: self.is_returning = False return False - ok = self.use_hearthstone_and_stop(get_state=get_state) - if not ok: - print(">>> [后勤-邮箱] 炉石失败,未继续跑邮箱。") - self.is_returning = False - return False + if use_hearthstone: + ok = self.use_hearthstone_and_stop(get_state=get_state) + if not ok: + print(">>> [后勤-邮箱] 炉石失败,未继续跑邮箱。") + self.is_returning = False + return False try: with open(route_file, "r", encoding="utf-8") as f: @@ -176,7 +234,13 @@ class LogisticsManager: if old_enable_mount is not None: patrol.enable_mount = False try: - ok = patrol.navigate_path(get_state, path, forward=True, arrival_threshold=VENDOR_ARRIVAL_THRESHOLD) + ok = patrol.navigate_path( + get_state, + path, + forward=True, + arrival_threshold=VENDOR_ARRIVAL_THRESHOLD, + snap_to_nearest=True, + ) finally: if old_enable_mount is not None: patrol.enable_mount = old_enable_mount @@ -213,6 +277,119 @@ class LogisticsManager: self.is_returning = False return True + def run_repair_flow(self, get_state, patrol, stop_check=None, use_hearthstone=True): + """ + 耐久低修理流程: + 炉石 -> 从修理路线最近点接入 -> 到达 NPC -> 按目标键 -> 按交互/修理键。 + """ + route_file = self._resolve_path(self.route_file) + if not route_file or not os.path.exists(route_file): + print(f">>> [后勤-修理] 修理路线不存在,已停止: {route_file}") + self.is_returning = False + return False + + if callable(stop_check) and stop_check(): + self.is_returning = False + return False + + if use_hearthstone: + ok = self.use_hearthstone_and_stop(get_state=get_state) + if not ok: + print(">>> [后勤-修理] 炉石失败,未继续跑修理路线。") + self.is_returning = False + return False + + try: + 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( + get_state, + path, + forward=True, + arrival_threshold=VENDOR_ARRIVAL_THRESHOLD, + snap_to_nearest=True, + ) + if not ok: + print(">>> [后勤-修理] 修理路线未完成,已停止。") + self.is_returning = False + return False + + if callable(stop_check) and stop_check(): + self.is_returning = False + return False + + patrol.stop_all() + print(f">>> [后勤-修理] 到达 NPC,按目标键: {self.repair_target_key}") + hw_ctrl.press(self.repair_target_key) + if not self._sleep_with_stop(0.5, stop_check=stop_check): + self.is_returning = False + return False + + print(f">>> [后勤-修理] 按修理交互键: {self.repair_interact_key}") + hw_ctrl.press(self.repair_interact_key) + if not self._sleep_with_stop(self.repair_interact_wait_sec, stop_check=stop_check): + self.is_returning = False + return False + + print(">>> [后勤-修理] 修理流程结束。") + self.is_returning = False + return True + + def run_pending_tasks(self, tasks, get_state, patrol, stop_check=None): + completed = [] + already_homed = False + for task in tasks: + if callable(stop_check) and stop_check(): + self.is_returning = False + return False, completed + + if task == "mail": + ok = self.run_bag_full_mail_flow( + get_state, + patrol, + stop_check=stop_check, + use_hearthstone=not already_homed, + ) + if not ok: + return False, completed + already_homed = True + completed.append(task) + continue + + if task == "repair": + ok = self.run_repair_flow( + get_state, + patrol, + stop_check=stop_check, + use_hearthstone=not already_homed, + ) + if not ok: + return False, completed + already_homed = True + completed.append(task) + continue + + if task == "hearthstone_stop": + ok = self.use_hearthstone_and_stop(get_state=get_state) + if not ok: + return False, completed + already_homed = True + completed.append(task) + + self.is_returning = False + return True, completed + def run_route1_round(self, get_state, patrol, route_file=None): """ 读取 route1.json 路径,先正向走完,执行交互(8、4),再反向走完,然后结束。 diff --git a/wow_multikey_gui.py b/wow_multikey_gui.py index 7f0e3fb..bf25a49 100644 --- a/wow_multikey_gui.py +++ b/wow_multikey_gui.py @@ -327,7 +327,9 @@ class GameLoopWorker(QThread): release_spirit_key=None, resurrect_key=None, enable_mouse_loot=True, - use_hardware_input=True, + use_hardware_input=None, + use_tianya_box=None, + use_ghost_box=None, turn_error_key=None, turn_error_hold_sec=None, distance_interact_pause_sec=None, @@ -374,7 +376,12 @@ class GameLoopWorker(QThread): self.release_spirit_key = release_spirit_key self.resurrect_key = resurrect_key self.enable_mouse_loot = enable_mouse_loot - self.use_hardware_input = bool(use_hardware_input) + if use_tianya_box is None and use_ghost_box is None: + self.use_tianya_box = True if use_hardware_input is None else bool(use_hardware_input) + self.use_ghost_box = False + else: + self.use_tianya_box = bool(use_tianya_box) + self.use_ghost_box = bool(use_ghost_box) self.turn_error_key = (turn_error_key or "s").strip().lower() or "s" try: self.turn_error_hold_sec = float(turn_error_hold_sec) @@ -393,10 +400,14 @@ class GameLoopWorker(QThread): self.log_signal.emit("❌ 无法导入 game_state 模块") return - hw_ctrl.configure(use_hardware_input=self.use_hardware_input) + hw_ctrl.configure(use_tianya_box=self.use_tianya_box, use_ghost_box=self.use_ghost_box) backend_text = { - "hardware": "硬件", - "hardware_unavailable": "硬件(不可用)", + "hardware": "天涯盒子", + "hardware_unavailable": "天涯盒子(不可用)", + "tianya": "天涯盒子", + "tianya_unavailable": "天涯盒子(不可用)", + "ghost": "幽灵盒子", + "ghost_unavailable": "幽灵盒子(不可用)", "directinput": "pydirectinput", "directinput_unavailable": "pydirectinput(不可用)", }.get(hw_ctrl.backend_label(), "未知") @@ -495,14 +506,19 @@ class GameLoopWorker(QThread): enable_mount=bool(layout.get("enable_mount", True)), ) self.logistics_manager = LogisticsManager(route_file=self.vendor_path) + 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.repair_target_key = str(layout.get("repair_target_key", "8") or "8") + self.logistics_manager.repair_interact_key = str(layout.get("repair_interact_key", "4") or "4") self.log_signal.emit(f"⛑️ 回城修理:开始执行路径({os.path.basename(self.vendor_path)})") get_state = lambda: parse_game_state() if self.running else None - ok = self.logistics_manager.run_route1_round( + ok = self.logistics_manager.run_repair_flow( get_state, self.patrol_controller, - route_file=self.vendor_path, + stop_check=lambda: not self.running, + use_hearthstone=True, ) if ok: self.log_signal.emit("✅ 回城修理:路径完成") @@ -1069,6 +1085,18 @@ class WoWMultiKeyGUI(QMainWindow): params_layout.addWidget(basic_group) + # 硬件参数 + hardware_group = QGroupBox("硬件参数") + hardware_layout = QHBoxLayout(hardware_group) + self.gs_use_tianya_box = QCheckBox("使用天涯盒子") + self.gs_use_tianya_box.setChecked(True) + self.gs_use_ghost_box = QCheckBox("使用幽灵盒子") + self.gs_use_ghost_box.setChecked(False) + hardware_layout.addWidget(self.gs_use_tianya_box) + hardware_layout.addWidget(self.gs_use_ghost_box) + hardware_layout.addStretch() + params_layout.addWidget(hardware_group) + # 游戏参数 game_group = QGroupBox("游戏参数") game_grid = QGridLayout(game_group) @@ -1140,6 +1168,29 @@ class WoWMultiKeyGUI(QMainWindow): self.gs_mail_send_wait.setSingleStep(1.0) self.gs_mail_send_wait.setValue(60.0) self.gs_mail_send_wait.setSuffix(" 秒") + self.gs_logistics_full_auto = QCheckBox("后勤完成后继续自动巡逻") + self.gs_logistics_full_auto.setChecked(False) + self.gs_bag_slot_threshold = QSpinBox() + self.gs_bag_slot_threshold.setRange(0, 100) + self.gs_bag_slot_threshold.setValue(2) + self.gs_bag_slot_threshold.setSuffix(" 格") + self.gs_durability_threshold = QSpinBox() + self.gs_durability_threshold.setRange(1, 100) + self.gs_durability_threshold.setValue(20) + self.gs_durability_threshold.setSuffix(" %") + self.gs_hearthstone_wait = QDoubleSpinBox() + self.gs_hearthstone_wait.setRange(1.0, 60.0) + self.gs_hearthstone_wait.setSingleStep(0.5) + self.gs_hearthstone_wait.setValue(12.0) + self.gs_hearthstone_wait.setSuffix(" 秒") + self.gs_repair_target_key = QLineEdit() + self.gs_repair_target_key.setPlaceholderText("如 8") + self.gs_repair_target_key.setMaxLength(16) + self.gs_repair_target_key.setText("8") + self.gs_repair_interact_key = QLineEdit() + self.gs_repair_interact_key.setPlaceholderText("如 4") + self.gs_repair_interact_key.setMaxLength(16) + self.gs_repair_interact_key.setText("4") self.gs_enable_mount = QCheckBox("启用上马") self.gs_enable_mount.setChecked(True) self.gs_mount_key = QLineEdit() @@ -1166,8 +1217,6 @@ class WoWMultiKeyGUI(QMainWindow): self.gs_resurrect_key.setText("0") self.gs_enable_mouse_loot = QCheckBox("启用扫雷拾取") self.gs_enable_mouse_loot.setChecked(True) - self.gs_use_hardware_input = QCheckBox("启用硬件控制(关闭则用 pydirectinput)") - self.gs_use_hardware_input.setChecked(True) # 网格填充 game_grid.addWidget(QLabel("剥皮等待时间:"), 0, 0) @@ -1192,31 +1241,42 @@ class WoWMultiKeyGUI(QMainWindow): game_grid.addWidget(QLabel("炉石按键:"), 4, 0) game_grid.addWidget(self.gs_hearthstone_key, 4, 1) - game_grid.addWidget(QLabel("释放灵魂按键:"), 4, 2) - game_grid.addWidget(self.gs_release_spirit_key, 4, 3) + game_grid.addWidget(QLabel("炉石等待:"), 4, 2) + game_grid.addWidget(self.gs_hearthstone_wait, 4, 3) - game_grid.addWidget(QLabel("需转身按键:"), 5, 0) - game_grid.addWidget(self.turn_error_key_edit, 5, 1) - game_grid.addWidget(QLabel("需转身按住时长:"), 6, 0) - game_grid.addWidget(self.turn_error_hold_spin, 6, 1) + game_grid.addWidget(QLabel("释放灵魂按键:"), 5, 0) + game_grid.addWidget(self.gs_release_spirit_key, 5, 1) + game_grid.addWidget(QLabel("复活按键:"), 5, 2) + game_grid.addWidget(self.gs_resurrect_key, 5, 3) + + game_grid.addWidget(QLabel("需转身按键:"), 6, 0) + game_grid.addWidget(self.turn_error_key_edit, 6, 1) + game_grid.addWidget(QLabel("需转身按住时长:"), 6, 2) + game_grid.addWidget(self.turn_error_hold_spin, 6, 3) game_grid.addWidget(QLabel("距离远暂停技能时长:"), 7, 0) game_grid.addWidget(self.distance_interact_pause_spin, 7, 1) - game_grid.addWidget(self.gs_use_hardware_input, 8, 0, 1, 2) - game_grid.addWidget(QLabel("复活按键:"), 6, 2) - game_grid.addWidget(self.gs_resurrect_key, 6, 3) - game_grid.addWidget(self.gs_bag_full_hearthstone, 9, 0, 1, 2) - game_grid.addWidget(self.gs_enable_bag_full_mail, 9, 2, 1, 2) - game_grid.addWidget(QLabel("邮箱交互键:"), 10, 0) - game_grid.addWidget(self.gs_mailbox_interact_key, 10, 1) - game_grid.addWidget(QLabel("收信人按键:"), 10, 2) - game_grid.addWidget(self.gs_mail_recipient_key, 10, 3) - game_grid.addWidget(QLabel("邮寄宏按键:"), 11, 0) - game_grid.addWidget(self.gs_mail_send_key, 11, 1) - game_grid.addWidget(QLabel("邮箱打开等待:"), 11, 2) - game_grid.addWidget(self.gs_mailbox_open_wait, 11, 3) - game_grid.addWidget(QLabel("邮寄后等待:"), 12, 0) - game_grid.addWidget(self.gs_mail_send_wait, 12, 1) + game_grid.addWidget(self.gs_bag_full_hearthstone, 8, 0, 1, 2) + game_grid.addWidget(self.gs_enable_bag_full_mail, 8, 2, 1, 2) + game_grid.addWidget(QLabel("邮箱交互键:"), 9, 0) + game_grid.addWidget(self.gs_mailbox_interact_key, 9, 1) + game_grid.addWidget(QLabel("收信人按键:"), 9, 2) + game_grid.addWidget(self.gs_mail_recipient_key, 9, 3) + game_grid.addWidget(QLabel("邮寄宏按键:"), 10, 0) + game_grid.addWidget(self.gs_mail_send_key, 10, 1) + game_grid.addWidget(QLabel("邮箱打开等待:"), 10, 2) + game_grid.addWidget(self.gs_mailbox_open_wait, 10, 3) + game_grid.addWidget(QLabel("邮寄后等待:"), 11, 0) + game_grid.addWidget(self.gs_mail_send_wait, 11, 1) + game_grid.addWidget(self.gs_logistics_full_auto, 11, 2, 1, 2) + game_grid.addWidget(QLabel("包格触发阈值:"), 12, 0) + game_grid.addWidget(self.gs_bag_slot_threshold, 12, 1) + game_grid.addWidget(QLabel("耐久触发阈值:"), 12, 2) + game_grid.addWidget(self.gs_durability_threshold, 12, 3) + game_grid.addWidget(QLabel("修理目标键:"), 13, 0) + game_grid.addWidget(self.gs_repair_target_key, 13, 1) + game_grid.addWidget(QLabel("修理交互键:"), 13, 2) + game_grid.addWidget(self.gs_repair_interact_key, 13, 3) params_layout.addWidget(game_group) @@ -1263,6 +1323,15 @@ class WoWMultiKeyGUI(QMainWindow): self.gs_mail_send_key.setText(str(cfg.get('mail_send_key', 'f8') or 'f8')) self.gs_mailbox_open_wait.setValue(float(cfg.get('mailbox_open_wait_sec', 2.0))) self.gs_mail_send_wait.setValue(float(cfg.get('mail_send_wait_sec', 60.0))) + self.gs_logistics_full_auto.setChecked(bool(cfg.get('logistics_full_auto', False))) + self.gs_bag_slot_threshold.setValue(int(cfg.get('bag_slot_threshold', 2))) + durability_threshold = float(cfg.get('durability_threshold', 0.2)) + if durability_threshold <= 1.0: + durability_threshold *= 100.0 + self.gs_durability_threshold.setValue(int(round(durability_threshold))) + self.gs_hearthstone_wait.setValue(float(cfg.get('hearthstone_wait_sec', 12.0))) + self.gs_repair_target_key.setText(str(cfg.get('repair_target_key', '8') or '8')) + self.gs_repair_interact_key.setText(str(cfg.get('repair_interact_key', '4') or '4')) self.gs_mount_retry.setValue(float(cfg.get('mount_retry_after_sec', 2.0))) self.gs_release_spirit_key.setText(str(cfg.get('release_spirit_key', '9') or '9')) self.gs_resurrect_key.setText(str(cfg.get('resurrect_key', '0') or '0')) @@ -1279,7 +1348,15 @@ class WoWMultiKeyGUI(QMainWindow): self.turn_error_hold_spin.setValue(float(bot_cfg.get('turn_error_hold_sec', 0.8))) self.distance_interact_pause_spin.setValue(float(bot_cfg.get('distance_interact_pause_sec', 1.0))) self.gs_enable_mouse_loot.setChecked(bool(bot_cfg.get('enable_mouse_loot', True))) - self.gs_use_hardware_input.setChecked(bool(bot_cfg.get('use_hardware_input', True))) + has_box_flags = ('use_tianya_box' in bot_cfg) or ('use_ghost_box' in bot_cfg) + if has_box_flags: + use_tianya_box = bool(bot_cfg.get('use_tianya_box', False)) + use_ghost_box = bool(bot_cfg.get('use_ghost_box', False)) + else: + use_tianya_box = bool(bot_cfg.get('use_hardware_input', True)) + use_ghost_box = False + self.gs_use_tianya_box.setChecked(use_tianya_box) + self.gs_use_ghost_box.setChecked(use_ghost_box) except Exception: self.skinning_wait_spin.setValue(1.5) self.food_key_edit.setText('f1') @@ -1289,7 +1366,8 @@ class WoWMultiKeyGUI(QMainWindow): self.turn_error_hold_spin.setValue(0.8) self.distance_interact_pause_spin.setValue(1.0) self.gs_enable_mouse_loot.setChecked(True) - self.gs_use_hardware_input.setChecked(True) + self.gs_use_tianya_box.setChecked(True) + self.gs_use_ghost_box.setChecked(False) def _save_params_config(self): """保存「参数配置」界面到 game_state_config.json(多分辨率)并写入 wow_multikey_qt.json(bot 参数)""" @@ -1314,6 +1392,12 @@ class WoWMultiKeyGUI(QMainWindow): cfg['mail_send_key'] = self.gs_mail_send_key.text().strip() or 'f8' cfg['mailbox_open_wait_sec'] = float(self.gs_mailbox_open_wait.value()) cfg['mail_send_wait_sec'] = float(self.gs_mail_send_wait.value()) + cfg['logistics_full_auto'] = self.gs_logistics_full_auto.isChecked() + cfg['bag_slot_threshold'] = int(self.gs_bag_slot_threshold.value()) + cfg['durability_threshold'] = float(self.gs_durability_threshold.value()) / 100.0 + cfg['hearthstone_wait_sec'] = float(self.gs_hearthstone_wait.value()) + cfg['repair_target_key'] = self.gs_repair_target_key.text().strip() or '8' + cfg['repair_interact_key'] = self.gs_repair_interact_key.text().strip() or '4' cfg['release_spirit_key'] = (self.gs_release_spirit_key.text().strip() or '9') cfg['resurrect_key'] = (self.gs_resurrect_key.text().strip() or '0') path = save_layout_config(cfg) @@ -1328,10 +1412,14 @@ class WoWMultiKeyGUI(QMainWindow): self.config['bot']['turn_error_hold_sec'] = float(self.turn_error_hold_spin.value()) self.config['bot']['distance_interact_pause_sec'] = float(self.distance_interact_pause_spin.value()) self.config['bot']['enable_mouse_loot'] = self.gs_enable_mouse_loot.isChecked() - self.config['bot']['use_hardware_input'] = self.gs_use_hardware_input.isChecked() + use_tianya_box = self.gs_use_tianya_box.isChecked() + use_ghost_box = self.gs_use_ghost_box.isChecked() + self.config['bot']['use_tianya_box'] = use_tianya_box + self.config['bot']['use_ghost_box'] = use_ghost_box + self.config['bot']['use_hardware_input'] = use_tianya_box or use_ghost_box self._save_main_config() from hardware_control import hw_ctrl - hw_ctrl.configure(use_hardware_input=self.gs_use_hardware_input.isChecked()) + hw_ctrl.configure(use_tianya_box=use_tianya_box, use_ghost_box=use_ghost_box) self.log(f"✅ 参数配置已保存至 {path},并更新 bot 参数") QMessageBox.information(self, "保存成功", f"参数配置已保存至:\n{path}\n\nBot 参数已写入:\n{self.config_path}") @@ -1824,7 +1912,8 @@ class WoWMultiKeyGUI(QMainWindow): resurrect_key = '0' enable_mouse_loot = self.gs_enable_mouse_loot.isChecked() - use_hardware_input = self.gs_use_hardware_input.isChecked() + use_tianya_box = self.gs_use_tianya_box.isChecked() + use_ghost_box = self.gs_use_ghost_box.isChecked() self.game_worker = GameLoopWorker( mode, waypoints_path=waypoints_path, prepare_route_path=prepare_route_path, vendor_path=vendor_path, @@ -1847,7 +1936,8 @@ class WoWMultiKeyGUI(QMainWindow): release_spirit_key=release_spirit_key, resurrect_key=resurrect_key, enable_mouse_loot=enable_mouse_loot, - use_hardware_input=use_hardware_input, + use_tianya_box=use_tianya_box, + use_ghost_box=use_ghost_box, turn_error_key=turn_error_key, turn_error_hold_sec=turn_error_hold_sec, distance_interact_pause_sec=distance_interact_pause_sec, @@ -1880,7 +1970,13 @@ class WoWMultiKeyGUI(QMainWindow): min_dist = float(self.record_min_distance_spin.value()) except Exception: min_dist = 0.5 - self.game_worker = GameLoopWorker(mode='record', record_filename=name, record_min_distance=min_dist) + self.game_worker = GameLoopWorker( + mode='record', + record_filename=name, + record_min_distance=min_dist, + use_tianya_box=self.gs_use_tianya_box.isChecked(), + use_ghost_box=self.gs_use_ghost_box.isChecked(), + ) self.game_worker.state_signal.connect(self.record_state_label.setText) self.game_worker.log_signal.connect(self.log) self.game_worker.finished.connect(self._on_record_finished) diff --git a/wow_multikey_qt.json b/wow_multikey_qt.json index b043fdd..e161442 100644 --- a/wow_multikey_qt.json +++ b/wow_multikey_qt.json @@ -11,6 +11,12 @@ "food_key": "f1", "eat_hp_threshold": 30, "eat_max_wait_sec": 30.0, - "enable_mouse_loot": true + "enable_mouse_loot": true, + "use_tianya_box": true, + "use_ghost_box": false, + "use_hardware_input": true, + "turn_error_key": "4", + "turn_error_hold_sec": 0.5, + "distance_interact_pause_sec": 1.0 } -} \ No newline at end of file +}