|
@@ -3,23 +3,102 @@
|
|
|
传送链: KillBehavior renwu_mov,1(任务指引, 等待图/异图→比奇);
|
|
传送链: KillBehavior renwu_mov,1(任务指引, 等待图/异图→比奇);
|
|
|
战斗: AutoFightBehavior(客户端 RequestLaunchCommand 复刻 — 贴身出刀
|
|
战斗: AutoFightBehavior(客户端 RequestLaunchCommand 复刻 — 贴身出刀
|
|
|
ATTACK_RADIUS=1, 4刀不中重锁, 10刀拉黑, 低血逃跑)。
|
|
ATTACK_RADIUS=1, 4刀不中重锁, 10刀拉黑, 低血逃跑)。
|
|
|
-比奇是开放主城(非副本), 死亡回城后仍在比奇 → 轮次循环续杀。
|
|
|
|
|
|
|
+无怪刷怪: 视野 15s 无可打怪 → 走向【点我刷怪】NPC → CS1010 开对话 →
|
|
|
|
|
+SC643 → CS1011 点确认按钮(协议与 kill.py 刷怪链同源, 2026-09-27 实跑
|
|
|
|
|
+定案; 服务器若回"已有怪物"提示则无副作用, 回视野有怪即继续杀)。
|
|
|
"""
|
|
"""
|
|
|
from __future__ import annotations
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
+import json
|
|
|
|
|
+import re
|
|
|
import time
|
|
import time
|
|
|
|
|
|
|
|
from pipeline.ctx import PipelineContext, TaskResult
|
|
from pipeline.ctx import PipelineContext, TaskResult
|
|
|
|
|
|
|
|
|
|
+from pbot.world.actor import Npc
|
|
|
|
|
+
|
|
|
TASK_TITLE = "T2 比奇大城杀怪20"
|
|
TASK_TITLE = "T2 比奇大城杀怪20"
|
|
|
|
|
|
|
|
KILL_TARGET = 20
|
|
KILL_TARGET = 20
|
|
|
BIQI_MAP = "比奇大城"
|
|
BIQI_MAP = "比奇大城"
|
|
|
_MAX_ROUNDS = 6
|
|
_MAX_ROUNDS = 6
|
|
|
|
|
|
|
|
|
|
+_LINK_RE: re.Pattern[str] = re.compile(r"link=@([^\|<>]+)")
|
|
|
|
|
+_NO_MON_SPAWN_AFTER_S = 15.0
|
|
|
|
|
+_SPAWN_GAP_S = 8.0
|
|
|
|
|
+_SPAWN_TALK_DIST = 3
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _find_spawn_npc(world) -> "Npc | None":
|
|
|
|
|
+ """视野内名字含'刷怪'的 NPC(KillBehavior._find_spawn_npc 同源)。"""
|
|
|
|
|
+ for npc in world.npcs.values():
|
|
|
|
|
+ if "刷怪" in npc.name:
|
|
|
|
|
+ return npc
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _try_spawn(ctx: PipelineContext, af, last: dict) -> bool:
|
|
|
|
|
+ """无怪时点【点我刷怪】: True=已处理(本轮不必再漫游)。
|
|
|
|
|
+
|
|
|
|
|
+ 链路: 走到 NPC 旁 → CS1010 开对话 → 下一 tick 收到 SC643 后点确认
|
|
|
|
|
+ 按钮。'已经有怪'提示 = 对话无副作用直接返回, 战斗循环自然接管。
|
|
|
|
|
+ """
|
|
|
|
|
+ from pbot.bot.wander import DIRS as WDIRS
|
|
|
|
|
+ from pbot.game.messages import protocol_map_name
|
|
|
|
|
+
|
|
|
|
|
+ world = ctx.world
|
|
|
|
|
+ me = world.me
|
|
|
|
|
+ now = time.time()
|
|
|
|
|
+ dlg = world.npc_dialog
|
|
|
|
|
+ # 阶段2: 对话已开 → 点确认按钮(刷怪本尊链接)
|
|
|
|
|
+ if dlg is not None and not last.get("dlg_clicked") \
|
|
|
|
|
+ and now - last.get("dlg_open_t", 0.0) < 10.0:
|
|
|
|
|
+ smsg = str(dlg.get("sMsg", ""))
|
|
|
|
|
+ act = next((a for a in _LINK_RE.findall(smsg)
|
|
|
|
|
+ if a not in ("exit",)), None)
|
|
|
|
|
+ if act is not None:
|
|
|
|
|
+ body = json.dumps({"UserID": dlg.get("UserID", "999999999"),
|
|
|
|
|
+ "Act": f"@{act}",
|
|
|
|
|
+ "index": dlg.get("index", 35)},
|
|
|
|
|
+ separators=(",", ":")).encode()
|
|
|
|
|
+ _ = ctx.conn.send_frame(1011, 0, 0, 0, ctx.sid, body)
|
|
|
|
|
+ ctx.log.info("[T2·刷怪] 对话点按 @%s", act)
|
|
|
|
|
+ last["dlg_clicked"] = True
|
|
|
|
|
+ world.npc_dialog = None
|
|
|
|
|
+ last["spawn_cd"] = now + _SPAWN_GAP_S
|
|
|
|
|
+ return True
|
|
|
|
|
+ # 阶段1: 找 NPC → 走近 → 开对话
|
|
|
|
|
+ snpc = _find_spawn_npc(world)
|
|
|
|
|
+ if snpc is None:
|
|
|
|
|
+ return False
|
|
|
|
|
+ dist = max(abs(snpc.x - me.x), abs(snpc.y - me.y))
|
|
|
|
|
+ if dist > _SPAWN_TALK_DIST:
|
|
|
|
|
+ if now - last.get("step_t", 0.0) < 0.7:
|
|
|
|
|
+ return True
|
|
|
|
|
+ d = af._dir8(me.x, me.y, snpc.x, snpc.y) # noqa: SLF001
|
|
|
|
|
+ dx, dy = WDIRS[d]
|
|
|
|
|
+ stride = 2 if dist > 4 else 1
|
|
|
|
|
+ mn = protocol_map_name(me.map_id, me.map_name)
|
|
|
|
|
+ _ = ctx.conn.send_frame(3013 if stride == 2 else 3011, 0, d,
|
|
|
|
|
+ (me.y << 16) | me.x, ctx.sid, mn.encode(),
|
|
|
|
|
+ pre=((me.y + dy * stride) << 16)
|
|
|
|
|
+ | (me.x + dx * stride))
|
|
|
|
|
+ last["step_t"] = now
|
|
|
|
|
+ return True
|
|
|
|
|
+ if now >= last.get("spawn_cd", 0.0):
|
|
|
|
|
+ body = json.dumps({"UserID": snpc.aid, "index": snpc.index},
|
|
|
|
|
+ separators=(",", ":")).encode()
|
|
|
|
|
+ _ = ctx.conn.send_frame(1010, 0, 0, 0, ctx.sid, body)
|
|
|
|
|
+ last["dlg_clicked"] = False
|
|
|
|
|
+ last["dlg_open_t"] = now
|
|
|
|
|
+ ctx.log.info("[T2·刷怪] 点NPC «%s» @(%d,%d)", snpc.name,
|
|
|
|
|
+ snpc.x, snpc.y)
|
|
|
|
|
+ last["spawn_cd"] = now + _SPAWN_GAP_S
|
|
|
|
|
+ return True
|
|
|
|
|
+
|
|
|
|
|
|
|
|
def run_task(ctx: PipelineContext) -> TaskResult:
|
|
def run_task(ctx: PipelineContext) -> TaskResult:
|
|
|
- """进比奇(renwu 链) → AutoFight 轮次杀满 20 只增量。"""
|
|
|
|
|
|
|
+ """进比奇(renwu 链) → AutoFight 轮次杀满 20 只; 无怪点刷怪 NPC。"""
|
|
|
import sys
|
|
import sys
|
|
|
from pathlib import Path
|
|
from pathlib import Path
|
|
|
|
|
|
|
@@ -30,7 +109,7 @@ def run_task(ctx: PipelineContext) -> TaskResult:
|
|
|
|
|
|
|
|
from pbot.bot.kill import KillBehavior
|
|
from pbot.bot.kill import KillBehavior
|
|
|
from pbot.bot.pathfind import load_walls_from_map
|
|
from pbot.bot.pathfind import load_walls_from_map
|
|
|
- from pbot.bot.runner import pump, run
|
|
|
|
|
|
|
+ from pbot.bot.runner import pump
|
|
|
from pipeline.tasks.heal_guard import ensure_heal
|
|
from pipeline.tasks.heal_guard import ensure_heal
|
|
|
|
|
|
|
|
root = Path(__file__).resolve().parents[2]
|
|
root = Path(__file__).resolve().parents[2]
|
|
@@ -67,11 +146,33 @@ def run_task(ctx: PipelineContext) -> TaskResult:
|
|
|
need = KILL_TARGET - total
|
|
need = KILL_TARGET - total
|
|
|
af = AutoFightBehavior(sid=ctx.sid, map_name=BIQI_MAP, walls=walls,
|
|
af = AutoFightBehavior(sid=ctx.sid, map_name=BIQI_MAP, walls=walls,
|
|
|
farthest_fn=None, kill_goal=need)
|
|
farthest_fn=None, kill_goal=need)
|
|
|
- r = run(ctx.conn, ctx.world, ctx.updater, af,
|
|
|
|
|
- duration_s=max(600.0, need * 60.0))
|
|
|
|
|
|
|
+ # 无怪刷怪监督循环: 替代 run() 的裸驱动, 在 AutoFight 决策外围
|
|
|
|
|
+ # 注入"视野无怪 15s → 点刷怪 NPC"逻辑(有怪时 AutoFight 自己接管)
|
|
|
|
|
+ af.on_enter(ctx.conn, ctx.world)
|
|
|
|
|
+ last: dict[str, float | bool] = {"no_mon_t": 0.0, "spawn_cd": 0.0}
|
|
|
|
|
+ deadline = time.time() + max(600.0, need * 60.0)
|
|
|
|
|
+ while time.time() < deadline:
|
|
|
|
|
+ _ = pump(ctx.conn, ctx.world, ctx.updater, 0.05)
|
|
|
|
|
+ _ = af.tick(ctx.conn, ctx.world)
|
|
|
|
|
+ monsters = [m for m in ctx.world.monsters.values()
|
|
|
|
|
+ if not m.is_dead
|
|
|
|
|
+ and int(m.key) < 1_000_000]
|
|
|
|
|
+ if monsters or af._target_key is not None: # noqa: SLF001
|
|
|
|
|
+ last["no_mon_t"] = 0.0
|
|
|
|
|
+ ctx.world.npc_dialog = None
|
|
|
|
|
+ continue
|
|
|
|
|
+ now = time.time()
|
|
|
|
|
+ if not last["no_mon_t"]:
|
|
|
|
|
+ last["no_mon_t"] = now
|
|
|
|
|
+ last["dlg_clicked"] = True # 怪清零后允许重新开对话
|
|
|
|
|
+ elif now - float(last["no_mon_t"]) > _NO_MON_SPAWN_AFTER_S:
|
|
|
|
|
+ _ = _try_spawn(ctx, af, last)
|
|
|
|
|
+ if af._done: # noqa: SLF001
|
|
|
|
|
+ break
|
|
|
total += af.kills
|
|
total += af.kills
|
|
|
|
|
+ r = "done" if af._done else "timeout" # noqa: SLF001
|
|
|
ctx.log.info("[T2] 第%d轮 result=%s 轮杀=%d 累计=%d/%d", rnd,
|
|
ctx.log.info("[T2] 第%d轮 result=%s 轮杀=%d 累计=%d/%d", rnd,
|
|
|
- r.value, af.kills, total, KILL_TARGET)
|
|
|
|
|
|
|
+ r, af.kills, total, KILL_TARGET)
|
|
|
if total >= KILL_TARGET:
|
|
if total >= KILL_TARGET:
|
|
|
return TaskResult.done(f"比奇击杀 {total}/{KILL_TARGET}")
|
|
return TaskResult.done(f"比奇击杀 {total}/{KILL_TARGET}")
|
|
|
return TaskResult.fail(f"{total}/{KILL_TARGET} 轮次耗尽")
|
|
return TaskResult.fail(f"{total}/{KILL_TARGET} 轮次耗尽")
|