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
|
||||
]
|
||||
]
|
||||