wait_wander2.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. # -*- coding: utf-8 -*-
  2. """
  3. wait_wander2.py — 等待图"走动"正确机制版: 读条帧 + 服务器路径驱动。
  4. 依据 传奇_bak/docs/逆向成果-0918/客户端行为状态机-抓包提炼.md (两区171实例统计):
  5. 等待图的角色移动不是 CS3011 格子帧(那套 recog=起点/p2=dir 语义在等待图全被 SC28 拒,
  6. 2026-09-23 实测 191 拒 0 确认), 而是"读条"机制:
  7. C>1011 @renwu_mov,1
  8. → S<20102 e6 {"mapID":"等待地图","x":16,"y":34,"targetType":100,"targetIndex":[34]}
  9. → C>3013 body="等待地图" p1=进度(读条窗口 5,5,6) p2=递增ms(步长≈131071)
  10. 每 550ms±20ms 一发; 角色沿路径跑动由服务器驱动(SC11 广播)
  11. 另: 5277=415B 挂机功能位图决定会话资格(简化初始化→服务器不下发 20102→读条挂起)。
  12. 用法:
  13. .venv/bin/python wait_wander2.py --zone 14504562 --name 萌新4143 \
  14. -u longwu988 -p Longwu1988 --minutes 2
  15. """
  16. import argparse
  17. import json
  18. import time
  19. import requests as rq
  20. import urllib3
  21. urllib3.disable_warnings()
  22. import login_996pc as L
  23. import quest_bot3 as Q
  24. from combat_bot import log
  25. from encode6bitbuf_final import decode6
  26. def pump_print(bot, secs, tag):
  27. """收帧 secs 秒, 打印自己相关位移/状态帧, 返回事件列表。"""
  28. t0 = time.time()
  29. evs_all = []
  30. while time.time() - t0 < secs and bot.alive:
  31. for e in bot.pump(0.1):
  32. evs_all.append(e)
  33. if e[0] in ("self_move", "self_move_atk", "map", "me_die", "me_revive",
  34. "teleport", "fly_in_self", "self_id_learned"):
  35. log(f" [{tag}] {e}")
  36. return evs_all
  37. class RailBot(Q.PushBot):
  38. """在 PushBot 基础上补 SC20102 寻路目标解析。"""
  39. def __init__(self, *a, **kw):
  40. super().__init__(*a, **kw)
  41. self.rail_target = None # SC20102 {mapID,x,y,targetType,...}
  42. def _dispatch(self, mid, recog, p1, p2, p3, body):
  43. ev = super()._dispatch(mid, recog, p1, p2, p3, body)
  44. if mid == 20102:
  45. # e6 层 JSON: {"mapID":..,"x":..,"y":..,"targetType":100,"targetIndex":[34]}
  46. j = None
  47. for v in Q.CombatBot._body_variants(body):
  48. try:
  49. j = json.loads(v.decode("utf-8"))
  50. if isinstance(j, dict):
  51. break
  52. except Exception:
  53. continue
  54. if isinstance(j, dict) and j.get("mapID"):
  55. self.rail_target = j
  56. log(f" [SC20102] 服务器寻路目标: {j}")
  57. return ev
  58. def main():
  59. ap = argparse.ArgumentParser()
  60. ap.add_argument("-u", "--user", default="longwu988")
  61. ap.add_argument("-p", "--password", default="Longwu1988")
  62. ap.add_argument("--zone", type=int, default=14504562)
  63. ap.add_argument("--name", default="萌新4143")
  64. ap.add_argument("--port", type=int, default=0)
  65. ap.add_argument("--minutes", type=float, default=2)
  66. args = ap.parse_args()
  67. args.minutes = min(args.minutes, 2)
  68. j = None
  69. for a in range(5):
  70. try:
  71. s = rq.Session()
  72. j, _ = L.do_login(s, args.user, args.password)
  73. if j.get("code") == 200:
  74. break
  75. except Exception as e:
  76. log(f"登录重试{a + 1}: {str(e)[:60]}")
  77. time.sleep(12)
  78. if not j or j.get("code") != 200:
  79. raise SystemExit(f"[!] 登录失败: {(j or {}).get('msg', '网络异常')}")
  80. jwt = j["data"]["token"]
  81. uid = str(j["data"]["userInfo"]["id"])
  82. # 进世界(enter_world 内部用 PushBot; 换 RailBot 需复制其流程)
  83. import cq2_pure_enter as P
  84. from combat_bot import send_init_frames
  85. for a in range(4):
  86. try:
  87. P.enter_game(s, jwt, args.zone)
  88. break
  89. except Exception:
  90. if a == 3:
  91. raise
  92. time.sleep(12)
  93. got = None
  94. for pt in ((args.port,) if args.port else (8021, 8061, 8001, 8041)):
  95. try:
  96. r = P.login_gate(args.zone, jwt, args.name, pt, uid)
  97. if r[0]:
  98. got = r
  99. break
  100. except SystemExit as e:
  101. log(f" 端口{pt}: {e}")
  102. except Exception as e:
  103. log(f" 端口{pt}: {str(e)[:60]}")
  104. if not got:
  105. raise SystemExit("[!] 登录服失败")
  106. key32, sid, rname, tkey, gp = got
  107. g = P.enter_game_gate(args.zone, rname, tkey, sid, gp, uid)
  108. bot = RailBot(g, sid, rname)
  109. P.heartbeat_loop(g, sid, bot.lock)
  110. t0 = time.time()
  111. while time.time() - t0 < 10:
  112. bot.pump(0.3)
  113. if 658 in bot.frame_log:
  114. break
  115. if 658 not in bot.frame_log:
  116. g.close()
  117. raise SystemExit("[!] SC658 超时, 世界未初始化")
  118. send_init_frames(bot, uid)
  119. for _ in range(24):
  120. bot.pump(0.5)
  121. if bot.me["id"] and bot.me.get("mapname"):
  122. break
  123. log(f"进世界: map={bot.me.get('mapname')} @{bot.me['x']},{bot.me['y']} uid={bot.me.get('uid')}")
  124. state = P.enable_auto_heal(g, bot.sid, want_on=True)
  125. log(f"喝药保护: {state}")
  126. try:
  127. if bot.me.get("mapname") != Q.WAIT_MAP:
  128. # 在比奇 → @renwu_mov,1 传回等待图(复刻 run_fsm 阶段1)
  129. if 1 in bot.mission_bodies:
  130. body3031 = json.dumps(bot.mission_bodies[1], separators=(",", ":")).encode()
  131. else:
  132. import os
  133. body3031 = open(os.path.join(Q.HERE, "docs", "cs3031_template.txt"), "rb").read()
  134. bot.send(3031, 0, 0, 0, bot.sid, body3031)
  135. bot.pump(0.5)
  136. bot.send(1011, 0, 0, 0, bot.sid, json.dumps(
  137. {"Act": "@renwu_mov,1", "UserID": "999999999", "index": 999999999},
  138. separators=(",", ":")).encode())
  139. t0 = time.time()
  140. while time.time() - t0 < 15 and bot.alive:
  141. bot.pump(0.3)
  142. if bot.me.get("mapname") == Q.WAIT_MAP:
  143. break
  144. log(f"renwu_mov 后: {bot.me.get('mapname')} @{bot.me['x']},{bot.me['y']}")
  145. # ---- 等待图: 读条机制走动 ----
  146. # 1) 触发: @renwu_mov,1 → 服务器应回 SC20102(寻路目标)并开始路径驱动
  147. bot.send(1011, 0, 0, 0, bot.sid, json.dumps(
  148. {"Act": "@renwu_mov,1", "UserID": "999999999", "index": 999999999},
  149. separators=(",", ":")).encode())
  150. pump_print(bot, 2.0, "触发")
  151. # 2) 读条推进: 每550ms CS3013(body=图名, p1=进度, p2=递增ms)
  152. # 进度序列: 读条窗口 5,5,6 → 挂机节拍 0~7 簇内递增(文档171实例统计)
  153. ms = 131071
  154. prog = [5, 5, 6, 0, 1, 2, 3, 4, 5, 6, 7]
  155. pi = 0
  156. t_end = time.time() + args.minutes * 60
  157. last_step = 0.0
  158. rep = time.time()
  159. moved_frames0 = bot.frame_log.get(11, 0)
  160. while time.time() < t_end and bot.alive:
  161. bot.pump(0.05)
  162. bot.tick_step_ack()
  163. now = time.time()
  164. if now - last_step >= 0.55:
  165. p1v = prog[pi % len(prog)]
  166. pi += 1
  167. ms += 131071
  168. bot.send(3013, 0, p1v, ms & 0xFFFF, (ms >> 16) & 0xFFFF,
  169. Q.WAIT_MAP.encode())
  170. last_step = now
  171. if now - rep >= 10:
  172. rep = now
  173. log(f"[RAIL] pos=({bot.me['x']},{bot.me['y']}) map={bot.me.get('mapname')} "
  174. f"SC20102目标={bot.rail_target} SC11累计={bot.frame_log.get(11, 0)} "
  175. f"(Δ{bot.frame_log.get(11, 0) - moved_frames0})")
  176. log(f"[结束] pos=({bot.me['x']},{bot.me['y']}) map={bot.me.get('mapname')} "
  177. f"SC11总Δ={bot.frame_log.get(11, 0) - moved_frames0} "
  178. f"目标={bot.rail_target}")
  179. finally:
  180. g.close()
  181. if __name__ == "__main__":
  182. main()