Pārlūkot izejas kodu

feat(pipeline): T7五行之力/T8沃玛挂机 任务文件

wuzilong 1 mēnesi atpakaļ
vecāks
revīzija
b2573dcf98

+ 128 - 0
pipeline_bot/pipeline/tasks/t07_wuxing.py

@@ -0,0 +1,128 @@
+"""T7 — 五行之力: renwu_qwyd,33 → NPC10031 → func17_wxqh,1。"""
+from __future__ import annotations
+
+import json
+import time
+from pathlib import Path
+
+from pipeline.ctx import PipelineContext, TaskResult
+
+TASK_TITLE = "T7 开启五行之力"
+
+_ASSETS = Path(__file__).resolve().parents[2] / "assets"
+_NPC_ID = "10031"
+_MENU_INDEX = 33
+_ZLIB_MAGIC = b"x\x9c"
+_SIGNAL_TASK = "新手指引五"  # SC-1 大帧文本: 任务链推进(协议定案 §三.2)
+_SIGNAL_ITEM = b'"Index":50675'  # SC-1 大帧文本: 五行珠入包(where=12)
+_TELEPORT_WAIT_S = 20.0
+_PANEL_WAIT_S = 10.0
+_PANEL_RECLICK_S = 5.0
+_SUCCESS_WAIT_S = 15.0
+_RESEND_WAIT_S = 10.0
+
+
+def _cs1011(ctx: PipelineContext, act: str) -> None:
+    """CS1011 QFunction 菜单 Act 帧(UserID=999999999 面板标准 ID)。"""
+    body = json.dumps({"UserID": "999999999", "Act": act,
+                       "index": 999999999}, separators=(",", ":")).encode()
+    _ = ctx.conn.send_frame(1011, 0, 0, 0, ctx.sid, body)
+
+
+def _click_npc(ctx: PipelineContext) -> None:
+    """CS1010 点击五行 NPC 10031(menuIndex=33)。"""
+    body = json.dumps({"UserID": _NPC_ID, "index": _MENU_INDEX},
+                      separators=(",", ":")).encode()
+    _ = ctx.conn.send_frame(1010, 0, 0, 0, ctx.sid, body)
+
+
+def _scan_batch_text(body: bytes) -> str | None:
+    """SC-1 大帧 zlib→decode6 后搜成功信号; 命中返回信号描述。
+
+    双信号(协议定案 §三.2): ①[主线]新手指引五(任务链推进);
+    ②五行珠入包 Index=50675。坏帧(zlib/decode 失败)只跳过不致命。
+    """
+    import zlib
+
+    from pbot.codec.bitbuf import decode6
+
+    if body[:2] != _ZLIB_MAGIC:
+        return None
+    try:
+        raw = zlib.decompress(body)
+        # 协议定案 §四.1: QFunction 大帧 body 编码 = zlib → decode6 双层
+        variants = (raw, decode6(raw))
+    except Exception:  # noqa: BLE001 — 坏帧不得中断任务
+        return None
+    for v in variants:
+        if _SIGNAL_TASK.encode() in v:
+            return "任务链推进至[主线]新手指引五"
+        if _SIGNAL_ITEM in v:
+            return "五行珠入包(Index=50675)"
+    return None
+
+
+def _wait_success(ctx: PipelineContext, timeout: float) -> str | None:
+    """收帧窗口扫成功信号: SC-1 大帧文本 or 新 SC643 面板已消费。"""
+    from pbot.bot.runner import pump
+
+    deadline = time.time() + timeout
+    while time.time() < deadline:
+        frames = pump(ctx.conn, ctx.world, ctx.updater, 0.5)
+        for f in frames:
+            if f.msg_id == -1:
+                if sig := _scan_batch_text(f.body):
+                    return sig
+            elif f.msg_id == 643:
+                # 独立 SC643 且 sMsg 不再含 yd_wxjh = 面板已被激活消费
+                dlg = ctx.world.npc_dialog
+                smsg = str(dlg.get("sMsg", "")) if dlg else ""
+                if "yd_wxjh" not in smsg:
+                    return "五行面板已消费(SC643 无 yd_wxjh)"
+    return None
+
+
+def run_task(ctx: PipelineContext) -> TaskResult:
+    """CS3031→传送盟重→点NPC10033→激活→扫双成功信号(超时重发一次)。"""
+    # 1) 任务面板体 + 前往(服务端自动传送盟重土城)
+    body = (_ASSETS / "cs3031_full.bin").read_bytes()
+    _ = ctx.conn.send_frame(3031, 0, 0, 0, ctx.sid, body)
+    time.sleep(1.2)  # 黄金时序: 3031→1011 间隔≥1.2s
+    ctx.pump(0.5)
+    _cs1011(ctx, "@renwu_qwyd,33")
+    # 2) 等传送落地盟重(20s 未落地即失败, 不尝试跑图)
+    deadline = time.time() + _TELEPORT_WAIT_S
+    while time.time() < deadline:
+        ctx.pump(0.5)
+        if "土城" in ctx.world.me.map_name:
+            break
+    if "土城" not in ctx.world.me.map_name:
+        return TaskResult.fail(f"未落地盟重(当前 {ctx.world.me.map_name})")
+    ctx.checklist.note("五行: 已落地盟重, 点击 NPC 10031")
+    # 3) 点 NPC → 等面板(yd_wxjh); 显式时间戳 5s 无面板重点一次
+    _click_npc(ctx)
+    last_click = time.time()
+    deadline = last_click + _PANEL_WAIT_S
+    seen_panel = False
+    while time.time() < deadline:
+        ctx.pump(0.5)
+        dlg = ctx.world.npc_dialog
+        if dlg and "yd_wxjh" in str(dlg.get("sMsg", "")):
+            seen_panel = True
+            break
+        if time.time() - last_click >= _PANEL_RECLICK_S:
+            _click_npc(ctx)
+            last_click = time.time()
+    if not seen_panel:
+        return TaskResult.fail("五行面板未出现(SC643 无 yd_wxjh)")
+    # 4) 激活 → 等成功信号; 15s 无信号重发一次再等 10s
+    time.sleep(0.5)
+    _cs1011(ctx, "@func17_wxqh,1")
+    if sig := _wait_success(ctx, _SUCCESS_WAIT_S):
+        return TaskResult.done(sig)
+    ctx.log.warning("[T7] %.0fs 无成功信号 — 重发 @func17_wxqh,1 一次",
+                    _SUCCESS_WAIT_S)
+    _cs1011(ctx, "@func17_wxqh,1")
+    if sig := _wait_success(ctx, _RESEND_WAIT_S):
+        return TaskResult.done(f"重发后: {sig}")
+    return TaskResult.fail("激活后 25s 未捕获成功信号(任务链/入包/面板消费均无)")

+ 113 - 0
pipeline_bot/pipeline/tasks/t08_woma_farm.py

@@ -0,0 +1,113 @@
+"""T8 — 传送员→沃玛寺庙挂机: 时长=ctx.woma_minutes(默认30min), 无杀数上限。"""
+from __future__ import annotations
+
+import time
+from typing import TYPE_CHECKING
+
+from pipeline.ctx import PipelineContext, TaskResult
+
+if TYPE_CHECKING:
+    from collections.abc import Callable
+
+TASK_TITLE = "T8 沃玛寺庙挂机"
+
+WOMA_MAP = "沃玛寺庙一层"
+_MAX_ROUNDS = 6
+
+
+def _make_farthest(walls: set[tuple[int, int]]
+                   ) -> Callable[[tuple[int, int]], tuple[tuple[int, int], int, bool]]:
+    """d022 BFS 最远点闭包工厂(高危图怪窝撤离用) — woma_kill50 同款。
+
+    walls 必须先经 load_walls_from_map 填充再创建本闭包(ruling: 先墙后函数)。
+    """
+    from collections import deque
+
+    from run_juece13_wugong_autofight import DIR_VECS
+
+    def _farthest(start: tuple[int, int]) -> tuple[tuple[int, int], int, bool]:
+        visited = {start}
+        dq = deque([(start, 0)])
+        far, far_d = start, 0
+        while dq:
+            (x, y), d = dq.popleft()
+            if d > far_d:
+                far, far_d = (x, y), d
+            for dx, dy in DIR_VECS:
+                nxt = (x + dx, y + dy)
+                if nxt in visited or not (0 <= nxt[0] < 250
+                                          and 0 <= nxt[1] < 250):
+                    continue
+                if nxt in walls:
+                    continue
+                visited.add(nxt)
+                dq.append((nxt, d + 1))
+        return far, far_d, len(visited) < 50
+
+    return _farthest
+
+
+def run_task(ctx: PipelineContext) -> TaskResult:
+    """传送沃玛寺庙一层并挂机到时长耗尽; 仅传送轮次耗尽才失败。"""
+    import sys
+    from pathlib import Path
+
+    root = Path(__file__).resolve().parents[2]
+    scripts = str(root / "scripts")
+    if scripts not in sys.path:
+        sys.path.insert(0, scripts)
+    import run_juece13_wugong_farthest as cave_mod
+    from run_juece13_wugong_autofight import AutoFightBehavior
+    from run_juece13_wugong_farthest import CaveEnterBehavior
+
+    from pbot.bot.behavior import TickResult
+    from pbot.bot.pathfind import load_walls_from_map
+    from pbot.bot.runner import pump, run
+
+    # 沃玛参数覆写(CaveEnterBehavior 运行时读模块全局;
+    # cap_woma_client 定案: ACK body=协议别名, 第二步 @act_move,3)
+    cave_mod.CAVE_MAP = WOMA_MAP
+    cave_mod._ACK_BODY = "沃玛一层"  # noqa: SLF001 — 协议别名非显示名
+    cave_mod._TELEPORT_ACTS = ("@jiuxian_move_1,1,3", "@act_move,3")  # noqa: SLF001
+
+    walls = load_walls_from_map(str(root / "assets/maps/d022.map"))
+    farthest = _make_farthest(walls)
+    deadline = time.time() + ctx.woma_minutes * 60.0
+    total = 0
+    rounds = 0
+    tele_fail = False
+    while time.time() < deadline:
+        rounds += 1
+        if rounds > _MAX_ROUNDS and ctx.world.me.map_name != WOMA_MAP:
+            tele_fail = True  # 轮次耗尽仍不在图内 = 传送不可能
+            break
+        if ctx.world.me.map_name != WOMA_MAP:
+            ctx.log.info("[T8] 第%d轮 进图(当前 %s)", rounds,
+                         ctx.world.me.map_name)
+            bh = CaveEnterBehavior(sid=ctx.sid, kill_target=0)
+            r = run(ctx.conn, ctx.world, ctx.updater, bh, duration_s=300.0)
+            if ctx.world.me.map_name != WOMA_MAP:
+                ctx.log.warning("[T8] 传送失败 result=%s — 3s 重试", r.value)
+                time.sleep(3.0)
+                continue
+            time.sleep(3.0)
+            _ = pump(ctx.conn, ctx.world, ctx.updater, 2.0)
+        remain = deadline - time.time()
+        if remain <= 0:
+            break
+        af = AutoFightBehavior(sid=ctx.sid, map_name=WOMA_MAP, walls=walls,
+                               farthest_fn=farthest, kill_goal=0)
+        r = run(ctx.conn, ctx.world, ctx.updater, af, duration_s=remain)
+        total += af.kills
+        ctx.log.info("[T8] 第%d轮 result=%s 轮杀=%d 累计=%d 剩余%.0fs",
+                     rounds, r.value, af.kills, total,
+                     max(0.0, deadline - time.time()))
+        if r is TickResult.DONE and ctx.world.me.map_name != WOMA_MAP:
+            time.sleep(3.0)  # 死亡回城: 下轮循环重新传送
+            _ = pump(ctx.conn, ctx.world, ctx.updater, 2.0)
+    ctx.checklist.note(f"T8 沃玛挂机结束: 累计 {total} 杀, "
+                       f"时长 {ctx.woma_minutes:.0f}min")
+    if tele_fail:
+        return TaskResult.fail(f"传送沃玛失败(轮次耗尽 {_MAX_ROUNDS}, "
+                               f"当前 {ctx.world.me.map_name}, 累计 {total} 杀)")
+    return TaskResult.done(f"挂机 {ctx.woma_minutes:.0f}min 累计 {total} 杀")