-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
184 lines (149 loc) · 6.56 KB
/
Copy pathmain.py
File metadata and controls
184 lines (149 loc) · 6.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
"""模式1自动丢球 — 独立入口
启动后自动选择窗口,进入连续丢球循环。
每次丢球:检测到可丢球画面 → 随机长按左键 → 随机间隔 → 下一次检测。
"""
import random
import time as _time
import win32gui
import config
from capture import capture_window_bgr
from input import hold_click_random
from vision import load_templates, match_template, preprocess
from window import list_windows_by_keyword
def _select_window() -> int | None:
keyword = config.WINDOW_TITLE_KEYWORD
windows = list_windows_by_keyword(keyword)
if not windows:
print(f"\n[错误] 未找到包含 \"{keyword}\" 的窗口,请确认游戏已启动。")
return None
print(f"\n找到 {len(windows)} 个匹配 \"{keyword}\" 的窗口:\n")
for i, (hwnd, title, (x, y, w, h)) in enumerate(windows, 1):
print(f" {i}. {title}")
print(f" 句柄: {hwnd} 位置: ({x}, {y}) 尺寸: {w}x{h}\n")
if len(windows) == 1:
hwnd, title, _ = windows[0]
print(f"仅找到一个窗口,自动选择: {title}\n")
else:
while True:
sel = input(f"请选择窗口 (1-{len(windows)}): ").strip()
try:
idx = int(sel) - 1
if 0 <= idx < len(windows):
break
except ValueError:
pass
print(f"输入无效,请输入 1-{len(windows)} 的数字")
hwnd, title, _ = windows[idx]
print(f"\n已选择: {title}\n")
return hwnd
def _ts() -> str:
from datetime import datetime
return datetime.now().strftime("%H:%M:%S")
def main() -> None:
print("=" * 60)
print(" RocoPilotLite — 模式1 独立版(自动丢球)")
print(" 随机长按 + 随机间隔")
print("=" * 60)
hwnd = _select_window()
if hwnd is None:
return
templates = load_templates(config.TEMPLATE_DIR, use_edge=config.USE_EDGE_MATCH)
print(f"[{_ts()}] 已加载 {len(templates)} 个模板: "
f"{', '.join(t.name for t in templates)}")
print(f"[{_ts()}] 丢球间隔: {config.BALL_INTERVAL_MIN_SEC}~{config.BALL_INTERVAL_MAX_SEC}s(随机)")
print(f"[{_ts()}] 长按时间: {config.BALL_HOLD_MIN_SEC}~{config.BALL_HOLD_MAX_SEC}s(随机)")
print(f"[{_ts()}] 匹配阈值: {config.MATCH_THRESHOLD}")
print(f"[{_ts()}] 按 Ctrl+C 退出,按 {config.PAUSE_HOTKEY.upper()} 暂停/继续")
print()
# ── 暂停热键 ──
paused = [False] # mutable for closure
pause_toggle = [False]
try:
import keyboard
keyboard.add_hotkey(config.PAUSE_HOTKEY, lambda: pause_toggle.__setitem__(0, True))
except Exception:
pass
last_ball_time = 0.0
next_ball_delay = 0.0
def _roi(bgr, img_w, img_h, left_r, top_r, w_r, h_r):
"""从图像中裁剪 ROI 区域。"""
l = max(0, min(img_w - 1, int(img_w * left_r)))
t = max(0, min(img_h - 1, int(img_h * top_r)))
rw = max(1, min(img_w - l, int(img_w * w_r)))
rh = max(1, min(img_h - t, int(img_h * h_r)))
return bgr[t:t + rh, l:l + rw]
while True:
# ── 暂停处理 ──
if pause_toggle[0]:
pause_toggle[0] = False
paused[0] = not paused[0]
if paused[0]:
print(f"[{_ts()}] ⏸ 已暂停(按 {config.PAUSE_HOTKEY.upper()} 继续)")
else:
print(f"[{_ts()}] ▶ 已恢复")
if paused[0]:
_time.sleep(0.1)
continue
# ── 检查窗口前台 ──
if win32gui.GetForegroundWindow() != hwnd:
_time.sleep(config.POLL_INTERVAL_SEC)
continue
# ── 截图 ──
full_bgr = capture_window_bgr(hwnd)
h, w = full_bgr.shape[:2]
scale = w / config.REF_WIDTH
elf_roi = _roi(full_bgr, w, h, config.ROI_LEFT_RATIO, 0.0, config.ROI_WIDTH_RATIO, config.ROI_HEIGHT_RATIO)
elf_score = match_template(
preprocess(elf_roi, use_edge=config.USE_EDGE_MATCH),
templates, config.TEMPLATE_ELF, scale,
)
ex_roi = _roi(full_bgr, w, h, config.ROI_LEFT_RATIO, config.ROI_TOP_RATIO, config.ROI_WIDTH_RATIO, config.ROI_HEIGHT_RATIO)
exchange_score = match_template(
preprocess(ex_roi, use_edge=config.USE_EDGE_MATCH),
templates, config.TEMPLATE_EXCHANGE, scale,
)
max_score = max(elf_score, exchange_score)
# exchange 图标在战斗中出现(用于切换精灵),此时丢球无意义
in_battle = (exchange_score >= config.MATCH_THRESHOLD and
exchange_score > elf_score)
now = _time.time()
# ── 冷却检查 ──
if in_battle and now - last_ball_time < next_ball_delay:
_time.sleep(config.POLL_INTERVAL_SEC)
continue
if now - last_ball_time < next_ball_delay:
_time.sleep(config.POLL_INTERVAL_SEC)
continue
# ── 丢球判断(分数稳定时减少日志刷屏)──
if in_battle or max_score < config.MATCH_THRESHOLD:
prev_skip = getattr(main, '_last_skip_state', None)
cur_state = (in_battle, round(max_score, 3))
if prev_skip != cur_state:
if in_battle:
print(f"[{_ts()}] 战斗中,等待战斗结束 (elf_P={elf_score:.3f} exchange={exchange_score:.3f})")
else:
print(f"[{_ts()}] 跳过:未达阈值 (elf_P={elf_score:.3f} exchange={exchange_score:.3f})")
main._last_skip_state = cur_state
_time.sleep(config.POLL_INTERVAL_SEC)
continue
# ── 执行丢球 ──
# 重置跳过状态,确保状态切换时重新打印日志
main._last_skip_state = None
print(f"[{_ts()}] 丢球! (elf_P={elf_score:.3f} exchange={exchange_score:.3f})")
actual_hold = hold_click_random(config.BALL_HOLD_MIN_SEC, config.BALL_HOLD_MAX_SEC)
if actual_hold is not None:
last_ball_time = _time.time()
next_ball_delay = random.uniform(
config.BALL_INTERVAL_MIN_SEC,
config.BALL_INTERVAL_MAX_SEC,
)
print(f"[{_ts()}] 丢球完成,长按 {actual_hold:.2f}s,下次间隔: {next_ball_delay:.1f}s")
else:
print(f"[{_ts()}] [警告] 丢球点击失败")
# ── 轮询等待 ──
_time.sleep(config.POLL_INTERVAL_SEC)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print(f"\n[{_ts()}] 已退出。")