add 巡逻模式优先上马
@@ -5,7 +5,7 @@ import sys
|
|||||||
import time
|
import time
|
||||||
import pydirectinput
|
import pydirectinput
|
||||||
|
|
||||||
from game_state import parse_game_state
|
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
|
||||||
@@ -91,7 +91,13 @@ class AutoBotMove:
|
|||||||
if waypoints is None:
|
if waypoints is None:
|
||||||
path = waypoints_path or get_config_path(WAYPOINTS_FILE)
|
path = waypoints_path or get_config_path(WAYPOINTS_FILE)
|
||||||
waypoints = load_waypoints(path) or DEFAULT_WAYPOINTS
|
waypoints = load_waypoints(path) or DEFAULT_WAYPOINTS
|
||||||
self.patrol_controller = CoordinatePatrol(waypoints)
|
layout = load_layout_config()
|
||||||
|
self.patrol_controller = CoordinatePatrol(
|
||||||
|
waypoints,
|
||||||
|
mount_key=str(layout.get("mount_key", "x") or "x"),
|
||||||
|
mount_hold_sec=float(layout.get("mount_hold_sec", 1.6)),
|
||||||
|
mount_retry_after_sec=float(layout.get("mount_retry_after_sec", 2.0)),
|
||||||
|
)
|
||||||
self.death_manager = DeathManager(self.patrol_controller)
|
self.death_manager = DeathManager(self.patrol_controller)
|
||||||
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)
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ ANGLE_DEADZONE_DEG = 5.0 # 死区,此范围内不按 A/D、只按 W,减
|
|||||||
# 随机行为:跳跃概率(每帧触发概率,0.005 ≈ 平均 200 帧一次)
|
# 随机行为:跳跃概率(每帧触发概率,0.005 ≈ 平均 200 帧一次)
|
||||||
RANDOM_JUMP_PROB = 0.05
|
RANDOM_JUMP_PROB = 0.05
|
||||||
|
|
||||||
|
|
||||||
class CoordinatePatrol:
|
class CoordinatePatrol:
|
||||||
"""按航点坐标巡逻,用 pyautogui 按键转向与前进。"""
|
"""按航点坐标巡逻,用 pyautogui 按键转向与前进。"""
|
||||||
|
|
||||||
@@ -26,6 +25,9 @@ class CoordinatePatrol:
|
|||||||
angle_threshold_deg=ANGLE_THRESHOLD_DEG,
|
angle_threshold_deg=ANGLE_THRESHOLD_DEG,
|
||||||
angle_deadzone_deg=ANGLE_DEADZONE_DEG,
|
angle_deadzone_deg=ANGLE_DEADZONE_DEG,
|
||||||
random_jump_prob=RANDOM_JUMP_PROB,
|
random_jump_prob=RANDOM_JUMP_PROB,
|
||||||
|
mount_key="x",
|
||||||
|
mount_hold_sec=1.6,
|
||||||
|
mount_retry_after_sec=2.0,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
@@ -33,6 +35,7 @@ class CoordinatePatrol:
|
|||||||
arrival_threshold: 到达判定距离(游戏坐标单位),默认 0.5
|
arrival_threshold: 到达判定距离(游戏坐标单位),默认 0.5
|
||||||
angle_threshold_deg: 朝向容差(度),超过此值才按 A/D 转向,默认 ANGLE_THRESHOLD_DEG
|
angle_threshold_deg: 朝向容差(度),超过此值才按 A/D 转向,默认 ANGLE_THRESHOLD_DEG
|
||||||
angle_deadzone_deg: 转向死区(度),此范围内不按 A/D、只按 W,默认 ANGLE_DEADZONE_DEG
|
angle_deadzone_deg: 转向死区(度),此范围内不按 A/D、只按 W,默认 ANGLE_DEADZONE_DEG
|
||||||
|
mount_key / mount_hold_sec / mount_retry_after_sec: 未上马时先上马(与 game_state_config / GUI 一致)
|
||||||
"""
|
"""
|
||||||
self.waypoints = waypoints
|
self.waypoints = waypoints
|
||||||
self.current_index = 0
|
self.current_index = 0
|
||||||
@@ -43,6 +46,33 @@ class CoordinatePatrol:
|
|||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
self.last_turn_end_time = 0 # 最近一次结束 A/D 转向的时间,供卡死检测排除原地转向
|
self.last_turn_end_time = 0 # 最近一次结束 A/D 转向的时间,供卡死检测排除原地转向
|
||||||
self.stuck_handler = StuckHandler()
|
self.stuck_handler = StuckHandler()
|
||||||
|
self._next_mount_allowed = 0.0
|
||||||
|
self.mount_key = str(mount_key).strip() or "x"
|
||||||
|
self.mount_hold_sec = float(mount_hold_sec)
|
||||||
|
self.mount_retry_after_sec = float(mount_retry_after_sec)
|
||||||
|
|
||||||
|
def _ensure_mounted(self, state):
|
||||||
|
"""
|
||||||
|
已上马返回 True。
|
||||||
|
未上马则松开移动键、按住上马键 MOUNT_HOLD_SEC,本帧不走路;返回 False。
|
||||||
|
state 无 mounted 字段时视为无法判断,不拦巡逻(兼容未开 LogicBeacon)。
|
||||||
|
"""
|
||||||
|
if "mounted" not in state:
|
||||||
|
return True
|
||||||
|
if state.get("mounted"):
|
||||||
|
return True
|
||||||
|
self.stop_all()
|
||||||
|
now = time.time()
|
||||||
|
if now < self._next_mount_allowed:
|
||||||
|
return False
|
||||||
|
self.logger.info(
|
||||||
|
f">>> 未上马,先按 {self.mount_key} {self.mount_hold_sec:.1f}s 上马"
|
||||||
|
)
|
||||||
|
pyautogui.keyDown(self.mount_key)
|
||||||
|
time.sleep(self.mount_hold_sec)
|
||||||
|
pyautogui.keyUp(self.mount_key)
|
||||||
|
self._next_mount_allowed = time.time() + self.mount_retry_after_sec
|
||||||
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_distance(p1, p2):
|
def get_distance(p1, p2):
|
||||||
@@ -168,6 +198,9 @@ class CoordinatePatrol:
|
|||||||
self.stop_all()
|
self.stop_all()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if not self._ensure_mounted(state):
|
||||||
|
return
|
||||||
|
|
||||||
# 2. 卡死检测:位移检测逻辑保持在巡逻中
|
# 2. 卡死检测:位移检测逻辑保持在巡逻中
|
||||||
if self.stuck_handler.check_stuck(state, self.last_turn_end_time):
|
if self.stuck_handler.check_stuck(state, self.last_turn_end_time):
|
||||||
self.logger.error("!!! 检测到卡死,启动脱困程序 !!!")
|
self.logger.error("!!! 检测到卡死,启动脱困程序 !!!")
|
||||||
@@ -250,6 +283,9 @@ class CoordinatePatrol:
|
|||||||
self.stop_all()
|
self.stop_all()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if not self._ensure_mounted(state):
|
||||||
|
return False
|
||||||
|
|
||||||
# 1. 卡死检测
|
# 1. 卡死检测
|
||||||
if self.stuck_handler.check_stuck(state, self.last_turn_end_time):
|
if self.stuck_handler.check_stuck(state, self.last_turn_end_time):
|
||||||
self.stop_all()
|
self.stop_all()
|
||||||
@@ -311,6 +347,7 @@ class CoordinatePatrol:
|
|||||||
"""
|
"""
|
||||||
按 path 依次走完所有点后返回。每次调用都必须传入 path。
|
按 path 依次走完所有点后返回。每次调用都必须传入 path。
|
||||||
get_state: 可调用对象,每次调用返回当前状态 dict,需包含 'x','y','facing'。
|
get_state: 可调用对象,每次调用返回当前状态 dict,需包含 'x','y','facing'。
|
||||||
|
若含 'mounted'(LogicBeacon),路径开始前会先上马,每段 navigate_to_point 也会校验。
|
||||||
path: [[x,y], ...] 或 [(x,y), ...](如 json.load 得到),本次要走的全部航点。
|
path: [[x,y], ...] 或 [(x,y), ...](如 json.load 得到),本次要走的全部航点。
|
||||||
forward: True=正序(0→n),False=倒序(n→0)。
|
forward: True=正序(0→n),False=倒序(n→0)。
|
||||||
阻塞直到走完 path 中所有点或出错,走完返回 True。内含卡死检测。
|
阻塞直到走完 path 中所有点或出错,走完返回 True。内含卡死检测。
|
||||||
@@ -321,6 +358,17 @@ class CoordinatePatrol:
|
|||||||
points = [(float(p[0]), float(p[1])) for p in path]
|
points = [(float(p[0]), float(p[1])) for p in path]
|
||||||
if not forward:
|
if not forward:
|
||||||
points = points[::-1]
|
points = points[::-1]
|
||||||
|
|
||||||
|
# 路径开始前:若 state 含 mounted 且未上马,先完成上马再逐点移动
|
||||||
|
while True:
|
||||||
|
state = get_state()
|
||||||
|
if state is None:
|
||||||
|
self.stop_all()
|
||||||
|
return False
|
||||||
|
if self._ensure_mounted(state):
|
||||||
|
break
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
for i, target in enumerate(points):
|
for i, target in enumerate(points):
|
||||||
self.logger.info(f">>> 路径点 {i + 1}/{len(points)}: {target}")
|
self.logger.info(f">>> 路径点 {i + 1}/{len(points)}: {target}")
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ _DEFAULTS = {
|
|||||||
"scan_region_height": 15,
|
"scan_region_height": 15,
|
||||||
"offset_left": 20,
|
"offset_left": 20,
|
||||||
"offset_top": 45,
|
"offset_top": 45,
|
||||||
|
# 巡逻上马(coordinate_patrol,与 GUI「参数配置」一致)
|
||||||
|
"mount_key": "x",
|
||||||
|
"mount_hold_sec": 1.6,
|
||||||
|
"mount_retry_after_sec": 2.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
SCREENSHOT_DIR = 'screenshot'
|
SCREENSHOT_DIR = 'screenshot'
|
||||||
|
|||||||
@@ -4,5 +4,8 @@
|
|||||||
"scan_region_width": 190,
|
"scan_region_width": 190,
|
||||||
"scan_region_height": 15,
|
"scan_region_height": 15,
|
||||||
"offset_left": 20,
|
"offset_left": 20,
|
||||||
"offset_top": 45
|
"offset_top": 45,
|
||||||
|
"mount_key": "x",
|
||||||
|
"mount_hold_sec": 1.6,
|
||||||
|
"mount_retry_after_sec": 2.0
|
||||||
}
|
}
|
||||||
|
|||||||
142
recorder/地狱火半岛剥皮310-375.json
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
12.1,
|
||||||
|
54.99
|
||||||
|
],
|
||||||
|
[
|
||||||
|
11.62,
|
||||||
|
54.79
|
||||||
|
],
|
||||||
|
[
|
||||||
|
11.16,
|
||||||
|
54.54
|
||||||
|
],
|
||||||
|
[
|
||||||
|
10.82,
|
||||||
|
54.13
|
||||||
|
],
|
||||||
|
[
|
||||||
|
10.54,
|
||||||
|
53.7
|
||||||
|
],
|
||||||
|
[
|
||||||
|
10.3,
|
||||||
|
53.24
|
||||||
|
],
|
||||||
|
[
|
||||||
|
10.06,
|
||||||
|
52.75
|
||||||
|
],
|
||||||
|
[
|
||||||
|
9.82,
|
||||||
|
52.31
|
||||||
|
],
|
||||||
|
[
|
||||||
|
9.55,
|
||||||
|
51.86
|
||||||
|
],
|
||||||
|
[
|
||||||
|
9.42,
|
||||||
|
51.35
|
||||||
|
],
|
||||||
|
[
|
||||||
|
9.39,
|
||||||
|
50.83
|
||||||
|
],
|
||||||
|
[
|
||||||
|
9.24,
|
||||||
|
50.35
|
||||||
|
],
|
||||||
|
[
|
||||||
|
9.15,
|
||||||
|
49.85
|
||||||
|
],
|
||||||
|
[
|
||||||
|
9.5,
|
||||||
|
49.44
|
||||||
|
],
|
||||||
|
[
|
||||||
|
9.94,
|
||||||
|
49.14
|
||||||
|
],
|
||||||
|
[
|
||||||
|
10.38,
|
||||||
|
48.85
|
||||||
|
],
|
||||||
|
[
|
||||||
|
10.81,
|
||||||
|
48.56
|
||||||
|
],
|
||||||
|
[
|
||||||
|
11.23,
|
||||||
|
48.26
|
||||||
|
],
|
||||||
|
[
|
||||||
|
11.65,
|
||||||
|
47.94
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.11,
|
||||||
|
47.65
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.59,
|
||||||
|
47.49
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.54,
|
||||||
|
48.01
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.44,
|
||||||
|
48.53
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.32,
|
||||||
|
49.03
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.2,
|
||||||
|
49.54
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.1,
|
||||||
|
50.07
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.1,
|
||||||
|
50.6
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.33,
|
||||||
|
51.1
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.55,
|
||||||
|
51.59
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.68,
|
||||||
|
52.11
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.78,
|
||||||
|
52.62
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.86,
|
||||||
|
53.12
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.83,
|
||||||
|
53.65
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.76,
|
||||||
|
54.17
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.1,
|
||||||
|
54.99
|
||||||
|
]
|
||||||
|
]
|
||||||
|
Before Width: | Height: | Size: 88 B After Width: | Height: | Size: 667 B |
|
Before Width: | Height: | Size: 74 B After Width: | Height: | Size: 91 B |
|
Before Width: | Height: | Size: 74 B After Width: | Height: | Size: 87 B |
|
Before Width: | Height: | Size: 74 B After Width: | Height: | Size: 84 B |
|
Before Width: | Height: | Size: 74 B After Width: | Height: | Size: 90 B |
|
Before Width: | Height: | Size: 74 B After Width: | Height: | Size: 83 B |
|
Before Width: | Height: | Size: 74 B After Width: | Height: | Size: 91 B |
|
Before Width: | Height: | Size: 74 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 74 B After Width: | Height: | Size: 84 B |
|
Before Width: | Height: | Size: 74 B After Width: | Height: | Size: 83 B |
@@ -69,11 +69,11 @@ class StuckHandler:
|
|||||||
pyautogui.keyDown(turn_key)
|
pyautogui.keyDown(turn_key)
|
||||||
|
|
||||||
# 倒车时间稍长一点,离开障碍物
|
# 倒车时间稍长一点,离开障碍物
|
||||||
time.sleep(0.3)
|
time.sleep(0.5)
|
||||||
|
|
||||||
# 3. 尝试跳跃脱离地形卡位
|
# 3. 尝试跳跃脱离地形卡位
|
||||||
pyautogui.press("space")
|
pyautogui.press("space")
|
||||||
time.sleep(0.3)
|
time.sleep(0.5)
|
||||||
|
|
||||||
pyautogui.keyUp(turn_key)
|
pyautogui.keyUp(turn_key)
|
||||||
pyautogui.keyUp("s")
|
pyautogui.keyUp("s")
|
||||||
|
|||||||
@@ -746,6 +746,24 @@ class WoWMultiKeyGUI(QMainWindow):
|
|||||||
self.skinning_wait_spin.setSuffix(" 秒")
|
self.skinning_wait_spin.setSuffix(" 秒")
|
||||||
params_right.addRow("剥皮等待时间:", self.skinning_wait_spin)
|
params_right.addRow("剥皮等待时间:", self.skinning_wait_spin)
|
||||||
|
|
||||||
|
self.gs_mount_key = QLineEdit()
|
||||||
|
self.gs_mount_key.setPlaceholderText("如 x")
|
||||||
|
self.gs_mount_key.setMaxLength(16)
|
||||||
|
self.gs_mount_key.setText("x")
|
||||||
|
self.gs_mount_hold = QDoubleSpinBox()
|
||||||
|
self.gs_mount_hold.setRange(0.1, 30.0)
|
||||||
|
self.gs_mount_hold.setSingleStep(0.1)
|
||||||
|
self.gs_mount_hold.setValue(1.6)
|
||||||
|
self.gs_mount_hold.setSuffix(" 秒")
|
||||||
|
self.gs_mount_retry = QDoubleSpinBox()
|
||||||
|
self.gs_mount_retry.setRange(0.0, 60.0)
|
||||||
|
self.gs_mount_retry.setSingleStep(0.1)
|
||||||
|
self.gs_mount_retry.setValue(2.0)
|
||||||
|
self.gs_mount_retry.setSuffix(" 秒")
|
||||||
|
params_right.addRow("上马按键 (mount_key):", self.gs_mount_key)
|
||||||
|
params_right.addRow("上马按住时长 (mount_hold_sec):", self.gs_mount_hold)
|
||||||
|
params_right.addRow("上马重试间隔 (mount_retry_after_sec):", self.gs_mount_retry)
|
||||||
|
|
||||||
params_row.addLayout(params_left)
|
params_row.addLayout(params_left)
|
||||||
params_row.addLayout(params_right)
|
params_row.addLayout(params_right)
|
||||||
params_layout.addWidget(params_content)
|
params_layout.addWidget(params_content)
|
||||||
@@ -780,6 +798,9 @@ class WoWMultiKeyGUI(QMainWindow):
|
|||||||
self.gs_scan_height.setValue(cfg.get('scan_region_height', 15))
|
self.gs_scan_height.setValue(cfg.get('scan_region_height', 15))
|
||||||
self.gs_offset_left.setValue(cfg.get('offset_left', 20))
|
self.gs_offset_left.setValue(cfg.get('offset_left', 20))
|
||||||
self.gs_offset_top.setValue(cfg.get('offset_top', 45))
|
self.gs_offset_top.setValue(cfg.get('offset_top', 45))
|
||||||
|
self.gs_mount_key.setText(str(cfg.get('mount_key', 'x') or 'x'))
|
||||||
|
self.gs_mount_hold.setValue(float(cfg.get('mount_hold_sec', 1.6)))
|
||||||
|
self.gs_mount_retry.setValue(float(cfg.get('mount_retry_after_sec', 2.0)))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log(f"加载参数配置失败: {e}")
|
self.log(f"加载参数配置失败: {e}")
|
||||||
try:
|
try:
|
||||||
@@ -800,6 +821,9 @@ class WoWMultiKeyGUI(QMainWindow):
|
|||||||
cfg['scan_region_height'] = self.gs_scan_height.value()
|
cfg['scan_region_height'] = self.gs_scan_height.value()
|
||||||
cfg['offset_left'] = self.gs_offset_left.value()
|
cfg['offset_left'] = self.gs_offset_left.value()
|
||||||
cfg['offset_top'] = self.gs_offset_top.value()
|
cfg['offset_top'] = self.gs_offset_top.value()
|
||||||
|
cfg['mount_key'] = (self.gs_mount_key.text().strip() or 'x')
|
||||||
|
cfg['mount_hold_sec'] = float(self.gs_mount_hold.value())
|
||||||
|
cfg['mount_retry_after_sec'] = float(self.gs_mount_retry.value())
|
||||||
path = save_layout_config(cfg)
|
path = save_layout_config(cfg)
|
||||||
# bot 参数写入主配置文件
|
# bot 参数写入主配置文件
|
||||||
self.config = self.config or {}
|
self.config = self.config or {}
|
||||||
|
|||||||