From a569f1935db0cae5548cbaf607d85687a267d812 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 21 Sep 2026 14:31:03 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E6=94=BE=E5=BC=83=E5=85=A5?= =?UTF-8?q?=E5=8F=A3=E7=95=8C=E9=9D=A2=E5=91=88=E7=8E=B0=E2=80=94=E2=80=94?= =?UTF-8?q?=E9=80=89=E8=B7=AF=E5=81=9C=E7=82=B9=E6=8C=82=E6=94=BE=E5=BC=83?= =?UTF-8?q?=E6=8C=89=E9=92=AE=EF=BC=88=E5=86=92=E9=99=A9=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E5=BC=80/=E5=8D=95=E5=85=B3=E7=9B=B4=E5=85=A5=E5=8F=A3?= =?UTF-8?q?=E5=85=B3=EF=BC=8C=E6=88=98=E6=96=97=E4=B8=AD=E6=8B=92=E7=BB=9D?= =?UTF-8?q?=E9=98=B2=E5=BE=A1=E5=88=86=E6=94=AF=EF=BC=9B=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E6=8B=8D=E6=9D=BF=E5=8F=B0=E8=B4=A6=20119=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/main.gd | 18 ++++++++++++++++-- .../presentation/views/route_overlay.gd | 19 +++++++++++++++++++ .../smoke/ui_probe.gd | 13 +++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/prototypes/dungeon-sweeper-vertical-slice/presentation/main.gd b/prototypes/dungeon-sweeper-vertical-slice/presentation/main.gd index 5ddceca9..2df53ddd 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/presentation/main.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/presentation/main.gd @@ -137,6 +137,7 @@ func _build_ui() -> void: review_overlay.closed.connect(_on_modal_closed.bind(review_overlay)) node_overlay.continue_pressed.connect(_on_node_continue) route_overlay.route_chosen.connect(_on_route_chosen) + route_overlay.abandon_requested.connect(_on_abandon_requested) # 放弃入口(2026-09-21 用户拍板——台账 119) shop_overlay.buy_requested.connect(_on_shop_buy) shop_overlay.leave_pressed.connect(_on_shop_leave) _end_overlay.review_pressed.connect(_on_end_review) @@ -223,6 +224,7 @@ func _start_full_level(level_index: int = 1) -> void: runner.wallet_changed.connect(_on_wallet_changed) _level_active = true _adventure_mode = false + route_overlay.set_abandon_available(false) # 单关测试直入口无冒险可放弃(放弃语义对象 = 整局冒险) _reset_run_scope() log_view.clear() var seed_val := _seed_value() @@ -250,6 +252,7 @@ func _start_adventure() -> void: adventure.adventure_finished.connect(_on_adventure_finished) adventure.level_started.connect(_on_adventure_level_started) _level_active = true + route_overlay.set_abandon_available(true) # 放弃入口挂选路停点(天然非战斗期——2026-09-21 用户拍板) _reset_run_scope() log_view.clear() var seed_val := _seed_value() @@ -311,7 +314,7 @@ func _on_level_finished(payload: Dictionary) -> void: _level_active = false var result: String = String(payload.get("result", "")) settlement = null - route_overlay.visible = false # 终局兜底(选路口非 Boss 节点才有——防御性收层) + _close_modal(route_overlay) # 终局兜底(放弃入口新场景:选路层在开态——走模态栈恢复其遮挡面;未开过时 close 容错) _sync_settlement_visuals() # → 全关 info_panel.set_status_cleared_feedback() # 状态槽全清可见反馈(增益状态×4④) stage.gray_all_enemies() # 死亡兜底:站桩灰化残位(禁瞬消语义的战斗收尾面) @@ -344,7 +347,7 @@ func _on_adventure_finished(summary: Dictionary) -> void: _level_active = false var result: String = String(summary.get("result", "")) settlement = null - route_overlay.visible = false # 终局兜底(选路口非 Boss 节点才有——防御性收层) + _close_modal(route_overlay) # 终局兜底(放弃入口新场景:选路层在开态——走模态栈恢复其遮挡面;未开过时 close 容错) _sync_settlement_visuals() # → 全关 info_panel.set_status_cleared_feedback() # 状态槽全清可见反馈(增益状态×4④) stage.gray_all_enemies() # 死亡兜底:站桩灰化残位(禁瞬消语义的战斗收尾面) @@ -608,6 +611,17 @@ func _on_route_chosen(node_id: int) -> void: push_error("[组合根] 选路被拒(编号 %d 不在候选)" % node_id) +func _on_abandon_requested() -> void: + # 放弃入口(2026-09-21 用户拍板「放弃接口也要有」——推翻 冒险结构.md「MVP 无界面入口」后置条款; + # 入口挂选路停点 = 天然非战斗期,拒绝分支仅防御在案):受理 → 冒险放弃结算(同失败型, + # 已获大奖励不追回)→ adventure_finished 同步到达 → 终局面收层 + if not _adventure_mode or adventure == null: + return + if not adventure.abandon(): + logger.log_ux("ux_reject", {"source": "abandon", "sub_reason": "battle_in_flight"}) + board_zone.toast.show_toast("战斗进行中——无法放弃", "warn") + + # ---- 商店覆盖层与 GP 钱包(GP+商店房实施批——写调用唯一归组合根)---- func _on_shop_buy(product_id: String) -> void: diff --git a/prototypes/dungeon-sweeper-vertical-slice/presentation/views/route_overlay.gd b/prototypes/dungeon-sweeper-vertical-slice/presentation/views/route_overlay.gd index 6e7e5e7f..53e7f852 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/presentation/views/route_overlay.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/presentation/views/route_overlay.gd @@ -5,10 +5,14 @@ # 唯一前进通道 = 点候选按钮 → route_chosen(node_id) → 组合根转 runner.pick_next_node。 # 高内聚边界:只读 runner 成文出口(nodes()/visited_ids()/node_index/next_candidate_ids())做渲染, # 无任何写面;图形为程序化占位质量(占位纪律)。 +# 放弃入口(2026-09-21 用户拍板「放弃接口也要有」——台账 119;推翻 冒险结构.md「MVP 无界面入口」 +# 后置条款):选路停点 = 天然非战斗期(接口契约:战斗中放弃一律拒绝),按钮常驻本层底部; +# 可见性归组合根(冒险模式开、单关测试直入口关——放弃的语义对象是"整局冒险")。 class_name RouteOverlay extends Control signal route_chosen(node_id: int) +signal abandon_requested const COL_ROOM_DONE := UiKit.GOLD_DIM # 已到——暗金 const COL_ROOM_CURRENT := UiKit.GOLD # 当前——奖励金 @@ -21,6 +25,7 @@ const NODE_LABELS := {"battle": "战", "reward": "赏", "rest": "疗", "boss": " var _runner = null var _btn_box: VBoxContainer var _title: Label +var abandon_btn: Button # 放弃入口(可见性归组合根 set_abandon_available——冒险模式开/单关测试直入口关) func _init() -> void: @@ -45,9 +50,23 @@ func _init() -> void: _btn_box = VBoxContainer.new() _btn_box.add_theme_constant_override("separation", 6) box.add_child(_btn_box) + abandon_btn = UiKit.button("🏳 放弃冒险(同失败结算——已获大奖励保留)") + abandon_btn.add_theme_color_override("font_color", UiKit.FLAG_RED) + abandon_btn.pressed.connect(_emit_abandon) + abandon_btn.visible = false + box.add_child(abandon_btn) visible = false +# 放弃入口可见性(组合根在冒险启动/单关直入口启动时切换——语义对象 = 整局冒险) +func set_abandon_available(v: bool) -> void: + abandon_btn.visible = v + + +func _emit_abandon() -> void: + abandon_requested.emit() + + # 打开选路口(cands = 候选节点引用数组;runner 只读用于整图渲染) func open_choices(cands: Array, runner) -> void: _runner = runner diff --git a/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_probe.gd b/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_probe.gd index 991db624..8d81b54f 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_probe.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_probe.gd @@ -13,10 +13,23 @@ func _ready() -> void: var main = load("res://presentation/main.tscn").instantiate() add_child(main) await get_tree().process_frame + # ---- 放弃入口(2026-09-21 用户拍板——台账 119):默认隐藏(单关测试直入口无冒险可放弃)---- + if main.route_overlay.abandon_btn == null or main.route_overlay.abandon_btn.visible: + print("[ui_probe] FAIL — 放弃按钮默认应隐藏(单关测试直入口关)") + get_tree().quit(1) + return main._start_full_level() await get_tree().process_frame await get_tree().process_frame var problems: Array = [] + # ---- 放弃入口·冒险模式可见 + 战斗中按下被拒(真实按钮路径——入口挂选路停点,战斗中拒绝为防御分支)---- + if main.route_overlay.abandon_btn == null or not main.route_overlay.abandon_btn.visible: + problems.append("冒险模式放弃按钮未开放(选路停点入口在案)") + else: + main.route_overlay.abandon_btn.pressed.emit() + await get_tree().process_frame + if main.adventure == null or String(main.adventure.state) != "running": + problems.append("首战中放弃未被拒(战斗域单事务在飞——state 应保持 running)") # ---- 既有断言(回归基线)---- if main.board_view._cells.is_empty(): problems.append("棋盘格未生成(cells=0)")