diff --git a/autowsgr/infra/config.py b/autowsgr/infra/config.py index 63a1907d..043a194a 100644 --- a/autowsgr/infra/config.py +++ b/autowsgr/infra/config.py @@ -11,7 +11,13 @@ from pathlib import Path from typing import Any, Literal -from pydantic import BaseModel, Field, ValidationError, field_validator, model_validator +from pydantic import ( + BaseModel, + Field, + ValidationError, + field_validator, + model_validator, +) from autowsgr.infra.logger import get_logger from autowsgr.types import ( @@ -481,8 +487,11 @@ def _coerce_dock_full_mode(cls, v: object) -> object: return v # 解装设置 - destroy_ship_work_mode: DestroyShipWorkMode = DestroyShipWorkMode.disable - """解装工作模式""" + destroy_ship_work_mode: DestroyShipWorkMode = Field(default=DestroyShipWorkMode.all) + """解装范围: all=拆解全部 / destroy_only=拆解舰种 / keep_only=保留舰种。 + + 值别名兼容, 见 :meth:`_coerce_destroy_mode`。 + """ destroy_ship_types: list[ShipType] = Field(default_factory=list) """指定舰种列表""" remove_equipment_mode: bool = True @@ -491,14 +500,20 @@ def _coerce_dock_full_mode(cls, v: object) -> object: @field_validator('destroy_ship_work_mode', mode='before') @classmethod def _coerce_destroy_mode(cls, v: object) -> object: - """允许用中文别名或英文成员名指定解装模式。""" + """允许用中文别名、英文成员名或旧别名指定解装范围。""" _alias: dict[str, int] = { - '不启用': 0, - 'disable': 0, - '黑名单': 1, - 'include': 1, - '白名单': 2, - 'exclude': 2, + '拆解全部': 0, + '不启用': 0, # 旧值 + 'all': 0, + 'disable': 0, # 旧值 + '拆解舰种': 1, + '黑名单': 1, # 旧值 + 'destroy_only': 1, + 'include': 1, # 旧值 + '保留舰种': 2, + '白名单': 2, # 旧值 + 'keep_only': 2, + 'exclude': 2, # 旧值 } if isinstance(v, str): key = v.strip() diff --git a/autowsgr/ops/decisive/chapter.py b/autowsgr/ops/decisive/chapter.py index 839d3d18..469b0be0 100644 --- a/autowsgr/ops/decisive/chapter.py +++ b/autowsgr/ops/decisive/chapter.py @@ -38,7 +38,7 @@ def _do_dock_full_destroy(self) -> None: _log.warning('[决战] 船坞已满,执行自动解装') self._ctrl.click(0.38, 0.565) if not destroy_ships_auto(self._ctx): - # 白名单覆盖全部舰种, 无可解装对象, 船坞仍满 + # 保留舰种覆盖全部舰种, 无可解装对象, 船坞仍满 raise DockFullError('决战中船坞已满,且无可解装舰种') return raise DockFullError('决战中检测到船坞已满,且未开启 full_destroy') diff --git a/autowsgr/ops/destroy.py b/autowsgr/ops/destroy.py index 752a474e..8d016bca 100644 --- a/autowsgr/ops/destroy.py +++ b/autowsgr/ops/destroy.py @@ -67,10 +67,10 @@ def destroy_ships_auto(ctx: GameContext, *, from_dialog: bool = False) -> bool: 是否调用本函数由调用方的 ``dock_full_destroy`` / ``full_destroy`` 开关决定; 此处只关心「怎么拆」:: - - ``destroy_ship_work_mode == disable``: 不启用舰种分类, 走快速拆解路线 + - ``destroy_ship_work_mode == all``: 拆解全部, 不启用舰种分类, 走快速拆解路线 (``ship_types=None`` → 不打开过滤器, 快速全选解装全部)。 - - ``include`` (黑名单): 解装 ``destroy_ship_types`` 指定舰种。 - - ``exclude`` (白名单): 解装除 ``destroy_ship_types`` 外的所有舰种。 + - ``destroy_only`` (拆解舰种): 解装 ``destroy_ship_types`` 指定舰种。 + - ``keep_only`` (保留舰种): 解装除 ``destroy_ship_types`` 外的所有舰种。 ``remove_equipment`` 取自 ``remove_equipment_mode``。 @@ -86,18 +86,18 @@ def destroy_ships_auto(ctx: GameContext, *, from_dialog: bool = False) -> bool: Returns ------- bool - ``True`` 已执行解装; ``False`` 仅在白名单覆盖全部舰种、无可解装对象时返回 + ``True`` 已执行解装; ``False`` 仅在保留舰种覆盖全部舰种、无可解装对象时返回 (此时船坞仍满, 调用方据此保持 DOCK_FULL)。 """ cfg = ctx.config mode = cfg.destroy_ship_work_mode - if mode == DestroyShipWorkMode.disable: - # 不启用舰种分类: 不过滤, 直接走快速全选拆解路线 + if mode == DestroyShipWorkMode.all: + # 拆解全部: 不过滤, 直接走快速全选拆解路线 ship_types = None - elif mode == DestroyShipWorkMode.include: + elif mode == DestroyShipWorkMode.destroy_only: ship_types = cfg.destroy_ship_types or None - else: # exclude (白名单): 解装除指定舰种外的所有 + else: # keep_only (保留舰种): 解装除指定舰种外的所有 protected = set(cfg.destroy_ship_types) # 防战筛选坐标尚未校准,自动解装保留该舰种,避免误点其他分类。 ship_types = [ @@ -106,7 +106,7 @@ def destroy_ships_auto(ctx: GameContext, *, from_dialog: bool = False) -> bool: if ShipType.AABG not in protected: _log.warning('[OPS] 防战筛选坐标未校准,本轮自动解装保留防战') if not ship_types: - _log.warning('[OPS] 白名单包含全部舰种, 无可解装对象, 跳过') + _log.warning('[OPS] 保留舰种覆盖全部舰种, 无可解装对象, 跳过') return False if from_dialog: diff --git a/autowsgr/types.py b/autowsgr/types.py index 68d3b813..467d3c7b 100644 --- a/autowsgr/types.py +++ b/autowsgr/types.py @@ -375,14 +375,18 @@ def relative_position_in_destroy(self) -> tuple[float, float]: class DestroyShipWorkMode(IntEnum): - """拆解工作模式。""" - - disable = 0 - """不启用舰种分类""" - include = 1 - """只拆指定舰种""" - exclude = 2 - """拆除指定舰种以外的所有舰种""" + """解装范围: ``destroy_ship_types`` 列表的用途。""" + + all = 0 + """拆解全部 (不按舰种分类, 走快速拆解路线)""" + destroy_only = 1 + """拆解舰种 (只拆列表内舰种)""" + keep_only = 2 + """保留舰种 (列表内舰种保留, 其余全拆)""" + + disable = 0 # 旧成员名, 等价 all + include = 1 # 旧成员名, 等价 destroy_only + exclude = 2 # 旧成员名, 等价 keep_only class DockFullAction(IntEnum): diff --git a/docs/architecture/context-and-config.md b/docs/architecture/context-and-config.md index e31430f2..98ac212e 100644 --- a/docs/architecture/context-and-config.md +++ b/docs/architecture/context-and-config.md @@ -95,7 +95,7 @@ UserConfig (顶层) ├── dock_full_destroy: bool # 船坞满自动清空 ├── repair_manually: bool # 手动修理模式 ├── bathroom_count: int # 修理位置总数 -├── destroy_ship_work_mode # 解装模式 (disable/include/exclude) +├── destroy_ship_work_mode # 解装范围 (all/destroy_only/keep_only) ├── destroy_ship_types # 解装舰种列表 └── remove_equipment_mode: bool # 解装前卸装备 ``` diff --git a/docs/architecture/ops.md b/docs/architecture/ops.md index bc38ebc3..a5cfbdbc 100644 --- a/docs/architecture/ops.md +++ b/docs/architecture/ops.md @@ -204,7 +204,7 @@ cook(ctx, ...) # 食堂烹饪 **文件**: `autowsgr/ops/destroy.py` ```python -destroy_ships(ctx, ...) # 解装舰船 (黑名单/白名单模式) +destroy_ships(ctx, ...) # 解装舰船 (拆解全部/拆解舰种/保留舰种范围) ``` ### 补给 diff --git a/docs/features/event-20260930.md b/docs/features/event-20260930.md index f980a2f9..0e04e149 100644 --- a/docs/features/event-20260930.md +++ b/docs/features/event-20260930.md @@ -28,7 +28,7 @@ 本次不伪造 native 类型或将防战混为导战。 防战解装筛选坐标没有实机样本:显式指定防战解装时,在点击前报错; -自动白名单解装额外保留防战并记录警告。未指定分类的全量解装模式沿用原逻辑。 +自动保留舰种解装额外保留防战并记录警告。未指定分类的全量解装模式沿用原逻辑。 ## 验证与调试 diff --git a/examples/usersettings.full.yaml b/examples/usersettings.full.yaml index 84a4a304..6379b4c5 100644 --- a/examples/usersettings.full.yaml +++ b/examples/usersettings.full.yaml @@ -76,10 +76,10 @@ repair_manually: false # 是否手动修理 bathroom_count: 2 # 浴室修理槽位数 (auto_bath_repair 空位调度用) # ── 解装设置 ── -destroy_ship_work_mode: 不启用 # 不启用 / 黑名单 / 白名单 -# 不启用: 解装所有舰船 -# 黑名单: 解装列表中的舰种 -# 白名单: 解装不在列表中的舰种 +destroy_ship_work_mode: 拆解全部 # 拆解全部 / 拆解舰种 / 保留舰种 +# 拆解全部: 解装所有舰船 (不按舰种分类, 走快速拆解) +# 拆解舰种: 只解装列表中的舰种 +# 保留舰种: 解装不在列表中的舰种 destroy_ship_types: [] # 指定舰种列表, 参照 ShipType 枚举使用中文 # - 航母 # - 潜艇 diff --git a/testing/infra/test_config.py b/testing/infra/test_config.py index 456eab58..03bb2a26 100644 --- a/testing/infra/test_config.py +++ b/testing/infra/test_config.py @@ -231,9 +231,46 @@ def test_destroy_ship_config(self, tmp_yaml: Callable[[str, str], Path]): """ path = tmp_yaml('destroy.yaml', content) cfg = UserConfig.from_yaml(path) - assert cfg.destroy_ship_work_mode == DestroyShipWorkMode.include + assert cfg.destroy_ship_work_mode == DestroyShipWorkMode.destroy_only assert len(cfg.destroy_ship_types) == 2 + @pytest.mark.parametrize( + ('value', 'expected'), + [ + ('拆解全部', DestroyShipWorkMode.all), + ('拆解舰种', DestroyShipWorkMode.destroy_only), + ('保留舰种', DestroyShipWorkMode.keep_only), + ], + ) + def test_destroy_ship_chinese_alias( + self, + tmp_yaml: Callable[[str, str], Path], + value: str, + expected: DestroyShipWorkMode, + ): + """中文别名 拆解全部 / 拆解舰种 / 保留舰种 均映射到对应枚举。""" + content = f"""\ +emulator: + type: "雷电" + serial: "emulator-5554" + path: "C:/fake/dnplayer.exe" +destroy_ship_work_mode: {value} +""" + cfg = UserConfig.from_yaml(tmp_yaml('destroy_alias.yaml', content)) + assert cfg.destroy_ship_work_mode == expected + + def test_destroy_ship_legacy_value_alias(self, tmp_yaml: Callable[[str, str], Path]): + """旧值别名 (不启用/黑名单/白名单、disable/include/exclude) 仍可用。""" + content = """\ +emulator: + type: "雷电" + serial: "emulator-5554" + path: "C:/fake/dnplayer.exe" +destroy_ship_work_mode: 白名单 +""" + cfg = UserConfig.from_yaml(tmp_yaml('destroy_legacy.yaml', content)) + assert cfg.destroy_ship_work_mode == DestroyShipWorkMode.keep_only + # ── FightConfig ── diff --git a/testing/ops/test_destroy_unit.py b/testing/ops/test_destroy_unit.py index a9210b78..ac582ab2 100644 --- a/testing/ops/test_destroy_unit.py +++ b/testing/ops/test_destroy_unit.py @@ -1,6 +1,6 @@ -"""destroy_ships_auto 模式调度单元测试 (无设备)。 +"""destroy_ships_auto 范围调度单元测试 (无设备)。 -验证 disable / include / exclude 三种工作模式 + remove_equipment 的派发逻辑, +验证 all / destroy_only / keep_only 三种解装范围 + remove_equipment 的派发逻辑, 以及 ``from_dialog`` 弹窗直达路线 (点弹窗「解装」直达建造页后复用 destroy_ships)。通过 monkeypatch 拦截导航 / UI 层, 不触发真实 IO。 """ @@ -51,39 +51,39 @@ def _fake_destroy_ships( return calls -def test_disable_uses_quick_route_no_filter(recorded: list[dict]): - """disable (不启用舰种分类): 不过滤, 走快速拆解路线, 解装全部。""" +def test_all_uses_quick_route_no_filter(recorded: list[dict]): + """all (拆解全部): 不过滤, 走快速拆解路线, 解装全部。""" from autowsgr.ops.destroy import destroy_ships_auto - ctx = _FakeCtx(_FakeConfig(DestroyShipWorkMode.disable)) + ctx = _FakeCtx(_FakeConfig(DestroyShipWorkMode.all)) assert destroy_ships_auto(ctx) is True assert recorded == [{'ship_types': None, 'remove_equipment': True}] -def test_include_passes_listed_types(recorded: list[dict]): +def test_destroy_only_passes_listed_types(recorded: list[dict]): from autowsgr.ops.destroy import destroy_ships_auto types = [ShipType.DD, ShipType.CL] - ctx = _FakeCtx(_FakeConfig(DestroyShipWorkMode.include, types=types, remove_eq=False)) + ctx = _FakeCtx(_FakeConfig(DestroyShipWorkMode.destroy_only, types=types, remove_eq=False)) assert destroy_ships_auto(ctx) is True assert recorded == [{'ship_types': types, 'remove_equipment': False}] -def test_include_empty_types_means_all(recorded: list[dict]): - """include + 空舰种列表 → ship_types=None (不过滤, 全量解装)。""" +def test_destroy_only_empty_types_means_all(recorded: list[dict]): + """destroy_only + 空舰种列表 → ship_types=None (不过滤, 全量解装)。""" from autowsgr.ops.destroy import destroy_ships_auto - ctx = _FakeCtx(_FakeConfig(DestroyShipWorkMode.include)) + ctx = _FakeCtx(_FakeConfig(DestroyShipWorkMode.destroy_only)) assert destroy_ships_auto(ctx) is True assert recorded == [{'ship_types': None, 'remove_equipment': True}] -def test_exclude_computes_complement(recorded: list[dict]): - """exclude (白名单): 解装除指定舰种外的所有非 Other 舰种。""" +def test_keep_only_computes_complement(recorded: list[dict]): + """keep_only (保留舰种): 解装除指定舰种外的所有非 Other 舰种。""" from autowsgr.ops.destroy import destroy_ships_auto protected = [ShipType.CV] - ctx = _FakeCtx(_FakeConfig(DestroyShipWorkMode.exclude, types=protected)) + ctx = _FakeCtx(_FakeConfig(DestroyShipWorkMode.keep_only, types=protected)) assert destroy_ships_auto(ctx) is True call = recorded[0] @@ -96,12 +96,12 @@ def test_exclude_computes_complement(recorded: list[dict]): assert call['remove_equipment'] is True -def test_exclude_all_types_returns_false(recorded: list[dict]): - """白名单覆盖全部非 Other 舰种 → 无可解装对象 → 返回 False。""" +def test_keep_only_all_types_returns_false(recorded: list[dict]): + """保留舰种覆盖全部非 Other 舰种 → 无可解装对象 → 返回 False。""" from autowsgr.ops.destroy import destroy_ships_auto all_real = [t for t in ShipType if t is not ShipType.Other] - ctx = _FakeCtx(_FakeConfig(DestroyShipWorkMode.exclude, types=all_real)) + ctx = _FakeCtx(_FakeConfig(DestroyShipWorkMode.keep_only, types=all_real)) assert destroy_ships_auto(ctx) is False assert recorded == [] @@ -136,7 +136,7 @@ def test_from_dialog_clicks_dialog_then_reuses_destroy_ships( ) types = [ShipType.DD, ShipType.CL] - ctx = _FakeCtx(_FakeConfig(DestroyShipWorkMode.include, types=types, remove_eq=False)) + ctx = _FakeCtx(_FakeConfig(DestroyShipWorkMode.destroy_only, types=types, remove_eq=False)) assert destroy_ships_auto(ctx, from_dialog=True) is True # 先点弹窗「解装」直达建造页, 再复用 destroy_ships (其 goto_page 幂等直达) assert calls == [ @@ -160,12 +160,12 @@ def test_default_skips_dialog_entry(self, monkeypatch: pytest.MonkeyPatch): lambda *_a, **_k: calls.append(('destroy',)), ) - ctx = _FakeCtx(_FakeConfig(DestroyShipWorkMode.disable)) + ctx = _FakeCtx(_FakeConfig(DestroyShipWorkMode.all)) assert destroy_ships_auto(ctx) is True assert calls == [('destroy',)] def test_from_dialog_exhausted_whitelist_skips_all(self, monkeypatch: pytest.MonkeyPatch): - """白名单覆盖全部舰种 → 弹窗不点、解装不执行。""" + """保留舰种覆盖全部舰种 → 弹窗不点、解装不执行。""" from autowsgr.ops.destroy import destroy_ships_auto calls: list[tuple] = [] @@ -181,6 +181,6 @@ def test_from_dialog_exhausted_whitelist_skips_all(self, monkeypatch: pytest.Mon ) all_real = [t for t in ShipType if t is not ShipType.Other] - ctx = _FakeCtx(_FakeConfig(DestroyShipWorkMode.exclude, types=all_real)) + ctx = _FakeCtx(_FakeConfig(DestroyShipWorkMode.keep_only, types=all_real)) assert destroy_ships_auto(ctx, from_dialog=True) is False assert calls == [] diff --git a/testing/ops/test_normal_fight_unit.py b/testing/ops/test_normal_fight_unit.py index 2de6b0dc..7c0e996d 100644 --- a/testing/ops/test_normal_fight_unit.py +++ b/testing/ops/test_normal_fight_unit.py @@ -490,7 +490,7 @@ def test_destroy_success_marks_destroyed_keeps_flag(self, monkeypatch: pytest.Mo assert result.dock_full_destroyed is True def test_destroy_exhausted_whitelist_keeps_flag(self, monkeypatch: pytest.MonkeyPatch): - """白名单覆盖全部舰种 → 无可解装对象 → 保持 DOCK_FULL。""" + """保留舰种覆盖全部舰种 → 无可解装对象 → 保持 DOCK_FULL。""" import autowsgr.ops.destroy as destroy_module monkeypatch.setattr(destroy_module, 'destroy_ships_auto', lambda _ctx, **_k: False) diff --git a/usersettings.yaml b/usersettings.yaml index f36a895c..0fe75a48 100644 --- a/usersettings.yaml +++ b/usersettings.yaml @@ -76,10 +76,10 @@ repair_manually: false # 是否手动修理 bathroom_count: 2 # 浴室修理槽位数 (auto_bath_repair 空位调度用) # ── 解装设置 ── -destroy_ship_work_mode: 不启用 # 不启用 / 黑名单 / 白名单 -# 不启用: 解装所有舰船 -# 黑名单: 解装列表中的舰种 -# 白名单: 解装不在列表中的舰种 +destroy_ship_work_mode: 拆解全部 # 拆解全部 / 拆解舰种 / 保留舰种 +# 拆解全部: 解装所有舰船 (不按舰种分类, 走快速拆解) +# 拆解舰种: 只解装列表中的舰种 +# 保留舰种: 解装不在列表中的舰种 destroy_ship_types: [] # 指定舰种列表, 参照 ShipType 枚举使用中文 # - 航母 # - 潜艇