first commit
BIN
__pycache__/auto_bot.cpython-311.pyc
Normal file
BIN
__pycache__/auto_bot_move.cpython-311.pyc
Normal file
BIN
__pycache__/combat_detector.cpython-311.pyc
Normal file
BIN
__pycache__/combat_engine.cpython-311.pyc
Normal file
BIN
__pycache__/config.cpython-311.pyc
Normal file
BIN
__pycache__/coordinate_patrol.cpython-311.pyc
Normal file
BIN
__pycache__/death_manager.cpython-311.pyc
Normal file
BIN
__pycache__/game_state.cpython-311.pyc
Normal file
BIN
__pycache__/logistics_manager.cpython-311.pyc
Normal file
BIN
__pycache__/player_movement.cpython-311.pyc
Normal file
BIN
__pycache__/player_position.cpython-311.pyc
Normal file
BIN
__pycache__/recorder.cpython-311.pyc
Normal file
BIN
__pycache__/stuck_handler.cpython-311.pyc
Normal file
BIN
__pycache__/text_finder.cpython-311.pyc
Normal file
158
auto_bot.py
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
import pydirectinput
|
||||||
|
|
||||||
|
from game_state import parse_game_state
|
||||||
|
from stuck_handler import StuckHandler
|
||||||
|
|
||||||
|
# 定义按键常量
|
||||||
|
KEY_TAB = '3'
|
||||||
|
KEY_LOOT = '4' # 假设你在游戏里设置了互动按键为 F
|
||||||
|
KEY_ATTACK = '2' # 假设你的主攻击技能是 1
|
||||||
|
|
||||||
|
def load_attack_loop(path):
|
||||||
|
"""从 JSON 加载攻击循环配置。支持 hp_below_percent/hp_key、mp_below_percent/mp_key(0=不启用)"""
|
||||||
|
if not path or not os.path.exists(path):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
cfg = json.load(f)
|
||||||
|
steps = cfg.get('steps') or []
|
||||||
|
trigger = float(cfg.get('trigger_chance', 0.3))
|
||||||
|
hp_below = int(cfg.get('hp_below_percent', 0)) or 0
|
||||||
|
mp_below = int(cfg.get('mp_below_percent', 0)) or 0
|
||||||
|
return {
|
||||||
|
'trigger_chance': trigger,
|
||||||
|
'steps': steps,
|
||||||
|
'hp_below_percent': hp_below,
|
||||||
|
'hp_key': (cfg.get('hp_key') or '').strip() or None,
|
||||||
|
'mp_below_percent': mp_below,
|
||||||
|
'mp_key': (cfg.get('mp_key') or '').strip() or None,
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class AutoBot:
|
||||||
|
def __init__(self, waypoints=None, attack_loop_path=None, skinning_wait_sec=None):
|
||||||
|
self.last_tab_time = 0
|
||||||
|
self.is_running = True
|
||||||
|
self.attack_loop_config = load_attack_loop(attack_loop_path)
|
||||||
|
self.tab_no_target_count = 0 # 连续按 Tab 仍无目标的次数,满 5 次则转向
|
||||||
|
self.stuck_handler = StuckHandler()
|
||||||
|
self.target_acquired_time = None # 本次获得目标的时间(目前仅用于调试/保留接口)
|
||||||
|
self.last_turn_end_time = 0 # 最近一次结束 A/D 转向的时间,供卡死检测排除
|
||||||
|
self.skinning_wait_sec = float(skinning_wait_sec) if skinning_wait_sec is not None else 1.5
|
||||||
|
|
||||||
|
def execute_disengage_loot(self):
|
||||||
|
"""从有战斗/目标切换到完全脱战的瞬间,执行拾取 + 剥皮。"""
|
||||||
|
try:
|
||||||
|
# 拾取
|
||||||
|
pydirectinput.press(KEY_LOOT)
|
||||||
|
time.sleep(0.5)
|
||||||
|
# 剥皮
|
||||||
|
pydirectinput.press(KEY_LOOT)
|
||||||
|
time.sleep(self.skinning_wait_sec)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def execute_combat_logic(self, state):
|
||||||
|
if self.attack_loop_config:
|
||||||
|
cfg = self.attack_loop_config
|
||||||
|
if random.random() >= cfg['trigger_chance']:
|
||||||
|
return
|
||||||
|
hp = state.get('hp')
|
||||||
|
if hp is not None and cfg.get('hp_below_percent') and cfg.get('hp_key') and hp < cfg['hp_below_percent']:
|
||||||
|
pydirectinput.press(cfg['hp_key'])
|
||||||
|
time.sleep(0.2)
|
||||||
|
mp = state.get('mp')
|
||||||
|
if mp is not None and cfg.get('mp_below_percent') and cfg.get('mp_key') and mp < cfg['mp_below_percent']:
|
||||||
|
pydirectinput.press(cfg['mp_key'])
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
# 每次攻击前选中目标
|
||||||
|
pydirectinput.press(KEY_LOOT)
|
||||||
|
for step in cfg['steps']:
|
||||||
|
key = step.get('key') or KEY_ATTACK
|
||||||
|
delay = float(step.get('delay', 0.5))
|
||||||
|
pydirectinput.press(key)
|
||||||
|
time.sleep(delay)
|
||||||
|
return
|
||||||
|
# 默认:模拟手动按键的节奏
|
||||||
|
if random.random() < 0.3:
|
||||||
|
pydirectinput.press(KEY_ATTACK)
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
def execute_logic(self, state):
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
# --- 场景 1:没有目标,按 Tab 选怪;连续 5 次无目标则随机左/右转再重试 ---
|
||||||
|
if not state['target']:
|
||||||
|
self.execute_disengage_loot()
|
||||||
|
self.target_acquired_time = None # 脱战/无目标时清零
|
||||||
|
self.stuck_handler.reset() # 避免脱战期间保留上次站桩的计时,导致重新选到目标立刻误判卡死
|
||||||
|
self.tab_no_target_count = min(self.tab_no_target_count, 5)
|
||||||
|
if self.tab_no_target_count >= 5:
|
||||||
|
# 随机左转或右转一段时间,再重新获取目标
|
||||||
|
turn_key = random.choice(["a", "d"]) # a=左转, d=右转
|
||||||
|
pydirectinput.keyDown(turn_key)
|
||||||
|
time.sleep(random.uniform(0.3, 0.6))
|
||||||
|
pydirectinput.keyUp(turn_key)
|
||||||
|
self.tab_no_target_count = 0
|
||||||
|
self.last_tab_time = current_time
|
||||||
|
self.last_turn_end_time = current_time # 供卡死检测排除刚转向的一小段时间
|
||||||
|
elif current_time - self.last_tab_time > random.uniform(0.5, 1.2):
|
||||||
|
pydirectinput.press(KEY_TAB)
|
||||||
|
self.last_tab_time = current_time
|
||||||
|
self.tab_no_target_count += 1
|
||||||
|
else:
|
||||||
|
self.tab_no_target_count = 0 # 有目标时清零
|
||||||
|
# 跑向怪阶段的卡死检测:只在目标血量为 100% 时认为是在“刚刚开怪、接近怪物”,避免残血目标也触发跑向怪卡死逻辑
|
||||||
|
target_hp = state.get('target_hp')
|
||||||
|
if target_hp >= 100:
|
||||||
|
try:
|
||||||
|
if self.stuck_handler.check_stuck(state, self.last_turn_end_time):
|
||||||
|
print(">>> [自动打怪] 可能卡住障碍物,执行脱困")
|
||||||
|
self.stuck_handler.resolve_stuck()
|
||||||
|
self.stuck_handler.reset()
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# --- 场景 2:有目标且不在战斗中,按交互键 ---
|
||||||
|
if state['target'] and not state['combat']:
|
||||||
|
if random.random() < 0.3: # 只有 30% 的循环频率触发按键,模拟真人频率
|
||||||
|
pydirectinput.press(KEY_LOOT)
|
||||||
|
|
||||||
|
# --- 场景 3:有目标且在战斗中,自动按技能 ---
|
||||||
|
elif state['target'] and state['combat']:
|
||||||
|
self.execute_combat_logic(state)
|
||||||
|
|
||||||
|
# --- 场景 3:脱战且有可拾取物(需要你在 Lua 增加拾取位,见下文) ---
|
||||||
|
# if not state['in_combat'] and state['has_loot']:
|
||||||
|
# pydirectinput.press(KEY_LOOT)
|
||||||
|
|
||||||
|
# 在 main 循环中使用:从 game_state 获取 state
|
||||||
|
if __name__ == "__main__":
|
||||||
|
bot = AutoBot()
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
state = parse_game_state()
|
||||||
|
if state:
|
||||||
|
target_hp = state.get('target_hp')
|
||||||
|
if target_hp is not None:
|
||||||
|
hp_part = f"血量:{state['hp']}% 目标血:{target_hp}% | 法力:{state['mp']}% | "
|
||||||
|
else:
|
||||||
|
hp_part = f"血量:{state['hp']}% | 法力:{state['mp']}% | "
|
||||||
|
print(
|
||||||
|
f"\r[状态] {hp_part}"
|
||||||
|
f"战斗:{'YES' if state['combat'] else 'NO '} | "
|
||||||
|
f"目标:{'YES' if state['target'] else 'NO '}",
|
||||||
|
end=""
|
||||||
|
)
|
||||||
|
bot.execute_logic(state)
|
||||||
|
time.sleep(0.1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n已停止。")
|
||||||
235
auto_bot_move.py
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import pydirectinput
|
||||||
|
|
||||||
|
from game_state import parse_game_state
|
||||||
|
from coordinate_patrol import CoordinatePatrol
|
||||||
|
from death_manager import DeathManager
|
||||||
|
from logistics_manager import LogisticsManager
|
||||||
|
|
||||||
|
# 定义按键常量
|
||||||
|
KEY_TAB = '3'
|
||||||
|
KEY_LOOT = '4' # 假设你在游戏里设置了互动按键为 F
|
||||||
|
KEY_ATTACK = '2' # 假设你的主攻击技能是 1
|
||||||
|
|
||||||
|
# 巡逻点配置文件
|
||||||
|
WAYPOINTS_FILE = 'waypoints.json'
|
||||||
|
|
||||||
|
# 默认巡逻航点(waypoints.json 不存在或无效时使用)
|
||||||
|
DEFAULT_WAYPOINTS = [(23.8, 71.0), (27.0, 79.0), (31.0, 72.0)]
|
||||||
|
|
||||||
|
|
||||||
|
def _config_base():
|
||||||
|
"""打包成 exe 时,优先从 exe 同目录读配置,否则用内嵌资源目录。"""
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
exe_dir = os.path.dirname(sys.executable)
|
||||||
|
if os.path.exists(exe_dir):
|
||||||
|
return exe_dir
|
||||||
|
return getattr(sys, '_MEIPASS', exe_dir)
|
||||||
|
return os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
def get_config_path(filename):
|
||||||
|
"""解析配置文件路径:先 exe 同目录,再打包内嵌目录。"""
|
||||||
|
base = _config_base()
|
||||||
|
p = os.path.join(base, filename)
|
||||||
|
if os.path.exists(p):
|
||||||
|
return p
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
meipass = getattr(sys, '_MEIPASS', '')
|
||||||
|
if meipass:
|
||||||
|
p2 = os.path.join(meipass, filename)
|
||||||
|
if os.path.exists(p2):
|
||||||
|
return p2
|
||||||
|
return os.path.join(base, filename)
|
||||||
|
|
||||||
|
|
||||||
|
def load_waypoints(path=None):
|
||||||
|
path = path or get_config_path(WAYPOINTS_FILE)
|
||||||
|
if os.path.exists(path):
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
return json.load(f)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def load_attack_loop(path):
|
||||||
|
"""从 JSON 加载攻击循环配置。支持 hp_below_percent/hp_key、mp_below_percent/mp_key(0=不启用)"""
|
||||||
|
if not path or not os.path.exists(path):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
cfg = json.load(f)
|
||||||
|
steps = cfg.get('steps') or []
|
||||||
|
trigger = float(cfg.get('trigger_chance', 0.3))
|
||||||
|
hp_below = int(cfg.get('hp_below_percent', 0)) or 0
|
||||||
|
mp_below = int(cfg.get('mp_below_percent', 0)) or 0
|
||||||
|
return {
|
||||||
|
'trigger_chance': trigger,
|
||||||
|
'steps': steps,
|
||||||
|
'hp_below_percent': hp_below,
|
||||||
|
'hp_key': (cfg.get('hp_key') or '').strip() or None,
|
||||||
|
'mp_below_percent': mp_below,
|
||||||
|
'mp_key': (cfg.get('mp_key') or '').strip() or None,
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class AutoBotMove:
|
||||||
|
def __init__(self, waypoints=None, waypoints_path=None, vendor_path=None, attack_loop_path=None, skinning_wait_sec=None):
|
||||||
|
self.last_tab_time = 0
|
||||||
|
self.is_running = True
|
||||||
|
self.is_moving = False
|
||||||
|
self.target_acquired_time = None # 本次获得目标的时间,用于仅在做「跑向怪」时做卡死检测
|
||||||
|
# 记录上一帧是否处于战斗/有目标,用于检测“刚刚脱战”的瞬间
|
||||||
|
self._was_in_combat_or_target = False
|
||||||
|
self.skinning_wait_sec = float(skinning_wait_sec) if skinning_wait_sec is not None else 1.5
|
||||||
|
self.attack_loop_config = load_attack_loop(attack_loop_path)
|
||||||
|
if waypoints is None:
|
||||||
|
path = waypoints_path or get_config_path(WAYPOINTS_FILE)
|
||||||
|
waypoints = load_waypoints(path) or DEFAULT_WAYPOINTS
|
||||||
|
self.patrol_controller = CoordinatePatrol(waypoints)
|
||||||
|
self.death_manager = DeathManager(self.patrol_controller)
|
||||||
|
vendor_file = vendor_path or get_config_path('vendor.json')
|
||||||
|
self.logistics_manager = LogisticsManager(vendor_file)
|
||||||
|
|
||||||
|
def execute_disengage_loot(self):
|
||||||
|
"""从有战斗/目标切换到完全脱战的瞬间,执行拾取 + 剥皮。"""
|
||||||
|
try:
|
||||||
|
# 拾取
|
||||||
|
pydirectinput.press(KEY_LOOT)
|
||||||
|
time.sleep(0.5)
|
||||||
|
# 剥皮
|
||||||
|
pydirectinput.press(KEY_LOOT)
|
||||||
|
time.sleep(self.skinning_wait_sec)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def execute_combat_logic(self, state):
|
||||||
|
if self.attack_loop_config:
|
||||||
|
cfg = self.attack_loop_config
|
||||||
|
if random.random() >= cfg['trigger_chance']:
|
||||||
|
return
|
||||||
|
hp = state.get('hp')
|
||||||
|
if hp is not None and cfg.get('hp_below_percent') and cfg.get('hp_key') and hp < cfg['hp_below_percent']:
|
||||||
|
pydirectinput.press(cfg['hp_key'])
|
||||||
|
time.sleep(0.2)
|
||||||
|
mp = state.get('mp')
|
||||||
|
if mp is not None and cfg.get('mp_below_percent') and cfg.get('mp_key') and mp < cfg['mp_below_percent']:
|
||||||
|
pydirectinput.press(cfg['mp_key'])
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
# 每次攻击前选中目标
|
||||||
|
pydirectinput.press(KEY_LOOT)
|
||||||
|
for step in cfg['steps']:
|
||||||
|
key = step.get('key') or KEY_ATTACK
|
||||||
|
delay = float(step.get('delay', 0.5))
|
||||||
|
pydirectinput.press(key)
|
||||||
|
time.sleep(delay)
|
||||||
|
return
|
||||||
|
# 默认:模拟手动按键的节奏
|
||||||
|
if random.random() < 0.3:
|
||||||
|
pydirectinput.press(KEY_ATTACK)
|
||||||
|
|
||||||
|
def execute_logic(self, state):
|
||||||
|
death = state.get('death_state', 0)
|
||||||
|
# 1. 死亡状态:尸体(1) 记录坐标并释放灵魂;灵魂(2) 跑尸
|
||||||
|
if death == 1:
|
||||||
|
self.patrol_controller.stop_all()
|
||||||
|
self.is_moving = False
|
||||||
|
self.patrol_controller.reset_stuck()
|
||||||
|
self.death_manager.on_death(state)
|
||||||
|
return
|
||||||
|
if death == 2:
|
||||||
|
self.death_manager.run_to_corpse(state)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 2. 后勤检查(脱战时):空格或耐久不足则回城
|
||||||
|
self.logistics_manager.check_logistics(state)
|
||||||
|
if self.logistics_manager.is_returning:
|
||||||
|
if self.is_moving:
|
||||||
|
self.patrol_controller.stop_all()
|
||||||
|
self.is_moving = False
|
||||||
|
self.patrol_controller.reset_stuck()
|
||||||
|
self.logistics_manager.run_route1_round(parse_game_state, self.patrol_controller)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 3. 战斗/有目标:停止移动,执行攻击逻辑;仅在「跑向怪」短窗口内做卡死检测
|
||||||
|
in_combat_or_target = bool(state['combat'] or state['target'])
|
||||||
|
if in_combat_or_target:
|
||||||
|
if self.target_acquired_time is None:
|
||||||
|
self.target_acquired_time = time.time()
|
||||||
|
if self.is_moving:
|
||||||
|
self.patrol_controller.stop_all()
|
||||||
|
self.is_moving = False
|
||||||
|
self.patrol_controller.reset_stuck()
|
||||||
|
# 跑向怪阶段的卡死检测:只在目标血量为 100% 时认为是在“刚刚开怪、接近怪物”,避免残血目标也触发跑向怪卡死逻辑
|
||||||
|
target_hp = state.get('target_hp')
|
||||||
|
if target_hp is not None and target_hp >= 100:
|
||||||
|
try:
|
||||||
|
if self.patrol_controller.stuck_handler.check_stuck(
|
||||||
|
state, self.patrol_controller.last_turn_end_time
|
||||||
|
):
|
||||||
|
print(">>> [战斗] 跑向怪时可能卡住障碍物,执行脱困")
|
||||||
|
self.patrol_controller.stop_all()
|
||||||
|
self.patrol_controller.stuck_handler.resolve_stuck()
|
||||||
|
self.patrol_controller.reset_stuck()
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self.execute_combat_logic(state)
|
||||||
|
self._was_in_combat_or_target = True
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# 从“有战斗/目标”切换到“完全脱战”的瞬间:尝试智能接回前方最近巡逻点
|
||||||
|
if self._was_in_combat_or_target:
|
||||||
|
# 执行脱战拾取
|
||||||
|
self.execute_disengage_loot()
|
||||||
|
x, y = state.get('x'), state.get('y')
|
||||||
|
if x is not None and y is not None:
|
||||||
|
try:
|
||||||
|
current_pos = (float(x), float(y))
|
||||||
|
# max_ahead: 前方查看 10 个点;max_dist: 最大允许接入距离 10
|
||||||
|
self.patrol_controller.snap_to_forward_waypoint(
|
||||||
|
current_pos, max_ahead=10, max_dist=10.0, skip_current=True
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self.target_acquired_time = None # 脱战/无目标时清零,下次获得目标再计时
|
||||||
|
self._was_in_combat_or_target = False
|
||||||
|
# 4. 没战斗没目标:巡逻(卡死检测在 patrol_controller.navigate 内)
|
||||||
|
self.is_moving = True
|
||||||
|
self.patrol_controller.navigate(state)
|
||||||
|
|
||||||
|
# 5. 顺便每隔几秒按一下 Tab(主动找怪)
|
||||||
|
if not state['target'] and (time.time() - self.last_tab_time > 2.0):
|
||||||
|
pydirectinput.press(KEY_TAB)
|
||||||
|
self.last_tab_time = time.time()
|
||||||
|
|
||||||
|
# 在 main 循环中使用:从 game_state 获取 state
|
||||||
|
if __name__ == "__main__":
|
||||||
|
bot = AutoBotMove()
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
state = parse_game_state()
|
||||||
|
if state:
|
||||||
|
death_txt = ('存活', '尸体', '灵魂')[state.get('death_state', 0)]
|
||||||
|
target_hp = state.get('target_hp')
|
||||||
|
if target_hp is not None:
|
||||||
|
hp_part = f"血:{state['hp']}% 目标血:{target_hp}% 法:{state['mp']}%"
|
||||||
|
else:
|
||||||
|
hp_part = f"血:{state['hp']}% 法:{state['mp']}%"
|
||||||
|
print(
|
||||||
|
f"\r[状态] {hp_part} | "
|
||||||
|
f"战斗:{'Y' if state['combat'] else 'N'} 目标:{'Y' if state['target'] else 'N'} | "
|
||||||
|
f"空格:{state.get('free_slots', 0)} 耐久:{state.get('durability', 0):.0%} {death_txt} | "
|
||||||
|
f"x:{state['x']} y:{state['y']} 朝向:{state['facing']:.1f}°",
|
||||||
|
end=""
|
||||||
|
)
|
||||||
|
bot.execute_logic(state)
|
||||||
|
time.sleep(0.1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n已停止。")
|
||||||
56
build.spec
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
# 魔兽世界自动巡逻/打怪 - PyInstaller 打包配置
|
||||||
|
# 打包命令: pyinstaller build.spec
|
||||||
|
|
||||||
|
block_cipher = None
|
||||||
|
|
||||||
|
# 需要随 exe 一起发布的数据文件(运行时会在 exe 同目录或临时目录解出)
|
||||||
|
added_files = [
|
||||||
|
('recorder\\*.json', 'recorder'),
|
||||||
|
('game_state_config.json', '.'),
|
||||||
|
]
|
||||||
|
|
||||||
|
a = Analysis(
|
||||||
|
['auto_bot_move.py'], # 主入口
|
||||||
|
pathex=[],
|
||||||
|
binaries=[],
|
||||||
|
datas=added_files,
|
||||||
|
hiddenimports=[
|
||||||
|
'pydirectinput',
|
||||||
|
'pygetwindow',
|
||||||
|
'PIL',
|
||||||
|
'PIL._tkinter_finder',
|
||||||
|
],
|
||||||
|
hookspath=[],
|
||||||
|
hooksconfig={},
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
win_no_prefer_redirects=False,
|
||||||
|
win_private_assemblies=False,
|
||||||
|
cipher=block_cipher,
|
||||||
|
noarchive=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||||
|
|
||||||
|
exe = EXE(
|
||||||
|
pyz,
|
||||||
|
a.scripts,
|
||||||
|
a.binaries,
|
||||||
|
a.zipfiles,
|
||||||
|
a.datas,
|
||||||
|
[],
|
||||||
|
name='AutoBotMove',
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
upx_exclude=[],
|
||||||
|
runtime_tmpdir=None,
|
||||||
|
console=True, # 保留控制台窗口,便于看状态和 Ctrl+C 停止
|
||||||
|
disable_windowed_traceback=False,
|
||||||
|
argv_emulation=False,
|
||||||
|
target_arch=None,
|
||||||
|
codesign_identity=None,
|
||||||
|
entitlements_file=None,
|
||||||
|
)
|
||||||
5515
build/build/Analysis-00.toc
Normal file
BIN
build/build/AutoBotMove.pkg
Normal file
3146
build/build/EXE-00.toc
Normal file
3124
build/build/PKG-00.toc
Normal file
BIN
build/build/PYZ-00.pyz
Normal file
2394
build/build/PYZ-00.toc
Normal file
8
build/build/Tree-00.toc
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
('recorder',
|
||||||
|
'recorder',
|
||||||
|
[],
|
||||||
|
'DATA',
|
||||||
|
[('recorder\\vendor.json', 'recorder\\vendor.json', 'DATA'),
|
||||||
|
('recorder\\waypoints.json', 'recorder\\waypoints.json', 'DATA'),
|
||||||
|
('recorder\\银色前线基地-修理商.json', 'recorder\\银色前线基地-修理商.json', 'DATA'),
|
||||||
|
('recorder\\银色前线基地-巡逻点.json', 'recorder\\银色前线基地-巡逻点.json', 'DATA')])
|
||||||
BIN
build/build/base_library.zip
Normal file
BIN
build/build/localpycs/pyimod01_archive.pyc
Normal file
BIN
build/build/localpycs/pyimod02_importers.pyc
Normal file
BIN
build/build/localpycs/pyimod03_ctypes.pyc
Normal file
BIN
build/build/localpycs/pyimod04_pywin32.pyc
Normal file
BIN
build/build/localpycs/struct.pyc
Normal file
208
build/build/warn-build.txt
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
|
||||||
|
This file lists modules PyInstaller was not able to find. This does not
|
||||||
|
necessarily mean this module is required for running your program. Python and
|
||||||
|
Python 3rd-party packages include a lot of conditional or optional modules. For
|
||||||
|
example the module 'ntpath' only exists on Windows, whereas the module
|
||||||
|
'posixpath' only exists on Posix systems.
|
||||||
|
|
||||||
|
Types if import:
|
||||||
|
* top-level: imported at the top-level - look at these first
|
||||||
|
* conditional: imported within an if-statement
|
||||||
|
* delayed: imported within a function
|
||||||
|
* optional: imported within a try-except-statement
|
||||||
|
|
||||||
|
IMPORTANT: Do NOT post this list to the issue-tracker. Use it as a basis for
|
||||||
|
tracking down the missing module yourself. Thanks!
|
||||||
|
|
||||||
|
missing module named pyimod02_importers - imported by C:\Users\南音\AppData\Local\Programs\Python\Python311\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgutil.py (delayed), C:\Users\南音\AppData\Local\Programs\Python\Python311\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgres.py (delayed)
|
||||||
|
missing module named pwd - imported by posixpath (delayed, conditional, optional), shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional), distutils.util (delayed, conditional, optional), distutils.archive_util (optional), netrc (delayed, conditional), getpass (delayed), http.server (delayed, optional), webbrowser (delayed), setuptools._distutils.archive_util (optional), setuptools._distutils.util (delayed, conditional, optional)
|
||||||
|
missing module named grp - imported by shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional), distutils.archive_util (optional), setuptools._distutils.archive_util (optional)
|
||||||
|
missing module named _posixsubprocess - imported by subprocess (conditional), multiprocessing.util (delayed)
|
||||||
|
missing module named fcntl - imported by subprocess (optional)
|
||||||
|
missing module named 'org.python' - imported by copy (optional), xml.sax (delayed, conditional)
|
||||||
|
missing module named org - imported by pickle (optional)
|
||||||
|
missing module named posix - imported by os (conditional, optional), posixpath (optional), shutil (conditional), importlib._bootstrap_external (conditional)
|
||||||
|
missing module named resource - imported by posix (top-level)
|
||||||
|
missing module named _manylinux - imported by packaging._manylinux (delayed, optional), setuptools._vendor.packaging._manylinux (delayed, optional), pkg_resources._vendor.packaging._manylinux (delayed, optional)
|
||||||
|
missing module named _posixshmem - imported by multiprocessing.resource_tracker (conditional), multiprocessing.shared_memory (conditional)
|
||||||
|
missing module named multiprocessing.set_start_method - imported by multiprocessing (top-level), multiprocessing.spawn (top-level)
|
||||||
|
missing module named multiprocessing.get_start_method - imported by multiprocessing (top-level), multiprocessing.spawn (top-level)
|
||||||
|
missing module named _frozen_importlib_external - imported by importlib._bootstrap (delayed), importlib (optional), importlib.abc (optional), zipimport (top-level)
|
||||||
|
excluded module named _frozen_importlib - imported by importlib (optional), importlib.abc (optional), zipimport (top-level)
|
||||||
|
missing module named multiprocessing.get_context - imported by multiprocessing (top-level), multiprocessing.pool (top-level), multiprocessing.managers (top-level), multiprocessing.sharedctypes (top-level)
|
||||||
|
missing module named multiprocessing.TimeoutError - imported by multiprocessing (top-level), multiprocessing.pool (top-level)
|
||||||
|
missing module named _scproxy - imported by urllib.request (conditional)
|
||||||
|
missing module named termios - imported by getpass (optional), tty (top-level)
|
||||||
|
missing module named 'java.lang' - imported by platform (delayed, optional), xml.sax._exceptions (conditional)
|
||||||
|
missing module named multiprocessing.BufferTooShort - imported by multiprocessing (top-level), multiprocessing.connection (top-level)
|
||||||
|
missing module named multiprocessing.AuthenticationError - imported by multiprocessing (top-level), multiprocessing.connection (top-level)
|
||||||
|
missing module named asyncio.DefaultEventLoopPolicy - imported by asyncio (delayed, conditional), asyncio.events (delayed, conditional)
|
||||||
|
missing module named pyparsing - imported by pkg_resources._vendor.pyparsing.diagram (top-level), setuptools._vendor.pyparsing.diagram (top-level)
|
||||||
|
missing module named railroad - imported by pkg_resources._vendor.pyparsing.diagram (top-level), setuptools._vendor.pyparsing.diagram (top-level)
|
||||||
|
missing module named readline - imported by site (delayed, optional), rlcompleter (optional), cmd (delayed, conditional, optional), code (delayed, conditional, optional), pdb (delayed, optional)
|
||||||
|
missing module named 'pkg_resources.extern.pyparsing' - imported by pkg_resources._vendor.packaging.markers (top-level), pkg_resources._vendor.packaging.requirements (top-level)
|
||||||
|
missing module named 'pkg_resources.extern.importlib_resources' - imported by pkg_resources._vendor.jaraco.text (optional)
|
||||||
|
missing module named 'pkg_resources.extern.more_itertools' - imported by pkg_resources._vendor.jaraco.functools (top-level)
|
||||||
|
missing module named 'com.sun' - imported by pkg_resources._vendor.appdirs (delayed, conditional, optional)
|
||||||
|
missing module named com - imported by pkg_resources._vendor.appdirs (delayed)
|
||||||
|
missing module named 'win32com.gen_py' - imported by win32com (conditional, optional)
|
||||||
|
missing module named _winreg - imported by platform (delayed, optional), pkg_resources._vendor.appdirs (delayed, conditional)
|
||||||
|
missing module named pkg_resources.extern.packaging - imported by pkg_resources.extern (top-level), pkg_resources (top-level)
|
||||||
|
missing module named pkg_resources.extern.appdirs - imported by pkg_resources.extern (top-level), pkg_resources (top-level)
|
||||||
|
missing module named 'pkg_resources.extern.jaraco' - imported by pkg_resources (top-level), pkg_resources._vendor.jaraco.text (top-level)
|
||||||
|
missing module named vms_lib - imported by platform (delayed, optional)
|
||||||
|
missing module named java - imported by platform (delayed)
|
||||||
|
missing module named usercustomize - imported by site (delayed, optional)
|
||||||
|
missing module named sitecustomize - imported by site (delayed, optional)
|
||||||
|
missing module named 'setuptools.extern.pyparsing' - imported by setuptools._vendor.packaging.requirements (top-level), setuptools._vendor.packaging.markers (top-level)
|
||||||
|
missing module named collections.Sequence - imported by collections (conditional), pyautogui (conditional), setuptools._vendor.ordered_set (optional)
|
||||||
|
missing module named collections.MutableSet - imported by collections (optional), setuptools._vendor.ordered_set (optional)
|
||||||
|
missing module named 'setuptools.extern.jaraco' - imported by setuptools._reqs (top-level), setuptools._entry_points (top-level), setuptools.command.egg_info (top-level), setuptools._vendor.jaraco.text (top-level)
|
||||||
|
missing module named setuptools.extern.importlib_resources - imported by setuptools.extern (conditional), setuptools._importlib (conditional), setuptools._vendor.jaraco.text (optional)
|
||||||
|
missing module named setuptools.extern.tomli - imported by setuptools.extern (delayed), setuptools.config.pyprojecttoml (delayed)
|
||||||
|
missing module named setuptools.extern.importlib_metadata - imported by setuptools.extern (conditional), setuptools._importlib (conditional)
|
||||||
|
missing module named setuptools.extern.ordered_set - imported by setuptools.extern (top-level), setuptools.dist (top-level)
|
||||||
|
missing module named setuptools.extern.packaging - imported by setuptools.extern (top-level), setuptools.dist (top-level), setuptools.command.egg_info (top-level), setuptools.depends (top-level)
|
||||||
|
missing module named 'setuptools.extern.more_itertools' - imported by setuptools.dist (top-level), setuptools.config.expand (delayed), setuptools._itertools (top-level), setuptools._entry_points (top-level), setuptools.msvc (top-level), setuptools._vendor.jaraco.functools (top-level)
|
||||||
|
missing module named 'setuptools.extern.packaging.version' - imported by setuptools.config.setupcfg (top-level), setuptools.msvc (top-level)
|
||||||
|
missing module named 'setuptools.extern.packaging.utils' - imported by setuptools.wheel (top-level)
|
||||||
|
missing module named 'setuptools.extern.packaging.tags' - imported by setuptools.wheel (top-level)
|
||||||
|
missing module named trove_classifiers - imported by setuptools.config._validate_pyproject.formats (optional)
|
||||||
|
missing module named 'setuptools.extern.packaging.specifiers' - imported by setuptools.config.setupcfg (top-level), setuptools.config._apply_pyprojecttoml (delayed)
|
||||||
|
missing module named 'setuptools.extern.packaging.requirements' - imported by setuptools.config.setupcfg (top-level)
|
||||||
|
missing module named importlib_metadata - imported by setuptools._importlib (delayed, optional)
|
||||||
|
missing module named PIL._imagingagg - imported by PIL (delayed, conditional, optional), PIL.ImageDraw (delayed, conditional, optional)
|
||||||
|
missing module named collections.Callable - imported by collections (optional), cffi.api (optional)
|
||||||
|
missing module named _dummy_thread - imported by cffi.lock (conditional, optional), numpy.core.arrayprint (optional)
|
||||||
|
missing module named dummy_thread - imported by cffi.lock (conditional, optional)
|
||||||
|
missing module named thread - imported by cffi.lock (conditional, optional), cffi.cparser (conditional, optional)
|
||||||
|
missing module named cStringIO - imported by cffi.ffiplatform (optional)
|
||||||
|
missing module named cPickle - imported by pycparser.ply.yacc (delayed, optional)
|
||||||
|
missing module named cffi._pycparser - imported by cffi (optional), cffi.cparser (optional)
|
||||||
|
missing module named olefile - imported by PIL.FpxImagePlugin (top-level), PIL.MicImagePlugin (top-level)
|
||||||
|
missing module named psutil - imported by numpy.testing._private.utils (delayed, optional)
|
||||||
|
missing module named numpy.core.result_type - imported by numpy.core (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.float_ - imported by numpy.core (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.number - imported by numpy.core (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.object_ - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.max - imported by numpy.core (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.all - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.errstate - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.bool_ - imported by numpy.core (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.inf - imported by numpy.core (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.isnan - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.array2string - imported by numpy.core (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.lib.imag - imported by numpy.lib (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.lib.real - imported by numpy.lib (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.lib.iscomplexobj - imported by numpy.lib (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.signbit - imported by numpy.core (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.isscalar - imported by numpy.core (delayed), numpy.testing._private.utils (delayed), numpy.lib.polynomial (top-level)
|
||||||
|
missing module named numpy.core.array - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.testing._private.utils (top-level), numpy.lib.polynomial (top-level)
|
||||||
|
missing module named numpy.core.isnat - imported by numpy.core (top-level), numpy.testing._private.utils (top-level)
|
||||||
|
missing module named numpy.core.ndarray - imported by numpy.core (top-level), numpy.testing._private.utils (top-level), numpy.lib.utils (top-level)
|
||||||
|
missing module named numpy.core.array_repr - imported by numpy.core (top-level), numpy.testing._private.utils (top-level)
|
||||||
|
missing module named numpy.core.arange - imported by numpy.core (top-level), numpy.testing._private.utils (top-level), numpy.fft.helper (top-level)
|
||||||
|
missing module named numpy.core.empty - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.testing._private.utils (top-level), numpy.fft.helper (top-level)
|
||||||
|
missing module named numpy.core.float32 - imported by numpy.core (top-level), numpy.testing._private.utils (top-level)
|
||||||
|
missing module named numpy.core.intp - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.testing._private.utils (top-level)
|
||||||
|
missing module named numpy.core.linspace - imported by numpy.core (top-level), numpy.lib.index_tricks (top-level)
|
||||||
|
missing module named numpy.core.iinfo - imported by numpy.core (top-level), numpy.lib.twodim_base (top-level)
|
||||||
|
missing module named numpy.core.transpose - imported by numpy.core (top-level), numpy.lib.function_base (top-level)
|
||||||
|
missing module named numpy._typing._ufunc - imported by numpy._typing (conditional)
|
||||||
|
missing module named numpy.uint - imported by numpy (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.core.asarray - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.lib.utils (top-level), numpy.fft._pocketfft (top-level), numpy.fft.helper (top-level)
|
||||||
|
missing module named numpy.core.integer - imported by numpy.core (top-level), numpy.fft.helper (top-level)
|
||||||
|
missing module named numpy.core.sqrt - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.fft._pocketfft (top-level)
|
||||||
|
missing module named numpy.core.conjugate - imported by numpy.core (top-level), numpy.fft._pocketfft (top-level)
|
||||||
|
missing module named numpy.core.swapaxes - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.fft._pocketfft (top-level)
|
||||||
|
missing module named numpy.core.zeros - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.fft._pocketfft (top-level)
|
||||||
|
missing module named numpy.core.reciprocal - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.sort - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.argsort - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.sign - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.count_nonzero - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.divide - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.matmul - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.asanyarray - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.atleast_2d - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.prod - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.amax - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.amin - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.moveaxis - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.geterrobj - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.finfo - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.lib.polynomial (top-level)
|
||||||
|
missing module named numpy.core.isfinite - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.sum - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.multiply - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.add - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.dot - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.lib.polynomial (top-level)
|
||||||
|
missing module named numpy.core.Inf - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.newaxis - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.complexfloating - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.inexact - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.cdouble - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.csingle - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.double - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.single - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.intc - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.empty_like - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named threadpoolctl - imported by numpy.lib.utils (delayed, optional)
|
||||||
|
missing module named numpy.core.ufunc - imported by numpy.core (top-level), numpy.lib.utils (top-level)
|
||||||
|
missing module named numpy.core.ones - imported by numpy.core (top-level), numpy.lib.polynomial (top-level)
|
||||||
|
missing module named numpy.core.hstack - imported by numpy.core (top-level), numpy.lib.polynomial (top-level)
|
||||||
|
missing module named numpy.core.atleast_1d - imported by numpy.core (top-level), numpy.lib.polynomial (top-level)
|
||||||
|
missing module named numpy.core.atleast_3d - imported by numpy.core (top-level), numpy.lib.shape_base (top-level)
|
||||||
|
missing module named numpy.core.vstack - imported by numpy.core (top-level), numpy.lib.shape_base (top-level)
|
||||||
|
missing module named pickle5 - imported by numpy.compat.py3k (optional)
|
||||||
|
missing module named yaml - imported by numpy.__config__ (delayed)
|
||||||
|
missing module named numpy.eye - imported by numpy (delayed), numpy.core.numeric (delayed)
|
||||||
|
missing module named numpy.recarray - imported by numpy (top-level), numpy.lib.recfunctions (top-level), numpy.ma.mrecords (top-level)
|
||||||
|
missing module named numpy.expand_dims - imported by numpy (top-level), numpy.ma.core (top-level)
|
||||||
|
missing module named numpy.array - imported by numpy (top-level), numpy.ma.core (top-level), numpy.ma.extras (top-level), numpy.ma.mrecords (top-level)
|
||||||
|
missing module named numpy.iscomplexobj - imported by numpy (top-level), numpy.ma.core (top-level)
|
||||||
|
missing module named numpy.amin - imported by numpy (top-level), numpy.ma.core (top-level)
|
||||||
|
missing module named numpy.amax - imported by numpy (top-level), numpy.ma.core (top-level)
|
||||||
|
missing module named numpy.isinf - imported by numpy (top-level), numpy.testing._private.utils (top-level)
|
||||||
|
missing module named numpy.isnan - imported by numpy (top-level), numpy.testing._private.utils (top-level)
|
||||||
|
missing module named numpy.isfinite - imported by numpy (top-level), numpy.testing._private.utils (top-level)
|
||||||
|
missing module named numpy.float64 - imported by numpy (top-level), numpy.array_api._typing (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.float32 - imported by numpy (top-level), numpy.array_api._typing (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.uint64 - imported by numpy (top-level), numpy.array_api._typing (top-level), numpy.random.mtrand (top-level), numpy.random.bit_generator (top-level), numpy.random._philox (top-level), numpy.random._sfc64 (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.uint32 - imported by numpy (top-level), numpy.array_api._typing (top-level), numpy.random.mtrand (top-level), numpy.random.bit_generator (top-level), numpy.random._generator (top-level), numpy.random._mt19937 (top-level)
|
||||||
|
missing module named numpy.uint16 - imported by numpy (top-level), numpy.array_api._typing (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.uint8 - imported by numpy (top-level), numpy.array_api._typing (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.int64 - imported by numpy (top-level), numpy.array_api._typing (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.int32 - imported by numpy (top-level), numpy.array_api._typing (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.int16 - imported by numpy (top-level), numpy.array_api._typing (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.int8 - imported by numpy (top-level), numpy.array_api._typing (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.bytes_ - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.str_ - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.void - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.object_ - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.datetime64 - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.timedelta64 - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.number - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.complexfloating - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.floating - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.integer - imported by numpy (top-level), numpy._typing._array_like (top-level), numpy.ctypeslib (top-level)
|
||||||
|
missing module named numpy.unsignedinteger - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.bool_ - imported by numpy (top-level), numpy._typing._array_like (top-level), numpy.ma.core (top-level), numpy.ma.mrecords (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.generic - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.dtype - imported by numpy (top-level), numpy._typing._array_like (top-level), numpy.array_api._typing (top-level), numpy.ma.mrecords (top-level), numpy.random.mtrand (top-level), numpy.random.bit_generator (top-level), numpy.random._philox (top-level), numpy.random._sfc64 (top-level), numpy.random._generator (top-level), numpy.random._mt19937 (top-level), numpy.ctypeslib (top-level)
|
||||||
|
missing module named numpy.ndarray - imported by numpy (top-level), numpy._typing._array_like (top-level), numpy.ma.core (top-level), numpy.ma.extras (top-level), numpy.lib.recfunctions (top-level), numpy.ma.mrecords (top-level), numpy.random.mtrand (top-level), numpy.random.bit_generator (top-level), numpy.random._philox (top-level), numpy.random._sfc64 (top-level), numpy.random._generator (top-level), numpy.random._mt19937 (top-level), numpy.ctypeslib (top-level)
|
||||||
|
missing module named numpy.ufunc - imported by numpy (top-level), numpy._typing (top-level), numpy.testing.overrides (top-level)
|
||||||
|
missing module named numpy.histogramdd - imported by numpy (delayed), numpy.lib.twodim_base (delayed)
|
||||||
|
missing module named numpy._distributor_init_local - imported by numpy (optional), numpy._distributor_init (optional)
|
||||||
|
missing module named defusedxml - imported by PIL.Image (optional)
|
||||||
|
missing module named Quartz - imported by pygetwindow._pygetwindow_macos (top-level), pyautogui._pyautogui_osx (optional)
|
||||||
|
missing module named 'Xlib.XK' - imported by pyautogui._pyautogui_x11 (top-level)
|
||||||
|
missing module named 'Xlib.ext' - imported by pyautogui._pyautogui_x11 (top-level)
|
||||||
|
missing module named Xlib - imported by mouseinfo (conditional), pyautogui._pyautogui_x11 (top-level)
|
||||||
|
missing module named 'Xlib.display' - imported by pyautogui._pyautogui_x11 (top-level)
|
||||||
|
missing module named AppKit - imported by pyperclip (delayed, conditional, optional), pyautogui._pyautogui_osx (top-level)
|
||||||
|
missing module named Tkinter - imported by mouseinfo (conditional, optional)
|
||||||
|
missing module named 'rubicon.objc' - imported by mouseinfo (conditional)
|
||||||
|
missing module named rubicon - imported by mouseinfo (conditional)
|
||||||
|
missing module named Foundation - imported by pyperclip (delayed, conditional, optional)
|
||||||
|
missing module named PyQt5 - imported by pyperclip (delayed, conditional, optional)
|
||||||
|
missing module named qtpy - imported by pyperclip (delayed, conditional, optional)
|
||||||
31743
build/build/xref-build.html
Normal file
6383
build/build_wow_multikey/Analysis-00.toc
Normal file
3590
build/build_wow_multikey/EXE-00.toc
Normal file
3568
build/build_wow_multikey/PKG-00.toc
Normal file
BIN
build/build_wow_multikey/PYZ-00.pyz
Normal file
2787
build/build_wow_multikey/PYZ-00.toc
Normal file
BIN
build/build_wow_multikey/WoW_MultiTool.pkg
Normal file
BIN
build/build_wow_multikey/base_library.zip
Normal file
BIN
build/build_wow_multikey/localpycs/pyimod01_archive.pyc
Normal file
BIN
build/build_wow_multikey/localpycs/pyimod02_importers.pyc
Normal file
BIN
build/build_wow_multikey/localpycs/pyimod03_ctypes.pyc
Normal file
BIN
build/build_wow_multikey/localpycs/pyimod04_pywin32.pyc
Normal file
BIN
build/build_wow_multikey/localpycs/struct.pyc
Normal file
223
build/build_wow_multikey/warn-build_wow_multikey.txt
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
|
||||||
|
This file lists modules PyInstaller was not able to find. This does not
|
||||||
|
necessarily mean this module is required for running your program. Python and
|
||||||
|
Python 3rd-party packages include a lot of conditional or optional modules. For
|
||||||
|
example the module 'ntpath' only exists on Windows, whereas the module
|
||||||
|
'posixpath' only exists on Posix systems.
|
||||||
|
|
||||||
|
Types if import:
|
||||||
|
* top-level: imported at the top-level - look at these first
|
||||||
|
* conditional: imported within an if-statement
|
||||||
|
* delayed: imported within a function
|
||||||
|
* optional: imported within a try-except-statement
|
||||||
|
|
||||||
|
IMPORTANT: Do NOT post this list to the issue-tracker. Use it as a basis for
|
||||||
|
tracking down the missing module yourself. Thanks!
|
||||||
|
|
||||||
|
missing module named 'org.python' - imported by copy (optional), xml.sax (delayed, conditional)
|
||||||
|
missing module named pwd - imported by posixpath (delayed, conditional, optional), shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional), distutils.util (delayed, conditional, optional), distutils.archive_util (optional), netrc (delayed, conditional), getpass (delayed), http.server (delayed, optional), webbrowser (delayed), setuptools._distutils.archive_util (optional), setuptools._distutils.util (delayed, conditional, optional)
|
||||||
|
missing module named grp - imported by shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional), distutils.archive_util (optional), setuptools._distutils.archive_util (optional)
|
||||||
|
missing module named posix - imported by os (conditional, optional), posixpath (optional), shutil (conditional), importlib._bootstrap_external (conditional)
|
||||||
|
missing module named resource - imported by posix (top-level)
|
||||||
|
missing module named _frozen_importlib_external - imported by importlib._bootstrap (delayed), importlib (optional), importlib.abc (optional), zipimport (top-level)
|
||||||
|
excluded module named _frozen_importlib - imported by importlib (optional), importlib.abc (optional), zipimport (top-level)
|
||||||
|
missing module named pyimod02_importers - imported by C:\Users\南音\AppData\Local\Programs\Python\Python311\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgutil.py (delayed), C:\Users\南音\AppData\Local\Programs\Python\Python311\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgres.py (delayed)
|
||||||
|
missing module named _posixsubprocess - imported by subprocess (conditional), multiprocessing.util (delayed)
|
||||||
|
missing module named fcntl - imported by subprocess (optional)
|
||||||
|
missing module named org - imported by pickle (optional)
|
||||||
|
missing module named _manylinux - imported by packaging._manylinux (delayed, optional), setuptools._vendor.packaging._manylinux (delayed, optional), pkg_resources._vendor.packaging._manylinux (delayed, optional)
|
||||||
|
missing module named _posixshmem - imported by multiprocessing.resource_tracker (conditional), multiprocessing.shared_memory (conditional)
|
||||||
|
missing module named multiprocessing.set_start_method - imported by multiprocessing (top-level), multiprocessing.spawn (top-level)
|
||||||
|
missing module named multiprocessing.get_start_method - imported by multiprocessing (top-level), multiprocessing.spawn (top-level)
|
||||||
|
missing module named multiprocessing.get_context - imported by multiprocessing (top-level), multiprocessing.pool (top-level), multiprocessing.managers (top-level), multiprocessing.sharedctypes (top-level)
|
||||||
|
missing module named multiprocessing.TimeoutError - imported by multiprocessing (top-level), multiprocessing.pool (top-level)
|
||||||
|
missing module named _scproxy - imported by urllib.request (conditional)
|
||||||
|
missing module named termios - imported by getpass (optional), tty (top-level)
|
||||||
|
missing module named 'java.lang' - imported by platform (delayed, optional), xml.sax._exceptions (conditional)
|
||||||
|
missing module named multiprocessing.BufferTooShort - imported by multiprocessing (top-level), multiprocessing.connection (top-level)
|
||||||
|
missing module named multiprocessing.AuthenticationError - imported by multiprocessing (top-level), multiprocessing.connection (top-level)
|
||||||
|
missing module named asyncio.DefaultEventLoopPolicy - imported by asyncio (delayed, conditional), asyncio.events (delayed, conditional)
|
||||||
|
missing module named pyparsing - imported by pkg_resources._vendor.pyparsing.diagram (top-level), setuptools._vendor.pyparsing.diagram (top-level)
|
||||||
|
missing module named railroad - imported by pkg_resources._vendor.pyparsing.diagram (top-level), setuptools._vendor.pyparsing.diagram (top-level)
|
||||||
|
missing module named readline - imported by site (delayed, optional), rlcompleter (optional), cmd (delayed, conditional, optional), code (delayed, conditional, optional), pdb (delayed, optional)
|
||||||
|
missing module named 'pkg_resources.extern.pyparsing' - imported by pkg_resources._vendor.packaging.markers (top-level), pkg_resources._vendor.packaging.requirements (top-level)
|
||||||
|
missing module named 'pkg_resources.extern.importlib_resources' - imported by pkg_resources._vendor.jaraco.text (optional)
|
||||||
|
missing module named 'pkg_resources.extern.more_itertools' - imported by pkg_resources._vendor.jaraco.functools (top-level)
|
||||||
|
missing module named 'com.sun' - imported by pkg_resources._vendor.appdirs (delayed, conditional, optional)
|
||||||
|
missing module named com - imported by pkg_resources._vendor.appdirs (delayed)
|
||||||
|
missing module named 'win32com.gen_py' - imported by win32com (conditional, optional)
|
||||||
|
missing module named _winreg - imported by platform (delayed, optional), pkg_resources._vendor.appdirs (delayed, conditional)
|
||||||
|
missing module named pkg_resources.extern.packaging - imported by pkg_resources.extern (top-level), pkg_resources (top-level)
|
||||||
|
missing module named pkg_resources.extern.appdirs - imported by pkg_resources.extern (top-level), pkg_resources (top-level)
|
||||||
|
missing module named 'pkg_resources.extern.jaraco' - imported by pkg_resources (top-level), pkg_resources._vendor.jaraco.text (top-level)
|
||||||
|
missing module named vms_lib - imported by platform (delayed, optional)
|
||||||
|
missing module named java - imported by platform (delayed)
|
||||||
|
missing module named usercustomize - imported by site (delayed, optional)
|
||||||
|
missing module named sitecustomize - imported by site (delayed, optional)
|
||||||
|
missing module named 'setuptools.extern.pyparsing' - imported by setuptools._vendor.packaging.requirements (top-level), setuptools._vendor.packaging.markers (top-level)
|
||||||
|
missing module named collections.Sequence - imported by collections (conditional), pyautogui (conditional), setuptools._vendor.ordered_set (optional)
|
||||||
|
missing module named collections.MutableSet - imported by collections (optional), setuptools._vendor.ordered_set (optional)
|
||||||
|
missing module named 'setuptools.extern.jaraco' - imported by setuptools._reqs (top-level), setuptools._entry_points (top-level), setuptools.command.egg_info (top-level), setuptools._vendor.jaraco.text (top-level)
|
||||||
|
missing module named setuptools.extern.importlib_resources - imported by setuptools.extern (conditional), setuptools._importlib (conditional), setuptools._vendor.jaraco.text (optional)
|
||||||
|
missing module named setuptools.extern.tomli - imported by setuptools.extern (delayed), setuptools.config.pyprojecttoml (delayed)
|
||||||
|
missing module named setuptools.extern.importlib_metadata - imported by setuptools.extern (conditional), setuptools._importlib (conditional)
|
||||||
|
missing module named setuptools.extern.ordered_set - imported by setuptools.extern (top-level), setuptools.dist (top-level)
|
||||||
|
missing module named setuptools.extern.packaging - imported by setuptools.extern (top-level), setuptools.dist (top-level), setuptools.command.egg_info (top-level), setuptools.depends (top-level)
|
||||||
|
missing module named 'setuptools.extern.more_itertools' - imported by setuptools.dist (top-level), setuptools.config.expand (delayed), setuptools._itertools (top-level), setuptools._entry_points (top-level), setuptools.msvc (top-level), setuptools._vendor.jaraco.functools (top-level)
|
||||||
|
missing module named 'setuptools.extern.packaging.version' - imported by setuptools.config.setupcfg (top-level), setuptools.msvc (top-level)
|
||||||
|
missing module named 'setuptools.extern.packaging.utils' - imported by setuptools.wheel (top-level)
|
||||||
|
missing module named 'setuptools.extern.packaging.tags' - imported by setuptools.wheel (top-level)
|
||||||
|
missing module named trove_classifiers - imported by setuptools.config._validate_pyproject.formats (optional)
|
||||||
|
missing module named 'setuptools.extern.packaging.specifiers' - imported by setuptools.config.setupcfg (top-level), setuptools.config._apply_pyprojecttoml (delayed)
|
||||||
|
missing module named 'setuptools.extern.packaging.requirements' - imported by setuptools.config.setupcfg (top-level)
|
||||||
|
missing module named importlib_metadata - imported by setuptools._importlib (delayed, optional)
|
||||||
|
missing module named PIL._imagingagg - imported by PIL (delayed, conditional, optional), PIL.ImageDraw (delayed, conditional, optional)
|
||||||
|
missing module named collections.Callable - imported by collections (optional), cffi.api (optional), socks (optional)
|
||||||
|
missing module named _dummy_thread - imported by cffi.lock (conditional, optional), numpy.core.arrayprint (optional)
|
||||||
|
missing module named dummy_thread - imported by cffi.lock (conditional, optional)
|
||||||
|
missing module named thread - imported by cffi.lock (conditional, optional), cffi.cparser (conditional, optional)
|
||||||
|
missing module named cStringIO - imported by cffi.ffiplatform (optional)
|
||||||
|
missing module named cPickle - imported by pycparser.ply.yacc (delayed, optional)
|
||||||
|
missing module named cffi._pycparser - imported by cffi (optional), cffi.cparser (optional)
|
||||||
|
missing module named olefile - imported by PIL.FpxImagePlugin (top-level), PIL.MicImagePlugin (top-level)
|
||||||
|
missing module named psutil - imported by numpy.testing._private.utils (delayed, optional)
|
||||||
|
missing module named numpy.core.result_type - imported by numpy.core (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.float_ - imported by numpy.core (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.number - imported by numpy.core (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.object_ - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.max - imported by numpy.core (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.all - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.errstate - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.bool_ - imported by numpy.core (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.inf - imported by numpy.core (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.isnan - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.array2string - imported by numpy.core (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.lib.imag - imported by numpy.lib (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.lib.real - imported by numpy.lib (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.lib.iscomplexobj - imported by numpy.lib (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.signbit - imported by numpy.core (delayed), numpy.testing._private.utils (delayed)
|
||||||
|
missing module named numpy.core.isscalar - imported by numpy.core (delayed), numpy.testing._private.utils (delayed), numpy.lib.polynomial (top-level)
|
||||||
|
missing module named numpy.core.array - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.testing._private.utils (top-level), numpy.lib.polynomial (top-level)
|
||||||
|
missing module named numpy.core.isnat - imported by numpy.core (top-level), numpy.testing._private.utils (top-level)
|
||||||
|
missing module named numpy.core.ndarray - imported by numpy.core (top-level), numpy.testing._private.utils (top-level), numpy.lib.utils (top-level)
|
||||||
|
missing module named numpy.core.array_repr - imported by numpy.core (top-level), numpy.testing._private.utils (top-level)
|
||||||
|
missing module named numpy.core.arange - imported by numpy.core (top-level), numpy.testing._private.utils (top-level), numpy.fft.helper (top-level)
|
||||||
|
missing module named numpy.core.empty - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.testing._private.utils (top-level), numpy.fft.helper (top-level)
|
||||||
|
missing module named numpy.core.float32 - imported by numpy.core (top-level), numpy.testing._private.utils (top-level)
|
||||||
|
missing module named numpy.core.intp - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.testing._private.utils (top-level)
|
||||||
|
missing module named numpy.core.linspace - imported by numpy.core (top-level), numpy.lib.index_tricks (top-level)
|
||||||
|
missing module named numpy.core.iinfo - imported by numpy.core (top-level), numpy.lib.twodim_base (top-level)
|
||||||
|
missing module named numpy.core.transpose - imported by numpy.core (top-level), numpy.lib.function_base (top-level)
|
||||||
|
missing module named numpy._typing._ufunc - imported by numpy._typing (conditional)
|
||||||
|
missing module named numpy.uint - imported by numpy (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.core.asarray - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.lib.utils (top-level), numpy.fft._pocketfft (top-level), numpy.fft.helper (top-level)
|
||||||
|
missing module named numpy.core.integer - imported by numpy.core (top-level), numpy.fft.helper (top-level)
|
||||||
|
missing module named numpy.core.sqrt - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.fft._pocketfft (top-level)
|
||||||
|
missing module named numpy.core.conjugate - imported by numpy.core (top-level), numpy.fft._pocketfft (top-level)
|
||||||
|
missing module named numpy.core.swapaxes - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.fft._pocketfft (top-level)
|
||||||
|
missing module named numpy.core.zeros - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.fft._pocketfft (top-level)
|
||||||
|
missing module named numpy.core.reciprocal - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.sort - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.argsort - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.sign - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.count_nonzero - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.divide - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.matmul - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.asanyarray - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.atleast_2d - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.prod - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.amax - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.amin - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.moveaxis - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.geterrobj - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.finfo - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.lib.polynomial (top-level)
|
||||||
|
missing module named numpy.core.isfinite - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.sum - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.multiply - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.add - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.dot - imported by numpy.core (top-level), numpy.linalg.linalg (top-level), numpy.lib.polynomial (top-level)
|
||||||
|
missing module named numpy.core.Inf - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.newaxis - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.complexfloating - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.inexact - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.cdouble - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.csingle - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.double - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.single - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.intc - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named numpy.core.empty_like - imported by numpy.core (top-level), numpy.linalg.linalg (top-level)
|
||||||
|
missing module named threadpoolctl - imported by numpy.lib.utils (delayed, optional)
|
||||||
|
missing module named numpy.core.ufunc - imported by numpy.core (top-level), numpy.lib.utils (top-level)
|
||||||
|
missing module named numpy.core.ones - imported by numpy.core (top-level), numpy.lib.polynomial (top-level)
|
||||||
|
missing module named numpy.core.hstack - imported by numpy.core (top-level), numpy.lib.polynomial (top-level)
|
||||||
|
missing module named numpy.core.atleast_1d - imported by numpy.core (top-level), numpy.lib.polynomial (top-level)
|
||||||
|
missing module named numpy.core.atleast_3d - imported by numpy.core (top-level), numpy.lib.shape_base (top-level)
|
||||||
|
missing module named numpy.core.vstack - imported by numpy.core (top-level), numpy.lib.shape_base (top-level)
|
||||||
|
missing module named pickle5 - imported by numpy.compat.py3k (optional)
|
||||||
|
missing module named yaml - imported by numpy.__config__ (delayed)
|
||||||
|
missing module named numpy.eye - imported by numpy (delayed), numpy.core.numeric (delayed)
|
||||||
|
missing module named numpy.recarray - imported by numpy (top-level), numpy.lib.recfunctions (top-level), numpy.ma.mrecords (top-level)
|
||||||
|
missing module named numpy.expand_dims - imported by numpy (top-level), numpy.ma.core (top-level)
|
||||||
|
missing module named numpy.array - imported by numpy (top-level), numpy.ma.core (top-level), numpy.ma.extras (top-level), numpy.ma.mrecords (top-level)
|
||||||
|
missing module named numpy.iscomplexobj - imported by numpy (top-level), numpy.ma.core (top-level)
|
||||||
|
missing module named numpy.amin - imported by numpy (top-level), numpy.ma.core (top-level)
|
||||||
|
missing module named numpy.amax - imported by numpy (top-level), numpy.ma.core (top-level)
|
||||||
|
missing module named numpy.isinf - imported by numpy (top-level), numpy.testing._private.utils (top-level)
|
||||||
|
missing module named numpy.isnan - imported by numpy (top-level), numpy.testing._private.utils (top-level)
|
||||||
|
missing module named numpy.isfinite - imported by numpy (top-level), numpy.testing._private.utils (top-level)
|
||||||
|
missing module named numpy.float64 - imported by numpy (top-level), numpy.array_api._typing (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.float32 - imported by numpy (top-level), numpy.array_api._typing (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.uint64 - imported by numpy (top-level), numpy.array_api._typing (top-level), numpy.random.mtrand (top-level), numpy.random.bit_generator (top-level), numpy.random._philox (top-level), numpy.random._sfc64 (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.uint32 - imported by numpy (top-level), numpy.array_api._typing (top-level), numpy.random.mtrand (top-level), numpy.random.bit_generator (top-level), numpy.random._generator (top-level), numpy.random._mt19937 (top-level)
|
||||||
|
missing module named numpy.uint16 - imported by numpy (top-level), numpy.array_api._typing (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.uint8 - imported by numpy (top-level), numpy.array_api._typing (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.int64 - imported by numpy (top-level), numpy.array_api._typing (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.int32 - imported by numpy (top-level), numpy.array_api._typing (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.int16 - imported by numpy (top-level), numpy.array_api._typing (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.int8 - imported by numpy (top-level), numpy.array_api._typing (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.bytes_ - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.str_ - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.void - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.object_ - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.datetime64 - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.timedelta64 - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.number - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.complexfloating - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.floating - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.integer - imported by numpy (top-level), numpy._typing._array_like (top-level), numpy.ctypeslib (top-level)
|
||||||
|
missing module named numpy.unsignedinteger - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.bool_ - imported by numpy (top-level), numpy._typing._array_like (top-level), numpy.ma.core (top-level), numpy.ma.mrecords (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level)
|
||||||
|
missing module named numpy.generic - imported by numpy (top-level), numpy._typing._array_like (top-level)
|
||||||
|
missing module named numpy.dtype - imported by numpy (top-level), numpy._typing._array_like (top-level), numpy.array_api._typing (top-level), numpy.ma.mrecords (top-level), numpy.random.mtrand (top-level), numpy.random.bit_generator (top-level), numpy.random._philox (top-level), numpy.random._sfc64 (top-level), numpy.random._generator (top-level), numpy.random._mt19937 (top-level), numpy.ctypeslib (top-level)
|
||||||
|
missing module named numpy.ndarray - imported by numpy (top-level), numpy._typing._array_like (top-level), numpy.ma.core (top-level), numpy.ma.extras (top-level), numpy.lib.recfunctions (top-level), numpy.ma.mrecords (top-level), numpy.random.mtrand (top-level), numpy.random.bit_generator (top-level), numpy.random._philox (top-level), numpy.random._sfc64 (top-level), numpy.random._generator (top-level), numpy.random._mt19937 (top-level), numpy.ctypeslib (top-level)
|
||||||
|
missing module named numpy.ufunc - imported by numpy (top-level), numpy._typing (top-level), numpy.testing.overrides (top-level)
|
||||||
|
missing module named numpy.histogramdd - imported by numpy (delayed), numpy.lib.twodim_base (delayed)
|
||||||
|
missing module named numpy._distributor_init_local - imported by numpy (optional), numpy._distributor_init (optional)
|
||||||
|
missing module named defusedxml - imported by PIL.Image (optional)
|
||||||
|
missing module named 'Xlib.XK' - imported by pyautogui._pyautogui_x11 (top-level)
|
||||||
|
missing module named 'Xlib.ext' - imported by pyautogui._pyautogui_x11 (top-level)
|
||||||
|
missing module named Xlib - imported by mouseinfo (conditional), pyautogui._pyautogui_x11 (top-level)
|
||||||
|
missing module named 'Xlib.display' - imported by pyautogui._pyautogui_x11 (top-level)
|
||||||
|
missing module named AppKit - imported by pyperclip (delayed, conditional, optional), pyautogui._pyautogui_osx (top-level)
|
||||||
|
missing module named Quartz - imported by pygetwindow._pygetwindow_macos (top-level), pyautogui._pyautogui_osx (optional)
|
||||||
|
missing module named Tkinter - imported by mouseinfo (conditional, optional)
|
||||||
|
missing module named 'rubicon.objc' - imported by mouseinfo (conditional)
|
||||||
|
missing module named rubicon - imported by mouseinfo (conditional)
|
||||||
|
missing module named Foundation - imported by pyperclip (delayed, conditional, optional)
|
||||||
|
missing module named PyQt5 - imported by pyperclip (delayed, conditional, optional)
|
||||||
|
missing module named qtpy - imported by pyperclip (delayed, conditional, optional)
|
||||||
|
missing module named simplejson - imported by requests.compat (conditional, optional)
|
||||||
|
missing module named dummy_threading - imported by requests.cookies (optional)
|
||||||
|
missing module named 'h2.events' - imported by urllib3.http2.connection (top-level)
|
||||||
|
missing module named 'h2.connection' - imported by urllib3.http2.connection (top-level)
|
||||||
|
missing module named h2 - imported by urllib3.http2.connection (top-level)
|
||||||
|
missing module named zstandard - imported by urllib3.util.request (optional), urllib3.response (optional)
|
||||||
|
missing module named brotli - imported by urllib3.util.request (optional), urllib3.response (optional)
|
||||||
|
missing module named brotlicffi - imported by urllib3.util.request (optional), urllib3.response (optional)
|
||||||
|
missing module named win_inet_pton - imported by socks (conditional, optional)
|
||||||
|
missing module named bcrypt - imported by cryptography.hazmat.primitives.serialization.ssh (optional)
|
||||||
|
missing module named cryptography.x509.UnsupportedExtension - imported by cryptography.x509 (optional), urllib3.contrib.pyopenssl (optional)
|
||||||
|
missing module named chardet - imported by requests (optional)
|
||||||
|
missing module named 'pyodide.ffi' - imported by urllib3.contrib.emscripten.fetch (delayed, optional)
|
||||||
|
missing module named pyodide - imported by urllib3.contrib.emscripten.fetch (top-level)
|
||||||
|
missing module named js - imported by urllib3.contrib.emscripten.fetch (top-level)
|
||||||
36239
build/build_wow_multikey/xref-build_wow_multikey.html
Normal file
40
build_exe.ps1
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Build WoW_MultiTool.exe (GUI)
|
||||||
|
# Usage: .\build_exe.ps1
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
Set-Location $PSScriptRoot
|
||||||
|
|
||||||
|
Write-Host "Checking dependencies..." -ForegroundColor Cyan
|
||||||
|
& pip show pyinstaller 2>$null | Out-Null
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Host "Installing dependencies..." -ForegroundColor Yellow
|
||||||
|
pip install -r requirements.txt
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Building WoW_MultiTool.exe ..." -ForegroundColor Cyan
|
||||||
|
pyinstaller --noconfirm build_wow_multikey.spec
|
||||||
|
if ($LASTEXITCODE -ne 0) { exit 1 }
|
||||||
|
|
||||||
|
$outExe = "dist\WoW_MultiTool.exe"
|
||||||
|
|
||||||
|
Write-Host "Copying recorder folder to dist..." -ForegroundColor Cyan
|
||||||
|
if (Test-Path "dist\recorder") { Remove-Item "dist\recorder" -Recurse -Force -ErrorAction SilentlyContinue }
|
||||||
|
if (Test-Path "recorder") { Copy-Item "recorder" "dist\recorder" -Recurse -Force }
|
||||||
|
|
||||||
|
Write-Host "Copying game_state_config.json to dist..." -ForegroundColor Cyan
|
||||||
|
if (Test-Path "game_state_config.json") { Copy-Item "game_state_config.json" "dist\game_state_config.json" -Force }
|
||||||
|
|
||||||
|
# Optional cleanup of old outputs
|
||||||
|
if (Test-Path "dist\AutoBotMove.exe") { Remove-Item "dist\AutoBotMove.exe" -Force -ErrorAction SilentlyContinue }
|
||||||
|
if (Test-Path "dist\vendor.json") { Remove-Item "dist\vendor.json" -Force -ErrorAction SilentlyContinue }
|
||||||
|
if (Test-Path "dist\waypoints.json") { Remove-Item "dist\waypoints.json" -Force -ErrorAction SilentlyContinue }
|
||||||
|
|
||||||
|
if (Test-Path $outExe) {
|
||||||
|
Write-Host "`nBuild done!" -ForegroundColor Green
|
||||||
|
Write-Host "Output: $outExe" -ForegroundColor Green
|
||||||
|
if (Test-Path "dist\recorder") { Write-Host "Copied: dist\recorder" -ForegroundColor Green }
|
||||||
|
exit 0
|
||||||
|
} else {
|
||||||
|
Write-Host "Build failed: $outExe not found." -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
57
build_wow_multikey.spec
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
# WoW 多键控制器 GUI - PyInstaller 打包配置
|
||||||
|
# 打包命令: pyinstaller build_wow_multikey.spec
|
||||||
|
|
||||||
|
block_cipher = None
|
||||||
|
|
||||||
|
# 巡逻打怪 / 录制模式需要
|
||||||
|
added_files = [
|
||||||
|
('recorder\\*.json', 'recorder'),
|
||||||
|
('game_state_config.json', '.'),
|
||||||
|
]
|
||||||
|
|
||||||
|
a = Analysis(
|
||||||
|
['wow_multikey_gui.py'],
|
||||||
|
pathex=[],
|
||||||
|
binaries=[],
|
||||||
|
datas=added_files,
|
||||||
|
hiddenimports=[
|
||||||
|
'win32gui', 'win32api', 'win32con',
|
||||||
|
'game_state', 'auto_bot_move', 'auto_bot', 'recorder',
|
||||||
|
'coordinate_patrol', 'death_manager', 'logistics_manager',
|
||||||
|
'stuck_handler', 'player_movement', 'player_position',
|
||||||
|
'pydirectinput', 'pygetwindow', 'pyautogui', 'PIL',
|
||||||
|
],
|
||||||
|
hookspath=[],
|
||||||
|
hooksconfig={},
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
win_no_prefer_redirects=False,
|
||||||
|
win_private_assemblies=False,
|
||||||
|
cipher=block_cipher,
|
||||||
|
noarchive=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||||
|
|
||||||
|
exe = EXE(
|
||||||
|
pyz,
|
||||||
|
a.scripts,
|
||||||
|
a.binaries,
|
||||||
|
a.zipfiles,
|
||||||
|
a.datas,
|
||||||
|
[],
|
||||||
|
name='WoW_MultiTool',
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
upx_exclude=[],
|
||||||
|
runtime_tmpdir=None,
|
||||||
|
console=False, # GUI 程序,无控制台窗口
|
||||||
|
disable_windowed_traceback=False,
|
||||||
|
argv_emulation=False,
|
||||||
|
target_arch=None,
|
||||||
|
codesign_identity=None,
|
||||||
|
entitlements_file=None,
|
||||||
|
)
|
||||||
11171
combat.log
Normal file
70
combat_detector.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
"""
|
||||||
|
战斗状态检测模块,通过模板匹配判断角色是否处于战斗中
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
import pyautogui
|
||||||
|
from config import GameConfig
|
||||||
|
|
||||||
|
|
||||||
|
class CombatDetector:
|
||||||
|
"""通过截图与模板匹配判断当前是否处于战斗状态"""
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
'images/1.png',
|
||||||
|
'images/2.png',
|
||||||
|
'images/3.png',
|
||||||
|
'images/4.png',
|
||||||
|
]
|
||||||
|
SCREENSHOT_PATH = 'screenshot/combat_screenshot.png'
|
||||||
|
|
||||||
|
def __init__(self, config: GameConfig):
|
||||||
|
self.config = config
|
||||||
|
self.screen_width, self.screen_height = pyautogui.size()
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
os.makedirs(os.path.dirname(self.SCREENSHOT_PATH), exist_ok=True)
|
||||||
|
|
||||||
|
def is_in_combat(self) -> bool:
|
||||||
|
"""判断是否处于战斗中。
|
||||||
|
|
||||||
|
截取屏幕左上半区域,逐一与模板图片做归一化相关系数匹配,
|
||||||
|
任一模板匹配值超过阈值即视为处于战斗状态。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 处于战斗中返回 True,否则返回 False
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
region_width = self.screen_width // 2
|
||||||
|
region_height = self.screen_height // 2
|
||||||
|
screenshot = pyautogui.screenshot(region=(0, 0, region_width, region_height))
|
||||||
|
screenshot.save(self.SCREENSHOT_PATH)
|
||||||
|
|
||||||
|
screenshot_cv = cv2.cvtColor(np.array(screenshot), cv2.COLOR_RGB2BGR)
|
||||||
|
|
||||||
|
for template_file in self.TEMPLATES:
|
||||||
|
if not os.path.exists(template_file):
|
||||||
|
self.logger.warning(f"模板文件不存在,跳过: {template_file}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
template = cv2.imread(template_file)
|
||||||
|
result = cv2.matchTemplate(
|
||||||
|
screenshot_cv,
|
||||||
|
template,
|
||||||
|
cv2.TM_CCOEFF_NORMED
|
||||||
|
)
|
||||||
|
_, max_val, _, _ = cv2.minMaxLoc(result)
|
||||||
|
|
||||||
|
if max_val > self.config.recognition_threshold:
|
||||||
|
self.logger.info(
|
||||||
|
f"战斗状态: 是(模板: {template_file},匹配值: {max_val:.2f})"
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
self.logger.info("战斗状态: 否(所有模板均未匹配)")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"判断战斗状态失败: {str(e)}")
|
||||||
|
return False
|
||||||
160
combat_engine.py
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
"""
|
||||||
|
战斗引擎模块,负责核心战斗逻辑实现
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import pyautogui
|
||||||
|
from config import GameConfig
|
||||||
|
from text_finder import TextFinder
|
||||||
|
from combat_detector import CombatDetector
|
||||||
|
from player_movement import PlayerMovement
|
||||||
|
from game_state import GameStateReader
|
||||||
|
|
||||||
|
|
||||||
|
class CombatEngine:
|
||||||
|
|
||||||
|
# 巡逻路径:依次移动到这些坐标,循环往复
|
||||||
|
PATROL_WAYPOINTS = [
|
||||||
|
(23.8, 71.0),
|
||||||
|
(27.0, 79.0),
|
||||||
|
(31.0, 72.0)
|
||||||
|
]
|
||||||
|
|
||||||
|
# 每走完 PATROL_STEP 坐标单位后检测一次目标;值越小检测越频繁但移动越碎
|
||||||
|
PATROL_STEP = 3.0
|
||||||
|
def __init__(self, config: GameConfig):
|
||||||
|
self.config = config
|
||||||
|
self.screen_width, self.screen_height = pyautogui.size()
|
||||||
|
self._setup_safety_measures()
|
||||||
|
self.text_finder = TextFinder()
|
||||||
|
self.combat_detector = CombatDetector(config)
|
||||||
|
self.player_movement = PlayerMovement()
|
||||||
|
self.game_state_reader = GameStateReader()
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def _setup_safety_measures(self) -> None:
|
||||||
|
"""设置安全防护措施"""
|
||||||
|
pyautogui.FAILSAFE = True
|
||||||
|
pyautogui.PAUSE = self.config.action_delay
|
||||||
|
|
||||||
|
def _find_target_on_screen(self) -> bool:
|
||||||
|
"""在屏幕上寻找怪物目标"""
|
||||||
|
try:
|
||||||
|
# 先按3键
|
||||||
|
pyautogui.press('3')
|
||||||
|
# 等待一段时间,确保画面更新
|
||||||
|
time.sleep(0.5)
|
||||||
|
pyautogui.press('4')
|
||||||
|
time.sleep(3)
|
||||||
|
if self.combat_detector.is_in_combat():
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"目标识别失败: {str(e)}")
|
||||||
|
|
||||||
|
def _execute_attack(self) -> None:
|
||||||
|
"""执行攻击动作"""
|
||||||
|
try:
|
||||||
|
# 等待1秒
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
if(self.combat_detector.is_in_combat()):
|
||||||
|
while(self.combat_detector.is_in_combat()):
|
||||||
|
pyautogui.press('2')
|
||||||
|
# time.sleep(1)
|
||||||
|
pyautogui.press('4')
|
||||||
|
else:
|
||||||
|
# time.sleep(1)
|
||||||
|
# 怪物死亡,按4键拾取物品
|
||||||
|
pyautogui.press('4')
|
||||||
|
time.sleep(0.5)
|
||||||
|
pyautogui.press('4')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"攻击执行失败: {str(e)}")
|
||||||
|
|
||||||
|
def run_combat_loop(self) -> None:
|
||||||
|
"""战斗主循环。
|
||||||
|
|
||||||
|
按顺序巡逻 PATROL_WAYPOINTS 中的坐标:
|
||||||
|
- 每移动一小段后检测是否存在目标;
|
||||||
|
- 找到目标立即停止移动,执行攻击,打完后继续巡逻;
|
||||||
|
- 未找到目标则继续向当前巡逻点前进,到达后切换下一个。
|
||||||
|
"""
|
||||||
|
waypoint_index = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# 获取并打印游戏状态(血量、法力、战斗、目标)
|
||||||
|
state = self.game_state_reader.get_game_state()
|
||||||
|
self.logger.info(
|
||||||
|
f"游戏状态 | 血量:{state['hp_percent']}% 法力:{state['mp_percent']}% "
|
||||||
|
f"战斗中:{state['in_combat']} 有目标:{state['has_target']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# target_x, target_y = self.PATROL_WAYPOINTS[waypoint_index]
|
||||||
|
# self.logger.info(f"前往巡逻点 ({target_x}, {target_y})")
|
||||||
|
|
||||||
|
# # 分段移动:每次最多走 MOVE_STEP_DURATION 秒,间隔检测目标
|
||||||
|
# target_found = self._move_until_target_or_arrived(target_x, target_y)
|
||||||
|
|
||||||
|
# if target_found:
|
||||||
|
# self.logger.info("发现目标,停止移动,开始攻击")
|
||||||
|
# self._execute_attack()
|
||||||
|
# self.logger.info("攻击结束,继续巡逻")
|
||||||
|
# else:
|
||||||
|
# self.logger.info(f"已到达巡逻点 ({target_x}, {target_y}),切换下一个")
|
||||||
|
# waypoint_index = (waypoint_index + 1) % len(self.PATROL_WAYPOINTS)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
if self.config.auto_recovery:
|
||||||
|
time.sleep(self.config.error_recovery_delay)
|
||||||
|
continue
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _move_until_target_or_arrived(self, target_x: float, target_y: float) -> bool:
|
||||||
|
"""向目标坐标移动,期间每走一小段就检测一次是否存在目标。
|
||||||
|
|
||||||
|
到达判定使用轴对齐方式(与 player_movement.move_to 保持一致),
|
||||||
|
避免欧氏距离与轴对齐判定不一致导致的死循环。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 找到目标返回 True;到达目标坐标未找到目标返回 False
|
||||||
|
"""
|
||||||
|
import math
|
||||||
|
from player_position import PlayerPosition
|
||||||
|
|
||||||
|
tol = self.config.move_tolerance
|
||||||
|
pos_reader = PlayerPosition()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
pos = pos_reader.get_position_with_retry()
|
||||||
|
if pos is None:
|
||||||
|
self.logger.warning("无法读取坐标,中止移动")
|
||||||
|
return False
|
||||||
|
|
||||||
|
dx = target_x - pos[0]
|
||||||
|
dy = target_y - pos[1]
|
||||||
|
|
||||||
|
# 轴对齐到达判定(与 player_movement.move_to 内部逻辑一致)
|
||||||
|
if abs(dx) <= tol and abs(dy) <= tol:
|
||||||
|
self.logger.info(f"已到达巡逻点 ({target_x}, {target_y})")
|
||||||
|
return False
|
||||||
|
|
||||||
|
distance = math.sqrt(dx ** 2 + dy ** 2)
|
||||||
|
|
||||||
|
# 检测目标(每移动一小段检测一次)
|
||||||
|
if self._find_target_on_screen():
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 向目标方向走最多 PATROL_STEP 坐标单位,走完后再检测一次目标
|
||||||
|
step = min(distance, self.PATROL_STEP)
|
||||||
|
ratio = step / distance
|
||||||
|
mid_x = pos[0] + dx * ratio
|
||||||
|
mid_y = pos[1] + dy * ratio
|
||||||
|
|
||||||
|
self.logger.info(f"移动小段 → ({mid_x:.2f}, {mid_y:.2f}),剩余距离 {distance:.2f}")
|
||||||
|
self.player_movement.move_to_position(mid_x, mid_y, tolerance=tol)
|
||||||
14
combat_loops/DK.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "DK",
|
||||||
|
"trigger_chance": 0.5,
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"key": "2",
|
||||||
|
"delay": 0.5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hp_below_percent": 0,
|
||||||
|
"hp_key": "",
|
||||||
|
"mp_below_percent": 0,
|
||||||
|
"mp_key": ""
|
||||||
|
}
|
||||||
40
config.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
"""
|
||||||
|
游戏配置模块
|
||||||
|
"""
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GameConfig:
|
||||||
|
# 图像识别参数
|
||||||
|
monster_template_path: str = 'monster_template.png'
|
||||||
|
recognition_threshold: float = 0.55
|
||||||
|
scan_interval: float = 1.0
|
||||||
|
|
||||||
|
# 操作参数
|
||||||
|
attack_button: str = 'left'
|
||||||
|
action_delay: float = 0.1
|
||||||
|
|
||||||
|
# 移动参数
|
||||||
|
move_tolerance: float = 0.5 # 到达巡逻点的判定半径(坐标单位)
|
||||||
|
|
||||||
|
# 系统参数
|
||||||
|
auto_recovery: bool = True
|
||||||
|
error_recovery_delay: float = 3.0
|
||||||
|
|
||||||
|
# 计算属性
|
||||||
|
@property
|
||||||
|
def monster_template(self) -> np.ndarray:
|
||||||
|
"""加载怪物模板图像"""
|
||||||
|
img = cv2.imread(self.monster_template_path, cv2.IMREAD_COLOR)
|
||||||
|
if img is None:
|
||||||
|
raise FileNotFoundError(f"模板图像未找到: {self.monster_template_path}")
|
||||||
|
return img
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_config(cls) -> 'GameConfig':
|
||||||
|
"""加载配置文件"""
|
||||||
|
# 实际项目中可以从配置文件读取
|
||||||
|
return cls()
|
||||||
349
coordinate_patrol.py
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
"""
|
||||||
|
坐标巡逻模块:按航点列表循环巡逻,朝向计算与 player_movement 约定一致(atan2(dx,-dy));内含卡死检测与脱困。
|
||||||
|
"""
|
||||||
|
import math
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import pyautogui
|
||||||
|
from stuck_handler import StuckHandler
|
||||||
|
|
||||||
|
# 转向与死区常量(度)
|
||||||
|
ANGLE_THRESHOLD_DEG = 15.0 # 朝向容差,超过此值才按 A/D 转向
|
||||||
|
ANGLE_DEADZONE_DEG = 5.0 # 死区,此范围内不按 A/D、只按 W,减少左右微调
|
||||||
|
|
||||||
|
# 随机行为:跳跃概率(每帧触发概率,0.005 ≈ 平均 200 帧一次)
|
||||||
|
RANDOM_JUMP_PROB = 0.05
|
||||||
|
|
||||||
|
|
||||||
|
class CoordinatePatrol:
|
||||||
|
"""按航点坐标巡逻,用 pyautogui 按键转向与前进。"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
waypoints,
|
||||||
|
arrival_threshold=0.5,
|
||||||
|
angle_threshold_deg=ANGLE_THRESHOLD_DEG,
|
||||||
|
angle_deadzone_deg=ANGLE_DEADZONE_DEG,
|
||||||
|
random_jump_prob=RANDOM_JUMP_PROB,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
waypoints: 航点列表,游戏坐标格式 [(x1, y1), (x2, y2), ...],如 [(26.0, 78.0), (30.0, 82.0)]
|
||||||
|
arrival_threshold: 到达判定距离(游戏坐标单位),默认 0.5
|
||||||
|
angle_threshold_deg: 朝向容差(度),超过此值才按 A/D 转向,默认 ANGLE_THRESHOLD_DEG
|
||||||
|
angle_deadzone_deg: 转向死区(度),此范围内不按 A/D、只按 W,默认 ANGLE_DEADZONE_DEG
|
||||||
|
"""
|
||||||
|
self.waypoints = waypoints
|
||||||
|
self.current_index = 0
|
||||||
|
self.arrival_threshold = arrival_threshold
|
||||||
|
self.angle_threshold_deg = angle_threshold_deg
|
||||||
|
self.angle_deadzone_deg = angle_deadzone_deg
|
||||||
|
self.random_jump_prob = float(random_jump_prob)
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
self.last_turn_end_time = 0 # 最近一次结束 A/D 转向的时间,供卡死检测排除原地转向
|
||||||
|
self.stuck_handler = StuckHandler()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_distance(p1, p2):
|
||||||
|
return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
|
||||||
|
|
||||||
|
def snap_to_forward_waypoint(self, current_pos, max_ahead=10, max_dist=10.0, skip_current=True):
|
||||||
|
"""
|
||||||
|
根据当前位置,将巡逻索引 current_index 智能“接回”前方最近的航点。
|
||||||
|
|
||||||
|
典型场景:战斗结束后,角色可能偏离路径,希望从前进方向上的最近航点继续巡逻,
|
||||||
|
而不是跑回战斗前的旧航点。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
current_pos: 当前坐标 (x, y),游戏坐标。
|
||||||
|
max_ahead: 向前最多查看多少个航点(以 current_index 为起点),默认 10。
|
||||||
|
max_dist: 认为“可以接回”的最大距离阈值,超过则不调整索引,单位同坐标,默认 10.0。
|
||||||
|
skip_current: 是否跳过 current_index 本身,直接从下一个点开始找,避免立刻又去原来的点。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True 表示已调整 current_index;False 表示未调整(距离太远或数据不完整)。
|
||||||
|
"""
|
||||||
|
if current_pos is None:
|
||||||
|
return False
|
||||||
|
if not self.waypoints:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
cx, cy = float(current_pos[0]), float(current_pos[1])
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
n = len(self.waypoints)
|
||||||
|
if n == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
start_idx = (self.current_index + (1 if skip_current else 0)) % n
|
||||||
|
best_idx = None
|
||||||
|
best_dist = None
|
||||||
|
|
||||||
|
# 在 current_index 之后的若干个点里寻找最近的一个
|
||||||
|
for offset in range(max_ahead + 1):
|
||||||
|
idx = (start_idx + offset) % n
|
||||||
|
wp = self.waypoints[idx]
|
||||||
|
d = self.get_distance((cx, cy), (float(wp[0]), float(wp[1])))
|
||||||
|
if (best_dist is None) or (d < best_dist):
|
||||||
|
best_dist = d
|
||||||
|
best_idx = idx
|
||||||
|
|
||||||
|
if best_idx is None or best_dist is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if best_dist > float(max_dist):
|
||||||
|
# 离前方所有点都太远,则不强行跳索引,维持原行为
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 将当前巡逻索引接到前方最近的点或其后一个点
|
||||||
|
self.current_index = best_idx
|
||||||
|
self.reset_stuck()
|
||||||
|
self.logger.info(
|
||||||
|
f">>> 战斗结束智能接回巡逻:current_pos={current_pos}, "
|
||||||
|
f"接入点索引={best_idx}, 距离={best_dist:.2f}"
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_target_heading_deg(self, current_pos, target_pos):
|
||||||
|
"""计算朝向目标所需的游戏朝向(度)。dx_actual ∝ sin(heading),dy_actual ∝ -cos(heading) → 目标朝向 = atan2(dx, -dy)。"""
|
||||||
|
dx = target_pos[0] - current_pos[0]
|
||||||
|
dy = target_pos[1] - current_pos[1]
|
||||||
|
if dx == 0 and dy == 0:
|
||||||
|
return 0.0
|
||||||
|
return math.degrees(math.atan2(dx, -dy)) % 360
|
||||||
|
|
||||||
|
def _turn_duration_from_angle(self, angle_diff_deg):
|
||||||
|
"""
|
||||||
|
专门为“边走边转”设计的非线性脉冲函数。
|
||||||
|
逻辑:
|
||||||
|
- 大于 45°:给予一个较长脉冲,快速切入方向。
|
||||||
|
- 15° 到 45°:线性修正。
|
||||||
|
- 小于 15°:极小脉冲,防止在直线上摇摆。
|
||||||
|
"""
|
||||||
|
abs_angle = abs(angle_diff_deg)
|
||||||
|
|
||||||
|
# 基础转向系数 (针对边走边转优化,值越小越不容易画龙)
|
||||||
|
# 推荐范围 0.002 - 0.0035
|
||||||
|
scale_factor = 0.0025
|
||||||
|
|
||||||
|
if abs_angle > 45:
|
||||||
|
# 大角度:给予一个明显的转向力,但不超过 0.2s 避免丢失 W 的推力感
|
||||||
|
base_duration = min(abs_angle * scale_factor * 1.2, 0.20)
|
||||||
|
elif abs_angle > 15:
|
||||||
|
# 中角度:标准线性修正
|
||||||
|
base_duration = abs_angle * scale_factor
|
||||||
|
else:
|
||||||
|
# 小角度微调:使用极短脉冲 (物理按键触发的底线)
|
||||||
|
base_duration = 0.04
|
||||||
|
|
||||||
|
# 随机化:模拟人工轻点按键的不规则性
|
||||||
|
# 这里的随机范围要小,否则会导致修正量不稳定
|
||||||
|
jitter = random.uniform(0.005, 0.015)
|
||||||
|
|
||||||
|
final_duration = base_duration + jitter
|
||||||
|
|
||||||
|
# 最终安全检查:严禁超过 0.25s,否则会造成明显的顿挫
|
||||||
|
return min(final_duration, 0.25)
|
||||||
|
|
||||||
|
def reset_stuck(self):
|
||||||
|
"""重置卡死检测状态(切航点、停止巡逻、死亡/后勤/战斗时由外部调用)。"""
|
||||||
|
self.stuck_handler.reset()
|
||||||
|
|
||||||
|
def navigate(self, state):
|
||||||
|
"""
|
||||||
|
优化版导航:支持边走边转,非阻塞平滑移动。
|
||||||
|
"""
|
||||||
|
if state is None:
|
||||||
|
self.stop_all()
|
||||||
|
return
|
||||||
|
|
||||||
|
# 1. 基础状态解析与校验
|
||||||
|
x, y = state.get("x"), state.get("y")
|
||||||
|
heading = state.get("facing")
|
||||||
|
if x is None or y is None or heading is None:
|
||||||
|
self.logger.warning("state 数据不完整,跳过导航帧")
|
||||||
|
self.stop_all()
|
||||||
|
return
|
||||||
|
|
||||||
|
# 2. 卡死检测:位移检测逻辑保持在巡逻中
|
||||||
|
if self.stuck_handler.check_stuck(state, self.last_turn_end_time):
|
||||||
|
self.logger.error("!!! 检测到卡死,启动脱困程序 !!!")
|
||||||
|
self.stop_all()
|
||||||
|
self.stuck_handler.resolve_stuck()
|
||||||
|
return
|
||||||
|
|
||||||
|
curr_pos = (float(x), float(y))
|
||||||
|
heading = float(heading)
|
||||||
|
target_pos = self.waypoints[self.current_index]
|
||||||
|
dist = self.get_distance(curr_pos, target_pos)
|
||||||
|
|
||||||
|
# 3. 到达判定(到点后平滑切换下一个航点,不再完全刹车)
|
||||||
|
if dist < self.arrival_threshold:
|
||||||
|
self.logger.info(f">>> 到达航点 {self.current_index},切换至下一目标(平滑过渡)")
|
||||||
|
self.current_index = (self.current_index + 1) % len(self.waypoints)
|
||||||
|
# 重置卡死检测,避免在航点附近因为轻微抖动被误判卡死
|
||||||
|
self.reset_stuck()
|
||||||
|
# 不再调用 stop_all(),让 W 保持按下,下一帧直接朝下一个航点继续前进
|
||||||
|
return
|
||||||
|
|
||||||
|
# 4. 计算航向逻辑
|
||||||
|
target_heading = self.get_target_heading_deg(curr_pos, target_pos)
|
||||||
|
angle_diff = (target_heading - heading + 180) % 360 - 180
|
||||||
|
abs_diff = abs(angle_diff)
|
||||||
|
|
||||||
|
# --- [平滑移动核心:边走边转控制层] ---
|
||||||
|
|
||||||
|
# A. 始终保持前进动力
|
||||||
|
pyautogui.keyDown("w")
|
||||||
|
|
||||||
|
# B. 转向决策逻辑
|
||||||
|
if abs_diff <= self.angle_deadzone_deg:
|
||||||
|
if random.random() < self.random_jump_prob: # 频率建议设低,约每 200 帧跳一次
|
||||||
|
self.logger.info(">>> 随机跳跃:模拟真人并辅助脱困")
|
||||||
|
pyautogui.press("space")
|
||||||
|
# 在死区内:绝对直线行驶,松开所有转向键
|
||||||
|
pyautogui.keyUp("a")
|
||||||
|
pyautogui.keyUp("d")
|
||||||
|
|
||||||
|
elif abs_diff > self.angle_threshold_deg:
|
||||||
|
# 超出阈值:执行平滑脉冲转向
|
||||||
|
turn_key = "d" if angle_diff > 0 else "a"
|
||||||
|
other_key = "a" if angle_diff > 0 else "d"
|
||||||
|
|
||||||
|
# 确保不会同时按下左右键
|
||||||
|
pyautogui.keyUp(other_key)
|
||||||
|
|
||||||
|
# 获取针对平滑移动优化的短脉冲时长
|
||||||
|
duration = self._turn_duration_from_angle(abs_diff)
|
||||||
|
|
||||||
|
# 关键:执行短促转向脉冲
|
||||||
|
pyautogui.keyDown(turn_key)
|
||||||
|
# 这里的 sleep 极短 (建议 < 0.1s),不会造成移动卡顿
|
||||||
|
time.sleep(duration)
|
||||||
|
pyautogui.keyUp(turn_key)
|
||||||
|
|
||||||
|
self.last_turn_end_time = time.time()
|
||||||
|
# self.logger.debug(f"修正航向: {turn_key} | 角度差: {angle_diff:.1f}°")
|
||||||
|
|
||||||
|
else:
|
||||||
|
# 在死区与阈值之间:为了平滑,不进行任何按键动作,仅靠 W 前进
|
||||||
|
pyautogui.keyUp("a")
|
||||||
|
pyautogui.keyUp("d")
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def navigate_to_point(self, state, target_pos, arrival_threshold=None):
|
||||||
|
"""
|
||||||
|
优化版:平滑导航,支持边走边转。
|
||||||
|
"""
|
||||||
|
if state is None:
|
||||||
|
self.stop_all()
|
||||||
|
return False
|
||||||
|
|
||||||
|
current_pos = (state.get('x'), state.get('y'))
|
||||||
|
current_facing = state.get('facing')
|
||||||
|
|
||||||
|
if None in current_pos or current_facing is None:
|
||||||
|
self.stop_all()
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 1. 卡死检测
|
||||||
|
if self.stuck_handler.check_stuck(state, self.last_turn_end_time):
|
||||||
|
self.stop_all()
|
||||||
|
self.stuck_handler.resolve_stuck()
|
||||||
|
return False
|
||||||
|
|
||||||
|
current_pos = (float(current_pos[0]), float(current_pos[1]))
|
||||||
|
current_facing = float(current_facing)
|
||||||
|
threshold = arrival_threshold if arrival_threshold is not None else self.arrival_threshold
|
||||||
|
|
||||||
|
# 2. 距离判断
|
||||||
|
dist = self.get_distance(current_pos, target_pos)
|
||||||
|
if dist < threshold:
|
||||||
|
self.stop_all()
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 3. 计算角度差
|
||||||
|
target_heading = self.get_target_heading_deg(current_pos, target_pos)
|
||||||
|
angle_diff = (target_heading - current_facing + 180) % 360 - 180
|
||||||
|
abs_diff = abs(angle_diff)
|
||||||
|
|
||||||
|
# --- 平滑移动核心逻辑 ---
|
||||||
|
|
||||||
|
# 只要没到终点,始终保持前进 W 键按下
|
||||||
|
pyautogui.keyDown("w")
|
||||||
|
|
||||||
|
# 4. 根据偏角决定转向动作
|
||||||
|
if abs_diff <= self.angle_deadzone_deg:
|
||||||
|
if random.random() < self.random_jump_prob: # 频率建议设低,约每 200 帧跳一次
|
||||||
|
self.logger.info(">>> 随机跳跃:模拟真人并辅助脱困")
|
||||||
|
pyautogui.press("space")
|
||||||
|
# 在死区内,确保转向键松开,直线前进
|
||||||
|
pyautogui.keyUp("a")
|
||||||
|
pyautogui.keyUp("d")
|
||||||
|
else:
|
||||||
|
# 在死区外,需要修正方向
|
||||||
|
turn_key = "d" if angle_diff > 0 else "a"
|
||||||
|
other_key = "a" if angle_diff > 0 else "d"
|
||||||
|
|
||||||
|
# 松开反方向键
|
||||||
|
pyautogui.keyUp(other_key)
|
||||||
|
|
||||||
|
# 计算本次修正的时长
|
||||||
|
# 边走边转时,duration 应比原地转向更短,建议使用 0.05s - 0.1s 的短脉冲
|
||||||
|
duration = self._turn_duration_from_angle(abs_diff)
|
||||||
|
|
||||||
|
# 限制单次修正时间,防止转过头导致“画龙”
|
||||||
|
safe_duration = min(duration, 0.15)
|
||||||
|
|
||||||
|
pyautogui.keyDown(turn_key)
|
||||||
|
time.sleep(safe_duration) # 短暂物理延迟确保按键生效
|
||||||
|
pyautogui.keyUp(turn_key)
|
||||||
|
|
||||||
|
self.last_turn_end_time = time.time()
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def navigate_path(self, get_state, path, forward=True, arrival_threshold=None):
|
||||||
|
"""
|
||||||
|
按 path 依次走完所有点后返回。每次调用都必须传入 path。
|
||||||
|
get_state: 可调用对象,每次调用返回当前状态 dict,需包含 'x','y','facing'。
|
||||||
|
path: [[x,y], ...] 或 [(x,y), ...](如 json.load 得到),本次要走的全部航点。
|
||||||
|
forward: True=正序(0→n),False=倒序(n→0)。
|
||||||
|
阻塞直到走完 path 中所有点或出错,走完返回 True。内含卡死检测。
|
||||||
|
"""
|
||||||
|
if not path:
|
||||||
|
self.stop_all()
|
||||||
|
return True
|
||||||
|
points = [(float(p[0]), float(p[1])) for p in path]
|
||||||
|
if not forward:
|
||||||
|
points = points[::-1]
|
||||||
|
for i, target in enumerate(points):
|
||||||
|
self.logger.info(f">>> 路径点 {i + 1}/{len(points)}: {target}")
|
||||||
|
while True:
|
||||||
|
state = get_state()
|
||||||
|
if state is None:
|
||||||
|
self.stop_all()
|
||||||
|
return False
|
||||||
|
arrived = self.navigate_to_point(state, target, arrival_threshold)
|
||||||
|
if arrived:
|
||||||
|
break
|
||||||
|
time.sleep(0.05)
|
||||||
|
self.stop_all()
|
||||||
|
self.logger.info(">>> 路径走完")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def press_key(self, key):
|
||||||
|
"""只按下指定键,抬起其他移动键。"""
|
||||||
|
for k in ("w", "a", "d"):
|
||||||
|
if k == key:
|
||||||
|
pyautogui.keyDown(k)
|
||||||
|
else:
|
||||||
|
pyautogui.keyUp(k)
|
||||||
|
|
||||||
|
def stop_all(self):
|
||||||
|
for key in ["w", "a", "s", "d"]:
|
||||||
|
pyautogui.keyUp(key)
|
||||||
38
death_manager.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import time
|
||||||
|
import math
|
||||||
|
import pyautogui
|
||||||
|
|
||||||
|
|
||||||
|
class DeathManager:
|
||||||
|
def __init__(self, patrol_system):
|
||||||
|
self.corpse_pos = None
|
||||||
|
self.patrol_system = patrol_system
|
||||||
|
self.is_running_to_corpse = False
|
||||||
|
|
||||||
|
def on_death(self, state):
|
||||||
|
"""1. 死亡瞬间调用:从 player_position 获取坐标并记录"""
|
||||||
|
self.corpse_pos = (state['x'], state['y'])
|
||||||
|
self.is_running_to_corpse = True
|
||||||
|
print(f">>> [系统] 记录死亡坐标: {self.corpse_pos},准备释放灵魂...")
|
||||||
|
pyautogui.press('9') # 绑定宏: /run RepopMe()
|
||||||
|
time.sleep(5) # 等待加载界面
|
||||||
|
|
||||||
|
def run_to_corpse(self, state):
|
||||||
|
"""2. 跑尸寻路逻辑:坐标与朝向从 player_position 获取"""
|
||||||
|
if not self.corpse_pos:
|
||||||
|
return
|
||||||
|
|
||||||
|
is_arrived = self.patrol_system.navigate_to_point(state, self.corpse_pos)
|
||||||
|
# 如果距离尸体很近(0.005 约等于 10-20 码)
|
||||||
|
if is_arrived:
|
||||||
|
print(">>> 已到达尸体附近,尝试复活...")
|
||||||
|
pyautogui.press('0') # 绑定宏: /run RetrieveCorpse()
|
||||||
|
time.sleep(5)
|
||||||
|
self.is_running_to_corpse = False
|
||||||
|
self.corpse_pos = None
|
||||||
|
return
|
||||||
|
|
||||||
|
def handle_resurrection_popup(self):
|
||||||
|
"""处理复活确认框"""
|
||||||
|
# 配合 Lua 插件自动点击,或者 Python 模拟按键
|
||||||
|
pydirectinput.press('enter')
|
||||||
BIN
dist/WoW_MultiTool.exe
vendored
Normal file
22
dist/combat_loops/dk.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "dk",
|
||||||
|
"trigger_chance": 0.3,
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"key": "2",
|
||||||
|
"delay": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "4",
|
||||||
|
"delay": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "4",
|
||||||
|
"delay": 0.5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hp_below_percent": 0,
|
||||||
|
"hp_key": "",
|
||||||
|
"mp_below_percent": 0,
|
||||||
|
"mp_key": ""
|
||||||
|
}
|
||||||
8
dist/game_state_config.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"pixel_size": 17,
|
||||||
|
"block_start_x": 30,
|
||||||
|
"scan_region_width": 155,
|
||||||
|
"scan_region_height": 15,
|
||||||
|
"offset_left": 20,
|
||||||
|
"offset_top": 45
|
||||||
|
}
|
||||||
30
dist/recorder/vendor.json
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
85.35,
|
||||||
|
74.90
|
||||||
|
],
|
||||||
|
[
|
||||||
|
85.36,
|
||||||
|
74.93
|
||||||
|
],
|
||||||
|
[
|
||||||
|
85.66,
|
||||||
|
75.35
|
||||||
|
],
|
||||||
|
[
|
||||||
|
86.1,
|
||||||
|
75.59
|
||||||
|
],
|
||||||
|
[
|
||||||
|
86.61,
|
||||||
|
75.59
|
||||||
|
],
|
||||||
|
[
|
||||||
|
87.08,
|
||||||
|
75.82
|
||||||
|
],
|
||||||
|
[
|
||||||
|
87.24,
|
||||||
|
75.88
|
||||||
|
]
|
||||||
|
]
|
||||||
70
dist/recorder/waypoints.json
vendored
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
85.35,
|
||||||
|
74.90
|
||||||
|
],
|
||||||
|
[
|
||||||
|
85.32,
|
||||||
|
74.86
|
||||||
|
],
|
||||||
|
[
|
||||||
|
85.02,
|
||||||
|
74.38
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84.8,
|
||||||
|
73.92
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84.6,
|
||||||
|
73.46
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84.39,
|
||||||
|
73.0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84.17,
|
||||||
|
72.55
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.88,
|
||||||
|
72.05
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.42,
|
||||||
|
71.84
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.3,
|
||||||
|
72.34
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.34,
|
||||||
|
72.9
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.37,
|
||||||
|
73.46
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.62,
|
||||||
|
73.94
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.97,
|
||||||
|
74.32
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84.41,
|
||||||
|
74.67
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84.94,
|
||||||
|
74.86
|
||||||
|
],
|
||||||
|
[
|
||||||
|
85.35,
|
||||||
|
74.90
|
||||||
|
]
|
||||||
|
]
|
||||||
70
dist/recorder/湿地刷布点.json
vendored
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
61.08,
|
||||||
|
73.18
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.26,
|
||||||
|
72.7
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.44,
|
||||||
|
72.21
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.62,
|
||||||
|
71.71
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.8,
|
||||||
|
71.23
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.98,
|
||||||
|
70.73
|
||||||
|
],
|
||||||
|
[
|
||||||
|
62.12,
|
||||||
|
70.23
|
||||||
|
],
|
||||||
|
[
|
||||||
|
62.3,
|
||||||
|
69.76
|
||||||
|
],
|
||||||
|
[
|
||||||
|
62.48,
|
||||||
|
69.26
|
||||||
|
],
|
||||||
|
[
|
||||||
|
62.3,
|
||||||
|
69.76
|
||||||
|
],
|
||||||
|
[
|
||||||
|
62.14,
|
||||||
|
70.25
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.99,
|
||||||
|
70.73
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.83,
|
||||||
|
71.21
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.67,
|
||||||
|
71.71
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.51,
|
||||||
|
72.2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.33,
|
||||||
|
72.68
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.17,
|
||||||
|
73.18
|
||||||
|
]
|
||||||
|
]
|
||||||
86
dist/recorder/灰熊丘陵巡逻点.json
vendored
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
10.76,
|
||||||
|
60.55
|
||||||
|
],
|
||||||
|
[
|
||||||
|
11.12,
|
||||||
|
60.93
|
||||||
|
],
|
||||||
|
[
|
||||||
|
11.48,
|
||||||
|
61.31
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.01,
|
||||||
|
61.3
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.53,
|
||||||
|
61.35
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.91,
|
||||||
|
61.72
|
||||||
|
],
|
||||||
|
[
|
||||||
|
13.28,
|
||||||
|
62.08
|
||||||
|
],
|
||||||
|
[
|
||||||
|
13.8,
|
||||||
|
62.14
|
||||||
|
],
|
||||||
|
[
|
||||||
|
14.32,
|
||||||
|
62.07
|
||||||
|
],
|
||||||
|
[
|
||||||
|
14.53,
|
||||||
|
61.58
|
||||||
|
],
|
||||||
|
[
|
||||||
|
14.42,
|
||||||
|
61.04
|
||||||
|
],
|
||||||
|
[
|
||||||
|
14.31,
|
||||||
|
60.51
|
||||||
|
],
|
||||||
|
[
|
||||||
|
14.18,
|
||||||
|
60.01
|
||||||
|
],
|
||||||
|
[
|
||||||
|
14.03,
|
||||||
|
59.51
|
||||||
|
],
|
||||||
|
[
|
||||||
|
13.57,
|
||||||
|
59.28
|
||||||
|
],
|
||||||
|
[
|
||||||
|
13.05,
|
||||||
|
59.42
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.54,
|
||||||
|
59.55
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.11,
|
||||||
|
59.88
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.01,
|
||||||
|
60.41
|
||||||
|
],
|
||||||
|
[
|
||||||
|
11.53,
|
||||||
|
60.58
|
||||||
|
],
|
||||||
|
[
|
||||||
|
10.76,
|
||||||
|
60.55
|
||||||
|
]
|
||||||
|
]
|
||||||
46
dist/recorder/祖达克-冰冷裂口巡逻点.json
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
32.54,
|
||||||
|
41.22
|
||||||
|
],
|
||||||
|
[
|
||||||
|
32.67,
|
||||||
|
40.7
|
||||||
|
],
|
||||||
|
[
|
||||||
|
32.85,
|
||||||
|
40.19
|
||||||
|
],
|
||||||
|
[
|
||||||
|
32.98,
|
||||||
|
39.67
|
||||||
|
],
|
||||||
|
[
|
||||||
|
32.84,
|
||||||
|
39.14
|
||||||
|
],
|
||||||
|
[
|
||||||
|
32.35,
|
||||||
|
39.26
|
||||||
|
],
|
||||||
|
[
|
||||||
|
32.17,
|
||||||
|
39.76
|
||||||
|
],
|
||||||
|
[
|
||||||
|
31.98,
|
||||||
|
40.25
|
||||||
|
],
|
||||||
|
[
|
||||||
|
31.77,
|
||||||
|
40.75
|
||||||
|
],
|
||||||
|
[
|
||||||
|
31.57,
|
||||||
|
41.23
|
||||||
|
],
|
||||||
|
[
|
||||||
|
32.54,
|
||||||
|
41.22
|
||||||
|
]
|
||||||
|
]
|
||||||
246
dist/recorder/祖达克-西莱图斯祭坛巡逻点.json
vendored
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
44.37,
|
||||||
|
34.77
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.37,
|
||||||
|
35.32
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.39,
|
||||||
|
35.82
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.4,
|
||||||
|
36.32
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.41,
|
||||||
|
36.82
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.42,
|
||||||
|
37.32
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.44,
|
||||||
|
37.82
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.46,
|
||||||
|
38.38
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.47,
|
||||||
|
38.94
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.49,
|
||||||
|
39.44
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.5,
|
||||||
|
39.94
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.52,
|
||||||
|
40.5
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.53,
|
||||||
|
41.0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.55,
|
||||||
|
41.55
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.56,
|
||||||
|
42.1
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.67,
|
||||||
|
42.62
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.71,
|
||||||
|
43.12
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.71,
|
||||||
|
43.67
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.68,
|
||||||
|
44.17
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.66,
|
||||||
|
44.67
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.63,
|
||||||
|
45.17
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.6,
|
||||||
|
45.71
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.58,
|
||||||
|
46.26
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.55,
|
||||||
|
46.76
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.52,
|
||||||
|
47.26
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.54,
|
||||||
|
47.82
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.43,
|
||||||
|
48.35
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.26,
|
||||||
|
48.85
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.02,
|
||||||
|
49.29
|
||||||
|
],
|
||||||
|
[
|
||||||
|
43.73,
|
||||||
|
49.75
|
||||||
|
],
|
||||||
|
[
|
||||||
|
43.35,
|
||||||
|
50.13
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.92,
|
||||||
|
49.87
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.81,
|
||||||
|
49.35
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.77,
|
||||||
|
48.81
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.71,
|
||||||
|
48.26
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.66,
|
||||||
|
47.71
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.6,
|
||||||
|
47.15
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.56,
|
||||||
|
46.6
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.56,
|
||||||
|
46.09
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.57,
|
||||||
|
45.55
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.58,
|
||||||
|
45.05
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.58,
|
||||||
|
44.54
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.59,
|
||||||
|
44.04
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.6,
|
||||||
|
43.52
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.6,
|
||||||
|
42.97
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.62,
|
||||||
|
42.47
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.64,
|
||||||
|
41.97
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.66,
|
||||||
|
41.42
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.68,
|
||||||
|
40.87
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.69,
|
||||||
|
40.32
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.7,
|
||||||
|
39.82
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.7,
|
||||||
|
39.32
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.7,
|
||||||
|
38.82
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.71,
|
||||||
|
38.27
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.71,
|
||||||
|
37.77
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.72,
|
||||||
|
37.25
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.73,
|
||||||
|
36.75
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.76,
|
||||||
|
36.2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.89,
|
||||||
|
35.7
|
||||||
|
],
|
||||||
|
[
|
||||||
|
43.25,
|
||||||
|
35.3
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.37,
|
||||||
|
34.77
|
||||||
|
]
|
||||||
|
]
|
||||||
26
dist/recorder/银色前线基地-修理商.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
85.23,
|
||||||
|
74.7
|
||||||
|
],
|
||||||
|
[
|
||||||
|
85.51,
|
||||||
|
75.15
|
||||||
|
],
|
||||||
|
[
|
||||||
|
85.8,
|
||||||
|
75.58
|
||||||
|
],
|
||||||
|
[
|
||||||
|
86.31,
|
||||||
|
75.64
|
||||||
|
],
|
||||||
|
[
|
||||||
|
86.81,
|
||||||
|
75.63
|
||||||
|
],
|
||||||
|
[
|
||||||
|
87.26,
|
||||||
|
75.89
|
||||||
|
]
|
||||||
|
]
|
||||||
74
dist/recorder/银色前线基地-巡逻点.json
vendored
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
85.31,
|
||||||
|
74.79
|
||||||
|
],
|
||||||
|
[
|
||||||
|
85.0,
|
||||||
|
74.37
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84.71,
|
||||||
|
73.94
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84.4,
|
||||||
|
73.49
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84.11,
|
||||||
|
73.07
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.81,
|
||||||
|
72.65
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.52,
|
||||||
|
73.08
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.44,
|
||||||
|
73.59
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.54,
|
||||||
|
74.12
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.58,
|
||||||
|
74.66
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.68,
|
||||||
|
75.18
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.77,
|
||||||
|
75.71
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.87,
|
||||||
|
76.23
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.97,
|
||||||
|
76.76
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84.36,
|
||||||
|
76.44
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84.62,
|
||||||
|
75.99
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84.89,
|
||||||
|
75.56
|
||||||
|
],
|
||||||
|
[
|
||||||
|
85.31,
|
||||||
|
74.79
|
||||||
|
]
|
||||||
|
]
|
||||||
34
dist/recorder/风暴峭壁-水晶蛛网洞穴.json
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
47.69,
|
||||||
|
70.25
|
||||||
|
],
|
||||||
|
[
|
||||||
|
47.55,
|
||||||
|
70.74
|
||||||
|
],
|
||||||
|
[
|
||||||
|
47.4,
|
||||||
|
71.22
|
||||||
|
],
|
||||||
|
[
|
||||||
|
47.25,
|
||||||
|
71.7
|
||||||
|
],
|
||||||
|
[
|
||||||
|
46.79,
|
||||||
|
71.5
|
||||||
|
],
|
||||||
|
[
|
||||||
|
46.93,
|
||||||
|
71.02
|
||||||
|
],
|
||||||
|
[
|
||||||
|
47.39,
|
||||||
|
70.8
|
||||||
|
],
|
||||||
|
[
|
||||||
|
47.69,
|
||||||
|
70.25
|
||||||
|
]
|
||||||
|
]
|
||||||
BIN
dist/screenshot/game_state.png
vendored
Normal file
|
After Width: | Height: | Size: 642 B |
BIN
dist/screenshot/game_state_combat.png
vendored
Normal file
|
After Width: | Height: | Size: 74 B |
BIN
dist/screenshot/game_state_hp.png
vendored
Normal file
|
After Width: | Height: | Size: 88 B |
BIN
dist/screenshot/game_state_logistics_death.png
vendored
Normal file
|
After Width: | Height: | Size: 83 B |
BIN
dist/screenshot/game_state_mp.png
vendored
Normal file
|
After Width: | Height: | Size: 90 B |
BIN
dist/screenshot/game_state_target.png
vendored
Normal file
|
After Width: | Height: | Size: 86 B |
BIN
dist/screenshot/game_state_x.png
vendored
Normal file
|
After Width: | Height: | Size: 84 B |
BIN
dist/screenshot/game_state_y.png
vendored
Normal file
|
After Width: | Height: | Size: 83 B |
32
dist/wow_multikey_qt.json
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"空格(跳跃)": {
|
||||||
|
"enabled": false,
|
||||||
|
"interval": 10,
|
||||||
|
"jitter": 2
|
||||||
|
},
|
||||||
|
"1": {
|
||||||
|
"enabled": false,
|
||||||
|
"interval": 10,
|
||||||
|
"jitter": 2
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"enabled": true,
|
||||||
|
"interval": 1,
|
||||||
|
"jitter": 0
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"enabled": false,
|
||||||
|
"interval": 10,
|
||||||
|
"jitter": 2
|
||||||
|
},
|
||||||
|
"4": {
|
||||||
|
"enabled": true,
|
||||||
|
"interval": 2,
|
||||||
|
"jitter": 0
|
||||||
|
},
|
||||||
|
"5": {
|
||||||
|
"enabled": false,
|
||||||
|
"interval": 10,
|
||||||
|
"jitter": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
19
docs/history.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# 更新记录(History)
|
||||||
|
|
||||||
|
## 2026-03-17
|
||||||
|
|
||||||
|
### 剥皮等待时间可配置(接入 GUI)
|
||||||
|
|
||||||
|
- **新增 GUI 参数**:在 `wow_multikey_gui.py` 的「参数配置」页新增 **剥皮等待时间**(秒)可调项,保存后用于控制脱战后剥皮等待时长。
|
||||||
|
- **Bot 接入参数**:
|
||||||
|
- `auto_bot_move.py`:`AutoBotMove` 新增 `skinning_wait_sec` 参数,`execute_disengage_loot()` 使用该值替代固定等待。
|
||||||
|
- `auto_bot.py`:`AutoBot` 新增 `skinning_wait_sec` 参数,`execute_disengage_loot()` 使用该值替代固定等待。
|
||||||
|
- **配置文件升级与兼容**:
|
||||||
|
- `wow_multikey_qt.json` 结构升级为 `{ "keys": {...}, "bot": {...} }`,其中 `bot.skinning_wait_sec` 存储剥皮等待秒数。
|
||||||
|
- 兼容旧版仅包含按键配置的结构:加载时会自动包裹为 `keys`,不会丢失旧配置。
|
||||||
|
|
||||||
|
### 使用方式
|
||||||
|
|
||||||
|
1. 打开 GUI → **参数配置** → 设置 **剥皮等待时间** → 点击 **保存配置**
|
||||||
|
2. 启动 **巡逻打怪** 或 **自动打怪**,剥皮等待时间按保存值生效。
|
||||||
|
|
||||||
123
docs/mount_travel_plan.md
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
# 脱战无目标上马跑路:实现方案(不改代码版)
|
||||||
|
|
||||||
|
本文档描述如何在 **`combat=False` 且 `target=False`** 时自动 **上马跑路/巡逻**,并在 **进入战斗或获得目标** 时立刻切回战斗逻辑。先给方案与落地路径,后续再按本文改代码实现。
|
||||||
|
|
||||||
|
## 目标与约束
|
||||||
|
|
||||||
|
- **目标**:没目标、没进战斗时,上马并持续移动(使用现有 `CoordinatePatrol.navigate()` 作为跑路/巡逻引擎)。
|
||||||
|
- **切回**:一旦 `combat=True` 或 `target=True`,立即停止移动并进入战斗逻辑(必要时下马/取消坐骑)。
|
||||||
|
- **稳定性**:避免反复按坐骑键导致“上马→下马→上马”抖动;避免与拾取/剥皮互相打断。
|
||||||
|
|
||||||
|
## 方案总览(按稳定性排序)
|
||||||
|
|
||||||
|
### 方案 A(推荐,最稳):增加 `mounted` 状态 → 状态机闭环
|
||||||
|
|
||||||
|
**核心思路**:像当前读 `combat/target` 一样,再增加一个 `mounted`(是否在坐骑上)状态输入,然后用“小状态机”确保行为可控。
|
||||||
|
|
||||||
|
- **进入赶路条件**:`not combat` 且 `not target`
|
||||||
|
- 若 `mounted=False`:按一次上马键/坐骑宏
|
||||||
|
- 等待 `mounted=True`(带超时,例如 3 秒)
|
||||||
|
- `mounted=True` 后开始 `navigate()`(持续按 W + 转向)
|
||||||
|
- **进入战斗条件**:`combat or target`
|
||||||
|
- `stop_all()` 立刻停下
|
||||||
|
- 如需要可按一次“取消坐骑/下马”(可选)
|
||||||
|
- 执行战斗逻辑
|
||||||
|
|
||||||
|
**优点**
|
||||||
|
- 不依赖固定 sleep,不卡延迟,几乎不会误触“下马”。
|
||||||
|
- 不会因为公共CD、网络卡顿、瞬时移动导致上马失败后一直走路。
|
||||||
|
|
||||||
|
**前置条件**
|
||||||
|
- 需要能从游戏侧输出 `mounted` 状态(WeakAura/插件/像素条协议增加一格均可)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 方案 B(无需新增状态,最快落地):盲按坐骑 + 冷却防抖
|
||||||
|
|
||||||
|
**核心思路**:不检测是否已经上马,只在满足条件时以**较低频率**尝试上马,并通过冷却与节流避免反复触发。
|
||||||
|
|
||||||
|
- **触发条件**(建议都满足):
|
||||||
|
- `not combat` 且 `not target`
|
||||||
|
- 不在“脱战拾取/剥皮冻结窗口”内(避免打断交互)
|
||||||
|
- 距离阈值满足(见方案 C)
|
||||||
|
- 距离上次尝试上马已超过 `mount_try_cooldown`(建议 8–12 秒)
|
||||||
|
- **动作**:
|
||||||
|
- 先 `stop_all()`(移动会导致上马失败)
|
||||||
|
- 按一次坐骑键/宏
|
||||||
|
- `sleep(0.2~0.4)`(给客户端一点时间响应)
|
||||||
|
- 再进入 `navigate()`
|
||||||
|
- **切回战斗**:
|
||||||
|
- 若 `combat or target`:立刻 `stop_all()` 并进入战斗逻辑
|
||||||
|
|
||||||
|
**优点**
|
||||||
|
- 不需要改游戏状态协议,改动面最小。
|
||||||
|
|
||||||
|
**风险**
|
||||||
|
- 如果你绑定的“坐骑键”在已上马时会取消坐骑,那么盲按可能导致“按一下下马”。因此必须:
|
||||||
|
- 冷却要长(8–12s)
|
||||||
|
- 触发条件要保守(最好配合方案 C 的距离阈值)
|
||||||
|
- 坐骑键尽量绑定为“更不容易误下马”的宏(见方案 D)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 方案 C(更像真人、效率更高):基于距离阈值决定是否上马
|
||||||
|
|
||||||
|
**核心思路**:只有在“要跑很远”的时候才上马,短距离巡逻不浪费上马时间,也减少误触发频率。
|
||||||
|
|
||||||
|
- 计算当前位置到下一航点距离 `dist`
|
||||||
|
- 当 `dist > mount_dist_threshold` 才允许上马尝试
|
||||||
|
|
||||||
|
**推荐阈值**
|
||||||
|
- `mount_dist_threshold = 8 ~ 15`(以你当前坐标系单位为准,按实际体感调整)
|
||||||
|
|
||||||
|
**与方案 B 的组合建议**
|
||||||
|
- 实际落地强烈建议用 **方案 C + 方案 B**:距离足够远才进行低频盲按上马。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 方案 D(强烈建议配套):使用“智能坐骑宏”降低误下马
|
||||||
|
|
||||||
|
为了降低脚本“盲按一次”带来的不确定性,建议把坐骑键绑定为更可控的宏/按键策略:
|
||||||
|
|
||||||
|
- **尽量避免**:同一个键在“已上马”时必定执行下马的行为(会增加误下马概率)
|
||||||
|
- **建议**:脚本侧通过冷却和距离阈值尽量“只按一次”,并把坐骑宏设计为“尽量只上马”
|
||||||
|
|
||||||
|
> 注:不同版本/服环境宏条件支持不完全一致;这里给的是方向,具体宏内容以你客户端可用语法为准。
|
||||||
|
|
||||||
|
## 推荐落地路径(按投入产出)
|
||||||
|
|
||||||
|
### 路径 1(最快能用):方案 C + 方案 B
|
||||||
|
|
||||||
|
- **一套就能跑起来**,不改 `game_state` 协议。
|
||||||
|
- 关键参数:
|
||||||
|
- `mount_try_cooldown`: 8–12 秒
|
||||||
|
- `mount_dist_threshold`: 8–15
|
||||||
|
- `mount_press_settle_sleep`: 0.2–0.4 秒
|
||||||
|
- 关键注意:
|
||||||
|
- 上马前必须 `stop_all()`,否则容易上马失败。
|
||||||
|
- 与拾取/剥皮冻结窗口协调:冻结窗口未结束前,不要上马、不导航。
|
||||||
|
|
||||||
|
### 路径 2(工业级稳定):方案 A
|
||||||
|
|
||||||
|
- 增加 `mounted` 状态输入,形成闭环状态机。
|
||||||
|
- 上马只在 `mounted=False` 时触发,几乎消除“误下马/抖动”。
|
||||||
|
|
||||||
|
## 与拾取/剥皮的冲突处理(重要)
|
||||||
|
|
||||||
|
读条/交互(拾取、剥皮)会被以下动作打断:
|
||||||
|
|
||||||
|
- 任何移动键(尤其是 `W`)或转向键脉冲
|
||||||
|
- 换目标(如 Tab)
|
||||||
|
- 再次进入战斗/获得目标
|
||||||
|
|
||||||
|
因此强烈建议:
|
||||||
|
|
||||||
|
- 在拾取/剥皮窗口内:**完全禁止导航与 Tab**(冻结移动/找怪)。
|
||||||
|
- 冻结窗口结束后再允许“上马与跑路”逻辑。
|
||||||
|
|
||||||
|
## 验收标准(你实现后怎么判断对不对)
|
||||||
|
|
||||||
|
- **脱战无目标**:角色停止原地抖动 → 成功上马 → 开始连续巡逻移动。
|
||||||
|
- **途中遭遇**:一出现 `combat=True` 或 `target=True` → 立即停下并进入战斗逻辑(不再继续按 W)。
|
||||||
|
- **不会抖动**:不会出现“每隔 1 秒按一下坐骑导致上马/下马来回切”的现象。
|
||||||
|
|
||||||
167
game_state.py
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import ctypes
|
||||||
|
import pyautogui
|
||||||
|
import pygetwindow as gw
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
# 解决 Windows 高 DPI 缩放问题
|
||||||
|
try:
|
||||||
|
ctypes.windll.shcore.SetProcessDpiAwareness(1)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 默认布局参数
|
||||||
|
_DEFAULTS = {
|
||||||
|
"pixel_size": 17,
|
||||||
|
"block_start_x": 30,
|
||||||
|
"scan_region_width": 155,
|
||||||
|
"scan_region_height": 15,
|
||||||
|
"offset_left": 20,
|
||||||
|
"offset_top": 45,
|
||||||
|
}
|
||||||
|
|
||||||
|
SCREENSHOT_DIR = 'screenshot'
|
||||||
|
CONFIG_FILE = 'game_state_config.json'
|
||||||
|
|
||||||
|
|
||||||
|
def _config_base():
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
return os.path.dirname(sys.executable)
|
||||||
|
return os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_config_path():
|
||||||
|
base = _config_base()
|
||||||
|
p = os.path.join(base, CONFIG_FILE)
|
||||||
|
if os.path.exists(p):
|
||||||
|
return p
|
||||||
|
if getattr(sys, 'frozen', False) and getattr(sys, '_MEIPASS', ''):
|
||||||
|
p2 = os.path.join(sys._MEIPASS, CONFIG_FILE)
|
||||||
|
if os.path.exists(p2):
|
||||||
|
return p2
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
def load_layout_config():
|
||||||
|
"""加载 game_state_config.json,返回布局参数字典"""
|
||||||
|
path = _get_config_path()
|
||||||
|
cfg = dict(_DEFAULTS)
|
||||||
|
if os.path.exists(path):
|
||||||
|
try:
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
loaded = json.load(f)
|
||||||
|
for k in _DEFAULTS:
|
||||||
|
if k in loaded:
|
||||||
|
cfg[k] = loaded[k]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
cfg['center_offset'] = cfg['pixel_size'] // 2
|
||||||
|
return cfg
|
||||||
|
|
||||||
|
|
||||||
|
def save_layout_config(cfg):
|
||||||
|
"""保存布局配置到 game_state_config.json"""
|
||||||
|
path = _get_config_path()
|
||||||
|
out = {k: cfg[k] for k in _DEFAULTS if k in cfg}
|
||||||
|
with open(path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(out, f, indent=2, ensure_ascii=False)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def get_wow_info():
|
||||||
|
windows = gw.getWindowsWithTitle('魔兽世界')
|
||||||
|
if not windows:
|
||||||
|
return None
|
||||||
|
wow = windows[0]
|
||||||
|
cfg = load_layout_config()
|
||||||
|
return {
|
||||||
|
"left": wow.left + cfg['offset_left'],
|
||||||
|
"top": wow.top + cfg['offset_top'],
|
||||||
|
"layout": cfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_game_state():
|
||||||
|
pos = get_wow_info()
|
||||||
|
if not pos:
|
||||||
|
return None
|
||||||
|
|
||||||
|
cfg = pos['layout']
|
||||||
|
w = cfg['scan_region_width']
|
||||||
|
h = cfg['scan_region_height']
|
||||||
|
pixel_size = cfg['pixel_size']
|
||||||
|
block_start_x = cfg['block_start_x']
|
||||||
|
center_offset = cfg['center_offset']
|
||||||
|
|
||||||
|
# 截图并保存到 screenshot 文件夹
|
||||||
|
screenshot = pyautogui.screenshot(region=(pos['left'], pos['top'], w, h))
|
||||||
|
os.makedirs(SCREENSHOT_DIR, exist_ok=True)
|
||||||
|
screenshot.save(os.path.join(SCREENSHOT_DIR, 'game_state.png'))
|
||||||
|
|
||||||
|
# 按截图像素分段:每 pixel_size 一格 → hp, mp, combat, target, logistics_death, x, y
|
||||||
|
CHANNEL_NAMES = ('hp', 'mp', 'combat', 'target', 'logistics_death', 'x', 'y')
|
||||||
|
for idx, name in enumerate(CHANNEL_NAMES):
|
||||||
|
left = block_start_x + idx * pixel_size
|
||||||
|
box = (left, 0, left + pixel_size, pixel_size)
|
||||||
|
crop = screenshot.crop(box)
|
||||||
|
crop.save(os.path.join(SCREENSHOT_DIR, f'game_state_{name}.png'))
|
||||||
|
|
||||||
|
# 每格取中心像素,避免裁到边缘混合色
|
||||||
|
def get_val(idx):
|
||||||
|
sample_x = block_start_x + (idx * pixel_size) + center_offset
|
||||||
|
sample_y = center_offset
|
||||||
|
return screenshot.getpixel((sample_x, sample_y))
|
||||||
|
|
||||||
|
state = {}
|
||||||
|
# idx 0=血(红), 1=法(蓝), 2=战斗(绿), 3=目标(红)
|
||||||
|
hp_px = get_val(0)
|
||||||
|
state['hp'] = round(hp_px[0] / 255 * 100)
|
||||||
|
state['target_hp'] = round(hp_px[1] / 255 * 100)
|
||||||
|
|
||||||
|
mp_px = get_val(1)
|
||||||
|
state['mp'] = round(mp_px[2] / 255 * 100)
|
||||||
|
|
||||||
|
cb_px = get_val(2)
|
||||||
|
state['combat'] = cb_px[1] > 150 # 绿色通道
|
||||||
|
|
||||||
|
tg_px = get_val(3)
|
||||||
|
state['target'] = tg_px[0] > 150 # 红色通道
|
||||||
|
|
||||||
|
# 第 5 像素:后勤与死亡 (R=空格数, G=耐久比例, B=死亡状态)
|
||||||
|
p5 = get_val(4)
|
||||||
|
state['free_slots'] = p5[0]
|
||||||
|
state['durability'] = round(p5[1] / 255.0, 3)
|
||||||
|
state['death_state'] = 0 if p5[2] < 50 else (1 if p5[2] < 200 else 2) # 0存活 1尸体 2灵魂
|
||||||
|
|
||||||
|
# 第 6 像素:x 坐标
|
||||||
|
p6 = get_val(5)
|
||||||
|
state['x'] = round((p6[0] + p6[1]/100), 2)
|
||||||
|
raw_deg = (p6[2] / 255) * 360.0
|
||||||
|
state['facing'] = (360.0 - raw_deg) % 360.0
|
||||||
|
|
||||||
|
# 第 7 像素:y 坐标
|
||||||
|
p7 = get_val(6)
|
||||||
|
state['y'] = round((p7[0] + p7[1]/100), 2)
|
||||||
|
|
||||||
|
return state
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("开始监控魔兽状态... (Ctrl+C 退出)")
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
state = parse_game_state()
|
||||||
|
if state:
|
||||||
|
death_txt = ('存活', '尸体', '灵魂')[state.get('death_state', 0)]
|
||||||
|
print(
|
||||||
|
f"\r[状态] 血:{state['hp']}% 法:{state['mp']}% 目标血:{state.get('target_hp', 0)}% | "
|
||||||
|
f"战斗:{'Y' if state['combat'] else 'N'} 目标:{'Y' if state['target'] else 'N'} | "
|
||||||
|
f"空格:{state.get('free_slots', 0)} 耐久:{state.get('durability', 0):.0%} {death_txt} | "
|
||||||
|
f"x:{state['x']} y:{state['y']} 朝向:{state['facing']:.1f}°",
|
||||||
|
end=""
|
||||||
|
)
|
||||||
|
time.sleep(0.5)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n已停止。")
|
||||||
8
game_state_config.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"pixel_size": 17,
|
||||||
|
"block_start_x": 30,
|
||||||
|
"scan_region_width": 155,
|
||||||
|
"scan_region_height": 15,
|
||||||
|
"offset_left": 20,
|
||||||
|
"offset_top": 45
|
||||||
|
}
|
||||||
BIN
images/1.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
images/2.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
images/3.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
images/4.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
images/bl.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
images/ls.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
images/ls1.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
images/yellow.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
85
logistics_manager.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import json
|
||||||
|
import math
|
||||||
|
import time
|
||||||
|
import pydirectinput
|
||||||
|
|
||||||
|
# 修理商所在位置(游戏坐标),按实际位置修改
|
||||||
|
VENDOR_POS = (30.08, 71.51)
|
||||||
|
# 到达判定距离(与 coordinate_patrol 一致)
|
||||||
|
VENDOR_ARRIVAL_THRESHOLD = 0.1
|
||||||
|
# 修理商所在位置配置文件
|
||||||
|
VENDOR_FILE = 'vendor.json'
|
||||||
|
|
||||||
|
|
||||||
|
class LogisticsManager:
|
||||||
|
def __init__(self, route_file=None):
|
||||||
|
self.need_repair = False
|
||||||
|
self.bag_full = False
|
||||||
|
self.is_returning = False
|
||||||
|
self.route_file = route_file or VENDOR_FILE
|
||||||
|
|
||||||
|
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 not self.is_returning:
|
||||||
|
print(f">>> [后勤警告] 背包/耐久不足!触发回城程序。")
|
||||||
|
self.is_returning = True
|
||||||
|
else:
|
||||||
|
self.is_returning = False
|
||||||
|
|
||||||
|
def return_home(self):
|
||||||
|
"""执行回城动作"""
|
||||||
|
# 1. 停止当前巡逻
|
||||||
|
# 2. 寻找安全点或直接使用炉石
|
||||||
|
print(">>> 正在释放炉石...")
|
||||||
|
pydirectinput.press('7') # 假设炉石在 7 号键
|
||||||
|
time.sleep(15) # 等待炉石施法
|
||||||
|
|
||||||
|
def handle_town_visit(self, state, patrol):
|
||||||
|
"""回城流程:用 state 取当前坐标与朝向,调用 patrol.navigate_to_point 前往修理商;到达后按 F3 交互"""
|
||||||
|
is_arrived = patrol.navigate_to_point(
|
||||||
|
state, VENDOR_POS,
|
||||||
|
arrival_threshold=VENDOR_ARRIVAL_THRESHOLD,
|
||||||
|
)
|
||||||
|
if is_arrived:
|
||||||
|
print(">>> 到达修理商,执行交互宏")
|
||||||
|
self._do_vendor_interact()
|
||||||
|
self.is_returning = False
|
||||||
|
|
||||||
|
def _do_vendor_interact(self):
|
||||||
|
"""执行与修理商/背包的交互按键(8、4)。"""
|
||||||
|
pydirectinput.press("8")
|
||||||
|
time.sleep(0.5)
|
||||||
|
pydirectinput.press("4")
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
def run_route1_round(self, get_state, patrol, route_file=None):
|
||||||
|
"""
|
||||||
|
读取 route1.json 路径,先正向走完,执行交互(8、4),再反向走完,然后结束。
|
||||||
|
get_state: 可调用对象,返回当前状态 dict(含 x, y, facing)。
|
||||||
|
patrol: CoordinatePatrol 实例,用于 navigate_path。
|
||||||
|
route_file: 路径 JSON 文件路径,默认使用 __init__ 中的 route_file。
|
||||||
|
"""
|
||||||
|
route_file = route_file or self.route_file
|
||||||
|
with open(route_file, "r", encoding="utf-8") as f:
|
||||||
|
path = json.load(f)
|
||||||
|
if not path:
|
||||||
|
print(">>> [后勤] route1 为空,跳过")
|
||||||
|
return
|
||||||
|
print(">>> [后勤] 开始 route1 正向")
|
||||||
|
ok = patrol.navigate_path(get_state, path, forward=True, arrival_threshold=VENDOR_ARRIVAL_THRESHOLD)
|
||||||
|
if not ok:
|
||||||
|
print(">>> [后勤] 正向未完成,中止")
|
||||||
|
return
|
||||||
|
print(">>> [后勤] 正向到达,执行交互")
|
||||||
|
self._do_vendor_interact()
|
||||||
|
print(">>> [后勤] 开始 route1 反向")
|
||||||
|
ok = patrol.navigate_path(get_state, path, forward=False, arrival_threshold=VENDOR_ARRIVAL_THRESHOLD)
|
||||||
|
if not ok:
|
||||||
|
print(">>> [后勤] 反向未完成")
|
||||||
|
return
|
||||||
|
print(">>> [后勤] route1 往返结束")
|
||||||
51
main.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
魔兽世界自动打怪主入口模块
|
||||||
|
使用图像识别技术定位怪物并模拟攻击操作
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
from typing import Optional, Tuple
|
||||||
|
from combat_engine import CombatEngine
|
||||||
|
from config import GameConfig
|
||||||
|
|
||||||
|
def setup_logging() -> None:
|
||||||
|
"""配置日志记录"""
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
|
handlers=[
|
||||||
|
logging.FileHandler('combat.log'),
|
||||||
|
logging.StreamHandler()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
"""主入口函数"""
|
||||||
|
try:
|
||||||
|
setup_logging()
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# 加载配置
|
||||||
|
config = GameConfig.load_config()
|
||||||
|
logger.info("游戏配置加载成功")
|
||||||
|
|
||||||
|
# 初始化战斗引擎
|
||||||
|
engine = CombatEngine(config)
|
||||||
|
logger.info("战斗引擎初始化完成")
|
||||||
|
|
||||||
|
# 启动自动战斗
|
||||||
|
logger.info("启动自动打怪系统...")
|
||||||
|
engine.run_combat_loop()
|
||||||
|
|
||||||
|
return 0
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info("用户中断程序执行")
|
||||||
|
return 0
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(f"程序异常终止: {str(e)}", exc_info=True)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
284
player_movement.py
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
import math
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import pyautogui
|
||||||
|
from player_position import PlayerPosition
|
||||||
|
|
||||||
|
# 游戏朝向约定:正北=0°,正西=90°,正南=180°,正东=270°(逆时针递增)
|
||||||
|
# 游戏坐标约定:人物面朝正北,按w一秒,坐标y会增加0.1 反之减少0.1 。如果人物面朝正东,按w一秒,坐标x会增加0.1 如果人物面朝正西,按w一秒,坐标x会减少0.1 。
|
||||||
|
# 按键w前进,按键a左转,按键d右转,按键s后退 (转向时最好按0.5s就停止,以免转向过度)
|
||||||
|
# 当前人物的坐标和朝向根据player_position.py模块获取
|
||||||
|
|
||||||
|
class PlayerMovement:
|
||||||
|
|
||||||
|
MOVE_SPEED = 0.1 # 每秒移动距离(坐标单位)
|
||||||
|
TURN_RATE = 216.0 # 每秒转向角度(度);实测约 216°/s(0.5s ≈ 108°)
|
||||||
|
MAX_TURN_DURATION = 0.5 # 原地转向单次最长按键时间(秒),避免转向过度
|
||||||
|
SMOOTH_TURN_DURATION = 0.15 # 边走边转时每次按键时长(秒)
|
||||||
|
ANGLE_TOLERANCE = 5.0 # 朝向允许误差(度)
|
||||||
|
COARSE_TURN_THRESHOLD = 90.0 # 超过此角度差则停下来原地粗对齐,再继续行走
|
||||||
|
HEADING_CHECK_INTERVAL = 5 # 每隔几次位置读取才做一次朝向校正
|
||||||
|
STUCK_CHECK_COUNT = 3 # 连续多少次坐标无明显位移则判定为卡死
|
||||||
|
STUCK_MOVE_THRESHOLD = 0.05 # 每次检测周期内最小期望位移(坐标单位)
|
||||||
|
# 实测方向:按 a 朝向减小(右转),按 d 朝向增大(左转)
|
||||||
|
# 游戏朝向为顺时针:北=0°, 东=90°, 南=180°, 西=270°
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.player_position = PlayerPosition()
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def get_required_heading(self, current_x: float, current_y: float,
|
||||||
|
target_x: float, target_y: float) -> float:
|
||||||
|
"""计算从当前位置朝向目标位置所需的游戏朝向角度。
|
||||||
|
|
||||||
|
实测游戏规律:
|
||||||
|
朝向 0° → 南(y 减小),朝向 180° → 北(y 增加)
|
||||||
|
朝向 90° → 东(x 增加),朝向 270° → 西(x 减小)
|
||||||
|
即:dx_actual ∝ sin(heading),dy_actual ∝ -cos(heading)
|
||||||
|
因此目标朝向 = atan2(dx, -dy)
|
||||||
|
验证:需北移(dy>0) → atan2(0,-1)=180° ✓;需东移(dx>0) → atan2(1,0)=90° ✓
|
||||||
|
"""
|
||||||
|
dx = target_x - current_x
|
||||||
|
dy = target_y - current_y
|
||||||
|
if dx == 0 and dy == 0:
|
||||||
|
return 0.0
|
||||||
|
return math.degrees(math.atan2(dx, -dy)) % 360
|
||||||
|
|
||||||
|
def turn_to_heading(self, target_heading: float, max_attempts: int = 10) -> bool:
|
||||||
|
"""转向到目标朝向。
|
||||||
|
|
||||||
|
按键 a 左转(朝向增大),按键 d 右转(朝向减小)。
|
||||||
|
每次按键不超过 MAX_TURN_DURATION 秒,多次微调直到误差在 ANGLE_TOLERANCE 内。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target_heading: 目标朝向角度(0~360)
|
||||||
|
max_attempts: 最大调整次数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否成功对准目标朝向
|
||||||
|
"""
|
||||||
|
for attempt in range(max_attempts):
|
||||||
|
current_heading = self.player_position.get_heading_with_retry()
|
||||||
|
if current_heading is None:
|
||||||
|
self.logger.warning("无法获取当前朝向,转向中止")
|
||||||
|
return False
|
||||||
|
|
||||||
|
diff = (target_heading - current_heading) % 360
|
||||||
|
|
||||||
|
if diff <= self.ANGLE_TOLERANCE or diff >= (360 - self.ANGLE_TOLERANCE):
|
||||||
|
self.logger.info(f"朝向已对齐:当前 {current_heading:.1f}°,目标 {target_heading:.1f}°")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 实测:d 使朝向增大,a 使朝向减小(与按键名称相反)
|
||||||
|
# diff ≤ 180:需增大朝向 → 按 d;diff > 180:需减小朝向 → 按 a
|
||||||
|
if diff <= 180:
|
||||||
|
key, angle_to_turn = 'd', diff
|
||||||
|
else:
|
||||||
|
key, angle_to_turn = 'a', 360 - diff
|
||||||
|
|
||||||
|
turn_duration = min(angle_to_turn / self.TURN_RATE, self.MAX_TURN_DURATION)
|
||||||
|
self.logger.info(
|
||||||
|
f"[转向 {attempt + 1}] 当前 {current_heading:.1f}° → 目标 {target_heading:.1f}°,"
|
||||||
|
f"按 '{key}' {turn_duration:.2f}s(需转 {angle_to_turn:.1f}°)"
|
||||||
|
)
|
||||||
|
pyautogui.keyDown(key)
|
||||||
|
time.sleep(turn_duration)
|
||||||
|
pyautogui.keyUp(key)
|
||||||
|
time.sleep(0.15) # 等待游戏刷新朝向
|
||||||
|
|
||||||
|
self.logger.warning(f"转向失败:超过最大尝试次数 {max_attempts}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def move_forward(self, duration: float):
|
||||||
|
"""按住 w 键向前移动指定时间。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
duration: 移动时间(秒)
|
||||||
|
"""
|
||||||
|
self.logger.info(f"向前移动 {duration:.2f}s")
|
||||||
|
pyautogui.keyDown('w')
|
||||||
|
time.sleep(duration)
|
||||||
|
pyautogui.keyUp('w')
|
||||||
|
|
||||||
|
def _escape_stuck(self):
|
||||||
|
"""卡死脱困组合拳:后退 + 随机转向 + 跳跃。
|
||||||
|
|
||||||
|
当连续多次检测到坐标无明显位移时调用,尝试脱离障碍物。
|
||||||
|
"""
|
||||||
|
import random
|
||||||
|
self.logger.warning("检测到角色卡死,执行脱困动作")
|
||||||
|
|
||||||
|
# 1. 松开前进键,后退 0.8s
|
||||||
|
pyautogui.keyUp('w')
|
||||||
|
pyautogui.keyDown('s')
|
||||||
|
time.sleep(0.8)
|
||||||
|
pyautogui.keyUp('s')
|
||||||
|
|
||||||
|
# 2. 随机左转或右转 0.3~0.5s
|
||||||
|
turn_key = random.choice(['a', 'd'])
|
||||||
|
turn_dur = random.uniform(0.3, 0.5)
|
||||||
|
pyautogui.keyDown(turn_key)
|
||||||
|
time.sleep(turn_dur)
|
||||||
|
pyautogui.keyUp(turn_key)
|
||||||
|
|
||||||
|
# 3. 跳跃(按空格)同时向前冲 0.5s
|
||||||
|
pyautogui.keyDown('w')
|
||||||
|
pyautogui.press('space')
|
||||||
|
time.sleep(0.5)
|
||||||
|
pyautogui.keyUp('w')
|
||||||
|
|
||||||
|
self.logger.info("脱困动作完成,重新开始前进")
|
||||||
|
|
||||||
|
def move_to(self, target_x: float, target_y: float,
|
||||||
|
position_tolerance: float = 0.5, max_iterations: int = 100) -> bool:
|
||||||
|
"""平滑移动人物到目标坐标(边走边转向)。
|
||||||
|
|
||||||
|
策略:
|
||||||
|
1. 起步前若角度偏差 > COARSE_TURN_THRESHOLD,先原地粗对齐;
|
||||||
|
2. 按住 w 持续前进,每次读取朝向后同时按 a/d 微调方向(边走边转);
|
||||||
|
3. 若行走中出现大角度偏差(绕过障碍等),短暂停步原地修正后继续;
|
||||||
|
4. 到达判定半径内松开 w,结束移动。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target_x, target_y: 目标坐标
|
||||||
|
position_tolerance: 到达判定半径(坐标单位),默认 0.5
|
||||||
|
max_iterations: 最大循环次数,防止死循环
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否成功到达目标位置
|
||||||
|
"""
|
||||||
|
self.logger.info(f"开始平滑移动到目标位置 ({target_x}, {target_y})")
|
||||||
|
|
||||||
|
# 起步前先判断是否已在目标范围内,避免重复调用时原地乱转
|
||||||
|
init_pos = self.player_position.get_position_with_retry()
|
||||||
|
if init_pos is None:
|
||||||
|
self.logger.warning("无法获取当前坐标,移动中止")
|
||||||
|
return False
|
||||||
|
if (abs(target_x - init_pos[0]) <= position_tolerance and
|
||||||
|
abs(target_y - init_pos[1]) <= position_tolerance):
|
||||||
|
self.logger.info(f"已在目标位置 ({target_x}, {target_y}) 范围内,无需移动")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 起步前粗对齐:角度偏差过大时先原地转向,避免一开始就走偏
|
||||||
|
init_heading = self.player_position.get_heading_with_retry()
|
||||||
|
if init_heading is not None:
|
||||||
|
init_required = self.get_required_heading(init_pos[0], init_pos[1], target_x, target_y)
|
||||||
|
init_diff = (init_required - init_heading) % 360
|
||||||
|
init_abs_diff = init_diff if init_diff <= 180 else 360 - init_diff
|
||||||
|
if init_abs_diff > self.COARSE_TURN_THRESHOLD:
|
||||||
|
self.logger.info(f"起步角度差 {init_abs_diff:.1f}°,先原地粗对齐")
|
||||||
|
if not self.turn_to_heading(init_required):
|
||||||
|
self.logger.warning("起步粗对齐失败,移动中止")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 按住 w 开始持续前进
|
||||||
|
pyautogui.keyDown('w')
|
||||||
|
# 卡死检测:记录最近 STUCK_CHECK_COUNT 次坐标,用于判断是否停滞
|
||||||
|
recent_positions = []
|
||||||
|
try:
|
||||||
|
for iteration in range(max_iterations):
|
||||||
|
# 每隔 HEADING_CHECK_INTERVAL 次才读一次朝向并校正
|
||||||
|
# 读取当前位置(OCR 耗时期间 w 持续按住,角色一直在走)
|
||||||
|
current_pos = self.player_position.get_position_with_retry()
|
||||||
|
if current_pos is None:
|
||||||
|
self.logger.warning("无法获取当前坐标,移动中止")
|
||||||
|
return False
|
||||||
|
|
||||||
|
current_x, current_y = current_pos
|
||||||
|
dx = target_x - current_x
|
||||||
|
dy = target_y - current_y
|
||||||
|
distance = math.sqrt(dx ** 2 + dy ** 2)
|
||||||
|
|
||||||
|
self.logger.info(
|
||||||
|
f"[平滑 {iteration + 1}] 当前 ({current_x}, {current_y}),"
|
||||||
|
f"目标 ({target_x}, {target_y}),剩余距离 {distance:.2f}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 卡死检测:维护滑动窗口,比较最新与最旧坐标的位移
|
||||||
|
recent_positions.append((current_x, current_y))
|
||||||
|
if len(recent_positions) > self.STUCK_CHECK_COUNT:
|
||||||
|
recent_positions.pop(0)
|
||||||
|
if len(recent_positions) == self.STUCK_CHECK_COUNT:
|
||||||
|
oldest_x, oldest_y = recent_positions[0]
|
||||||
|
moved = math.sqrt((current_x - oldest_x) ** 2 + (current_y - oldest_y) ** 2)
|
||||||
|
if moved < self.STUCK_MOVE_THRESHOLD:
|
||||||
|
self._escape_stuck()
|
||||||
|
recent_positions.clear()
|
||||||
|
pyautogui.keyDown('w')
|
||||||
|
|
||||||
|
# x 和 y 都在容差范围内即视为到达
|
||||||
|
if abs(dx) <= position_tolerance and abs(dy) <= position_tolerance:
|
||||||
|
self.logger.info(f"已到达目标位置 ({target_x}, {target_y})")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 接近目标时松开 w 停下,再做一次静止判定,避免冲过头后死循环
|
||||||
|
if distance <= position_tolerance * 2:
|
||||||
|
pyautogui.keyUp('w')
|
||||||
|
time.sleep(0.2)
|
||||||
|
final_pos = self.player_position.get_position_with_retry()
|
||||||
|
if final_pos is not None:
|
||||||
|
fdx = target_x - final_pos[0]
|
||||||
|
fdy = target_y - final_pos[1]
|
||||||
|
if abs(fdx) <= position_tolerance and abs(fdy) <= position_tolerance:
|
||||||
|
self.logger.info(f"已到达目标位置 ({target_x}, {target_y})")
|
||||||
|
return True
|
||||||
|
# 静止后仍未到达,重新按住 w 继续前进
|
||||||
|
pyautogui.keyDown('w')
|
||||||
|
|
||||||
|
# 每隔 HEADING_CHECK_INTERVAL 次读一次朝向,减少 OCR 频率
|
||||||
|
if iteration % self.HEADING_CHECK_INTERVAL != 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
current_heading = self.player_position.get_heading_with_retry()
|
||||||
|
if current_heading is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
required_heading = self.get_required_heading(current_x, current_y, target_x, target_y)
|
||||||
|
diff = (required_heading - current_heading) % 360
|
||||||
|
abs_diff = diff if diff <= 180 else 360 - diff
|
||||||
|
|
||||||
|
if abs_diff > self.COARSE_TURN_THRESHOLD:
|
||||||
|
# 偏差过大:停步原地粗修正,再重新按住 w
|
||||||
|
pyautogui.keyUp('w')
|
||||||
|
self.logger.info(f"偏差 {abs_diff:.1f}° 过大,停步原地修正")
|
||||||
|
if not self.turn_to_heading(required_heading):
|
||||||
|
self.logger.warning("修正转向失败,移动中止")
|
||||||
|
return False
|
||||||
|
pyautogui.keyDown('w')
|
||||||
|
|
||||||
|
elif abs_diff > self.ANGLE_TOLERANCE:
|
||||||
|
# 小幅偏差:边走边转,同时按住 w 和转向键
|
||||||
|
# diff ≤ 180 需增大朝向 → d;diff > 180 需减小朝向 → a
|
||||||
|
turn_key = 'd' if diff <= 180 else 'a'
|
||||||
|
turn_time = min(abs_diff / self.TURN_RATE, self.SMOOTH_TURN_DURATION)
|
||||||
|
self.logger.info(
|
||||||
|
f"边走边转:按 '{turn_key}' {turn_time:.2f}s(偏差 {abs_diff:.1f}°)"
|
||||||
|
)
|
||||||
|
pyautogui.keyDown(turn_key)
|
||||||
|
time.sleep(turn_time)
|
||||||
|
pyautogui.keyUp(turn_key)
|
||||||
|
else:
|
||||||
|
# 方向已对齐,让角色多走一段再重新检测,减少 OCR 频率
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
pyautogui.keyUp('w')
|
||||||
|
|
||||||
|
self.logger.warning(f"移动失败:超过最大迭代次数 {max_iterations}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def move_to_position(self, target_x: float, target_y: float,
|
||||||
|
tolerance: float = 0.5, max_iterations: int = 100) -> bool:
|
||||||
|
"""移动人物到目标坐标(combat_engine 对外接口)。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target_x, target_y: 目标坐标
|
||||||
|
tolerance: 到达判定半径(坐标单位),默认 0.5
|
||||||
|
max_iterations: 最大循环次数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否成功到达目标位置
|
||||||
|
"""
|
||||||
|
return self.move_to(target_x, target_y,
|
||||||
|
position_tolerance=tolerance,
|
||||||
|
max_iterations=max_iterations)
|
||||||
148
player_position.py
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
"""
|
||||||
|
玩家位置模块,用于获取人物当前所在坐标
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
from typing import Optional, Tuple
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
import pyautogui
|
||||||
|
from text_finder import TextFinder
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerPosition:
|
||||||
|
"""玩家位置类,用于获取人物当前所在坐标"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.screen_width, self.screen_height = pyautogui.size()
|
||||||
|
self.text_finder = TextFinder()
|
||||||
|
|
||||||
|
def get_position(self) -> Optional[Tuple[float, float]]:
|
||||||
|
"""获取人物当前所在坐标
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[Tuple[float, float]]: 坐标 (x, y),如果识别失败返回 None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 截图左上角区域
|
||||||
|
region_width = self.screen_width // 3
|
||||||
|
region_height = self.screen_height // 4
|
||||||
|
screenshot = pyautogui.screenshot(region=(0, 0, region_width, region_height))
|
||||||
|
os.makedirs('screenshot', exist_ok=True)
|
||||||
|
screenshot.save('screenshot/position_screenshot.png')
|
||||||
|
|
||||||
|
# 使用 text_finder 识别所有文字
|
||||||
|
text_info_list = self.text_finder.recognize_text_from_image(screenshot)
|
||||||
|
|
||||||
|
if not text_info_list:
|
||||||
|
print("未识别到任何文字")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 打印识别到的所有文字,用于调试
|
||||||
|
print(f"识别到的文字: {[info['text'] for info in text_info_list]}")
|
||||||
|
|
||||||
|
# 匹配坐标格式 xx / xx
|
||||||
|
# 匹配格式:坐标:x:25.77y:68.82 或 坐标:x25.77y:68.82(冒号可能被OCR漏识别)
|
||||||
|
coord_pattern = re.compile(r'坐标[::]?\s*[xX][::]?\s*(\d+\.?\d*)\s*[yY][::]?\s*(\d+\.?\d*)')
|
||||||
|
|
||||||
|
for text_info in text_info_list:
|
||||||
|
text = text_info.get('text', '')
|
||||||
|
match = coord_pattern.search(text)
|
||||||
|
if match:
|
||||||
|
x_coord = float(match.group(1))
|
||||||
|
y_coord = float(match.group(2))
|
||||||
|
print(f"找到坐标: ({x_coord}, {y_coord})")
|
||||||
|
return (x_coord, y_coord)
|
||||||
|
|
||||||
|
print("未找到坐标格式")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"获取位置失败: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_position_with_retry(self, max_retries: int = 3, retry_delay: float = 0.5) -> Optional[Tuple[float, float]]:
|
||||||
|
"""带重试机制的获取人物当前所在坐标
|
||||||
|
|
||||||
|
Args:
|
||||||
|
max_retries (int): 最大重试次数,默认为 3
|
||||||
|
retry_delay (float): 每次重试的间隔时间(秒),默认为 0.5
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[Tuple[float, float]]: 坐标 (x, y),如果识别失败返回 None
|
||||||
|
"""
|
||||||
|
for i in range(max_retries):
|
||||||
|
position = self.get_position()
|
||||||
|
if position:
|
||||||
|
return position
|
||||||
|
|
||||||
|
if i < max_retries - 1:
|
||||||
|
print(f"第 {i + 1} 次尝试失败,{retry_delay} 秒后重试...")
|
||||||
|
time.sleep(retry_delay)
|
||||||
|
|
||||||
|
print(f"经过 {max_retries} 次尝试,仍未获取到坐标")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_heading(self) -> Optional[float]:
|
||||||
|
"""获取人物当前朝向
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[float]: 朝向角度,如果识别失败返回 None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
region_width = self.screen_width // 3
|
||||||
|
region_height = self.screen_height // 4
|
||||||
|
screenshot = pyautogui.screenshot(region=(0, 0, region_width, region_height))
|
||||||
|
os.makedirs('screenshot', exist_ok=True)
|
||||||
|
screenshot.save('screenshot/heading_screenshot.png')
|
||||||
|
|
||||||
|
text_info_list = self.text_finder.recognize_text_from_image(screenshot)
|
||||||
|
|
||||||
|
if not text_info_list:
|
||||||
|
print("未识别到任何文字")
|
||||||
|
return None
|
||||||
|
|
||||||
|
print(f"识别到的文字: {[info['text'] for info in text_info_list]}")
|
||||||
|
|
||||||
|
# 朝向格式可能有冒号也可能没有:朝向:123 或 朝向123
|
||||||
|
heading_pattern = re.compile(r'朝向[::]?\s*(\d+\.?\d*)\s*')
|
||||||
|
|
||||||
|
for text_info in text_info_list:
|
||||||
|
text = text_info.get('text', '')
|
||||||
|
text = text.replace(' ', '')
|
||||||
|
|
||||||
|
match = heading_pattern.search(text)
|
||||||
|
if match:
|
||||||
|
heading = float(match.group(1))
|
||||||
|
print(f"找到朝向: {heading}°")
|
||||||
|
return heading
|
||||||
|
|
||||||
|
print("未找到朝向格式")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"获取朝向失败: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_heading_with_retry(self, max_retries: int = 3, retry_delay: float = 0.5) -> Optional[float]:
|
||||||
|
"""带重试机制的获取人物当前朝向
|
||||||
|
|
||||||
|
Args:
|
||||||
|
max_retries (int): 最大重试次数,默认为 3
|
||||||
|
retry_delay (float): 每次重试的间隔时间(秒),默认为 0.5
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[float]: 朝向角度,如果识别失败返回 None
|
||||||
|
"""
|
||||||
|
for i in range(max_retries):
|
||||||
|
heading = self.get_heading()
|
||||||
|
if heading is not None:
|
||||||
|
return heading
|
||||||
|
|
||||||
|
if i < max_retries - 1:
|
||||||
|
print(f"第 {i + 1} 次尝试失败,{retry_delay} 秒后重试...")
|
||||||
|
time.sleep(retry_delay)
|
||||||
|
|
||||||
|
print(f"经过 {max_retries} 次尝试,仍未获取到朝向")
|
||||||
|
return None
|
||||||
70
recorder.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import json
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
from game_state import parse_game_state
|
||||||
|
|
||||||
|
# 默认配置文件名
|
||||||
|
WAYPOINTS_FILE = 'waypoints.json'
|
||||||
|
# 记录两个点之间的最小间距阈值(游戏坐标单位)
|
||||||
|
DEFAULT_MIN_DISTANCE = 0.5
|
||||||
|
|
||||||
|
|
||||||
|
class WaypointRecorder:
|
||||||
|
def __init__(self, min_distance=DEFAULT_MIN_DISTANCE, waypoints_file=None, on_point_added=None):
|
||||||
|
self.waypoints = []
|
||||||
|
self.min_distance = min_distance
|
||||||
|
self.last_pos = (0, 0)
|
||||||
|
self.waypoints_file = waypoints_file or WAYPOINTS_FILE
|
||||||
|
self.on_point_added = on_point_added # 可选回调: (pos, count) => None
|
||||||
|
|
||||||
|
def get_distance(self, p1, p2):
|
||||||
|
return math.sqrt((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2)
|
||||||
|
|
||||||
|
def record(self, state):
|
||||||
|
"""state 来自 game_state.parse_game_state(),坐标字段为 x, y。"""
|
||||||
|
if state is None:
|
||||||
|
return
|
||||||
|
x, y = state.get('x'), state.get('y')
|
||||||
|
if x is None or y is None:
|
||||||
|
return
|
||||||
|
curr_pos = (float(x), float(y))
|
||||||
|
|
||||||
|
# 初始点记录
|
||||||
|
if self.last_pos == (0, 0):
|
||||||
|
self.add_point(curr_pos)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 只有当移动距离超过阈值时才记录点
|
||||||
|
dist = self.get_distance(curr_pos, self.last_pos)
|
||||||
|
if dist >= self.min_distance:
|
||||||
|
self.add_point(curr_pos)
|
||||||
|
|
||||||
|
def add_point(self, pos):
|
||||||
|
self.waypoints.append(pos)
|
||||||
|
self.last_pos = pos
|
||||||
|
if self.on_point_added:
|
||||||
|
self.on_point_added(pos, len(self.waypoints))
|
||||||
|
else:
|
||||||
|
print(f"已记录点: {pos} | 当前总点数: {len(self.waypoints)}")
|
||||||
|
|
||||||
|
def save(self, path=None):
|
||||||
|
out_path = path or self.waypoints_file
|
||||||
|
with open(out_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(self.waypoints, f, indent=4, ensure_ascii=False)
|
||||||
|
return out_path
|
||||||
|
|
||||||
|
# 使用示例(整合进你的主循环)
|
||||||
|
if __name__ == "__main__":
|
||||||
|
recorder = WaypointRecorder()
|
||||||
|
print("开始录制模式:请在游戏内跑动。按 Ctrl+C 停止并保存。")
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
state = parse_game_state()
|
||||||
|
if state:
|
||||||
|
recorder.record(state)
|
||||||
|
time.sleep(0.5)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
path = recorder.save()
|
||||||
|
print(f"路径已保存至 {path}")
|
||||||
30
recorder/vendor.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
85.35,
|
||||||
|
74.90
|
||||||
|
],
|
||||||
|
[
|
||||||
|
85.36,
|
||||||
|
74.93
|
||||||
|
],
|
||||||
|
[
|
||||||
|
85.66,
|
||||||
|
75.35
|
||||||
|
],
|
||||||
|
[
|
||||||
|
86.1,
|
||||||
|
75.59
|
||||||
|
],
|
||||||
|
[
|
||||||
|
86.61,
|
||||||
|
75.59
|
||||||
|
],
|
||||||
|
[
|
||||||
|
87.08,
|
||||||
|
75.82
|
||||||
|
],
|
||||||
|
[
|
||||||
|
87.24,
|
||||||
|
75.88
|
||||||
|
]
|
||||||
|
]
|
||||||
70
recorder/waypoints.json
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
85.35,
|
||||||
|
74.90
|
||||||
|
],
|
||||||
|
[
|
||||||
|
85.32,
|
||||||
|
74.86
|
||||||
|
],
|
||||||
|
[
|
||||||
|
85.02,
|
||||||
|
74.38
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84.8,
|
||||||
|
73.92
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84.6,
|
||||||
|
73.46
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84.39,
|
||||||
|
73.0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84.17,
|
||||||
|
72.55
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.88,
|
||||||
|
72.05
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.42,
|
||||||
|
71.84
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.3,
|
||||||
|
72.34
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.34,
|
||||||
|
72.9
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.37,
|
||||||
|
73.46
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.62,
|
||||||
|
73.94
|
||||||
|
],
|
||||||
|
[
|
||||||
|
83.97,
|
||||||
|
74.32
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84.41,
|
||||||
|
74.67
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84.94,
|
||||||
|
74.86
|
||||||
|
],
|
||||||
|
[
|
||||||
|
85.35,
|
||||||
|
74.90
|
||||||
|
]
|
||||||
|
]
|
||||||
70
recorder/湿地刷布点.json
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
61.08,
|
||||||
|
73.18
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.26,
|
||||||
|
72.7
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.44,
|
||||||
|
72.21
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.62,
|
||||||
|
71.71
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.8,
|
||||||
|
71.23
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.98,
|
||||||
|
70.73
|
||||||
|
],
|
||||||
|
[
|
||||||
|
62.12,
|
||||||
|
70.23
|
||||||
|
],
|
||||||
|
[
|
||||||
|
62.3,
|
||||||
|
69.76
|
||||||
|
],
|
||||||
|
[
|
||||||
|
62.48,
|
||||||
|
69.26
|
||||||
|
],
|
||||||
|
[
|
||||||
|
62.3,
|
||||||
|
69.76
|
||||||
|
],
|
||||||
|
[
|
||||||
|
62.14,
|
||||||
|
70.25
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.99,
|
||||||
|
70.73
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.83,
|
||||||
|
71.21
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.67,
|
||||||
|
71.71
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.51,
|
||||||
|
72.2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.33,
|
||||||
|
72.68
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61.17,
|
||||||
|
73.18
|
||||||
|
]
|
||||||
|
]
|
||||||
86
recorder/灰熊丘陵巡逻点.json
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
10.76,
|
||||||
|
60.55
|
||||||
|
],
|
||||||
|
[
|
||||||
|
11.12,
|
||||||
|
60.93
|
||||||
|
],
|
||||||
|
[
|
||||||
|
11.48,
|
||||||
|
61.31
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.01,
|
||||||
|
61.3
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.53,
|
||||||
|
61.35
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.91,
|
||||||
|
61.72
|
||||||
|
],
|
||||||
|
[
|
||||||
|
13.28,
|
||||||
|
62.08
|
||||||
|
],
|
||||||
|
[
|
||||||
|
13.8,
|
||||||
|
62.14
|
||||||
|
],
|
||||||
|
[
|
||||||
|
14.32,
|
||||||
|
62.07
|
||||||
|
],
|
||||||
|
[
|
||||||
|
14.53,
|
||||||
|
61.58
|
||||||
|
],
|
||||||
|
[
|
||||||
|
14.42,
|
||||||
|
61.04
|
||||||
|
],
|
||||||
|
[
|
||||||
|
14.31,
|
||||||
|
60.51
|
||||||
|
],
|
||||||
|
[
|
||||||
|
14.18,
|
||||||
|
60.01
|
||||||
|
],
|
||||||
|
[
|
||||||
|
14.03,
|
||||||
|
59.51
|
||||||
|
],
|
||||||
|
[
|
||||||
|
13.57,
|
||||||
|
59.28
|
||||||
|
],
|
||||||
|
[
|
||||||
|
13.05,
|
||||||
|
59.42
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.54,
|
||||||
|
59.55
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.11,
|
||||||
|
59.88
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12.01,
|
||||||
|
60.41
|
||||||
|
],
|
||||||
|
[
|
||||||
|
11.53,
|
||||||
|
60.58
|
||||||
|
],
|
||||||
|
[
|
||||||
|
10.76,
|
||||||
|
60.55
|
||||||
|
]
|
||||||
|
]
|
||||||
46
recorder/祖达克-冰冷裂口巡逻点.json
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
32.54,
|
||||||
|
41.22
|
||||||
|
],
|
||||||
|
[
|
||||||
|
32.67,
|
||||||
|
40.7
|
||||||
|
],
|
||||||
|
[
|
||||||
|
32.85,
|
||||||
|
40.19
|
||||||
|
],
|
||||||
|
[
|
||||||
|
32.98,
|
||||||
|
39.67
|
||||||
|
],
|
||||||
|
[
|
||||||
|
32.84,
|
||||||
|
39.14
|
||||||
|
],
|
||||||
|
[
|
||||||
|
32.35,
|
||||||
|
39.26
|
||||||
|
],
|
||||||
|
[
|
||||||
|
32.17,
|
||||||
|
39.76
|
||||||
|
],
|
||||||
|
[
|
||||||
|
31.98,
|
||||||
|
40.25
|
||||||
|
],
|
||||||
|
[
|
||||||
|
31.77,
|
||||||
|
40.75
|
||||||
|
],
|
||||||
|
[
|
||||||
|
31.57,
|
||||||
|
41.23
|
||||||
|
],
|
||||||
|
[
|
||||||
|
32.54,
|
||||||
|
41.22
|
||||||
|
]
|
||||||
|
]
|
||||||
246
recorder/祖达克-西莱图斯祭坛巡逻点.json
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
44.37,
|
||||||
|
34.77
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.37,
|
||||||
|
35.32
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.39,
|
||||||
|
35.82
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.4,
|
||||||
|
36.32
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.41,
|
||||||
|
36.82
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.42,
|
||||||
|
37.32
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.44,
|
||||||
|
37.82
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.46,
|
||||||
|
38.38
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.47,
|
||||||
|
38.94
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.49,
|
||||||
|
39.44
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.5,
|
||||||
|
39.94
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.52,
|
||||||
|
40.5
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.53,
|
||||||
|
41.0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.55,
|
||||||
|
41.55
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.56,
|
||||||
|
42.1
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.67,
|
||||||
|
42.62
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.71,
|
||||||
|
43.12
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.71,
|
||||||
|
43.67
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.68,
|
||||||
|
44.17
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.66,
|
||||||
|
44.67
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.63,
|
||||||
|
45.17
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.6,
|
||||||
|
45.71
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.58,
|
||||||
|
46.26
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.55,
|
||||||
|
46.76
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.52,
|
||||||
|
47.26
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.54,
|
||||||
|
47.82
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.43,
|
||||||
|
48.35
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.26,
|
||||||
|
48.85
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.02,
|
||||||
|
49.29
|
||||||
|
],
|
||||||
|
[
|
||||||
|
43.73,
|
||||||
|
49.75
|
||||||
|
],
|
||||||
|
[
|
||||||
|
43.35,
|
||||||
|
50.13
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.92,
|
||||||
|
49.87
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.81,
|
||||||
|
49.35
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.77,
|
||||||
|
48.81
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.71,
|
||||||
|
48.26
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.66,
|
||||||
|
47.71
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.6,
|
||||||
|
47.15
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.56,
|
||||||
|
46.6
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.56,
|
||||||
|
46.09
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.57,
|
||||||
|
45.55
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.58,
|
||||||
|
45.05
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.58,
|
||||||
|
44.54
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.59,
|
||||||
|
44.04
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.6,
|
||||||
|
43.52
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.6,
|
||||||
|
42.97
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.62,
|
||||||
|
42.47
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.64,
|
||||||
|
41.97
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.66,
|
||||||
|
41.42
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.68,
|
||||||
|
40.87
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.69,
|
||||||
|
40.32
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.7,
|
||||||
|
39.82
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.7,
|
||||||
|
39.32
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.7,
|
||||||
|
38.82
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.71,
|
||||||
|
38.27
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.71,
|
||||||
|
37.77
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.72,
|
||||||
|
37.25
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.73,
|
||||||
|
36.75
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.76,
|
||||||
|
36.2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42.89,
|
||||||
|
35.7
|
||||||
|
],
|
||||||
|
[
|
||||||
|
43.25,
|
||||||
|
35.3
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44.37,
|
||||||
|
34.77
|
||||||
|
]
|
||||||
|
]
|
||||||
26
recorder/银色前线基地-修理商.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
85.23,
|
||||||
|
74.7
|
||||||
|
],
|
||||||
|
[
|
||||||
|
85.51,
|
||||||
|
75.15
|
||||||
|
],
|
||||||
|
[
|
||||||
|
85.8,
|
||||||
|
75.58
|
||||||
|
],
|
||||||
|
[
|
||||||
|
86.31,
|
||||||
|
75.64
|
||||||
|
],
|
||||||
|
[
|
||||||
|
86.81,
|
||||||
|
75.63
|
||||||
|
],
|
||||||
|
[
|
||||||
|
87.26,
|
||||||
|
75.89
|
||||||
|
]
|
||||||
|
]
|
||||||