# -*- coding: utf-8 -*- """ wait_wander2.py — 等待图"走动"正确机制版: 读条帧 + 服务器路径驱动。 依据 传奇_bak/docs/逆向成果-0918/客户端行为状态机-抓包提炼.md (两区171实例统计): 等待图的角色移动不是 CS3011 格子帧(那套 recog=起点/p2=dir 语义在等待图全被 SC28 拒, 2026-09-23 实测 191 拒 0 确认), 而是"读条"机制: C>1011 @renwu_mov,1 → S<20102 e6 {"mapID":"等待地图","x":16,"y":34,"targetType":100,"targetIndex":[34]} → C>3013 body="等待地图" p1=进度(读条窗口 5,5,6) p2=递增ms(步长≈131071) 每 550ms±20ms 一发; 角色沿路径跑动由服务器驱动(SC11 广播) 另: 5277=415B 挂机功能位图决定会话资格(简化初始化→服务器不下发 20102→读条挂起)。 用法: .venv/bin/python wait_wander2.py --zone 14504562 --name 萌新4143 \ -u longwu988 -p Longwu1988 --minutes 2 """ import argparse import json import time import requests as rq import urllib3 urllib3.disable_warnings() import login_996pc as L import quest_bot3 as Q from combat_bot import log from encode6bitbuf_final import decode6 def pump_print(bot, secs, tag): """收帧 secs 秒, 打印自己相关位移/状态帧, 返回事件列表。""" t0 = time.time() evs_all = [] while time.time() - t0 < secs and bot.alive: for e in bot.pump(0.1): evs_all.append(e) if e[0] in ("self_move", "self_move_atk", "map", "me_die", "me_revive", "teleport", "fly_in_self", "self_id_learned"): log(f" [{tag}] {e}") return evs_all class RailBot(Q.PushBot): """在 PushBot 基础上补 SC20102 寻路目标解析。""" def __init__(self, *a, **kw): super().__init__(*a, **kw) self.rail_target = None # SC20102 {mapID,x,y,targetType,...} def _dispatch(self, mid, recog, p1, p2, p3, body): ev = super()._dispatch(mid, recog, p1, p2, p3, body) if mid == 20102: # e6 层 JSON: {"mapID":..,"x":..,"y":..,"targetType":100,"targetIndex":[34]} j = None for v in Q.CombatBot._body_variants(body): try: j = json.loads(v.decode("utf-8")) if isinstance(j, dict): break except Exception: continue if isinstance(j, dict) and j.get("mapID"): self.rail_target = j log(f" [SC20102] 服务器寻路目标: {j}") return ev 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=14504562) ap.add_argument("--name", default="萌新4143") ap.add_argument("--port", type=int, default=0) ap.add_argument("--minutes", type=float, default=2) args = ap.parse_args() args.minutes = min(args.minutes, 2) j = None 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) if not j or j.get("code") != 200: raise SystemExit(f"[!] 登录失败: {(j or {}).get('msg', '网络异常')}") jwt = j["data"]["token"] uid = str(j["data"]["userInfo"]["id"]) # 进世界(enter_world 内部用 PushBot; 换 RailBot 需复制其流程) import cq2_pure_enter as P from combat_bot import send_init_frames for a in range(4): try: P.enter_game(s, jwt, args.zone) break except Exception: if a == 3: raise time.sleep(12) got = None for pt in ((args.port,) if args.port else (8021, 8061, 8001, 8041)): try: r = P.login_gate(args.zone, jwt, args.name, pt, uid) if r[0]: got = r break except SystemExit as e: log(f" 端口{pt}: {e}") except Exception as e: log(f" 端口{pt}: {str(e)[:60]}") if not got: raise SystemExit("[!] 登录服失败") key32, sid, rname, tkey, gp = got g = P.enter_game_gate(args.zone, rname, tkey, sid, gp, uid) bot = RailBot(g, sid, rname) 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 if 658 not in bot.frame_log: g.close() raise SystemExit("[!] SC658 超时, 世界未初始化") send_init_frames(bot, uid) for _ in range(24): bot.pump(0.5) if bot.me["id"] and bot.me.get("mapname"): break log(f"进世界: map={bot.me.get('mapname')} @{bot.me['x']},{bot.me['y']} uid={bot.me.get('uid')}") state = P.enable_auto_heal(g, bot.sid, want_on=True) log(f"喝药保护: {state}") try: if bot.me.get("mapname") != Q.WAIT_MAP: # 在比奇 → @renwu_mov,1 传回等待图(复刻 run_fsm 阶段1) if 1 in bot.mission_bodies: body3031 = json.dumps(bot.mission_bodies[1], separators=(",", ":")).encode() else: import os body3031 = open(os.path.join(Q.HERE, "docs", "cs3031_template.txt"), "rb").read() bot.send(3031, 0, 0, 0, bot.sid, body3031) bot.pump(0.5) bot.send(1011, 0, 0, 0, bot.sid, json.dumps( {"Act": "@renwu_mov,1", "UserID": "999999999", "index": 999999999}, separators=(",", ":")).encode()) t0 = time.time() while time.time() - t0 < 15 and bot.alive: bot.pump(0.3) if bot.me.get("mapname") == Q.WAIT_MAP: break log(f"renwu_mov 后: {bot.me.get('mapname')} @{bot.me['x']},{bot.me['y']}") # ---- 等待图: 读条机制走动 ---- # 1) 触发: @renwu_mov,1 → 服务器应回 SC20102(寻路目标)并开始路径驱动 bot.send(1011, 0, 0, 0, bot.sid, json.dumps( {"Act": "@renwu_mov,1", "UserID": "999999999", "index": 999999999}, separators=(",", ":")).encode()) pump_print(bot, 2.0, "触发") # 2) 读条推进: 每550ms CS3013(body=图名, p1=进度, p2=递增ms) # 进度序列: 读条窗口 5,5,6 → 挂机节拍 0~7 簇内递增(文档171实例统计) ms = 131071 prog = [5, 5, 6, 0, 1, 2, 3, 4, 5, 6, 7] pi = 0 t_end = time.time() + args.minutes * 60 last_step = 0.0 rep = time.time() moved_frames0 = bot.frame_log.get(11, 0) while time.time() < t_end and bot.alive: bot.pump(0.05) bot.tick_step_ack() now = time.time() if now - last_step >= 0.55: p1v = prog[pi % len(prog)] pi += 1 ms += 131071 bot.send(3013, 0, p1v, ms & 0xFFFF, (ms >> 16) & 0xFFFF, Q.WAIT_MAP.encode()) last_step = now if now - rep >= 10: rep = now log(f"[RAIL] pos=({bot.me['x']},{bot.me['y']}) map={bot.me.get('mapname')} " f"SC20102目标={bot.rail_target} SC11累计={bot.frame_log.get(11, 0)} " f"(Δ{bot.frame_log.get(11, 0) - moved_frames0})") log(f"[结束] pos=({bot.me['x']},{bot.me['y']}) map={bot.me.get('mapname')} " f"SC11总Δ={bot.frame_log.get(11, 0) - moved_frames0} " f"目标={bot.rail_target}") finally: g.close() if __name__ == "__main__": main()