This repository was archived by the owner on Jul 24, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain_bot.py
More file actions
460 lines (407 loc) · 15.4 KB
/
Copy pathmain_bot.py
File metadata and controls
460 lines (407 loc) · 15.4 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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# -*- coding: utf-8 -*-
"""
FORZA HORIZON 6 AUTOBOT — Main Entry Point (main_bot.py)
=========================================================
Entry point for the full automation system. Provides an interactive
language → mode → phase selection flow.
Four-phase loop:
Farm Points → Buy Cars → Upgrade Cars → Trash Cars → (loop)
Usage:
python main_bot.py
python main_bot.py --web
python main_bot.py --console
python main_bot.py --lang en
python main_bot.py --lang zh
python main_bot.py --debug
"""
from macro import (
STATE_BUY_CARS,
STATE_FARM_POINTS,
STATE_TRASH_CARS,
STATE_UPGRADE_CARS,
STATE_WHEELSPIN,
run_master_bot_loop,
)
# ---------------------------------------------------------------------------
# Bilingual string tables
# ---------------------------------------------------------------------------
_STRINGS: dict[str, dict[str, str]] = {
# Language selector (always bilingual)
"lang_title": {
"en": " FH6-AFK — Language / 语言选择",
"zh": " FH6-AFK — Language / 语言选择",
},
"lang_opt_en": {
"en": " [1] 🇺🇸 English",
"zh": " [1] 🇺🇸 English",
},
"lang_opt_zh": {
"en": " [2] 🇨🇳 中文",
"zh": " [2] 🇨🇳 中文",
},
"lang_prompt": {
"en": " Select language [1/2] (default 1): ",
"zh": " Select language [1/2] (default 1): ",
},
# Mode selector
"mode_title": {
"en": " FH6-AFK — Mode Selection",
"zh": " FH6-AFK — 启动模式选择",
},
"mode_web": {
"en": " [1] 🌐 Web UI Dashboard",
"zh": " [1] 🌐 Web UI 控制面板",
},
"mode_web_desc": {
"en": " Browser-based GUI, supports remote monitoring",
"zh": " 浏览器可视化操作,支持手机远程监控",
},
"mode_console": {
"en": " [2] 💻 Terminal Console",
"zh": " [2] 💻 终端控制台模式",
},
"mode_console_desc": {
"en": " Classic CLI interaction for advanced users",
"zh": " 经典命令行交互,适合高级用户",
},
"mode_prompt": {
"en": " Select mode [1/2] (default 1): ",
"zh": " 选择模式 [1/2] (默认 1): ",
},
# Phase selector
"phase_title": {
"en": " FORZA HORIZON 6 AUTOBOT — Startup Menu",
"zh": " FORZA HORIZON 6 AUTOBOT — 启动菜单",
},
"phase_1": {
"en": " [1] 🏎️ Farm Points (STATE_FARM_POINTS)",
"zh": " [1] 🏎️ 刷技能点 (STATE_FARM_POINTS)",
},
"phase_1_desc": {
"en": " OCR scan skill points → auto-grind EventLab to 999",
"zh": " OCR 扫描技能点 → 自动跑 EventLab 刷满 999",
},
"phase_2": {
"en": " [2] 🛒 Buy Cars (STATE_BUY_CARS)",
"zh": " [2] 🛒 买车 (STATE_BUY_CARS)",
},
"phase_2_desc": {
"en": " Navigate to Car Collection → bulk-buy Subaru Impreza",
"zh": " 导航至 Car Collection → 批量购买 Subaru Impreza",
},
"phase_3": {
"en": " [3] ⚡ Upgrade Cars (STATE_UPGRADE_CARS)",
"zh": " [3] ⚡ 加技能点 (STATE_UPGRADE_CARS)",
},
"phase_3_desc": {
"en": " Enter garage → spend skill points on each Impreza",
"zh": " 进入车库 → 逐辆选择 Impreza 并消耗技能点升级",
},
"phase_4": {
"en": " [4] 🗑️ Trash Cars (STATE_TRASH_CARS)",
"zh": " [4] 🗑️ 卖车 (STATE_TRASH_CARS)",
},
"phase_4_desc": {
"en": " Enter garage → batch-remove upgraded Imprezas",
"zh": " 进入车库 → 批量移除已升级完的 Impreza",
},
"phase_5": {
"en": " [5] ⏭️ Skip Buy (Farm → Upgrade → Trash loop)",
"zh": " [5] ⏭️ 跳过买车 (刷点 → 加点 → 卖车 循环)",
},
"phase_5_desc": {
"en": " Skip buying phase; use cars already in garage",
"zh": " 跳过买车阶段,适用于车库已有未加点的车",
},
"phase_6": {
"en": " [6] 🎰 Wheelspin (STATE_WHEELSPIN)",
"zh": " [6] 🎰 抽奖 (STATE_WHEELSPIN)",
},
"phase_6_desc": {
"en": " Navigate to MY HORIZON → Super Wheelspin → claim all spins",
"zh": " 导航至 MY HORIZON → Super Wheelspin → 抽完所有累积转盘",
},
"phase_0": {
"en": " [0] 🔄 Auto Loop (default: full cycle from Farm)",
"zh": " [0] 🔄 自动循环 (默认:从刷点开始完整循环)",
},
"phase_prompt": {
"en": " Select phase [0-6] (default 0): ",
"zh": " 选择阶段 [0-6] (默认 0): ",
},
# Feedback
"selected": {"en": " ✅ Selected: ", "zh": " ✅ 已选择: "},
"skip_buy_msg": {
"en": " ✅ Selected: Skip Buy loop (Farm → Upgrade → Trash)",
"zh": " ✅ 已选择: 跳过买车循环 (刷点 → 加点 → 卖车)",
},
"invalid": {
"en": " ❌ Invalid choice.",
"zh": " ❌ 无效选择。",
},
"launching_web": {
"en": " 🚀 Launching Web UI",
"zh": " 🚀 正在启动 Web UI",
},
"checking_update": {
"en": "Checking for updates",
"zh": "正在检查更新",
},
"update_available": {
"en": "New version v{ver} available! (current: v{current})",
"zh": "发现新版本 v{ver}!(当前: v{current})",
},
"update_hint": {
"en": "Run with --update to apply, or update via Web UI.",
"zh": "使用 --update 参数更新,或在 Web UI 中一键更新。",
},
"tesseract_missing": {
"en": (
" ❌ Tesseract OCR not detected — the bot cannot read the screen without it.\n"
" Install from: https://github.com/UB-Mannheim/tesseract/releases\n"
" (The default install path is auto-detected; no need to add it to PATH.)"
),
"zh": (
" ❌ 未检测到 Tesseract OCR —— 缺少它无法识别游戏画面,程序已退出。\n"
" 请从此处安装:https://github.com/UB-Mannheim/tesseract/releases\n"
" (默认安装路径会被自动检测,无需手动加入 PATH。)"
),
},
"vigembus_missing": {
"en": (
" ❌ ViGEmBus driver not detected!\n"
" The bot needs it to simulate Xbox controller inputs.\n"
" Launching the installer now. Please complete the installation and restart the bot."
),
"zh": (
" ❌ 未检测到 ViGEmBus 手柄驱动!\n"
" 程序需要它来模拟 Xbox 手柄按键操作。\n"
" 即将自动启动安装程序,请在安装完成后重新运行本程序。"
),
},
}
# Phase display names
_PHASE_NAMES: dict[str, dict[str | None, str]] = {
"en": {
None: "Auto Loop",
STATE_FARM_POINTS: "Farm Points",
STATE_BUY_CARS: "Buy Cars",
STATE_UPGRADE_CARS: "Upgrade Cars",
STATE_TRASH_CARS: "Trash Cars",
STATE_WHEELSPIN: "Wheelspin",
},
"zh": {
None: "自动循环",
STATE_FARM_POINTS: "刷技能点",
STATE_BUY_CARS: "买车",
STATE_UPGRADE_CARS: "加技能点",
STATE_TRASH_CARS: "卖车",
STATE_WHEELSPIN: "抽奖",
},
}
def _t(key: str, lang: str) -> str:
"""Look up a translated string by key and language code."""
return _STRINGS[key][lang]
# ---------------------------------------------------------------------------
# Interactive selectors
# ---------------------------------------------------------------------------
def _select_language() -> str:
"""Display language selection menu.
Returns:
str: "en" or "zh"
"""
print("\n" + "=" * 50)
print(_t("lang_title", "en"))
print("=" * 50)
print()
print(_t("lang_opt_en", "en"))
print()
print(_t("lang_opt_zh", "en"))
print()
print("=" * 50)
while True:
choice: str = input(_t("lang_prompt", "en")).strip()
if choice in ("", "1"):
return "en"
if choice == "2":
return "zh"
print(" ❌ Invalid")
def _select_mode(lang: str) -> str:
"""Display mode selection menu in the chosen language.
Args:
lang: Language code ("en" or "zh").
Returns:
str: "web" or "console"
"""
print("\n" + "=" * 50)
print(_t("mode_title", lang))
print("=" * 50)
print()
print(_t("mode_web", lang))
print(_t("mode_web_desc", lang))
print()
print(_t("mode_console", lang))
print(_t("mode_console_desc", lang))
print()
print("=" * 50)
while True:
choice: str = input(_t("mode_prompt", lang)).strip()
if choice in ("", "1"):
return "web"
if choice == "2":
return "console"
print(_t("invalid", lang))
def show_start_menu(lang: str = "en"):
"""Display the startup phase selection menu.
Args:
lang: Language code ("en" or "zh").
Returns:
tuple[str | None, bool]: (state constant, skip_buy flag)
"""
print("\n" + "=" * 58)
print(_t("phase_title", lang))
print("=" * 58)
print()
for i in range(1, 7):
print(_t(f"phase_{i}", lang))
print(_t(f"phase_{i}_desc", lang))
print()
print(_t("phase_0", lang))
print()
print("=" * 58)
state_map: dict[str, str | None] = {
"0": None,
"1": STATE_FARM_POINTS,
"2": STATE_BUY_CARS,
"3": STATE_UPGRADE_CARS,
"4": STATE_TRASH_CARS,
"5": STATE_FARM_POINTS,
"6": STATE_WHEELSPIN,
}
while True:
choice = input(_t("phase_prompt", lang)).strip()
if choice == "":
choice = "0"
if choice in state_map:
selected = state_map[choice]
skip_buy = choice == "5"
if skip_buy:
print(f"\n{_t('skip_buy_msg', lang)}")
else:
print(f"\n{_t('selected', lang)}{_PHASE_NAMES[lang][selected]}")
print()
return selected, skip_buy
else:
print(_t("invalid", lang))
# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------
if __name__ == "__main__":
import argparse
import sys
# Force UTF-8 stdout/stderr (prevents colorama cp1252 crash on emoji)
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
if hasattr(sys.stderr, "reconfigure"):
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
parser = argparse.ArgumentParser(description="FH6 AutoBot — Full AFK Automation Tool")
parser.add_argument("--web", action="store_true", help="Launch Web UI directly")
parser.add_argument("--console", action="store_true", help="Launch console directly")
parser.add_argument("--lang", choices=["en", "zh"], default=None, help="Language (en/zh)")
parser.add_argument("--port", type=int, default=6800, help="Web UI port (default 6800)")
parser.add_argument(
"--debug",
action="store_true",
help="Enable debug image output to debug/ folder",
)
parser.add_argument("--update", action="store_true", help="Check and apply update, then exit")
parser.add_argument("--skip-update", action="store_true", help="Skip update check on startup")
args = parser.parse_args()
# Cleanup old EXE from previous update
from engine.updater import cleanup_old_exe
cleanup_old_exe()
# Enable debug file output if requested
if args.debug:
from engine.ocr import enable_debug_files
enable_debug_files()
print(" [DEBUG] Debug image output enabled → debug/")
# Step 1: Language selection
lang: str = args.lang if args.lang else _select_language()
# Apply language globally for all runtime logs
from engine.i18n import set_lang
set_lang(lang)
# Step 1.5: Handle --update mode (download + replace + restart)
if args.update:
from engine.updater import execute_update
from engine.version import __version__
print(f"\n 🔍 {_t('checking_update', lang)} (current: v{__version__})...\n")
def _print_progress(downloaded: int, total: int) -> None:
if total > 0:
pct: int = int(downloaded / total * 100)
mb_done: float = downloaded / 1_048_576
mb_total: float = total / 1_048_576
print(f"\r ⬇️ {mb_done:.1f} / {mb_total:.1f} MB ({pct}%)", end="", flush=True)
result: str = execute_update(progress_cb=_print_progress)
if result:
print(f"\n ❌ {result}\n")
# If update succeeded, process already exited via sys.exit(0)
sys.exit(0)
# Step 1.6: Background update check (non-blocking, log-only)
if not args.skip_update:
from engine.updater import background_check
from engine.version import __version__
def _on_update_found(info: dict) -> None:
ver: str = info.get("version", "?")
url: str = info.get("release_url", "")
try:
print(f"\n \u2728 {_t('update_available', lang).format(ver=ver, current=__version__)}")
print(f" {_t('update_hint', lang)}")
if url:
print(f" {url}\n")
except UnicodeEncodeError:
print(f"\n [UPDATE] New version v{ver} available (current: v{__version__})")
background_check(on_update_found=_on_update_found)
# Pre-flight: Tesseract OCR is mandatory for all screen reading. Detect & configure
# its path once here (process-global, inherited by the Web worker thread). If it is
# unavailable, exit immediately with a clear message instead of looping forever later.
from engine.ocr import setup_tesseract
if not setup_tesseract():
print(f"\n{_t('tesseract_missing', lang)}\n")
sys.exit(1)
# Pre-flight: Check ViGEmBus driver
def check_vigembus() -> bool:
try:
import vgamepad
# Try to initialize the gamepad to see if the driver is installed
vgamepad.VX360Gamepad()
return True
except Exception:
return False
if not check_vigembus():
print(f"\n{_t('vigembus_missing', lang)}\n")
import os
from engine.runtime import get_base_dir
installer_path = os.path.join(get_base_dir(), "tools", "drivers", "ViGEmBus_Setup.exe")
if os.path.exists(installer_path):
try:
os.startfile(installer_path)
except Exception as e:
print(f" [ERROR] Failed to launch installer: {e}")
else:
print(" [ERROR] Bundled installer not found. Please install ViGEmBus manually from GitHub.")
sys.exit(1)
# Step 2: Mode selection (CLI flags take priority)
if args.web:
mode = "web"
elif args.console:
mode = "console"
else:
mode = _select_mode(lang)
# Step 3: Launch
if mode == "web":
from web.server import start_server
print(f"\n{_t('launching_web', lang)} (http://localhost:{args.port}) ...\n")
start_server(port=args.port)
else:
initial_state, skip_buy = show_start_menu(lang)
run_master_bot_loop(initial_state=initial_state, skip_buy=skip_buy)