| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- # -*- coding: utf-8 -*-
- """
- wait_wander5.py — 终极对齐实验: 完整复刻客户端移动链路。
- Lua 真源链(解密源码/mod_fgcq/stab/scripts):
- SC20102(T=1) → AutoMoveBeginCommand:
- autoProxy:SetAutoTarget(data) + PushMove(dest) + InputMove(dest)
- → 若 autoMoveType==SERVER/MINIMAP 且 GetPathPointSize()>0:
- RequestAutoMoveNotify(START, destX, destY) ← CS5013(recog=1, p1=X, p2=Y)
- InputMoveCommand: A* pathPoints → PlayerInputProxy
- BehaviorMove(每帧): checkNextMovePos(下一格, 直线可并1-3格)
- → SetMoveTo+SetAction(WALK/RUN) → 动画步时(~550ms) 到点
- → handleActionBegin: LuaSendMsg(3011/3013, point, 0, dir, lastPoint, mapID)
- → 等 SC11 自回显/SC1 确认(-5s超时兜底) → 下一格
- 到达: AutoMoveEndCommand → RequestAutoMoveNotify(FINISH=3)
- 本实验: 滞留带→NPC传比奇→@renwu_mov回等待图→[SC20102到达]→CS5013 START
- → 严格550ms节拍逐格走向 SC20102 目标 → 每步等确认 → 到达发 FINISH。
- 全帧日志。若 SC11 出现自回显 → 走动打通。
- """
- import argparse
- import json
- import os
- 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, pack_point, lo16, hi16, DIRS, proto_map_name
- from mir_map import load as map_load
- class RailBot(Q.PushBot):
- def __init__(self, *a, **kw):
- super().__init__(*a, **kw)
- self.sent_log = {}
- self.rail = None # SC20102 目标
- self.sc28_dropped = 0
- def send(self, mid, recog, p1, p2, p3, body=b"", tail=None):
- if mid in (3011, 3013):
- self.sent_log[mid] = self.sent_log.get(mid, 0) + 1
- return super().send(mid, recog, p1, p2, p3, body, tail)
- def _dispatch(self, mid, recog, p1, p2, p3, body):
- ev = super()._dispatch(mid, recog, p1, p2, p3, body)
- if mid == 20102:
- j = self._jbody(body) or {}
- if j.get("mapID"):
- self.rail = j
- log(f" << SC20102 ★ {j}")
- elif mid in (11, 13):
- j = self._jbody(body) or {}
- uid = str(j.get("UserID", ""))
- if uid in ((self.me.get("uid") or ""),
- (self.me.get("sid_short") or ""),
- str(self.me.get("id") or "")):
- log(f" << SC{mid} ★★自己移动确认 → ({p1},{p2 & 0x7FF})")
- elif mid == 28:
- self.sc28_dropped += 0 if self.frame_log.get(28, 0) <= self.sc28_dropped else 0
- return ev
- def step_dir(self, d, dist=1):
- dx, dy = DIRS[d]
- x, y = self.me["x"], self.me["y"]
- lx, ly = self.me.get("last_x", x), self.me.get("last_y", y)
- mid = 3013 if dist >= 2 else 3011
- self.send(mid, pack_point(x, y), 0, d, pack_point(lx, ly),
- proto_map_name(self.me).encode())
- self.me["last_x"], self.me["last_y"] = x, y
- self.me["x"], self.me["y"], self.me["dir"] = x + dx * dist, y + dy * dist, d
- self._step_pending_ack = (dx * dist, dy * dist, time.time() + Q.STEP_MS / 1000.0)
- self._last_step_t = time.time()
- def enter(args):
- 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"])
- 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']}")
- return g, bot
- def npc_talk_to_biqi(bot):
- t0 = time.time()
- while time.time() - t0 < 8 and not bot.npcs:
- bot.pump(0.4)
- npc = bot.find_guide_npc()
- if not npc:
- return bot.me.get("mapname") == "比奇大城"
- aid = npc["aid"]
- for idx in [npc["index"], 35, 34]:
- if idx == npc["index"] and 35 == npc["index"]:
- idxs = [35]
- bot.pending_map_ack = "比奇大城"
- bot.send(1010, 0, 0, 0, bot.sid, json.dumps(
- {"UserID": aid, "index": idx}, separators=(",", ":")).encode())
- bot.send(5012, 0, 0, 0, bot.sid, b"0")
- bot.send(5027, 0, 0, 0, bot.sid, b"0")
- t1 = time.time()
- while time.time() - t1 < 8 and bot.alive:
- evs = bot.pump(0.3)
- if bot.me.get("mapname") == "比奇大城":
- bot.pending_map_ack = None
- return True
- talk = next((e[1] for e in evs if e[0] == "npc_talk"), None)
- if talk:
- acts = bot.parse_sui_acts(str(talk.get("sMsg", "")))
- act = next((a for a in acts if a not in ("exit", "close")), None)
- if act:
- bot.npc_click_act(talk, act)
- bot.pending_map_ack = None
- if bot.me.get("mapname") == "比奇大城":
- return True
- return bot.me.get("mapname") == "比奇大城"
- def renwu_back_to_wait(bot):
- if 1 in bot.mission_bodies:
- body3031 = json.dumps(bot.mission_bodies[1], separators=(",", ":")).encode()
- else:
- 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 < 12 and bot.alive:
- bot.pump(0.3)
- if bot.me.get("mapname") == Q.WAIT_MAP:
- break
- # 静置吃掉传送期过期 SC28, 重置锚点
- time.sleep(0.5)
- t1 = time.time()
- while time.time() - t1 < 2.0:
- bot.pump(0.2)
- bot.me["last_x"], bot.me["last_y"] = bot.me["x"], bot.me["y"]
- bot._step_pending_ack = None
- log(f"回到等待图 @{bot.me['x']},{bot.me['y']} "
- f"SC20102目标={bot.rail}")
- return bot.me.get("mapname") == Q.WAIT_MAP and bot.me["y"] > 4
- def rail_walk(bot, seconds, m):
- """SC20102→CS5013 START→逐格550ms走向目标(完整复刻客户端链)。"""
- t_end = time.time() + seconds
- started = False
- last_step = 0.0
- rep = time.time()
- rejected = 0
- confirmed = 0
- while time.time() < t_end and bot.alive:
- evs = bot.pump(0.05)
- bot.tick_step_ack()
- for e in evs:
- if e[0] == "self_move":
- confirmed += 1
- elif e[0] in ("pull_back", "pull_back_bogus_dropped"):
- rejected += 1
- if not started and bot.rail and bot.rail.get("x") is not None:
- tx, ty = int(bot.rail["x"]), int(bot.rail["y"])
- bot.send(5013, 1, tx, ty, 0, b"")
- started = True
- last_step = time.time() # 握手后第一个550ms才开始走
- log(f" >> CS5013 START → ({tx},{ty}) [复刻AutoMoveBegin]")
- now = time.time()
- if started and bot.rail and now - last_step >= Q.STEP_MS / 1000.0:
- tx, ty = int(bot.rail["x"]), int(bot.rail["y"])
- x, y = bot.me["x"], bot.me["y"]
- if abs(tx - x) <= 1 and abs(ty - y) <= 1:
- bot.send(5013, 3, 0, 0, 0, b"")
- log(f" >> CS5013 FINISH @到达 ({x},{y})")
- # 到达后重新触发寻路(保持会话活跃, 循环走动)
- bot.send(1011, 0, 0, 0, bot.sid, json.dumps(
- {"Act": "@renwu_mov,1", "UserID": "999999999",
- "index": 999999999}, separators=(",", ":")).encode())
- started = False
- bot.rail = None
- last_step = now
- continue
- dx = max(-1, min(1, tx - x))
- dy = max(-1, min(1, ty - y))
- if dx and dy and not (m.walkable(x + dx, y) or m.walkable(x, y + dy)):
- if m.walkable(x + dx, y):
- dy = 0
- elif m.walkable(x, y + dy):
- dx = 0
- d = next(i for i, (ox, oy) in enumerate(DIRS) if ox == dx and oy == dy)
- bot.step_dir(d, 1)
- last_step = now
- if now - rep >= 15:
- rep = now
- log(f"[STAT] pos=({bot.me['x']},{bot.me['y']}) rail={bot.rail} "
- f"发帧={bot.sent_log} 确认={confirmed} 拒绝={rejected}")
- if started:
- bot.send(5013, 2, 0, 0, 0, b"")
- log(f"[结束] pos=({bot.me['x']},{bot.me['y']}) 发帧={bot.sent_log} "
- f"确认={confirmed} 拒绝={rejected}")
- 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="萌新8195")
- 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)
- m = map_load("0139")
- g, bot = enter(args)
- try:
- Q.P.enable_auto_heal(g, bot.sid, want_on=True)
- log("喝药保护: on")
- if bot.me.get("mapname") == Q.WAIT_MAP and bot.me["y"] <= 4:
- log("[1] 滞留带 → NPC传比奇")
- if not npc_talk_to_biqi(bot):
- log("[!] 未传比奇")
- return
- if bot.me.get("mapname") != Q.WAIT_MAP:
- log("[2] 比奇 → @renwu_mov 回等待图")
- if not renwu_back_to_wait(bot):
- log("[!] 未回等待图可走区")
- return
- log("[3] SC20102→CS5013→逐格走(完整链复刻)")
- rail_walk(bot, args.minutes * 60, m)
- finally:
- g.close()
- if __name__ == "__main__":
- main()
|