wait_wander3.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. # -*- coding: utf-8 -*-
  2. """
  3. wait_wander3.py — 等待图走动·Lua真源对齐版 + 全帧日志。
  4. Lua 真源(解密源码/mod_fgcq/stab/scripts/logic/gamePlayerController.lua):
  5. handleActionBegin: LuaSendMsg(3013/3011, point, 0, dir, lastPoint, mapID)
  6. point = Y<<16|X(当前格), lastPoint=上一格, body=MapID字符串
  7. → 跑(CS3013, 移动2格)与走(CS3011, 移动1格)同布局; 抓包成功序列 3013×2~4→3011;
  8. combat_bot.send_run docstring: 早期等待图 22 步跑帧全通。
  9. SC28 MOVE_FAIL(MapPos2WorldPos(p1,p2) 直接序) → ACTION_IDLE_LOCK, recog=1 可立即再走。
  10. 日志: 每发一帧(CS3013/3011 全参数)+每收一帧(SC28/SC11/SC1/SC5020/SC41 原始参数)。
  11. 用法:
  12. .venv/bin/python wait_wander3.py --zone 14504562 --name 萌新4143 \
  13. -u longwu988 -p Longwu1988 --minutes 2
  14. """
  15. import argparse
  16. import json
  17. import time
  18. import requests as rq
  19. import urllib3
  20. urllib3.disable_warnings()
  21. import login_996pc as L
  22. import quest_bot3 as Q
  23. from combat_bot import log, pack_point, lo16, hi16
  24. class RunBot(Q.PushBot):
  25. """全帧日志 + CS5013 寻路握手 + 跑帧走动。
  26. Lua 真源(AutoProxy:RequestAutoMoveNotify → LuaSendMsg(5013, act, destX, destY)):
  27. act: 1=START(recog=1, p1=X, p2=Y) / 2=ABORT / 3=FINISH
  28. AutoMoveBeginCommand: SC20102(T=1 服务器驱动)到达后, 若已有路径点必发 START;
  29. AutoMoveEndCommand: 完成发 FINISH, 中断发 ABORT。未注册寻路会话 → 服务器拒走。
  30. """
  31. def __init__(self, *a, **kw):
  32. super().__init__(*a, **kw)
  33. self.sent_log = {} # 发帧统计
  34. self.verbose = True # 全帧日志开关
  35. self.auto_move_on = False # CS5013 会话已注册
  36. def auto_move_notify(self, act, dest_x=0, dest_y=0):
  37. """CS5013: recog=act(1/2/3), p1=destX, p2=destY(仅START带)。"""
  38. self.send(5013, act, dest_x, dest_y, 0, b"")
  39. if act == 1:
  40. self.auto_move_on = True
  41. log(f" >> CS5013 START → ({dest_x},{dest_y})")
  42. else:
  43. self.auto_move_on = False
  44. log(f" >> CS5013 {'FINISH' if act == 3 else 'ABORT'}")
  45. def send(self, mid, recog, p1, p2, p3, body=b"", tail=None):
  46. if mid in (3011, 3013):
  47. self.sent_log[mid] = self.sent_log.get(mid, 0) + 1
  48. if self.verbose:
  49. x, y = lo16(recog), hi16(recog)
  50. lx, ly = lo16(p3), hi16(p3)
  51. log(f" >> CS{mid} 起点=({x},{y}) dir={p2} last=({lx},{ly}) "
  52. f"body={body[:20]!r} #{self.sent_log[mid]}")
  53. return super().send(mid, recog, p1, p2, p3, body, tail)
  54. def _dispatch(self, mid, recog, p1, p2, p3, body):
  55. ev = super()._dispatch(mid, recog, p1, p2, p3, body)
  56. if mid == 20102:
  57. j = self._jbody(body) or {}
  58. log(f" << SC20102 ★寻路目标 {j or body[:60]!r}")
  59. self._auto_find_target = j
  60. if self.verbose and mid in (28, 11, 1, 5020, 41, 13, 800, 634, 51, 50, 5013):
  61. y2 = p2 & 0x7FF if (p2 & 0x800) else p2
  62. raw = body[:60]
  63. if mid in (11, 13):
  64. # body=encode6(JSON), 只打印自己的
  65. try:
  66. j = self._jbody(body) or {}
  67. uid = str(j.get("UserID", ""))
  68. mine = (uid == (self.me.get("uid") or "")
  69. or uid == (self.me.get("sid_short") or "")
  70. or uid == str(self.me.get("id") or ""))
  71. if mine:
  72. log(f" << SC{mid} ★自己移动确认 → ({p1},{y2})")
  73. except Exception:
  74. pass
  75. if mid in (1, 28, 5020, 41, 5013):
  76. log(f" << SC{mid} recog={recog} p=({p1},{y2},{p3}) body={raw!r}")
  77. elif mid in (800, 634, 51, 50):
  78. log(f" << SC{mid} p=({p1},{y2}) body={raw!r}")
  79. return ev
  80. # ---------- 跑帧步进(对齐 Lua handleActionBegin) ----------
  81. def run_step(self, d, dist=2):
  82. """CS3013 跑 dist 格(服务器按 dir 推算落点, 真实客户端跑=2格)。"""
  83. from combat_bot import DIRS, proto_map_name
  84. dx, dy = DIRS[d]
  85. x, y = self.me["x"], self.me["y"]
  86. lx, ly = self.me.get("last_x", x), self.me.get("last_y", y)
  87. self.send(3013, pack_point(x, y), 0, d, pack_point(lx, ly),
  88. proto_map_name(self.me).encode())
  89. self.me["last_x"], self.me["last_y"] = x, y
  90. self.me["x"], self.me["y"], self.me["dir"] = x + dx * dist, y + dy * dist, d
  91. self._step_pending_ack = (dx * dist, dy * dist, time.time() + Q.STEP_MS / 1000.0)
  92. self._last_step_t = time.time()
  93. def walk_step(self, d):
  94. """CS3011 走 1 格。"""
  95. from combat_bot import DIRS, proto_map_name
  96. dx, dy = DIRS[d]
  97. x, y = self.me["x"], self.me["y"]
  98. lx, ly = self.me.get("last_x", x), self.me.get("last_y", y)
  99. self.send(3011, pack_point(x, y), 0, d, pack_point(lx, ly),
  100. proto_map_name(self.me).encode())
  101. self.me["last_x"], self.me["last_y"] = x, y
  102. self.me["x"], self.me["y"], self.me["dir"] = x + dx, y + dy, d
  103. self._step_pending_ack = (dx, dy, time.time() + Q.STEP_MS / 1000.0)
  104. self._last_step_t = time.time()
  105. def enter(args):
  106. j = None
  107. for a in range(5):
  108. try:
  109. s = rq.Session()
  110. j, _ = L.do_login(s, args.user, args.password)
  111. if j.get("code") == 200:
  112. break
  113. except Exception as e:
  114. log(f"登录重试{a + 1}: {str(e)[:60]}")
  115. time.sleep(12)
  116. if not j or j.get("code") != 200:
  117. raise SystemExit(f"[!] 登录失败: {(j or {}).get('msg', '网络异常')}")
  118. jwt = j["data"]["token"]
  119. uid = str(j["data"]["userInfo"]["id"])
  120. import cq2_pure_enter as P
  121. from combat_bot import send_init_frames
  122. for a in range(4):
  123. try:
  124. P.enter_game(s, jwt, args.zone)
  125. break
  126. except Exception:
  127. if a == 3:
  128. raise
  129. time.sleep(12)
  130. got = None
  131. for pt in ((args.port,) if args.port else (8021, 8061, 8001, 8041)):
  132. try:
  133. r = P.login_gate(args.zone, jwt, args.name, pt, uid)
  134. if r[0]:
  135. got = r
  136. break
  137. except SystemExit as e:
  138. log(f" 端口{pt}: {e}")
  139. except Exception as e:
  140. log(f" 端口{pt}: {str(e)[:60]}")
  141. if not got:
  142. raise SystemExit("[!] 登录服失败")
  143. key32, sid, rname, tkey, gp = got
  144. g = P.enter_game_gate(args.zone, rname, tkey, sid, gp, uid)
  145. bot = RunBot(g, sid, rname)
  146. P.heartbeat_loop(g, sid, bot.lock)
  147. t0 = time.time()
  148. while time.time() - t0 < 10:
  149. bot.pump(0.3)
  150. if 658 in bot.frame_log:
  151. break
  152. if 658 not in bot.frame_log:
  153. g.close()
  154. raise SystemExit("[!] SC658 超时")
  155. send_init_frames(bot, uid)
  156. for _ in range(24):
  157. bot.pump(0.5)
  158. if bot.me["id"] and bot.me.get("mapname"):
  159. break
  160. log(f"进世界: map={bot.me.get('mapname')} @{bot.me['x']},{bot.me['y']} "
  161. f"uid={bot.me.get('uid')}")
  162. return g, bot
  163. def to_wait_map(bot):
  164. if bot.me.get("mapname") == Q.WAIT_MAP:
  165. return
  166. if 1 in bot.mission_bodies:
  167. body3031 = json.dumps(bot.mission_bodies[1], separators=(",", ":")).encode()
  168. else:
  169. import os
  170. body3031 = open(os.path.join(Q.HERE, "docs", "cs3031_template.txt"), "rb").read()
  171. bot.send(3031, 0, 0, 0, bot.sid, body3031)
  172. bot.pump(0.5)
  173. bot.send(1011, 0, 0, 0, bot.sid, json.dumps(
  174. {"Act": "@renwu_mov,1", "UserID": "999999999", "index": 999999999},
  175. separators=(",", ":")).encode())
  176. t0 = time.time()
  177. while time.time() - t0 < 15 and bot.alive:
  178. bot.pump(0.3)
  179. if bot.me.get("mapname") == Q.WAIT_MAP:
  180. break
  181. log(f"renwu_mov 后: {bot.me.get('mapname')} @{bot.me['x']},{bot.me['y']}")
  182. def main():
  183. ap = argparse.ArgumentParser()
  184. ap.add_argument("-u", "--user", default="longwu988")
  185. ap.add_argument("-p", "--password", default="Longwu1988")
  186. ap.add_argument("--zone", type=int, default=14504562)
  187. ap.add_argument("--name", default="萌新4143")
  188. ap.add_argument("--port", type=int, default=0)
  189. ap.add_argument("--minutes", type=float, default=2)
  190. args = ap.parse_args()
  191. args.minutes = min(args.minutes, 2)
  192. g, bot = enter(args)
  193. try:
  194. state = Q.P.enable_auto_heal(g, bot.sid, want_on=True)
  195. log(f"喝药保护: {state}")
  196. to_wait_map(bot)
  197. log("=" * 60)
  198. log("开始等待图走动实验: CS5013握手 + 跑帧, 全帧日志")
  199. log("=" * 60)
  200. # ---- 第1步: 触发任务寻路, 等 SC20102(服务器寻路目标) ----
  201. bot.send(1011, 0, 0, 0, bot.sid, json.dumps(
  202. {"Act": "@renwu_mov,1", "UserID": "999999999", "index": 999999999},
  203. separators=(",", ":")).encode())
  204. t0 = time.time()
  205. while time.time() - t0 < 8 and bot.alive:
  206. bot.pump(0.1)
  207. tgt = getattr(bot, "_auto_find_target", None)
  208. if tgt and tgt.get("x") is not None:
  209. break
  210. tgt = getattr(bot, "_auto_find_target", None)
  211. if tgt and tgt.get("x") is not None:
  212. log(f"SC20102 已到: 目标=({tgt.get('x')},{tgt.get('y')}) "
  213. f"mapID={tgt.get('mapID')} T={tgt.get('T')}")
  214. # ---- 第2步: CS5013 START 注册寻路会话(Lua: LuaSendMsg(5013, 1, x, y)) ----
  215. bot.auto_move_notify(1, tgt.get("x", 0), tgt.get("y", 0))
  216. else:
  217. log("[!] SC20102 未到(8s) — 用 NPC 坐标兜底注册")
  218. bot.auto_move_notify(1, 10, 0)
  219. confirmed0 = getattr(bot, "confirmed_at", None) is not None
  220. t_end = time.time() + args.minutes * 60
  221. last_step = 0.0
  222. rep = time.time()
  223. d = 5
  224. pulled_dirs = {}
  225. seq = 0
  226. while time.time() < t_end and bot.alive:
  227. evs = bot.pump(0.05)
  228. bot.tick_step_ack()
  229. pulled = any(e[0] in ("pull_back", "pull_back_bogus_dropped",
  230. "pull_back_zero_ignored") for e in evs)
  231. moved = any(e[0] == "self_move" for e in evs)
  232. if pulled:
  233. pulled_dirs[bot.me.get("dir")] = pulled_dirs.get(bot.me.get("dir"), 0) + 1
  234. # 服务器中途又推了新寻路目标 → 跟随更新
  235. tgt2 = getattr(bot, "_auto_find_target", None)
  236. if tgt2 and tgt2.get("_seen") != seq:
  237. tgt2["_seen"] = seq
  238. log(f"[跟随] SC20102 更新 → ({tgt2.get('x')},{tgt2.get('y')})")
  239. now = time.time()
  240. if now - last_step >= Q.STEP_MS / 1000.0 + 0.05:
  241. if pulled:
  242. cands = sorted(range(8), key=lambda k: (pulled_dirs.get(k, 0), k))
  243. d = cands[0]
  244. seq += 1
  245. if seq <= 4 or seq % 3 == 0:
  246. bot.run_step(d)
  247. else:
  248. bot.walk_step(d)
  249. last_step = now
  250. if now - rep >= 15:
  251. rep = now
  252. log(f"[STAT] pos=({bot.me['x']},{bot.me['y']}) "
  253. f"map={bot.me.get('mapname')} "
  254. f"发帧={bot.sent_log} 被拒方向统计={pulled_dirs}")
  255. # ---- 收尾: CS5013 FINISH ----
  256. bot.auto_move_notify(3)
  257. log("=" * 60)
  258. log(f"[结束] pos=({bot.me['x']},{bot.me['y']}) 发帧={bot.sent_log} "
  259. f"被拒方向={pulled_dirs} confirmed0={confirmed0} "
  260. f"confirmed_now={getattr(bot, 'confirmed_at', None) is not None}")
  261. fl = sorted(bot.frame_log.items(), key=lambda kv: -kv[1])
  262. log(f"收帧Top15: {fl[:15]}")
  263. finally:
  264. g.close()
  265. if __name__ == "__main__":
  266. main()