Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions autowsgr/combat/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ class Coords:
CLICK_RESULT = (0.953, 0.954)
"""点击战果页面继续。"""

# ── 驻防 ──
GARRISON_CANCEL = (0.624, 0.565)
"""驻防取消按钮 (与强化/解装确认弹窗取消钮 ≈(0.62, 0.57) 同布局)。"""

# ── 加速点击 ──
SPEED_UP_NORMAL = (0.260, 0.963)
"""常规战移动加速点击。"""
Expand Down Expand Up @@ -177,6 +181,11 @@ def click_result(device: AndroidController) -> None:
device.click(*Coords.CLICK_RESULT)


def click_garrison_cancel(device: AndroidController) -> None:
"""点击驻防取消按钮 (回港)。"""
device.click(*Coords.GARRISON_CANCEL)


def click_speed_up(device: AndroidController, *, battle_mode: bool = False) -> None:
"""点击加速(移动中或战役中)。

Expand Down
27 changes: 24 additions & 3 deletions autowsgr/combat/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
click_enter_fight,
click_fight_condition,
click_formation,
click_garrison_cancel,
click_image,
click_night_battle,
click_proceed,
Expand Down Expand Up @@ -63,6 +64,7 @@
CombatPhase.RESULT: '_handle_result',
CombatPhase.EXP_SETTLEMENT: '_handle_exp_settlement',
CombatPhase.GET_SHIP: '_handle_get_ship',
CombatPhase.GARRISON: '_handle_garrison',
CombatPhase.PROCEED: '_handle_proceed',
CombatPhase.FLAGSHIP_SEVERE_DAMAGE: '_handle_flagship_severe_damage',
CombatPhase.DOCK_FULL: '_handle_dock_full',
Expand Down Expand Up @@ -513,10 +515,14 @@ def _result_successors(self, phase: CombatPhase) -> list[CombatPhase]:
到达点逐页推进); 快速: 经验页是 pass_through 过渡页, 不在到达集。

RESULT 之后: PROCEED / 终态页 / GET_SHIP / 旗舰大破 (+经验页, 慢速);
EXP_SETTLEMENT 之后: 同上但去掉 EXP_SETTLEMENT 自身;
GET_SHIP 之后: 同上但去掉 GET_SHIP 自身。
驻防 (MAP 模式, 非必现) / EXP_SETTLEMENT 之后: 同上但去掉
EXP_SETTLEMENT 自身; GET_SHIP 之后: 同上但去掉 GET_SHIP 自身。
"""
successors = [CombatPhase.PROCEED, CombatPhase.FLAGSHIP_SEVERE_DAMAGE]
successors = [
CombatPhase.PROCEED,
CombatPhase.FLAGSHIP_SEVERE_DAMAGE,
CombatPhase.GARRISON, # 驻防: 仅 MAP 转移图含此状态
]
end_phase = self._plan.end_phase
if end_phase is not None:
successors.append(end_phase)
Expand Down Expand Up @@ -549,6 +555,21 @@ def _handle_get_ship(self) -> ConditionFlag:
self._click_result_until_closed(CombatPhase.GET_SHIP)
return ConditionFlag.FIGHT_CONTINUE

def _handle_garrison(self) -> ConditionFlag:
"""处理驻防提示 — 点击取消即强制回港,结束战斗。"""
_log.info('[Combat] 检测到驻防,点击取消 (回港)')
click_garrison_cancel(self._device)
self._last_action = 'garrison_cancel'

self._history.add(
CombatEvent(
event_type=EventType.AUTO_RETURN,
node=self._node,
action='驻防回港',
)
)
return ConditionFlag.FIGHT_END

def _handle_proceed(self) -> ConditionFlag:
"""处理继续前进 / 回港决策。

Expand Down
5 changes: 5 additions & 0 deletions autowsgr/combat/recognizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ def _get_event_map_title_templates() -> list[ImageTemplate]:
default_timeout=5.0,
after_match_delay=1.0,
),
CombatPhase.GARRISON: PhaseSignature(
template_key=TemplateKey.GARRISON,
default_timeout=3.0, # 非必现: 等待窗口短
confidence=0.85,
),
CombatPhase.FLAGSHIP_SEVERE_DAMAGE: PhaseSignature(
template_key=TemplateKey.FLAGSHIP_DAMAGE,
default_timeout=7.5,
Expand Down
10 changes: 9 additions & 1 deletion autowsgr/combat/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ class CombatPhase(Enum):
GET_SHIP = auto()
"""获取舰船掉落。"""

# ── 驻防 ──
GARRISON = auto()
"""驻防提示(结算结束出门前,掉落/无掉落均可能出现)。"""

# ── 旗舰大破 ──
FLAGSHIP_SEVERE_DAMAGE = auto()
"""旗舰大破强制回港。"""
Expand Down Expand Up @@ -155,7 +159,8 @@ def _build_map_transitions(
]

# RESULT 之后
after_result = [CombatPhase.PROCEED]
# 驻防 (非必现) 与继续前进是互斥分支: 出现驻防即强制回港, 不再有PROCEED、
after_result = [CombatPhase.GARRISON, CombatPhase.PROCEED]
if ep is not None:
after_result.append(ep)
after_result += [CombatPhase.GET_SHIP, CombatPhase.FLAGSHIP_SEVERE_DAMAGE]
Expand Down Expand Up @@ -225,6 +230,9 @@ def _build_map_transitions(
# GET_SHIP 后继 = 经验结算后继 去掉 GET_SHIP 自身
t[CombatPhase.GET_SHIP] = [p for p in after_result if p != CombatPhase.GET_SHIP]

# 取消驻防 = 强制回港 (与 PROCEED 点"回港"同语义), 后续只可能是结束页。
t[CombatPhase.GARRISON] = [ep] if ep is not None else []

if ep is not None:
t[CombatPhase.FLAGSHIP_SEVERE_DAMAGE] = [ep]

Expand Down
Binary file added autowsgr/data/images/combat/garrison_540p.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions autowsgr/image_resources/combat.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class CombatTemplates:
| RESULT_PAGE | combat/result_page_540p.png |
| GET_SHIP | combat/get_ship_540p.png |
| GET_ITEM | combat/get_item_540p.png |
| GARRISON | combat/garrison_540p.png |
| END_MAP_PAGE | combat/end_map_page_540p.png |
| END_BATTLE_PAGE | combat/end_battle_page_540p.png |
| END_EXERCISE_PAGE | combat/end_exercise_page_540p.png |
Expand All @@ -72,6 +73,7 @@ class CombatTemplates:
RESULT_PAGE = LazyTemplate('combat/result_page_540p.png', 'result_page')
GET_SHIP = LazyTemplate('combat/get_ship_540p.png', 'get_ship')
GET_ITEM = LazyTemplate('combat/get_item_540p.png', 'get_item')
GARRISON = LazyTemplate('combat/garrison_540p.png', 'garrison')

# ── 战斗终止态 ──
END_MAP_PAGE = LazyTemplate('combat/end_map_page_540p.png', 'end_map_page')
Expand Down
2 changes: 2 additions & 0 deletions autowsgr/image_resources/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class TemplateKey(Enum):
GET_SHIP = 'get_ship'
GET_ITEM = 'get_item'
GET_SHIP_OR_ITEM = 'get_ship_or_item'
GARRISON = 'garrison'

# ── 战斗终止态 ──
END_MAP_PAGE = 'end_map_page'
Expand Down Expand Up @@ -99,6 +100,7 @@ def _build_map() -> dict[TemplateKey, list[ImageTemplate]]:
TemplateKey.GET_SHIP: [T.GET_SHIP],
TemplateKey.GET_ITEM: [T.GET_ITEM],
TemplateKey.GET_SHIP_OR_ITEM: [T.GET_SHIP, T.GET_ITEM],
TemplateKey.GARRISON: [T.GARRISON],
# 战斗终止态
TemplateKey.END_MAP_PAGE: [T.END_MAP_PAGE],
TemplateKey.END_BATTLE_PAGE: [T.END_BATTLE_PAGE],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

该代码变更为 TemplateKey 枚举新增了 GARRISON 值,并在 _build_map 中建立了对应映射,逻辑上合理。

但需注意:

  • 请确认 T.GARRISON 已在对应的模板类/模块中正确定义(即 ImageTemplate.GARRISON 存在)。如果缺失,会导致运行期 AttributeError,破坏现有功能。
  • 同时确认 GARRISON 模板资源文件是否已添加,且路径与模板名匹配。

若以上均已满足,则此改动无其他问题。

Expand Down
9 changes: 9 additions & 0 deletions testing/combat/test_combat.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ def test_normal_formation_no_branch(self):
result = resolve_successors(normal, CombatPhase.FORMATION, '')
assert CombatPhase.FIGHT_PERIOD in result

def test_map_after_result_includes_garrison(self):
"""MAP 模式: 驻防 (非必现) 挂在结算共同后继 after_result 上,
掉落/无掉落路径都可能弹出; 取消驻防 = 强制回港, 只到结束页。"""
normal = MODE_TRANSITIONS[CombatMode.NORMAL]
for phase in (CombatPhase.RESULT, CombatPhase.EXP_SETTLEMENT, CombatPhase.GET_SHIP):
result = resolve_successors(normal, phase, '')
assert CombatPhase.GARRISON in result, f'{phase.name} 后继应含 GARRISON'
assert resolve_successors(normal, CombatPhase.GARRISON, '') == [CombatPhase.MAP_PAGE]

def test_battle_transitions(self):
battle = MODE_TRANSITIONS[CombatMode.BATTLE]
# SINGLE 模式无 PROCEED,直接从 START_FIGHT 开始
Expand Down
41 changes: 41 additions & 0 deletions testing/combat/test_result_dismiss_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from autowsgr.combat import handlers as handlers_mod
from autowsgr.combat.handlers import PhaseHandlersMixin
from autowsgr.combat.state import CombatPhase
from autowsgr.types import ConditionFlag


class _Host(PhaseHandlersMixin):
Expand Down Expand Up @@ -120,6 +121,7 @@ def test_event_result_includes_end_phase(self):
CombatPhase.EVENT_MAP_PAGE,
CombatPhase.GET_SHIP,
CombatPhase.EXP_SETTLEMENT,
CombatPhase.GARRISON,
}

def test_fast_result_excludes_exp(self):
Expand All @@ -130,6 +132,7 @@ def test_fast_result_excludes_exp(self):
CombatPhase.FLAGSHIP_SEVERE_DAMAGE,
CombatPhase.MAP_PAGE,
CombatPhase.GET_SHIP,
CombatPhase.GARRISON,
}

def test_campaign_result_no_end_phase(self):
Expand All @@ -140,6 +143,7 @@ def test_campaign_result_no_end_phase(self):
CombatPhase.FLAGSHIP_SEVERE_DAMAGE,
CombatPhase.GET_SHIP,
CombatPhase.EXP_SETTLEMENT,
CombatPhase.GARRISON,
}

def test_exp_settlement_excludes_self(self):
Expand All @@ -158,6 +162,43 @@ def test_get_ship_excludes_self_and_exp(self):
assert CombatPhase.EXP_SETTLEMENT not in successors
assert CombatPhase.MAP_PAGE in successors

def test_map_result_includes_garrison(self):
"""落点集合含驻防 (非必现, 供复检识别弹出后交主循环)。

驻防仅存在于 MAP 转移图, SINGLE 无法进入该状态; 落点集合无需按
模式区分, 统一含 GARRISON。
"""
host, _, _ = _make_host([], end_phase=CombatPhase.MAP_PAGE, collect_result_info=True)
assert CombatPhase.GARRISON in host._result_successors(CombatPhase.RESULT)


class TestHandleGarrison:
"""_handle_garrison — 点击取消即强制回港。"""

def test_returns_fight_end(self):
"""取消驻防 = 回港: 返回 FIGHT_END, 引擎立即结束战斗 (与 PROCEED 回港同语义)。"""
host, _device, _ = _make_host([], end_phase=CombatPhase.MAP_PAGE)
flag = host._handle_garrison()
assert flag == ConditionFlag.FIGHT_END

def test_clicks_garrison_cancel_coordinate(self):
"""点击驻防取消坐标 Coords.GARRISON_CANCEL (与 actions.click_garrison_cancel 一致)。"""
from autowsgr.combat.actions import Coords

host, device, _ = _make_host([], end_phase=CombatPhase.MAP_PAGE)
host._handle_garrison()
device.click.assert_called_once_with(*Coords.GARRISON_CANCEL)

def test_records_auto_return_event(self):
"""记录 AUTO_RETURN 事件 (action='驻防回港')。"""
host, _, _ = _make_host([], end_phase=CombatPhase.MAP_PAGE)
host._handle_garrison()
host._history.add.assert_called_once()
event = host._history.add.call_args.args[0]
assert event.event_type.name == 'AUTO_RETURN'
assert event.action == '驻防回港'
assert event.node == 'A'


class TestHandleResultModes:
"""_handle_result 快速穿行 / 慢速采集双模式。"""
Expand Down
Loading