first commit
This commit is contained in:
158
auto_bot.py
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已停止。")
|
||||
Reference in New Issue
Block a user