# -*- coding: utf-8 -*- """ quest_bot.py —— 事件驱动任务机器人(常驻连接, 服务器推送驱动状态机)。 架构(对照真实客户端 C++ recv_loop + Lua RegisterMsgHandler): 常驻 socket + 心跳线程 + 主循环持续泵收 状态: ENTER_WORLD → GOTO_TASK(服务器驱动寻路, 跟踪SC11/13/634/801) → FIGHT(@startguaji 挂机, SC32 计击杀) → STOP(@stopguaji) → DONE 关键协议(2026-09-22 真实客户端抓包实证): CS1011 {"Act":"@renwu_mov,1","UserID":"999999999","index":999999999} 任务前往 CS1010 {"UserID":"10032","index":34} 点NPC(新手接待) CS3199 {"act":"@startguaji","index":104,"subid":2} 挂机开 CS3199 {"act":"@stopguaji","index":104,"subid":2} 挂机停 CS5286/CS5283 挂机开/停伴生帧 SC11/13 走/跑广播: body=JSON{UserID}, p1=X p2=Y, p3=dir|type<<16 用法: .venv/bin/python quest_bot.py --zone 14492165 --name 矿工6543 [--kill 2] """ import argparse import json import random import socket import struct import sys import threading import time import requests as rq import urllib3 urllib3.disable_warnings() HERE = __file__.rsplit("/", 1)[0] sys.path.insert(0, HERE) from encode6bitbuf_final import encode6, decode6 # noqa: E402 import login_996pc as L # noqa: E402 import cq2_pure_enter as P # noqa: E402 from combat_bot import CombatBot, send_init_frames # noqa: E402 def log(m): print(f"[{time.strftime('%H:%M:%S')}] {m}", flush=True) class QuestBot(CombatBot): """事件驱动扩展: 自己的移动跟踪 + 任务状态机。""" def __init__(self, sock, sid, name): super().__init__(sock, sid, name) self.state = "ENTER_WORLD" self.last_event_t = time.time() self.last_move_t = time.time() self.mon_kills = 0 # 视野内怪物死亡(SC32, 非自己击杀也算) self.attack_seen = 0 # SC14 攻击动作广播 self.task_pos = None # 任务点坐标(到达后记录) def _dispatch(self, mid, recog, p1, p2, p3, body): ev = [] if mid in (11, 13): # 走/跑广播: body={UserID}, p1=X p2=Y j = self._jbody(body) or {} uid = str(j.get("UserID", "")) myuid = self.me.get("uid") if uid and (uid == myuid or (myuid and uid in myuid) or (myuid and myuid in uid)): self.me["x"], self.me["y"] = p1, p2 self.last_move_t = time.time() ev.append(("self_move", p1, p2)) else: self.last_event_t = time.time() elif mid == 1: # 行为确认(自己也走这里) self.last_event_t = time.time() elif mid == 32: try: aid = int(body.decode("utf-8", "replace").strip()) except ValueError: aid = 0 if aid in self.monsters: self.mon_kills += 1 ev.append(("mon_die", aid)) self.last_event_t = time.time() elif mid in (634, 801): # 换图/传送特效 self.last_move_t = time.time() # 传送也算移动中 ev.append(("teleport", mid)) else: ev += super()._dispatch(mid, recog, p1, p2, p3, body) if mid in (51, 50): # 地图/出生 self.last_move_t = time.time() if ev: self.last_event_t = time.time() return ev def enter_world(s, jwt, uid, zone, name, port): P.enter_game(s, jwt, zone) got = None for pt in ((port,) if port else (8021, 8061, 8001, 8041)): try: r = P.login_gate(zone, jwt, name, pt, uid) if r[0]: got = r break except SystemExit as e: log(f" 端口{pt}: {str(e)[:50]}") except Exception as e: log(f" 端口{pt}: {str(e)[:50]}") if not got: raise SystemExit("[!] 登录服全端口失败") key32, sid, rname, tkey, gp = got g = P.enter_game_gate(zone, rname, tkey, sid, gp, uid) bot = QuestBot(g, sid, rname) bot.me["uid"] = None # SC50 后填 P.heartbeat_loop(g, sid, bot.lock) t0 = time.time() while time.time() - t0 < 10: bot.pump(0.3) if 658 in bot.frame_log: break log(f"SC658 {'✓' if 658 in bot.frame_log else '✗'}") send_init_frames(bot, uid) for _ in range(24): bot.pump(0.5) if bot.me["id"] and bot.me.get("mapname"): break # 主玩家完整 UserID(SC42/50 body info[0]) 供 SC11/13 识别自己 log(f"进世界: {bot.me.get('mapname')} @{bot.me['x']},{bot.me['y']} id={bot.me['id']}") return g, bot def main(): ap = argparse.ArgumentParser() ap.add_argument("-u", "--user", default="longwu988") ap.add_argument("-p", "--password", default="Longwu1988") ap.add_argument("--zone", type=int, default=14492165) ap.add_argument("--name", default="矿工6543") ap.add_argument("--port", type=int, default=0) ap.add_argument("--kill", type=int, default=2) ap.add_argument("--minutes", type=float, default=12) args = ap.parse_args() for a in range(5): try: s = rq.Session() j, _ = L.do_login(s, args.user, args.password) if j.get("code") == 200: break except Exception as e: log(f"登录重试{a+1}: {str(e)[:60]}") time.sleep(12) jwt = j["data"]["token"] uid = str(j["data"]["userInfo"]["id"]) g, bot = enter_world(s, jwt, uid, args.zone, args.name, args.port) tail = 100 def send(mid, recog, p1, p2, p3, body=b""): nonlocal tail tail += 1 g.sendall(encode6(struct.pack("<9i", 0, mid, recog, p1, p2, p3, 0, len(body), tail)) + body) def act_3199(act, index, subid): send(3199, 0, 0, 0, bot.sid, json.dumps( {"act": act, "index": index, "subid": subid}, separators=(",", ":")).encode()) # ---- 喝药保护 ---- state = P.enable_auto_heal(g, bot.sid, want_on=True) log(f"喝药保护: {state}") # ---- 状态机主循环(常驻, 事件驱动) ---- deadline = time.time() + args.minutes * 60 rep = 0.0 started_guaji = False task_sent = False while time.time() < deadline and bot.alive: evs = bot.pump(0.4) # 持续泵, 永不长时间阻塞 for e in evs: if e[0] in ("self_move", "teleport", "mon_die", "map", "me_born"): log(f"EV {e}") # 通知态: 打印进度 if time.time() - rep > 10: rep = time.time() log(f"[{bot.state}] pos=({bot.me['x']},{bot.me['y']}) map={bot.me.get('mapname')} " f"kills={bot.mon_kills} SC13={bot.frame_log.get(13,0)} SC32={bot.frame_log.get(32,0)}") if bot.state == "GOTO_TASK": still = time.time() - bot.last_move_t > 10 if still and not task_sent: pass if not task_sent: send(1011, 0, 0, 0, bot.sid, json.dumps( {"Act": "@renwu_mov,1", "UserID": "999999999", "index": 999999999}, separators=(",", ":")).encode()) task_sent = True bot.last_move_t = time.time() log("已发 @renwu_mov,1 (服务器驱动寻路)") elif time.time() - bot.last_move_t > 12 and time.time() - bot.last_event_t > 5: bot.state = "FIGHT" bot.task_pos = (bot.me["x"], bot.me["y"]) log(f"寻路静止, 任务点认定 @({bot.me['x']},{bot.me['y']})") elif bot.state == "FIGHT" and not started_guaji: act_3199("@startguaji", 104, 2) send(5286, 0, 0, 0, bot.sid) started_guaji = True bot.last_event_t = time.time() log("已发 @startguaji, 等待击杀(SC32)...") elif bot.state == "FIGHT" and started_guaji: if bot.mon_kills >= args.kill: act_3199("@stopguaji", 104, 2) send(5283, 0, 0, 0, bot.sid) log(f"★ 目标达成: {bot.mon_kills} 杀, 已停挂机") bot.state = "DONE" break # 长时间无事件 → 重发一次挂机(保持活性) if time.time() - bot.last_event_t > 60: act_3199("@startguaji", 104, 2) send(5286, 0, 0, 0, bot.sid) bot.last_event_t = time.time() log("(活性) 重发 @startguaji") if bot.state == "ENTER_WORLD": bot.state = "GOTO_TASK" log(f"结束: state={bot.state} kills={bot.mon_kills} pos=({bot.me['x']},{bot.me['y']}) map={bot.me.get('mapname')}") log(f"收帧: {sorted(bot.frame_log.items(), key=lambda kv: -kv[1])[:14]}") g.close() if __name__ == "__main__": main()