| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- """run_juece13_kill30.py — 裁决13区角色【新角色】→ 任务指引 → 比奇大城 → 杀30怪。
- 链路(全部复用 pbot 框架已实测模块):
- 登录盒子 → EnterGameClient → 登录服选角(已有角色直接拿票据)
- → 游戏服 CS20 → 17帧初始化 → SC50/SC51 出生(等待地图)
- → KillBehavior 完整状态机:
- renwu_3031 → 1.0s 黄金时序 → CS1011 @renwu_mov,1(任务指引)
- → SC20102 受理 → 传送比奇(或等待图 NPC 链) → ACK
- → A* 跑向杀怪区(287,622) → CS3014 近战
- → 无怪: CS5284 问服务器怪位 + 新颖度锚点跑图 + 刷怪NPC
- → 有怪: A* 跑向怪 → 攻击半径内 0.65s/刀 → SC31/SC32 计杀
- → 达标 30 杀退出
- 用法:
- uv run python scripts/run_juece13_kill30.py -u longwu988 -p Longwu1988 \
- --zone 14570166 --name 新角色 --kill 30
- """
- from __future__ import annotations
- import argparse
- import logging
- import re
- import sys
- import time
- import requests
- from pbot.bot.kill import KillBehavior
- from pbot.bot.runner import run
- from pbot.config import Account
- from pbot.errors import PbotError
- from pbot.game.session import enter_world
- from pbot.login.box_client import enter_game_client, login
- from pbot.transport.gateway import login_gate_with_retry
- from pbot.types import ZoneId
- _LOG = logging.getLogger("juece13")
- _PORTS: tuple[int, ...] = (8021, 8061, 8001, 8041)
- # 等待图→比奇 NPC 链日志辅助: 从 SC643 提取可点链接
- _LINK_RE: re.Pattern[str] = re.compile(r"link=@([^\|<>]+)")
- def setup_logging() -> None:
- """根日志: 全量 INFO, 关键阶段带分隔条, 便于后续问题分析。"""
- logging.basicConfig(
- level=logging.INFO,
- format="%(asctime)s %(levelname)-7s %(name)s: %(message)s",
- stream=sys.stdout,
- )
- def banner(msg: str, *args: object) -> None:
- """阶段分隔条日志(支持 %-style 格式化参数)。"""
- _LOG.info("======== %s ========", msg % args if args else msg)
- def parse_args() -> argparse.Namespace:
- """命令行: 账号/区/角色/杀怪数。"""
- ap = argparse.ArgumentParser()
- _ = ap.add_argument("-u", "--user", required=True)
- _ = ap.add_argument("-p", "--password", required=True)
- _ = ap.add_argument("--zone", type=int, required=True)
- _ = ap.add_argument("--name", required=True, help="已有角色名")
- _ = ap.add_argument("--kill", type=int, default=30)
- _ = ap.add_argument("--login-port", type=int, default=0)
- return ap.parse_args()
- def main() -> int:
- """全链路: 登录 → 选角 → 进游戏 → 任务指引传比奇 → 杀30怪。"""
- a = parse_args()
- setup_logging()
- banner(f"[0] 启动: 区={a.zone} 角色={a.name} 目标击杀={a.kill}")
- http = requests.Session()
- http.trust_env = False
- http.verify = False
- # ---- [1] 盒子登录 ----
- banner("[1] 盒子登录")
- jwt, uid = login(Account(username=a.user, password=a.password))
- _LOG.info("[✓] uid=%d", uid)
- # ---- [2] EnterGameClient 选区上报 ----
- banner(f"[2] EnterGameClient zone={a.zone}")
- for attempt in range(4):
- try:
- _ = enter_game_client(http, jwt, ZoneId(a.zone))
- _LOG.info("[✓] 上报成功")
- break
- except Exception as e:
- if attempt == 3:
- raise
- _LOG.warning("[!] 第%d次失败(%s), 12s 后重试", attempt + 1, e)
- time.sleep(12)
- # ---- [3] 登录服选角(已有角色 → 票据) ----
- banner("[3] 登录服选角: %s", a.name)
- ports = (a.login_port,) if a.login_port else _PORTS
- key32, sid, real_name, ticket_key, game_port = login_gate_with_retry(
- ZoneId(a.zone), jwt, uid, a.name, ports)
- _LOG.info("[票据] key32=%s sid=%d 角色=%s ticket=%s 游戏端口=%d",
- key32.decode(errors="replace"), sid, real_name, ticket_key,
- game_port)
- # ---- [4] 进游戏 ----
- banner("[4] 进游戏(CS20 → SC658 → 17帧初始化 → 出生)")
- stop: list[bool] = [False]
- conn, world, updater = enter_world(ZoneId(a.zone), real_name, ticket_key,
- sid, game_port, uid, stop=stop)
- _LOG.info("[✓] 出生 %s @(%d,%d) actor=%s kills=%d",
- world.me.map_name, world.me.x, world.me.y,
- world.me.actor_key, world.kills)
- try:
- # ---- [5] 喝药保护(进游戏先决项, 上一轮已开也再确认一次) ----
- banner("[5] 喝药保护确认")
- from pbot.game.heal import enable_auto_heal
- state = enable_auto_heal(conn, sid, want_on=True, world=world)
- if state == "on":
- _LOG.info("[✓] 喝药保护已开启(服务端 but_1 确认)")
- else:
- _LOG.warning("[!] 喝药状态=%s(不阻断, 继续任务链)", state)
- # ---- [6] 杀怪 30 只 ----
- # renwu_act: KillBehavior 默认 renwu_mov,2 是任务二; 我们要任务一
- # "前往新手村/比奇大城" = renwu_mov,1(SC643 弹窗 link 实测)。行为内部
- # 会优先从实时 SC643 弹窗读 Act, 此处只是无弹窗兜底。
- banner("[6] 打怪链启动: 任务指引 → 比奇大城 → 杀 %d 怪", a.kill)
- _LOG.info("当前状态: map=%s @(%d,%d) 怪视野=%d NPC视野=%d",
- world.me.map_name, world.me.x, world.me.y,
- len(world.monsters), len(world.npcs))
- kill = KillBehavior(
- sid=sid,
- kill_target=a.kill,
- renwu_act="renwu_mov,1", # 任务一: 前往新手村(比奇大城方向)
- allow_reroll=True, # 无怪/卡死时允许重走 renwu 链换落点
- roam=True,
- )
- # 时长上限: 30怪按 120s/怪 兜底 + 转场 600s
- duration = max(600.0, a.kill * 120.0 + 600.0)
- _LOG.info("行为超时上限: %.0fs(达标即提前退出)", duration)
- result = run(conn, world, updater, kill, duration_s=duration)
- banner("[7] 结果")
- _LOG.info("result=%s 击杀=%d/%d 最终位置=(%d,%d) 地图=%s",
- result.value, world.kills, a.kill,
- world.me.x, world.me.y, world.me.map_name)
- top = sorted(world.frame_counts.items(), key=lambda kv: -kv[1])[:15]
- _LOG.info("收帧Top15(问题分析用): %s", top)
- ok = world.kills >= a.kill
- _LOG.info("[%s] %s", "✓" if ok else "✗",
- "任务完成" if ok else "未达标(看上方日志定位)")
- return 0 if ok else 1
- finally:
- stop[0] = True
- conn.close()
- _LOG.info("连接已关闭")
- if __name__ == "__main__":
- try:
- sys.exit(main())
- except PbotError as e:
- _LOG.error("失败: %s", e.detail)
- sys.exit(1)
|