# -*- coding: utf-8 -*- """ wait_wander4.py — 等待图走动修复版: 绕开滞留带, 从可走区内走。 根因(2026-09-23 定案): 0139.map 客户端文件: y=0-2 整行不可走(墙), 可走区 y>=3(x25-35 一带 y>=8); 服务器把登录角色放在滞留带 y=0 (31,0), 该带内一切 CS3011/3013 全被拒 (SC28 recog=24, 新旧角色/走跑帧/5013握手均同样 — 三轮对照实验)。 实证可走路径: 07:59 轮, 角色从比奇 @renwu_mov → SC634 落 (24,27)[可走区], 随后 20+ 步无拉回。 修复流程: 1. 在滞留带(y<=4) → CS1010 点新手接待NPC(idx候选) → 传比奇 (07:47 实证) 2. 比奇 → CS3031 + @renwu_mov,1 → SC634 传回等待图可走区 (07:59 实证) 3. 可走区内自由走动(0139 网格 A* 随机游走), 全帧日志 用法: .venv/bin/python wait_wander4.py --zone 14504562 --name 萌新8195 \ -u longwu988 -p Longwu1988 --minutes 2 """ 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, lo16, hi16, DIRS, proto_map_name from mir_map import load as map_load class WalkBot(Q.PushBot): """全帧日志 + SC20102→CS5013 寻路握手 + 网格游走。""" def __init__(self, *a, **kw): super().__init__(*a, **kw) self.sent_log = {} self.rail_target = None # SC20102 {mapID,x,y,targetType,...} self.rail_on = False # CS5013 START 已发 def auto_move_notify(self, act, x=0, y=0): """CS5013(Lua: LuaSendMsg(5013, act, destX, destY)): 1=START 2=ABORT 3=FINISH。""" self.send(5013, act, x, y, 0, b"") self.rail_on = (act == 1) log(f" >> CS5013 act={act} ({x},{y})") 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 if self.sent_log[mid] <= 40: log(f" >> CS{mid} 起点=({lo16(recog)},{hi16(recog)}) dir={p2} " f"last=({lo16(p3)},{hi16(p3)}) #{self.sent_log[mid]}") 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 in (28, 11, 1, 5013, 800, 634, 51, 20102): y2 = p2 & 0x7FF if (p2 & 0x800) else p2 if mid == 28: if getattr(self, "_sc28_n", 0) < 40: self._sc28_n = getattr(self, "_sc28_n", 0) + 1 log(f" << SC28 recog={recog} 拉回→({p1},{y2})") 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},{y2})") elif mid in (1, 5013, 20102): log(f" << SC{mid} recog={recog} p=({p1},{y2},{p3}) " f"body={body[:60]!r}") elif mid in (800, 634, 51): log(f" << SC{mid} p=({p1},{y2}) body={body[:50]!r}") 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 = WalkBot(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']} " f"uid={bot.me.get('uid')}") return g, bot def npc_talk_to_biqi(bot): """滞留带 → 点NPC → 传比奇(quest_bot3 stage[3] 同款: idx候选+对话按钮)。""" t0 = time.time() while time.time() - t0 < 8 and not bot.npcs: bot.pump(0.4) npc = bot.find_guide_npc() if not npc: log("[!] 视野内无NPC") return False aid = npc["aid"] idx_cands = [npc["index"]] + [i for i in (35, 34) if i != npc["index"]] base_tp = bot.teleports for idx in idx_cands: 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") log(f"[NPC] CS1010 idx={idx} ...") t1 = time.time() clicks = 0 while time.time() - t1 < 10 and bot.alive: evs = bot.pump(0.3) bot.tick_step_ack() if bot.me.get("mapname") == "比奇大城" or bot.teleports > base_tp: bot.pending_map_ack = None log(f"[NPC] ★已传比奇 @{bot.me['x']},{bot.me['y']}") return True talk = next((e[1] for e in evs if e[0] == "npc_talk"), None) if talk is None: continue acts = bot.parse_sui_acts(str(talk.get("sMsg", ""))) log(f"[NPC对话] acts={acts}") if clicks < 3: act = next((a for a in acts if a not in ("exit", "close", "guanbi")), None) if act: bot.npc_click_act(talk, act) clicks += 1 log(f"[NPC对话] 点 @{act}") bot.pending_map_ack = None return bot.me.get("mapname") == "比奇大城" def renwu_back_to_wait(bot): """比奇 → @renwu_mov → 传回等待图可走区(07:59 实证落点 ~ (24,27))。""" 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) for act in ("@renwu_mov,1", "@renwu_mov,2"): bot.send(1011, 0, 0, 0, bot.sid, json.dumps( {"Act": act, "UserID": "999999999", "index": 999999999}, separators=(",", ":")).encode()) t0 = time.time() while time.time() - t0 < 10 and bot.alive: evs = bot.pump(0.3) bot.tick_step_ack() if bot.me.get("mapname") == Q.WAIT_MAP: log(f"★回到等待图 @{bot.me['x']},{bot.me['y']} (act={act})") break if bot.me.get("mapname") == Q.WAIT_MAP: # 传送动画期服务器仍按旧位置结算走路帧 → 静置 2.5s 吃掉过期 SC28, # 再以服务器坐标为锚重置 lastPoint(09:03 竞态实证: 0秒发步被拉回) time.sleep(0.5) t1 = time.time() while time.time() - t1 < 2.0 and bot.alive: bot.pump(0.2) bot.tick_step_ack() bot.me["last_x"] = bot.me["x"] bot.me["last_y"] = bot.me["y"] bot._step_pending_ack = None if bot.me["y"] > 4: return True log(" 仍在滞留带 y<=4") break return bot.me.get("mapname") == Q.WAIT_MAP and bot.me["y"] > 4 def wander_interior(bot, seconds, m): """可走区内随机游走。文件网格仅作方向微调(服务器网格与0139有偏差, 09:12 实证: 按文件网格硬拦截会在(19,29)停摆); 拒绝靠近距SC28校正+换向。""" t_end = time.time() + seconds last_step = 0.0 rep = time.time() accepted = 0 rejected = 0 consec_rej = 0 dest = None 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": accepted += 1 consec_rej = 0 elif e[0] in ("pull_back", "pull_back_bogus_dropped"): rejected += 1 now = time.time() x, y = bot.me["x"], bot.me["y"] if dest is None or (abs(dest[0] - x) <= 1 and abs(dest[1] - y) <= 1): for _ in range(20): nx = x + random.randint(-12, 12) ny = y + random.randint(-12, 12) 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 and now - last_step >= Q.STEP_MS / 1000.0 + 0.05: 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 if consec_rej >= 3: d = random.randrange(8) consec_rej = 0 else: 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=({x},{y}) 目标={dest} 发帧={bot.sent_log} " f"SC11确认={accepted} SC28拒绝={rejected}") log(f"[游走结束] pos=({bot.me['x']},{bot.me['y']}) 发帧={bot.sent_log} " f"确认={accepted} 拒绝={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: state = Q.P.enable_auto_heal(g, bot.sid, want_on=True) log(f"喝药保护: {state}") mname = bot.me.get("mapname") if mname == Q.WAIT_MAP and bot.me["y"] <= 4: log("[阶段1] 滞留带 → NPC对话传比奇") if not npc_talk_to_biqi(bot): log("[!] 未传比奇, 中止") return mname = bot.me.get("mapname") if mname != Q.WAIT_MAP: log("[阶段2] 比奇 → @renwu_mov 传回等待图可走区") if not renwu_back_to_wait(bot): log("[!] 未回到等待图可走区, 中止") return log(f"[阶段3] 等待图可走区走动 @{bot.me['x']},{bot.me['y']}") wander_interior(bot, args.minutes * 60, m) finally: g.close() if __name__ == "__main__": main()