wait_wander5.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. # -*- coding: utf-8 -*-
  2. """
  3. wait_wander5.py — 终极对齐实验: 完整复刻客户端移动链路。
  4. Lua 真源链(解密源码/mod_fgcq/stab/scripts):
  5. SC20102(T=1) → AutoMoveBeginCommand:
  6. autoProxy:SetAutoTarget(data) + PushMove(dest) + InputMove(dest)
  7. → 若 autoMoveType==SERVER/MINIMAP 且 GetPathPointSize()>0:
  8. RequestAutoMoveNotify(START, destX, destY) ← CS5013(recog=1, p1=X, p2=Y)
  9. InputMoveCommand: A* pathPoints → PlayerInputProxy
  10. BehaviorMove(每帧): checkNextMovePos(下一格, 直线可并1-3格)
  11. → SetMoveTo+SetAction(WALK/RUN) → 动画步时(~550ms) 到点
  12. → handleActionBegin: LuaSendMsg(3011/3013, point, 0, dir, lastPoint, mapID)
  13. → 等 SC11 自回显/SC1 确认(-5s超时兜底) → 下一格
  14. 到达: AutoMoveEndCommand → RequestAutoMoveNotify(FINISH=3)
  15. 本实验: 滞留带→NPC传比奇→@renwu_mov回等待图→[SC20102到达]→CS5013 START
  16. → 严格550ms节拍逐格走向 SC20102 目标 → 每步等确认 → 到达发 FINISH。
  17. 全帧日志。若 SC11 出现自回显 → 走动打通。
  18. """
  19. import argparse
  20. import json
  21. import os
  22. import time
  23. import requests as rq
  24. import urllib3
  25. urllib3.disable_warnings()
  26. import login_996pc as L
  27. import quest_bot3 as Q
  28. from combat_bot import log, pack_point, lo16, hi16, DIRS, proto_map_name
  29. from mir_map import load as map_load
  30. class RailBot(Q.PushBot):
  31. def __init__(self, *a, **kw):
  32. super().__init__(*a, **kw)
  33. self.sent_log = {}
  34. self.rail = None # SC20102 目标
  35. self.sc28_dropped = 0
  36. def send(self, mid, recog, p1, p2, p3, body=b"", tail=None):
  37. if mid in (3011, 3013):
  38. self.sent_log[mid] = self.sent_log.get(mid, 0) + 1
  39. return super().send(mid, recog, p1, p2, p3, body, tail)
  40. def _dispatch(self, mid, recog, p1, p2, p3, body):
  41. ev = super()._dispatch(mid, recog, p1, p2, p3, body)
  42. if mid == 20102:
  43. j = self._jbody(body) or {}
  44. if j.get("mapID"):
  45. self.rail = j
  46. log(f" << SC20102 ★ {j}")
  47. elif mid in (11, 13):
  48. j = self._jbody(body) or {}
  49. uid = str(j.get("UserID", ""))
  50. if uid in ((self.me.get("uid") or ""),
  51. (self.me.get("sid_short") or ""),
  52. str(self.me.get("id") or "")):
  53. log(f" << SC{mid} ★★自己移动确认 → ({p1},{p2 & 0x7FF})")
  54. elif mid == 28:
  55. self.sc28_dropped += 0 if self.frame_log.get(28, 0) <= self.sc28_dropped else 0
  56. return ev
  57. def step_dir(self, d, dist=1):
  58. dx, dy = DIRS[d]
  59. x, y = self.me["x"], self.me["y"]
  60. lx, ly = self.me.get("last_x", x), self.me.get("last_y", y)
  61. mid = 3013 if dist >= 2 else 3011
  62. self.send(mid, pack_point(x, y), 0, d, pack_point(lx, ly),
  63. proto_map_name(self.me).encode())
  64. self.me["last_x"], self.me["last_y"] = x, y
  65. self.me["x"], self.me["y"], self.me["dir"] = x + dx * dist, y + dy * dist, d
  66. self._step_pending_ack = (dx * dist, dy * dist, time.time() + Q.STEP_MS / 1000.0)
  67. self._last_step_t = time.time()
  68. def enter(args):
  69. j = None
  70. for a in range(5):
  71. try:
  72. s = rq.Session()
  73. j, _ = L.do_login(s, args.user, args.password)
  74. if j.get("code") == 200:
  75. break
  76. except Exception as e:
  77. log(f"登录重试{a + 1}: {str(e)[:60]}")
  78. time.sleep(12)
  79. if not j or j.get("code") != 200:
  80. raise SystemExit(f"[!] 登录失败: {(j or {}).get('msg', '网络异常')}")
  81. jwt = j["data"]["token"]
  82. uid = str(j["data"]["userInfo"]["id"])
  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']}")
  124. return g, bot
  125. def npc_talk_to_biqi(bot):
  126. t0 = time.time()
  127. while time.time() - t0 < 8 and not bot.npcs:
  128. bot.pump(0.4)
  129. npc = bot.find_guide_npc()
  130. if not npc:
  131. return bot.me.get("mapname") == "比奇大城"
  132. aid = npc["aid"]
  133. for idx in [npc["index"], 35, 34]:
  134. if idx == npc["index"] and 35 == npc["index"]:
  135. idxs = [35]
  136. bot.pending_map_ack = "比奇大城"
  137. bot.send(1010, 0, 0, 0, bot.sid, json.dumps(
  138. {"UserID": aid, "index": idx}, separators=(",", ":")).encode())
  139. bot.send(5012, 0, 0, 0, bot.sid, b"0")
  140. bot.send(5027, 0, 0, 0, bot.sid, b"0")
  141. t1 = time.time()
  142. while time.time() - t1 < 8 and bot.alive:
  143. evs = bot.pump(0.3)
  144. if bot.me.get("mapname") == "比奇大城":
  145. bot.pending_map_ack = None
  146. return True
  147. talk = next((e[1] for e in evs if e[0] == "npc_talk"), None)
  148. if talk:
  149. acts = bot.parse_sui_acts(str(talk.get("sMsg", "")))
  150. act = next((a for a in acts if a not in ("exit", "close")), None)
  151. if act:
  152. bot.npc_click_act(talk, act)
  153. bot.pending_map_ack = None
  154. if bot.me.get("mapname") == "比奇大城":
  155. return True
  156. return bot.me.get("mapname") == "比奇大城"
  157. def renwu_back_to_wait(bot):
  158. if 1 in bot.mission_bodies:
  159. body3031 = json.dumps(bot.mission_bodies[1], separators=(",", ":")).encode()
  160. else:
  161. body3031 = open(os.path.join(Q.HERE, "docs", "cs3031_template.txt"), "rb").read()
  162. bot.send(3031, 0, 0, 0, bot.sid, body3031)
  163. bot.pump(0.5)
  164. bot.send(1011, 0, 0, 0, bot.sid, json.dumps(
  165. {"Act": "@renwu_mov,1", "UserID": "999999999", "index": 999999999},
  166. separators=(",", ":")).encode())
  167. t0 = time.time()
  168. while time.time() - t0 < 12 and bot.alive:
  169. bot.pump(0.3)
  170. if bot.me.get("mapname") == Q.WAIT_MAP:
  171. break
  172. # 静置吃掉传送期过期 SC28, 重置锚点
  173. time.sleep(0.5)
  174. t1 = time.time()
  175. while time.time() - t1 < 2.0:
  176. bot.pump(0.2)
  177. bot.me["last_x"], bot.me["last_y"] = bot.me["x"], bot.me["y"]
  178. bot._step_pending_ack = None
  179. log(f"回到等待图 @{bot.me['x']},{bot.me['y']} "
  180. f"SC20102目标={bot.rail}")
  181. return bot.me.get("mapname") == Q.WAIT_MAP and bot.me["y"] > 4
  182. def rail_walk(bot, seconds, m):
  183. """SC20102→CS5013 START→逐格550ms走向目标(完整复刻客户端链)。"""
  184. t_end = time.time() + seconds
  185. started = False
  186. last_step = 0.0
  187. rep = time.time()
  188. rejected = 0
  189. confirmed = 0
  190. while time.time() < t_end and bot.alive:
  191. evs = bot.pump(0.05)
  192. bot.tick_step_ack()
  193. for e in evs:
  194. if e[0] == "self_move":
  195. confirmed += 1
  196. elif e[0] in ("pull_back", "pull_back_bogus_dropped"):
  197. rejected += 1
  198. if not started and bot.rail and bot.rail.get("x") is not None:
  199. tx, ty = int(bot.rail["x"]), int(bot.rail["y"])
  200. bot.send(5013, 1, tx, ty, 0, b"")
  201. started = True
  202. last_step = time.time() # 握手后第一个550ms才开始走
  203. log(f" >> CS5013 START → ({tx},{ty}) [复刻AutoMoveBegin]")
  204. now = time.time()
  205. if started and bot.rail and now - last_step >= Q.STEP_MS / 1000.0:
  206. tx, ty = int(bot.rail["x"]), int(bot.rail["y"])
  207. x, y = bot.me["x"], bot.me["y"]
  208. if abs(tx - x) <= 1 and abs(ty - y) <= 1:
  209. bot.send(5013, 3, 0, 0, 0, b"")
  210. log(f" >> CS5013 FINISH @到达 ({x},{y})")
  211. # 到达后重新触发寻路(保持会话活跃, 循环走动)
  212. bot.send(1011, 0, 0, 0, bot.sid, json.dumps(
  213. {"Act": "@renwu_mov,1", "UserID": "999999999",
  214. "index": 999999999}, separators=(",", ":")).encode())
  215. started = False
  216. bot.rail = None
  217. last_step = now
  218. continue
  219. dx = max(-1, min(1, tx - x))
  220. dy = max(-1, min(1, ty - y))
  221. if dx and dy and not (m.walkable(x + dx, y) or m.walkable(x, y + dy)):
  222. if m.walkable(x + dx, y):
  223. dy = 0
  224. elif m.walkable(x, y + dy):
  225. dx = 0
  226. d = next(i for i, (ox, oy) in enumerate(DIRS) if ox == dx and oy == dy)
  227. bot.step_dir(d, 1)
  228. last_step = now
  229. if now - rep >= 15:
  230. rep = now
  231. log(f"[STAT] pos=({bot.me['x']},{bot.me['y']}) rail={bot.rail} "
  232. f"发帧={bot.sent_log} 确认={confirmed} 拒绝={rejected}")
  233. if started:
  234. bot.send(5013, 2, 0, 0, 0, b"")
  235. log(f"[结束] pos=({bot.me['x']},{bot.me['y']}) 发帧={bot.sent_log} "
  236. f"确认={confirmed} 拒绝={rejected}")
  237. def main():
  238. ap = argparse.ArgumentParser()
  239. ap.add_argument("-u", "--user", default="longwu988")
  240. ap.add_argument("-p", "--password", default="Longwu1988")
  241. ap.add_argument("--zone", type=int, default=14504562)
  242. ap.add_argument("--name", default="萌新8195")
  243. ap.add_argument("--port", type=int, default=0)
  244. ap.add_argument("--minutes", type=float, default=2)
  245. args = ap.parse_args()
  246. args.minutes = min(args.minutes, 2)
  247. m = map_load("0139")
  248. g, bot = enter(args)
  249. try:
  250. Q.P.enable_auto_heal(g, bot.sid, want_on=True)
  251. log("喝药保护: on")
  252. if bot.me.get("mapname") == Q.WAIT_MAP and bot.me["y"] <= 4:
  253. log("[1] 滞留带 → NPC传比奇")
  254. if not npc_talk_to_biqi(bot):
  255. log("[!] 未传比奇")
  256. return
  257. if bot.me.get("mapname") != Q.WAIT_MAP:
  258. log("[2] 比奇 → @renwu_mov 回等待图")
  259. if not renwu_back_to_wait(bot):
  260. log("[!] 未回等待图可走区")
  261. return
  262. log("[3] SC20102→CS5013→逐格走(完整链复刻)")
  263. rail_walk(bot, args.minutes * 60, m)
  264. finally:
  265. g.close()
  266. if __name__ == "__main__":
  267. main()