run_juece13_kill30.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. """run_juece13_kill30.py — 裁决13区角色【新角色】→ 任务指引 → 比奇大城 → 杀30怪。
  2. 链路(全部复用 pbot 框架已实测模块):
  3. 登录盒子 → EnterGameClient → 登录服选角(已有角色直接拿票据)
  4. → 游戏服 CS20 → 17帧初始化 → SC50/SC51 出生(等待地图)
  5. → KillBehavior 完整状态机:
  6. renwu_3031 → 1.0s 黄金时序 → CS1011 @renwu_mov,1(任务指引)
  7. → SC20102 受理 → 传送比奇(或等待图 NPC 链) → ACK
  8. → A* 跑向杀怪区(287,622) → CS3014 近战
  9. → 无怪: CS5284 问服务器怪位 + 新颖度锚点跑图 + 刷怪NPC
  10. → 有怪: A* 跑向怪 → 攻击半径内 0.65s/刀 → SC31/SC32 计杀
  11. → 达标 30 杀退出
  12. 用法:
  13. uv run python scripts/run_juece13_kill30.py -u longwu988 -p Longwu1988 \
  14. --zone 14570166 --name 新角色 --kill 30
  15. """
  16. from __future__ import annotations
  17. import argparse
  18. import logging
  19. import re
  20. import sys
  21. import time
  22. import requests
  23. from pbot.bot.kill import KillBehavior
  24. from pbot.bot.runner import run
  25. from pbot.config import Account
  26. from pbot.errors import PbotError
  27. from pbot.game.session import enter_world
  28. from pbot.login.box_client import enter_game_client, login
  29. from pbot.transport.gateway import login_gate_with_retry
  30. from pbot.login.zone_ports import resolve_zone_gateway
  31. from pbot.types import ZoneId
  32. _LOG = logging.getLogger("juece13")
  33. # 等待图→比奇 NPC 链日志辅助: 从 SC643 提取可点链接
  34. _LINK_RE: re.Pattern[str] = re.compile(r"link=@([^\|<>]+)")
  35. def setup_logging() -> None:
  36. """根日志: 全量 INFO, 关键阶段带分隔条, 便于后续问题分析。"""
  37. logging.basicConfig(
  38. level=logging.INFO,
  39. format="%(asctime)s %(levelname)-7s %(name)s: %(message)s",
  40. stream=sys.stdout,
  41. )
  42. def banner(msg: str, *args: object) -> None:
  43. """阶段分隔条日志(支持 %-style 格式化参数)。"""
  44. _LOG.info("======== %s ========", msg % args if args else msg)
  45. def parse_args() -> argparse.Namespace:
  46. """命令行: 账号/区/角色/杀怪数。"""
  47. ap = argparse.ArgumentParser()
  48. _ = ap.add_argument("-u", "--user", required=True)
  49. _ = ap.add_argument("-p", "--password", required=True)
  50. _ = ap.add_argument("--zone", type=int, required=True)
  51. _ = ap.add_argument("--name", required=True, help="已有角色名")
  52. _ = ap.add_argument("--kill", type=int, default=30)
  53. _ = ap.add_argument("--login-port", type=int, default=0)
  54. return ap.parse_args()
  55. def main() -> int:
  56. """全链路: 登录 → 选角 → 进游戏 → 任务指引传比奇 → 杀30怪。"""
  57. a = parse_args()
  58. setup_logging()
  59. banner(f"[0] 启动: 区={a.zone} 角色={a.name} 目标击杀={a.kill}")
  60. http = requests.Session()
  61. http.trust_env = False
  62. http.verify = False
  63. # ---- [1] 盒子登录 ----
  64. banner("[1] 盒子登录")
  65. jwt, uid = login(Account(username=a.user, password=a.password))
  66. _LOG.info("[✓] uid=%d", uid)
  67. # ---- [2] EnterGameClient 选区上报 ----
  68. banner(f"[2] EnterGameClient zone={a.zone}")
  69. for attempt in range(4):
  70. try:
  71. _ = enter_game_client(http, jwt, ZoneId(a.zone))
  72. _LOG.info("[✓] 上报成功")
  73. break
  74. except Exception as e:
  75. if attempt == 3:
  76. raise
  77. _LOG.warning("[!] 第%d次失败(%s), 12s 后重试", attempt + 1, e)
  78. time.sleep(12)
  79. # ---- [3] 登录服选角(已有角色 → 票据) ----
  80. banner("[3] 登录服选角: %s", a.name)
  81. gw_host, conn_port, real_port = (("", a.login_port, a.login_port)
  82. if a.login_port else resolve_zone_gateway(a.zone))
  83. ports = (conn_port,)
  84. key32, sid, real_name, ticket_key, game_port = login_gate_with_retry(
  85. ZoneId(a.zone), jwt, uid, a.name, ports, host=gw_host, real_port=real_port)
  86. _LOG.info("[票据] key32=%s sid=%d 角色=%s ticket=%s 游戏端口=%d",
  87. key32.decode(errors="replace"), sid, real_name, ticket_key,
  88. game_port)
  89. # ---- [4] 进游戏 ----
  90. banner("[4] 进游戏(CS20 → SC658 → 17帧初始化 → 出生)")
  91. stop: list[bool] = [False]
  92. conn, world, updater = enter_world(ZoneId(a.zone), real_name, ticket_key,
  93. sid, game_port, uid, stop=stop, host=gw_host)
  94. _LOG.info("[✓] 出生 %s @(%d,%d) actor=%s kills=%d",
  95. world.me.map_name, world.me.x, world.me.y,
  96. world.me.actor_key, world.kills)
  97. try:
  98. # ---- [5] 喝药保护(进游戏先决项, 上一轮已开也再确认一次) ----
  99. banner("[5] 喝药保护确认")
  100. from pbot.game.heal import enable_auto_heal
  101. state = enable_auto_heal(conn, sid, want_on=True, world=world)
  102. if state == "on":
  103. _LOG.info("[✓] 喝药保护已开启(服务端 but_1 确认)")
  104. else:
  105. _LOG.warning("[!] 喝药状态=%s(不阻断, 继续任务链)", state)
  106. # ---- [6] 杀怪 30 只 ----
  107. # renwu_act: KillBehavior 默认 renwu_mov,2 是任务二; 我们要任务一
  108. # "前往新手村/比奇大城" = renwu_mov,1(SC643 弹窗 link 实测)。行为内部
  109. # 会优先从实时 SC643 弹窗读 Act, 此处只是无弹窗兜底。
  110. banner("[6] 打怪链启动: 任务指引 → 比奇大城 → 杀 %d 怪", a.kill)
  111. _LOG.info("当前状态: map=%s @(%d,%d) 怪视野=%d NPC视野=%d",
  112. world.me.map_name, world.me.x, world.me.y,
  113. len(world.monsters), len(world.npcs))
  114. kill = KillBehavior(
  115. sid=sid,
  116. kill_target=a.kill,
  117. renwu_act="renwu_mov,1", # 任务一: 前往新手村(比奇大城方向)
  118. allow_reroll=True, # 无怪/卡死时允许重走 renwu 链换落点
  119. roam=True,
  120. )
  121. # 时长上限: 30怪按 120s/怪 兜底 + 转场 600s
  122. duration = max(600.0, a.kill * 120.0 + 600.0)
  123. _LOG.info("行为超时上限: %.0fs(达标即提前退出)", duration)
  124. result = run(conn, world, updater, kill, duration_s=duration)
  125. banner("[7] 结果")
  126. _LOG.info("result=%s 击杀=%d/%d 最终位置=(%d,%d) 地图=%s",
  127. result.value, world.kills, a.kill,
  128. world.me.x, world.me.y, world.me.map_name)
  129. top = sorted(world.frame_counts.items(), key=lambda kv: -kv[1])[:15]
  130. _LOG.info("收帧Top15(问题分析用): %s", top)
  131. ok = world.kills >= a.kill
  132. _LOG.info("[%s] %s", "✓" if ok else "✗",
  133. "任务完成" if ok else "未达标(看上方日志定位)")
  134. return 0 if ok else 1
  135. finally:
  136. stop[0] = True
  137. conn.close()
  138. _LOG.info("连接已关闭")
  139. if __name__ == "__main__":
  140. try:
  141. sys.exit(main())
  142. except PbotError as e:
  143. _LOG.error("失败: %s", e.detail)
  144. sys.exit(1)