|
|
@@ -1,14 +1,11 @@
|
|
|
-"""喝药保护开关 — CS3199 UI 动作链(backup/cq2_pure_enter.enable_auto_heal 迁移)。
|
|
|
-
|
|
|
-实测流程:
|
|
|
- CS3199 {"index":109,"act":"@openmianban,23","subid":1} 打开喝药面板
|
|
|
- CS3199 {"index":303,"act":"@chiyao_kaiqi","subid":1} 切换开关(toggle)
|
|
|
-回包 SC3213 藏在 SC-1(zlib) 批量帧子帧里, `id=yd_cy1` 控件的
|
|
|
-`but_1.png`=on / `but_0.png`=off。开关是 toggle: 先读状态, 不符再切。
|
|
|
-
|
|
|
-状态读取双通道(2026-09-28 修): 心跳线程 pump 与本函数抢 socket,
|
|
|
-裸 recv 常饿死(历史全轮 None) — 优先从 world.ui_panels(pump 拆包
|
|
|
-归属地)读, 裸收帧作补充。
|
|
|
+"""喝药保护开关 — CS3199 UI 动作。
|
|
|
+
|
|
|
+2026-10-01 定案(hoe_success 抓包 + 三轮探针):
|
|
|
+ CS3199 {"subid":1,"act":"@chiyao_kaiqi","index":303} toggle(翻转)
|
|
|
+ → 服务器 130ms 内推 SC-1 子帧面板: id=yd_cy1 的 nimg=but_1(on)/but_0(off)
|
|
|
+ 真客户端无 openmianban 前置帧(该前置已删 — 曾致 toggle 静默)。
|
|
|
+toggle 非置位: 已 on 则零帧直返; off 才切一次; 读态取到达序号最新的
|
|
|
+面板(init 期旧 off 缓存会掩盖 toggle 后新值, 2026-10-01 误切实证)。
|
|
|
"""
|
|
|
|
|
|
import json
|
|
|
@@ -19,16 +16,12 @@ from typing import Final
|
|
|
|
|
|
from pbot.transport.gateway import GatewayConnection
|
|
|
from pbot.world.mirror import WorldMirror
|
|
|
+from pbot.world.update import WorldUpdater
|
|
|
|
|
|
_LOG = logging.getLogger("pbot.game")
|
|
|
|
|
|
-_OPEN_PANEL_INDEX: Final[int] = 109
|
|
|
_TOGGLE_INDEX: Final[int] = 303
|
|
|
-# run16 定案: 固定 tail=900-903 在登录流程 >60s 时(端口重试/心跳推进)
|
|
|
-# 变成回退号, 服务器静默丢弃全部 3199 → toggle 永不生效 → 恒 off。
|
|
|
-# tail 纪律(README): 全链唯一递增 — 改走主链自动递增(tail=None)。
|
|
|
_STATE_WAIT_S: Final[float] = 2.0
|
|
|
-_PANEL_KEY: Final[int] = 23 # openmianban,23 → ui_panels 键
|
|
|
|
|
|
_BUT_STATE_PAT: Final[re.Pattern[bytes]] = re.compile(rb"id=yd_cy1[^}]*?but_[01]\.png", re.DOTALL)
|
|
|
|
|
|
@@ -41,32 +34,39 @@ def _send_action(conn: GatewayConnection, sid: int, act: str, index: int) -> Non
|
|
|
|
|
|
|
|
|
def _state_from_panels(world: WorldMirror) -> str | None:
|
|
|
- """从 world.ui_panels(SC3213 → pump 拆包归属)读 yd_cy1 状态。"""
|
|
|
- for content in world.ui_panels.values():
|
|
|
+ """读 yd_cy1 状态 — 取到达序号最新的面板。
|
|
|
+
|
|
|
+ 旧缓存会掩盖 toggle 后新值(2026-10-01 T1 三轮误切实证:
|
|
|
+ init 期 off 面板排在 dict 前面恒被读中)。
|
|
|
+ """
|
|
|
+ best_key: int | None = None
|
|
|
+ best_seq = -1
|
|
|
+ for key, content in world.ui_panels.items():
|
|
|
if "yd_cy1" not in content:
|
|
|
continue
|
|
|
- return "on" if "but_1.png" in content else "off"
|
|
|
- return None
|
|
|
+ seq = world.panel_seq.get(key, -1)
|
|
|
+ if seq > best_seq:
|
|
|
+ best_seq, best_key = seq, key
|
|
|
+ if best_key is None:
|
|
|
+ return None
|
|
|
+ content = world.ui_panels[best_key]
|
|
|
+ return "on" if "but_1.png" in content else "off"
|
|
|
|
|
|
|
|
|
-def _read_heal_state(conn: GatewayConnection, world: WorldMirror) -> str | None:
|
|
|
- """收帧窗口内解析喝药开关状态(但_1=on / but_0=off); 未捕获返回 None。"""
|
|
|
- state = _state_from_panels(world)
|
|
|
- if state is not None:
|
|
|
- return state
|
|
|
- import zlib
|
|
|
+def _read_heal_state(conn: GatewayConnection, world: WorldMirror,
|
|
|
+ refresh: bool = False) -> str | None:
|
|
|
+ """读喝药开关状态。
|
|
|
+
|
|
|
+ refresh=True 时先收 0.5s 新帧再读(toggle 后轮询用, 绕开可能滞后的
|
|
|
+ panels 缓存); False 直接读缓存。
|
|
|
+ """
|
|
|
+ if refresh:
|
|
|
+ from pbot.game.session import apply_batch
|
|
|
+ apply_batch(world, _UPDATER, conn.recv_frames(0.5))
|
|
|
+ return _state_from_panels(world)
|
|
|
|
|
|
- for f in conn.recv_frames(_STATE_WAIT_S):
|
|
|
- if f.msg_id == 60000 or f.body[:2] != b"x\x9c":
|
|
|
- continue
|
|
|
- try:
|
|
|
- dec = zlib.decompress(f.body)
|
|
|
- except zlib.error:
|
|
|
- continue
|
|
|
- m = _BUT_STATE_PAT.search(dec)
|
|
|
- if m:
|
|
|
- state = "on" if b"but_1" in m.group(0) else "off"
|
|
|
- return state
|
|
|
+
|
|
|
+_UPDATER = WorldUpdater()
|
|
|
|
|
|
|
|
|
def enable_auto_heal(conn: GatewayConnection, sid: int,
|
|
|
@@ -74,33 +74,35 @@ def enable_auto_heal(conn: GatewayConnection, sid: int,
|
|
|
) -> str | None:
|
|
|
"""开启/关闭喝药保护。返回最终状态 'on'/'off'/None(未捕获)。
|
|
|
|
|
|
- run14 实证: state 读不到时盲 toggle 是赌博(奇偶不定, 曾把 on 切成
|
|
|
- off 后洞穴无喝药死亡)。修正: 每轮 toggle 后必须**读到**状态且符合
|
|
|
- 期望才收工; 读不到就继续读(面板 SC3213 迟到), 3 轮读不到则最后
|
|
|
- toggle 一次并再验证 — 终态以读到的为准, 未读到返回 None。
|
|
|
+ @chiyao_kaiqi 是 toggle(翻转非置位) — 盲切奇偶不定(run14 曾把 on
|
|
|
+ 切 off 致死)。纪律: 最多切一次。
|
|
|
+ 1) 读态(最新面板优先)已符 → 直接返回, 不发任何帧;
|
|
|
+ 2) 不符 → 发一次 toggle → 8s 窗口轮询读态(toggle 后服务器 130ms
|
|
|
+ 即推新面板, 但窗口放大防抖), 读到期望 → 返回;
|
|
|
+ 3) 读到反值(切过头) → 再切一次纠正(此为已知翻转, 非盲切), 仍不达
|
|
|
+ → 返回实读值由调用方决策。
|
|
|
world 非 None 时走 ui_panels 通道(免疫心跳线程抢帧)。
|
|
|
"""
|
|
|
probe = WorldMirror() if world is None else world
|
|
|
want = "on" if want_on else "off"
|
|
|
- _send_action(conn, sid, "@openmianban,23", _OPEN_PANEL_INDEX)
|
|
|
- time.sleep(1.0)
|
|
|
- state: str | None = None
|
|
|
- # run16c 定案: init 序列必发一次 chiyao_kaiqi(toggle), 上一局遗留 on
|
|
|
- # 会被切成 off; 且 toggle 后 SC3213 面板刷新可达 2s — 每 toggle 后
|
|
|
- # 在窗口内轮询重读(0.25s 一次), 读到期望立即收工
|
|
|
- for i in range(3):
|
|
|
- state = _read_heal_state(conn, probe)
|
|
|
- if state == want:
|
|
|
- _LOG.info("喝药开关已为 %s(读态验证)", state)
|
|
|
- return state
|
|
|
+ state = _read_heal_state(conn, probe, refresh=True)
|
|
|
+ if state == want:
|
|
|
+ _LOG.info("喝药开关已为 %s(读态验证, 零帧)", state)
|
|
|
+ return state
|
|
|
+ for attempt in range(2):
|
|
|
_send_action(conn, sid, "@chiyao_kaiqi", _TOGGLE_INDEX)
|
|
|
- deadline = time.time() + (2.0 if i == 0 else 1.5)
|
|
|
+ deadline = time.time() + 8.0
|
|
|
while time.time() < deadline:
|
|
|
time.sleep(0.25)
|
|
|
- after = _read_heal_state(conn, probe)
|
|
|
+ after = _read_heal_state(conn, probe, refresh=True)
|
|
|
if after == want:
|
|
|
- _LOG.info("喝药开关切至 %s(toggle后读态验证)", after)
|
|
|
+ _LOG.info("喝药开关切至 %s(toggle x%d 后读态验证)",
|
|
|
+ after, attempt + 1)
|
|
|
return after
|
|
|
- state = _read_heal_state(conn, probe)
|
|
|
+ state = _read_heal_state(conn, probe, refresh=True)
|
|
|
+ if state is not None and state != want:
|
|
|
+ _LOG.info("toggle x%d 读到反值 %s — 再纠正一次",
|
|
|
+ attempt + 1, state)
|
|
|
+ continue
|
|
|
_LOG.warning("喝药开关终态未验证(最后读到 %s, 期望 %s)", state, want)
|
|
|
return state if state == want else (state or None)
|