| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384 |
- # -*- coding: utf-8 -*-
- """
- walk_final.py — 等待图走动·终版: 全帧分类日志 + 0139 网格感知游走。
- 2026-09-23 定案的行走规则(黄金抓包 cap_gold + Lua 真源 + SPY 实捕三方一致):
- 【服务器下行帧 · 走路相关】(Lua 处理器已核对, 见 gameMapController/netActorController)
- SC11/12/13 移动广播: body.UserID 查表, 自己的 → SetMoveTo+SetConfirmed(true)
- = 移动成功确认(本服布局: recog=X, p1=Y, p2=dir — SPY 抓包定案)
- SC28 MOVE_FAIL 拉回: setPosition(接受格) + ACTION_IDLE_LOCK
- SC1 行为确认: SetConfirmed(true)
- SC634 跨图传送: recog=X, p1=Y(旧代码按 p1/p2 解析 — 已修), body=图名JSON
- SC51 场景创建: 出生坐标(本服 recog=X, p1=Y)
- SC20102 服务器寻路目标(T=1)
- 可忽略: SC5020(buff)/SC41/SC881(表现)/SC10/30(视野雷达, 杀怪才用)
- 【客户端上行 · 每步三帧组】(黄金抓包逐帧还原)
- CS3011: recog=0, p1=dir, p2=pack_point(起点), p3=sid, body=图名
- CS1 : recog=落点X, p1=落点Y, p2=dir, p3=32368232, body=encode6(+GOOD/ms)
- CS28 : recog=锚点X, p1=锚点Y, p2=0, p3=32368232, body=空(锚点=传送落点,恒定)
- 链式规则: 3011.p2 起点 = 上一步 CS1 落点; 断链或撞墙 → SC28 拉回
- 【地图规则】0139.map(等待图): y=0-2 滞留带(墙), y>=3 可走;
- 走进墙格必被 SC28 拒 — 游走必须查网格!
- 用法:
- .venv/bin/python walk_final.py --zone 14504562 --name 斗士9906 \
- -u z1098210167 -p yang535006 --minutes 2 [--verbose]
- """
- import argparse
- import json
- import os
- import random
- 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, DIRS, proto_map_name, lo16, hi16
- from encode6bitbuf_final import encode6
- from mir_map import load as map_load
- # 走路相关下行帧分类(其余静默计数)
- WALK_FRAMES = {11, 12, 13, 28, 1, 634, 51, 801, 20102, 800}
- IGNORE_FRAMES = {60000, 5037, -1, 41, 5020, 10, 30, 35, 881, 50, 14, 32, 55}
- class FinalWalkBot(Q.PushBot):
- """全帧分类日志 + 三帧组链式走动 + 网格感知。"""
- def __init__(self, *a, **kw):
- super().__init__(*a, **kw)
- self.good_ms = 98996961125 + random.randint(0, 5000)
- self.chain_pos = None # 链式当前位置(=上步CS1落点)
- self.anchor_pos = None # CS28 锚点(传送落点)
- self.verbose = False
- self.stats = {"ok": 0, "rej": 0, "walk_frames": {}}
- # ---------- 上行三帧组 ----------
- # ---------- 上行: 只发 CS3011 ----------
- def send(self, mid, recog, p1, p2, p3, body=b"", tail=None):
- if self.verbose and mid in (3011, 3013, 5012, 5027, 1010, 1011, 3031):
- x, y = lo16(p2), (p2 >> 16) & 0xFFFF
- log(f" [TX→S] CS{mid} recog={recog} p1={p1} "
- f"p2={p2}({x},{y}) p3={p3} tail={tail if tail is not None else 'auto'}")
- return super().send(mid, recog, p1, p2, p3, body, tail)
- def step_true(self, d, m=None, run=False):
- """移动帧(cap_unlock 双组抓包定案):
- CS3011(走1格) / CS3013(跑2格), 布局相同:
- recog=0, p1=dir, p2=pack_point(起点), p3=sid, body=图名。
- ★ renwu_mov 传送后的等待图: 3011 被 M2 锁定(48帧全拒铁证),
- 3013 放行(真客户端 SC20102 后连跑3帧全确认) — 任务寻路强制跑步。
- 服务器回 SC1(+GOOD) 确认落点后才能走下一步。"""
- x, y = self.chain_pos
- dx, dy = DIRS[d]
- dist = 2 if run else 1
- nx, ny = x + dx * dist, y + dy * dist
- if m is not None and not m.walkable(nx, ny):
- return False
- mid = 3013 if run else 3011
- self.send(mid, 0, d, pack_point(x, y), self.sid,
- proto_map_name(self.me).encode())
- self._pending_step = (nx, ny) # 等 SC1 确认后才更新链位
- self.me["dir"] = d
- return True
- # ---------- 下行帧分类处理 ----------
- def _dispatch(self, mid, recog, p1, p2, p3, body):
- """接收=RX(服务器下行)。tail 判源: 下行帧 tail 通常=0/乱数;
- body 密文态: 明文JSON/encode6(e6)/zlib(-1批量) 自动剥层(_jbody)。"""
- ev = super()._dispatch(mid, recog, p1, p2, p3, body)
- self.stats["walk_frames"][mid] = self.stats["walk_frames"].get(mid, 0) + 1
- if not self.verbose:
- return ev
- y2 = p2 & 0x7FF if (p2 & 0x800) else p2
- if mid in (11, 12, 13):
- j = self._jbody(body) or {}
- uid = str(j.get("UserID", ""))
- mine = uid in ((self.me.get("uid") or ""),
- (self.me.get("sid_short") or ""),
- str(self.me.get("id") or ""))
- tag = "★自己" if mine else "他人"
- if mine:
- self.stats["ok"] += 1
- log(f" [S→RX] SC{mid} {tag}移动 UserID={uid[:12]} "
- f"@({recog},{p1}) dir={p2} [SetMoveTo+Confirmed]")
- elif mid == 28:
- if p3 == 32368232:
- # 我方 CS28 锚点帧的服务器侧回显(p3同魔数) — 非拒绝
- log(f" [S→RX] SC28 锚点回显({recog},{p1}) — 非拒绝")
- else:
- self.stats["rej"] += 1
- log(f" [S→RX] SC28 ✗真MOVE_FAIL 拉回({recog},{p1}) "
- f"dir={p3 & 0xFF} [IDLE_LOCK]")
- elif mid == 1:
- # SC1 = 服务器移动确认(下行): recog=落点X, p1=落点Y, p2=dir,
- # body=encode6(+GOOD/ms), tail=817 — 确认后才能发下一步
- pend = getattr(self, "_pending_step", None)
- if pend is not None:
- self.chain_pos = pend
- self.me["x"], self.me["y"] = pend
- self.me["last_x"], self.me["last_y"] = pend
- self._pending_step = None
- self.stats["ok"] += 1
- if self.verbose:
- log(f" [S→RX] SC1 ★移动确认({recog},{p1}) dir={p2} "
- f"[链位→{pend}]")
- else:
- log(f" [S→RX] SC1 确认({recog},{p1}) [无待确认步]")
- elif mid == 634:
- log(f" [S→RX] SC634 跨图传送→({recog},{p1}) "
- f"body={self._jbody(body) or body[:20]!r}")
- elif mid == 51:
- log(f" [S→RX] SC51 场景创建({recog},{p1})")
- elif mid in (800, 801):
- log(f" [S→RX] SC{mid} 传送({recog},{p1},{y2})")
- elif mid == 20102:
- j = self._jbody(body) or {}
- log(f" [S→RX] SC20102 服务器寻路目标 {j}")
- elif mid not in IGNORE_FRAMES:
- log(f" [S→RX] SC{mid} recog={recog} p=({p1},{y2},{p3}) "
- f"body={body[:24]!r} [未分类]")
- return ev
- 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 = FinalWalkBot(g, sid, rname)
- P.heartbeat_loop(g, sid, sender=bot.send)
- 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 超时")
- # 先收 SC51 拿到当前图名(5027 需要正确图名), 再 init(对齐真客户端时序)
- t_pre = time.time()
- while time.time() - t_pre < 3:
- bot.pump(0.2)
- if bot.me.get("mapname"):
- break
- 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]:
- 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_to_wait(bot):
- """比奇/任意图 → @renwu_mov → 等待图可走区, 建链式起点+锚点。"""
- 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.3)
- bot.send(1011, 0, 0, 0, bot.sid, json.dumps(
- {"Act": "@renwu_mov,1", "UserID": "999999999", "index": 999999999},
- separators=(",", ":")).encode())
- # 黄金时序: 1011后立即 5012/5027×2+cTh×2, 不等传送
- for _ in range(2):
- for mid in (5012, 5027):
- bot.send(mid, 0, 0, 0, bot.sid, Q.WAIT_MAP.encode())
- bot.send(5012, 0, 0, 0, 32611848, b"cTh")
- # 等 SC634 权威落点(已修解析: recog=X, p1=Y)
- t0 = time.time()
- while time.time() - t0 < 6 and bot.alive:
- bot.pump(0.1)
- if bot.me.get("mapname") == Q.WAIT_MAP and bot.me["y"] > 4:
- break
- x0, y0 = bot.me["x"], bot.me["y"]
- if y0 < 20:
- x0, y0 = 24, 27
- # M2封速系统(996引擎, 网络资料+抓包交叉定案):
- # MOVEINTERVAL/CHECKOVSPDINV/MAXOVSPDCNT=3 — 移动包间隔过快会累计超速计数,
- # 超限后所有移动包被丢(OVERSPEEDPUNISH)+SC28拉回, 且此前的高频实验帧
- # 早已污染计数 → 传送后必须静置洗计数, 走路间隔取保守值(≥1.4s/步,
- # 抓包实证真客户端点击间隔≈1.4s全通过)。
- log(f"传送落点({x0},{y0}) — 静置 10s 洗封速计数...")
- t_lock = time.time()
- while time.time() - t_lock < 10.0 and bot.alive:
- bot.pump(0.2)
- bot.chain_pos = (x0, y0)
- bot.anchor_pos = (x0, y0)
- log(f"链式建立: 起点=({x0},{y0}) 步间隔1.5s")
- return True
- def wander(bot, seconds, m):
- """网格感知游走: 发一步 CS3011 → 等 SC1 确认 → 再发下一步。
- (真客户端节拍: 点击后连续 3011, 服务器 SC1 逐帧确认)"""
- t_end = time.time() + seconds
- rep = time.time()
- dest = None
- last_step = 0.0
- while time.time() < t_end and bot.alive:
- evs = bot.pump(0.05)
- pulled = any(e[0].startswith("pull_back") for e in evs)
- if pulled:
- # SC28拉回=M2丢包: 退避3s并重置链位到服务器位置
- bot.chain_pos = (bot.me["x"], bot.me["y"])
- bot._pending_step = None
- last_step = time.time() + 3.0
- log(f" [退避] SC28拉回→({bot.chain_pos[0]},{bot.chain_pos[1]}), 3s后再试")
- now = time.time()
- if now < last_step + 1.5:
- continue
- if getattr(bot, "_pending_step", None) is not None:
- continue
- x, y = bot.chain_pos
- if dest is None or (abs(dest[0] - x) <= 1 and abs(dest[1] - y) <= 1):
- for _ in range(30):
- nx = x + random.randint(-10, 10)
- ny = y + random.randint(-10, 10)
- if (m.in_bounds(nx, ny) and m.walkable(nx, ny)
- and abs(nx - x) + abs(ny - y) > 3):
- dest = (nx, ny)
- log(f"[游走] 新目标 ({nx},{ny})")
- break
- else:
- dest = None
- if dest:
- dx = max(-1, min(1, dest[0] - x))
- dy = max(-1, min(1, dest[1] - 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)
- # renwu 后等待图任务态: 必须跑步(3013), 走(3011)被 M2 锁定
- if not bot.step_true(d, m, run=True):
- dest = None
- last_step = now
- if now - rep >= 15:
- rep = now
- log(f"[STAT] 链位={bot.chain_pos} 目标={dest} "
- f"确认={bot.stats['ok']} 拒绝={bot.stats['rej']}")
- log(f"[结束] 链位={bot.chain_pos} 确认={bot.stats['ok']} "
- f"拒绝={bot.stats['rej']}")
- def main():
- ap = argparse.ArgumentParser()
- ap.add_argument("-u", "--user", default="z1098210167")
- ap.add_argument("-p", "--password", default="yang535006")
- ap.add_argument("--zone", type=int, default=14504562)
- ap.add_argument("--name", default="斗士9906")
- ap.add_argument("--port", type=int, default=0)
- ap.add_argument("--minutes", type=float, default=2)
- ap.add_argument("--verbose", action="store_true", help="全帧分类日志")
- args = ap.parse_args()
- args.minutes = min(args.minutes, 2)
- m = map_load("0139")
- g, bot = enter(args)
- bot.verbose = args.verbose
- 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:
- # 登录落点已在可走区(上次下线位置) — 直接走, 不做任何传送!
- bot.chain_pos = (bot.me["x"], bot.me["y"])
- log(f"[0] 落点已在可走区 @{bot.me['x']},{bot.me['y']} — 直接走")
- elif bot.me.get("mapname") == Q.WAIT_MAP and bot.me["y"] <= 4:
- log("[1] 滞留带 → NPC传比奇")
- if not npc_talk_to_biqi(bot):
- log("[!] 未传比奇")
- return
- time.sleep(1)
- for _ in range(6):
- bot.pump(0.4)
- if bot.me.get("mapname") != Q.WAIT_MAP or bot.me["y"] <= 4:
- log("[2] → @renwu_mov 回等待图可走区")
- if not renwu_to_wait(bot):
- log("[!] 建链失败")
- return
- log(f"[3] 网格感知游动开始 @{bot.me['x']},{bot.me['y']}")
- wander(bot, args.minutes * 60, m)
- finally:
- g.close()
- if __name__ == "__main__":
- main()
|