Files
wow/auto_bot.py
2026-03-18 09:04:37 +08:00

158 lines
6.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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_key0=不启用)"""
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已停止。")