T3 呈现层结构段: main.gd 1395→1237三件迁出(RowPacer排播泵/AuxInteractController两步交互含-9,-9哨兵改显式无目标/GP影子账本独立件)+模态栈收口(settings/camp/tune改走_open_modal+_input_locked改ModalStack.has_open查询+三例外)+HUD单通道(收尾反馈/结算视觉进presenter,play_round_sweep时序敏感留组合根注记)+链式穿透清除(variation回调注入/phase经snapshot)+confirm_dialog构建序复位删move_child创可贴+登记序树序漂移复位;保留面=main._row_queue委托/pace_delay_for/_flush_row_queue/_adventure_gp_view写面全数核对过;后续卡注=main到900还需迁三选一编排与debug接线(06登记)
This commit is contained in:
1 parent
d1460327a4
commit
0691a188fd
10 files changed
+473
-283
No files matched your search
@@ -0,0 +1,141 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# 魔法道具两步交互控制器(迭代 7 T4;T3 拆件批 2026-10-02 自 main.gd 迁出——规范 1.1 单一职责):
|
||||
# C 区槽位选中/取消(toggle)→ 棋盘点格使用目标拾取的两步交互状态机 + 库存/叠加层只读投影装配。
|
||||
# 数据源 = runner 级库存只读面(query_inventory/query_overlays)+ 表行投影(display_name/target/effect_kinds);
|
||||
# 写调用唯一经 战斗结算.use_aux(core 侧四步编排——拒绝零消耗 toast 可辨认)。
|
||||
# 装配纪律:settlement/runner 随关卡逐关更替——经组合根现值回调注入(仿 identity_resolver 只读回调
|
||||
# 先例);呈现句柄(信息面板/toast/棋盘叠加层)由组合根注入;ADR-0001——信号连接不在此发生,
|
||||
# info_panel.aux_slot_clicked 由组合根接至 on_slot_clicked。
|
||||
# 无目标使用显式化(T3 收口):AP 类(target=none)本无坐标可给——原调用面伪造 Vector2i(-9,-9)
|
||||
# 哨兵坐标;现调用面一律 attempt_use(handle)(cell 缺省=null=显式「无目标」),core 契约的
|
||||
# Vector2i 形参收口于本类常量(aux_items.validate_use 对 ap 行忽略目标值,哨兵不出本类)。
|
||||
extends RefCounted
|
||||
|
||||
const NO_TARGET := Vector2i(-9, -9) # core use_aux 形参占位(仅 target=none 行到达——validate_use 忽略该值)
|
||||
|
||||
var db
|
||||
var meta # 解锁门控数据源(工坊解锁×活行交集——冒险开始组装;组合根 _ready 建立后注入)
|
||||
var info_panel: HeroInfoPanelView
|
||||
var toast: ToastStack # 反馈面(board_zone.toast 注入)
|
||||
var board_view: BoardView
|
||||
var _settlement_get: Callable # () → 当前战斗结算现值(组合根 lambda)
|
||||
var _runner_get: Callable # () → 当前关卡推进器现值(组合根 lambda)
|
||||
var _use_selection := 0 # 选中句柄(迭代 7 T4——0=未选中;选中期棋盘点击=使用目标拾取)
|
||||
|
||||
|
||||
func _init(db_ref, meta_ref, info_panel_ref: HeroInfoPanelView, toast_ref: ToastStack,
|
||||
board_view_ref: BoardView, settlement_get: Callable, runner_get: Callable) -> void:
|
||||
db = db_ref
|
||||
meta = meta_ref
|
||||
info_panel = info_panel_ref
|
||||
toast = toast_ref
|
||||
board_view = board_view_ref
|
||||
_settlement_get = settlement_get
|
||||
_runner_get = runner_get
|
||||
|
||||
|
||||
# aux 解锁门控集组装(迭代 7 T5——拍板③甲:工坊解锁 ∩ 活行词表;罗盘退役/失义件不入集)
|
||||
func unlocked_set() -> Dictionary:
|
||||
var out := {}
|
||||
if meta == null:
|
||||
return out
|
||||
for row in db.aux_active_rows():
|
||||
var iid := String(row.get("item_id", ""))
|
||||
if meta.save.item_unlocked(iid):
|
||||
out[iid] = true
|
||||
return out
|
||||
|
||||
|
||||
# 选中态读数(组合根棋盘点击劫持判据——0=未选中)
|
||||
func active_selection() -> int:
|
||||
return _use_selection
|
||||
|
||||
|
||||
# 槽点击(第一步):AP 类(target=none)点击即用(无目标);空间类进/出选中态(toggle 取消)。
|
||||
func on_slot_clicked(slot_handle: int) -> void:
|
||||
var settlement = _settlement_get.call()
|
||||
var runner = _runner_get.call()
|
||||
if settlement == null or runner == null or runner.aux_inventory == null:
|
||||
return
|
||||
if slot_handle == 0:
|
||||
_use_selection = 0
|
||||
refresh_slots()
|
||||
return
|
||||
var rec: Dictionary = {}
|
||||
for r in inventory_projection():
|
||||
if int(r.get("handle", 0)) == slot_handle:
|
||||
rec = r
|
||||
break
|
||||
if rec.is_empty() or bool(rec.get("used", false)):
|
||||
return
|
||||
var arow: Dictionary = db.aux_item_row(String(rec.get("item_id", "")))
|
||||
if String(arow.get("target", "none")) == "none":
|
||||
attempt_use(slot_handle) # AP 类:显式无目标即用(不再伪造哨兵坐标)
|
||||
return
|
||||
if not bool(rec.get("usable", true)):
|
||||
toast.show_toast("当前无可预读的未开格", "warn")
|
||||
return
|
||||
_use_selection = 0 if _use_selection == slot_handle else slot_handle # toggle 取消
|
||||
refresh_slots()
|
||||
if _use_selection != 0:
|
||||
toast.show_toast("%s:点击目标格使用(9 宫窗口),再点道具取消" % String(rec.get("name", "")), "info")
|
||||
|
||||
|
||||
# 使用受理(第二步;cell=空间类目标格,null=无目标;core 侧 use_aux 四步编排——拒绝零消耗 toast 可辨认)
|
||||
func attempt_use(handle: int, cell: Variant = null) -> void:
|
||||
var settlement = _settlement_get.call()
|
||||
if settlement == null:
|
||||
return
|
||||
var target: Vector2i = cell if cell != null else NO_TARGET
|
||||
var v: Dictionary = settlement.use_aux(handle, target)
|
||||
if not bool(v.ok):
|
||||
toast.show_toast(String(v.get("reason", "无法使用")), "warn")
|
||||
return
|
||||
_use_selection = 0
|
||||
refresh_slots()
|
||||
refresh_overlays()
|
||||
var arow: Dictionary = db.aux_item_row(String(v.get("item_id", "")))
|
||||
toast.show_toast("已使用 %s" % String(arow.get("display_name", "")), "info")
|
||||
|
||||
|
||||
# 库存投影(C 区四槽取数面——字形=名首字;usable=板级零信息预判回调接排雷玩法只读面)
|
||||
func inventory_projection() -> Array:
|
||||
var runner = _runner_get.call()
|
||||
if runner == null or runner.aux_inventory == null:
|
||||
return []
|
||||
var settlement = _settlement_get.call()
|
||||
var ms = settlement.minesweeper if settlement != null else null
|
||||
var cb := Callable()
|
||||
if ms != null:
|
||||
cb = func(_c: Vector2i) -> bool:
|
||||
for y in ms.current_board().height:
|
||||
for x in ms.current_board().width:
|
||||
if not ms.is_revealed(Vector2i(x, y)):
|
||||
return true
|
||||
return false
|
||||
var out: Array = []
|
||||
for rec in runner.aux_inventory.query_inventory(cb):
|
||||
var arow: Dictionary = db.aux_item_row(String(rec.get("item_id", "")))
|
||||
var dn := String(arow.get("display_name", ""))
|
||||
out.append({
|
||||
"handle": int(rec.get("handle", 0)),
|
||||
"item_id": String(rec.get("item_id", "")),
|
||||
"glyph": dn.left(1) if dn != "" else "?",
|
||||
"name": dn,
|
||||
"used": bool(rec.get("used", false)),
|
||||
"usable": bool(rec.get("usable", true)),
|
||||
})
|
||||
return out
|
||||
|
||||
|
||||
func refresh_slots() -> void:
|
||||
info_panel.set_aux_inventory(inventory_projection(), _use_selection)
|
||||
|
||||
|
||||
# 叠加层投影注入(use 成功/三清后——棋盘标记与预读徽章重算)
|
||||
func refresh_overlays() -> void:
|
||||
var runner = _runner_get.call()
|
||||
if runner == null or runner.aux_inventory == null or board_view == null:
|
||||
return
|
||||
var ov: Dictionary = runner.aux_inventory.query_overlays()
|
||||
board_view.set_aux_overlays(ov.get("mine_reveal", []), ov.get("reward_preview", []))
|
||||
@@ -0,0 +1 @@
|
||||
uid://cgqtlpkl0ypn0
|
||||
@@ -0,0 +1,31 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# GP 冒险钱包呈现侧影子账本(迭代 6 T7 卡⑩;T3 拆件批 2026-10-02 自 main.gd 迁出——规范 1.1):
|
||||
# 冒险进行中余额影子 + 收束清零拍转存的最终合计,供冒险终局面板 GP 行取数。
|
||||
# 手工镜像边界:wallet_changed gain/buy 载荷 = 入账后总额(level_runner.gd:482/539),"clear" 到达即
|
||||
# 影子转存 final 并归零——纯呈现侧账本,零核心触碰(C1 呈现层方案);写入口唯一 = apply_event
|
||||
#(组合根 _on_wallet_changed 投喂;探针合成注入经 set_view_total——组合根委托面转发)。
|
||||
extends RefCounted
|
||||
|
||||
var _view := 0 # 冒险进行中余额影子(gain/buy 载荷直录)
|
||||
var _final := 0 # 收束清零拍转存的最终合计("clear" 到达时自影子转存)
|
||||
|
||||
|
||||
func apply_event(event: String, gp: int) -> void:
|
||||
match event:
|
||||
"gain", "buy":
|
||||
_view = gp
|
||||
"clear":
|
||||
_final = _view
|
||||
_view = 0
|
||||
|
||||
|
||||
func set_view_total(v: int) -> void:
|
||||
_view = v # 探针合成注入面(ui_probe 影子账清零转存断言)
|
||||
|
||||
|
||||
func view_total() -> int:
|
||||
return _view
|
||||
|
||||
|
||||
func final_total() -> int:
|
||||
return _final
|
||||
@@ -0,0 +1 @@
|
||||
uid://uk77ei6hjhca
|
||||
@@ -83,6 +83,29 @@ static func _refresh_economy(ap_bar: ApBarView, s: Dictionary, db, refresh_remai
|
||||
ap_bar.set_stop_enabled(bool(s.hero.get("alive", false)) and digging)
|
||||
|
||||
|
||||
# ---- 战斗收尾反馈与结算视觉同步(T3 HUD 单通道批 2026-10-02——组合根直改视图收敛入此) ----
|
||||
|
||||
# battle_over 收尾反馈:死亡兜底灰化 → 主角态回 idle → RAP 清零脉冲(调试收尾路)→ 状态全清可见反馈。
|
||||
# 调试/冒险两路共用;reset_rap=false = 冒险收束(RAP 清零由战斗末拍已毕,收束拍不再重放脉冲)。
|
||||
static func battle_end_feedback(stage: BattleStageView, ap_bar: ApBarView, reset_rap: bool) -> void:
|
||||
stage.gray_all_enemies() # 死亡兜底:站桩灰化残位(禁瞬消语义的战斗收尾面)
|
||||
stage.settle_hero_idle() # 主角态回 idle(迭代 6 T3 三态收尾)
|
||||
if reset_rap:
|
||||
ap_bar.set_rap(0) # RAP 清零脉冲 + 微文案(载体 2026-09-26 迁 D 区 AP 条)
|
||||
stage.hero_standee.set_effects_cleared_feedback() # 状态全清可见反馈(增益状态×4④——载体迁 B 区站桩)
|
||||
|
||||
|
||||
# 结算阶段视觉同步(round_end → 下一轮):D 区去饱和 + 禁挖光 + 明细带 settle/dig 换挡 + 轮次文案。
|
||||
# 光带演出(play_round_sweep)属时序敏感触发,留在组合根(上升沿检测随行)——本条只做态驱动 setter。
|
||||
static func sync_settlement_visuals(stage: BattleStageView, board_view: BoardView,
|
||||
log_view: BattleLogView, settling: bool, phase_text: String) -> void:
|
||||
board_view.set_desaturated(settling)
|
||||
board_view.set_dig_glow_enabled(not settling)
|
||||
stage.set_settle_focus(settling)
|
||||
log_view.set_mode("settle" if settling else "dig")
|
||||
stage.set_round_text(phase_text)
|
||||
|
||||
|
||||
# 战斗外(节点间隙/关卡结束)——HP 读 runner 携带值
|
||||
static func _refresh_out_of_battle(stage: BattleStageView, info: HeroInfoPanelView,
|
||||
ap_bar: ApBarView, runner, level_active: bool, db) -> void:
|
||||
|
||||
+119
-277
@@ -18,7 +18,16 @@
|
||||
# 渲染与文案归 views/,核心状态→文案装配归 hud_presenter.gd 与 battle_narrator.gd,节点构建归
|
||||
# ui_assembler.gd,模态层级归 modal_stack.gd,批量模拟编排归 tune_sim_controller.gd,调试模式编排归
|
||||
# debug_session.gd——本类不拼呈现字符串。
|
||||
# ADR-0001 纪律:呈现层永不直接订阅 排雷玩法/行动经济——只订 战斗结算/runner 转发信号;相位读取仅用于驱动视图状态。
|
||||
# T3 拆件批(2026-10-02,规范 1.1 单一职责):结算行排播泵迁 row_pacer.gd(本类只入队/排空/读队列,
|
||||
# 探针/测试委托面 _row_queue/pace_delay_for/_flush_row_queue 原位保留);魔法道具两步交互迁
|
||||
# aux_interact_controller.gd(本类只接线与转发意图,无目标使用显式化——哨兵坐标不出控制器);
|
||||
# GP 冒险钱包影子账迁 gp_shadow_ledger.gd(本类留探针同名读写委托面)。模态开合全量走
|
||||
# _open_modal/_close_modal 单口,输入锁=modal_stack.has_open() 开态查询+三例外;battle 收尾反馈与
|
||||
# 结算视觉 setter 收敛 hud_presenter 单通道(时序敏感演出触发留本类并注释);settlement 相位读取改经
|
||||
# presentation_snapshot 成文面、变异表读取直读本根 db(settlement.db 即同一实例)——禁
|
||||
# settlement.action_economy / settlement.db 链式穿透。
|
||||
# ADR-0001 纪律:呈现层永不直接订阅 排雷玩法/行动经济——只订 战斗结算/runner 转发信号;相位读取仅用于
|
||||
# 驱动视图状态;信号连接只在组合根(row_pacer/aux_interact_controller 零连接,只收注入)。
|
||||
extends Control
|
||||
|
||||
const UiAssembler := preload("res://presentation/ui_assembler.gd")
|
||||
@@ -26,6 +35,9 @@ const Narrator := preload("res://presentation/battle_narrator.gd")
|
||||
const ModalStack := preload("res://presentation/modal_stack.gd")
|
||||
const TuneSimController := preload("res://presentation/tune_sim_controller.gd")
|
||||
const DebugSessionScript := preload("res://presentation/debug_session.gd")
|
||||
const RowPacer := preload("res://presentation/row_pacer.gd") # 结算行排播泵(T3 拆件批)
|
||||
const AuxInteractController := preload("res://presentation/aux_interact_controller.gd") # 魔法道具两步交互(T3 拆件批)
|
||||
const GpShadowLedgerScript := preload("res://presentation/gp_shadow_ledger.gd") # GP 影子账本(T3 拆件批)
|
||||
const SaveManagerScript := preload("res://core/save_manager.gd") # 存档承载(preload 避开全局类缓存时序——与 DebugSessionScript 同款)
|
||||
const MetaProgressionScript := preload("res://core/meta_progression.gd") # 元进度账本服务(元进度实现批——同 preload 纪律)
|
||||
|
||||
@@ -67,16 +79,32 @@ var board_zone: BoardZoneView # D 区(含 ap_bar/board_view/toast/
|
||||
var board_view: BoardView # 探针兼容别名(= board_zone.board_view)
|
||||
var ap_bar: ApBarView # 探针兼容别名(= board_zone.ap_bar)
|
||||
var settlement_fx: SettlementFxView # 结算演出覆盖层(跨区弹道/光带——A–D 之上、模态之下)
|
||||
|
||||
# T3 拆件批(2026-10-02)——三件迁出后的组合根持有面(职责见各类头注;装配见 _build_ui)
|
||||
var _row_pacer = null # 结算行排播泵(row_pacer.gd——本类只入队/排空/读队列)
|
||||
var _aux = null # 魔法道具两步交互(aux_interact_controller.gd——本类只接线与转发意图)
|
||||
var _gp_ledger := GpShadowLedgerScript.new() # GP 冒险钱包影子账(gp_shadow_ledger.gd——wallet 事件投喂)
|
||||
|
||||
# GP 影子账委托面(探针/终局面板取数保留——ui_probe 合成注入经 set 转发账本,读面即账本实况)
|
||||
var _adventure_gp_view: int:
|
||||
get:
|
||||
return _gp_ledger.view_total()
|
||||
set(value):
|
||||
_gp_ledger.set_view_total(value)
|
||||
var _adventure_gp_final: int:
|
||||
get:
|
||||
return _gp_ledger.final_total()
|
||||
|
||||
# 排播队列只读委托面(探针/测试读 main._row_queue——RowPacer 迁移后保留的同名可读面,共享同一 Array 引用)
|
||||
var _row_queue: Array:
|
||||
get:
|
||||
return _row_pacer.pending_rows()
|
||||
|
||||
# 模态 overlay(z 序 = 登记序;pattern 6 层级纪律)
|
||||
var legend_overlay: LegendOverlay
|
||||
var review_overlay: SettlementReviewOverlay
|
||||
var node_overlay: NodeOverlay
|
||||
var shop_overlay: ShopOverlay # 商店覆盖层(GP+商店房实施批——商店房节点兑付呈现)
|
||||
var _adventure_gp_view := 0 # GP 冒险钱包呈现侧影子(迭代 6 T7——终局面板 GP 行取数面,
|
||||
# wallet_changed gain/buy 载荷=入账后总额 level_runner.gd:482/539)
|
||||
var _adventure_gp_final := 0 # 收束清零拍暂存的最终合计("clear" 到达时自影子转存)
|
||||
var _aux_use_selection := 0 # 魔法道具选中句柄(迭代 7 T4——0=未选中;选中期棋盘点击=使用目标拾取)
|
||||
var _aux_unlocked := {} # aux 解锁门控集(迭代 7 T5——工坊解锁×活行交集,冒险开始组装)
|
||||
var camp_overlay: CampOverlay # 营地覆盖层(元进度实现批——主菜单营地入口兑付呈现)
|
||||
var settings_store # 系统级设置独立存储(迭代 8 卡④——音量/全屏/减动效)
|
||||
var settings_overlay: SettingsOverlay # 设置覆盖层(迭代 8 卡④——最小设置面)
|
||||
@@ -89,17 +117,6 @@ var _refresh_used_round := 0 # 本轮已受理刷新次数(遥测 round_r
|
||||
var _boss_active := false # Boss 遭遇变体(阶段指示附 Boss 名)
|
||||
var _was_settling := false # 结算相位上升沿检测(结算轮光带触发——演出链批)
|
||||
|
||||
# ---- 结算行排播队列(迭代 8 卡⑦——演出层排播:逻辑先行零回压,行按段分组阶梯延迟进明细带)----
|
||||
# 硬契约:核心层零 await 不破——本队列只做信号的"后置消费",结算顺序/结果/时序逻辑零改动(§3 契约 1);
|
||||
# 减动效开=延迟归零走同帧直落快路(行呈现不跳过——§3 契约 5);模态开启前强制排空(明细回看/停点
|
||||
# 覆盖层所见明细带恒完整)。
|
||||
var _row_queue: Array = [] # 待排播行(step_row 载荷原样入队;__round_line 标记=轮首分隔行)
|
||||
var _row_pace_wait := 0.0 # 距下一行排播的剩余秒(_process 消耗)
|
||||
var _pace_prev_segment := "" # 上一排播行的段型(段间/段内延迟判据)
|
||||
var _was_pumping := false # 排播活跃边沿(T17——末行消费帧触发结算视觉回挖掘态刷新)
|
||||
const PACE_SEGMENT_GAP := 0.3 # 段间阶梯延迟(代拍板:白名单 0.2–0.4s 内取值,追认清单呈报)
|
||||
const PACE_ROW_GAP := 0.22 # 段内间隔(三轮 #3 上调 0.06→0.22——逐条演出可感知,配合数字浮现/状态脉冲)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
db = get_node("/root/ConfigDB")
|
||||
@@ -141,9 +158,24 @@ func _build_ui() -> void:
|
||||
board_view = battle_ui.board_view # 探针兼容别名(= board_zone.board_view)
|
||||
ap_bar = battle_ui.ap_bar # 探针兼容别名(= board_zone.ap_bar)
|
||||
settlement_fx = battle_ui.settlement_fx
|
||||
|
||||
# 结算行排播泵(T3 拆件批——迁 row_pacer.gd;本类注入视图句柄与呈现刷新回调,零信号连接)
|
||||
_row_pacer = RowPacer.new()
|
||||
_row_pacer.log_view = log_view
|
||||
_row_pacer.stage = stage
|
||||
_row_pacer.board_zone = board_zone
|
||||
_row_pacer.settlement_fx = settlement_fx
|
||||
_row_pacer.board_view = board_view
|
||||
_row_pacer.refresh_request = _refresh_presentations
|
||||
add_child(_row_pacer)
|
||||
# 魔法道具两步交互(T3 拆件批——迁 aux_interact_controller.gd;settlement/runner 随关卡更替,
|
||||
# 经现值回调注入——仿 identity_resolver 只读回调先例)
|
||||
_aux = AuxInteractController.new(db, meta, info_panel, board_zone.toast, board_view,
|
||||
func() -> Variant: return settlement, func() -> Variant: return runner)
|
||||
|
||||
log_view.review_requested.connect(_open_review_from_band)
|
||||
board_zone.action_requested.connect(_on_board_action)
|
||||
info_panel.aux_slot_clicked.connect(_on_aux_slot_clicked) # 迭代 7 T4——两步交互第一步
|
||||
info_panel.aux_slot_clicked.connect(_aux.on_slot_clicked) # 迭代 7 T4——两步交互第一步
|
||||
board_zone.stop_pressed.connect(_on_stop_pressed)
|
||||
board_zone.refresh_confirmed.connect(_on_refresh_confirmed)
|
||||
board_zone.legend_toggled.connect(_on_legend_toggled)
|
||||
@@ -159,7 +191,8 @@ func _build_ui() -> void:
|
||||
logger = PlaytestLogger.new()
|
||||
logger.ap_per_round = int(db.action_economy.ap_per_round)
|
||||
|
||||
# 模态覆盖层(登记序 = 树序 = z 遮挡序:图例 < 明细回看 < 节点 < 选路 < 终局 < 商店 < 调整板 < 开始)
|
||||
# 模态覆盖层(登记序 = 树序 = z 遮挡序:图例 < 明细回看 < 节点 < 关卡画面 < 三选一 < 终局
|
||||
# < 商店 < 营地 < 调整板 < 设置 < 确认弹窗)
|
||||
var overlays: Dictionary = UiAssembler.build_overlays(self, db, int(db.hero.atk), int(db.hero.max_hp))
|
||||
legend_overlay = overlays.legend_overlay
|
||||
review_overlay = overlays.review_overlay
|
||||
@@ -190,10 +223,6 @@ func _build_ui() -> void:
|
||||
settings_overlay = SettingsOverlay.new(settings_store) # 迭代 8 卡④——最小设置面(音量/全屏/减动效)
|
||||
add_child(settings_overlay)
|
||||
settings_overlay.closed.connect(_on_settings_closed)
|
||||
confirm_dialog = ConfirmDialog.new() # 二轮修改批 A——新冒险互斥确认弹窗
|
||||
add_child(confirm_dialog)
|
||||
confirm_dialog.confirmed.connect(_on_confirm_new_adventure)
|
||||
confirm_dialog.cancelled.connect(_on_confirm_cancelled)
|
||||
# 提升三选一覆盖层(迭代 10——z 序 = 节点层之上、终局层之下;确认后同拍推进)
|
||||
bonus_overlay = BonusPickOverlay.new()
|
||||
add_child(bonus_overlay)
|
||||
@@ -201,10 +230,6 @@ func _build_ui() -> void:
|
||||
bonus_overlay.replace_chosen.connect(_on_bonus_replace_chosen)
|
||||
bonus_overlay.continue_pressed.connect(_on_bonus_continue)
|
||||
move_child(bonus_overlay, _end_overlay.get_index())
|
||||
_modals = ModalStack.new()
|
||||
for m: Control in [legend_overlay, review_overlay, node_overlay, bonus_overlay, level_screen, _end_overlay, shop_overlay, camp_overlay, tune_panel, settings_overlay, confirm_dialog]:
|
||||
_register_modal(m)
|
||||
_tune_sim = TuneSimController.new(db, tune_panel, int(db.hero.max_hp))
|
||||
|
||||
var start: Dictionary = UiAssembler.build_start_overlay(self, db)
|
||||
_start_overlay = start.overlay
|
||||
@@ -227,6 +252,20 @@ func _build_ui() -> void:
|
||||
var diag_btn := UiAssembler.build_diag_button(self)
|
||||
diag_btn.pressed.connect(_on_diagnose_pressed)
|
||||
|
||||
# 确认弹窗(二轮修改批 A——新冒险互斥确认弹窗化):树序落在开始层/过场层/诊断钮之后 = 子级最末
|
||||
#(T3 收口批——「登记序 = 树序 = z 序」纪律复位,confirm_dialog.open 的 move_child 运行时自提
|
||||
# 创可贴随之删除;弹窗只在开始菜单域弹出,绘制序恒最上)
|
||||
confirm_dialog = ConfirmDialog.new()
|
||||
add_child(confirm_dialog)
|
||||
confirm_dialog.confirmed.connect(_on_confirm_new_adventure)
|
||||
confirm_dialog.cancelled.connect(_on_confirm_cancelled)
|
||||
|
||||
# 模态登记(缺陷⑤纪律——登记序 = 树序:含 move_child 调位后的关卡画面/三选一终位)
|
||||
_modals = ModalStack.new()
|
||||
for m: Control in [legend_overlay, review_overlay, node_overlay, level_screen, bonus_overlay, _end_overlay, shop_overlay, camp_overlay, tune_panel, settings_overlay, confirm_dialog]:
|
||||
_register_modal(m)
|
||||
_tune_sim = TuneSimController.new(db, tune_panel, int(db.hero.max_hp))
|
||||
|
||||
|
||||
func _fatal_screen(msg: String) -> void:
|
||||
var l := UiKit.label(msg, 20, true)
|
||||
@@ -260,10 +299,9 @@ func _on_modal_closed(m: Control) -> void:
|
||||
|
||||
|
||||
func _input_locked() -> bool:
|
||||
# 模态/非战斗期棋盘输入锁(各 overlay 自身可见即锁定)
|
||||
return legend_overlay.is_open() or review_overlay.is_open() or _end_overlay.is_open() \
|
||||
or node_overlay.visible or level_screen.visible or shop_overlay.visible or tune_panel.visible \
|
||||
or _start_overlay.visible or _inter_overlay.visible or confirm_dialog.visible or settlement == null
|
||||
# 模态/非战斗期棋盘输入锁(T3 收口批——开态查询归 modal_stack.has_open(),登记册单一来源,
|
||||
# 不再手工罗列各层;例外三件:开始层/过场层(非模态全屏态,不在登记册)与战斗缺位(棋盘输入无载体))
|
||||
return _modals.has_open() or _start_overlay.visible or _inter_overlay.visible or settlement == null
|
||||
|
||||
|
||||
# ==================== 冒险模式入口(唯一试玩入口——单关测试直入口退役 2026-09-23 用户拍板)====================
|
||||
@@ -324,7 +362,7 @@ func _resume_from_menu() -> void:
|
||||
log_view.clear()
|
||||
logger.start_session(int(bp.get("master_seed", 0)))
|
||||
adventure.set_hero_override(tune_panel.atk_pct(), tune_panel.hp_pct())
|
||||
if not adventure.resume_adventure(bp, {"hero_override": meta.hero_override() if meta != null else {}, "aux_unlocked": _aux_unlocked_set()}):
|
||||
if not adventure.resume_adventure(bp, {"hero_override": meta.hero_override() if meta != null else {}, "aux_unlocked": _aux.unlocked_set()}):
|
||||
_fatal_screen("续玩恢复失败——见输出日志")
|
||||
return
|
||||
|
||||
@@ -362,7 +400,7 @@ func _start_adventure() -> void:
|
||||
# 永久强化注入(元进度实现批):冒险开始参数面——meta 现算 override 显式传参(全零 = 白板)
|
||||
# 首次冒险脚本(关卡节点图生成 v0.4):存档布尔位 false = 全关固定模板 0(教学重放);置位后逐关抽签
|
||||
var first_script: bool = not save_manager.is_first_adventure_done()
|
||||
if not adventure.start_adventure(seed_val, {"hero_override": meta.hero_override() if meta != null else {}, "first_script": first_script, "aux_unlocked": _aux_unlocked_set()}):
|
||||
if not adventure.start_adventure(seed_val, {"hero_override": meta.hero_override() if meta != null else {}, "first_script": first_script, "aux_unlocked": _aux.unlocked_set()}):
|
||||
_fatal_screen("冒险启动失败——见输出日志")
|
||||
return
|
||||
|
||||
@@ -547,9 +585,7 @@ func _on_adventure_finished(summary: Dictionary) -> void:
|
||||
settlement = null
|
||||
_exit_level_screen() # 终局兜底:独立屏在开态(放弃入口)也先回战斗屏呈现摘要——屏级互斥单一出口
|
||||
_sync_settlement_visuals() # → 全关
|
||||
stage.hero_standee.set_effects_cleared_feedback() # 状态全清可见反馈(增益状态×4④——载体 2026-09-26 迁 B 区站桩)
|
||||
stage.gray_all_enemies() # 死亡兜底:站桩灰化残位(禁瞬消语义的战斗收尾面)
|
||||
stage.settle_hero_idle() # 主角态回 idle(迭代 6 T3 三态收尾)
|
||||
HudPresenter.battle_end_feedback(stage, ap_bar, false) # 灰化/idle 回正/状态全清——HUD 单通道(T3 收口批)
|
||||
# 存档入账(元进度实现批):settle = Boss 两档入账 + 工坊解锁 + 摘要入档 + 写盘(唯一写档点不变——
|
||||
# 调试两层不经过本处理器,天然不写档不污染生涯数据)。
|
||||
var detail: Dictionary[String, Variant] = {}
|
||||
@@ -663,14 +699,19 @@ func _scaled(base: int, pct: int) -> int:
|
||||
return maxi(1, int(round(float(base) * (100.0 + float(pct)) / 100.0)))
|
||||
|
||||
|
||||
# 奖励徽章身份解析(组合根守卫:仅挖掘期 Active 相位内查询——地图战斗资源 Ready 域外零读、零诊断噪音)
|
||||
# 奖励徽章身份解析(组合根守卫:仅挖掘期 Active 相位内查询——地图战斗资源 Ready 域外零读、零诊断噪音;
|
||||
# 相位取数经 presentation_snapshot 成文面——禁 settlement.action_economy 链式穿透,T3 收口批)
|
||||
func _identity_lookup(c: Vector2i):
|
||||
if settlement == null or settlement.minesweeper.current_board() == null or settlement.action_economy.phase != settlement.action_economy.PHASE_ACTIVE:
|
||||
if settlement == null or settlement.minesweeper.current_board() == null:
|
||||
return null
|
||||
if String(settlement.presentation_snapshot().get("phase", "")) != ActionEconomy.PHASE_ACTIVE:
|
||||
return null
|
||||
return settlement.query_reward_identity(c)
|
||||
|
||||
|
||||
# 地形变异 数字遮蔽解析(信息型 hide_number——格带该效果变异即遮蔽;读取方按格子变异数据行动)
|
||||
# 地形变异 数字遮蔽解析(信息型 hide_number——格带该效果变异即遮蔽;读取方按格子变异数据行动。
|
||||
# 变异表直读本根 db——settlement.db 即组合根注入的同一 ConfigDB 实例(AdventureRunner/DebugSession
|
||||
# 构造传参),免 settlement 内部链式穿透,T3 收口批)
|
||||
func _variant_mask_lookup(c: Vector2i) -> bool:
|
||||
if settlement == null:
|
||||
return false
|
||||
@@ -678,14 +719,14 @@ func _variant_mask_lookup(c: Vector2i) -> bool:
|
||||
if b == null:
|
||||
return false
|
||||
for vid in b.variant_ids(c):
|
||||
var vrow: Dictionary = settlement.db.variation.row_by_id(String(vid))
|
||||
var vrow: Dictionary = db.variation.row_by_id(String(vid))
|
||||
if String(vrow.get("effect", "")) == "hide_number":
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
# 地形变异 角标解析(迭代 6 T6——伤害型变异未开格视觉标记;读取方按格子变异数据行动,规则本体零特判。
|
||||
# 策略纹理非数值墙:标记=玩家绕行/承担的决策输入,难度本体仍住数值包络)
|
||||
# 策略纹理非数值墙:标记=玩家绕行/承担的决策输入,难度本体仍住数值包络。变异表直读本根 db——同上款收口)
|
||||
func _variant_mark_lookup(c: Vector2i) -> String:
|
||||
if settlement == null:
|
||||
return ""
|
||||
@@ -693,7 +734,7 @@ func _variant_mark_lookup(c: Vector2i) -> String:
|
||||
if b == null:
|
||||
return ""
|
||||
for vid in b.variant_ids(c):
|
||||
var vrow: Dictionary = settlement.db.variation.row_by_id(String(vid))
|
||||
var vrow: Dictionary = db.variation.row_by_id(String(vid))
|
||||
if String(vrow.get("effect", "")) == "flat_damage":
|
||||
return "flat_damage"
|
||||
return ""
|
||||
@@ -709,10 +750,7 @@ func _on_battle_finished(payload: Dictionary) -> void:
|
||||
# 仅调试模式接线(冒险的关卡结束归 AdventureRunner._on_level_finished——大奖励/冒险终局分派)
|
||||
var result: String = String(payload.get("result", ""))
|
||||
_sync_settlement_visuals() # → 全关(battle_over 反馈面)
|
||||
stage.gray_all_enemies() # 死亡兜底:站桩灰化残位(禁瞬消语义的战斗收尾面)
|
||||
stage.settle_hero_idle() # 主角态回 idle(迭代 6 T3 三态收尾)
|
||||
ap_bar.set_rap(0) # RAP 清零脉冲 + 微文案(载体 2026-09-26 迁 D 区 AP 条)
|
||||
stage.hero_standee.set_effects_cleared_feedback() # 状态全清可见反馈(增益状态×4④——载体迁 B 区站桩)
|
||||
HudPresenter.battle_end_feedback(stage, ap_bar, true) # 灰化/idle 回正/RAP 清零/状态全清——HUD 单通道(T3 收口批)
|
||||
var decision: Dictionary = _debug.on_battle_finished(payload)
|
||||
match String(decision.outcome):
|
||||
"next_layer":
|
||||
@@ -751,126 +789,34 @@ func _wire_battle(settlement_ref) -> void:
|
||||
settlement_ref.board_changed.connect(_on_board_changed)
|
||||
|
||||
|
||||
# ==================== 结算行排播(泵本体归 row_pacer.gd——本节仅入口与探针/测试委托面)====================
|
||||
|
||||
func _on_step_row(row: Dictionary) -> void:
|
||||
# 卡⑦ 排播入口:减动效=延迟归零同帧直落(行呈现不跳过);常态入队按段阶梯播放
|
||||
if UiKit.motion_scale < 1.0:
|
||||
_consume_row(row)
|
||||
_row_pacer.consume_now(row)
|
||||
return
|
||||
_row_queue.append(row)
|
||||
_row_pacer.enqueue(row)
|
||||
|
||||
|
||||
# 行消费(排播队列的出队步 / 减动效快路共用):明细带入带+演出链+呈现刷新——与卡⑦前排位逐位一致
|
||||
func _consume_row(row: Dictionary) -> void:
|
||||
if row.has("__round_line"):
|
||||
log_view.finish_settlement_round() # 上轮摘要行(明细带 pattern 3)随轮首分隔行落带
|
||||
log_view.append_bb("[color=#e0b860]── 第 %d 轮 ──[/color]" % int(row["__round_line"]))
|
||||
return
|
||||
log_view.append_row(row)
|
||||
stage.on_step_row_fx(row)
|
||||
_play_cross_zone_fx(row) # 跨区因果演出(结算演出链批):开格之因 → 站桩之果
|
||||
_play_settlement_mark(row) # 三轮 #3——逐条标出:Buff/Debuff 色环脉冲 → 伤害/治疗数字浮现
|
||||
# 高光演出(战斗结算×3①② 占位):完美排雷横幅 / 标中旗加重弹道
|
||||
if String(row.get("type", "")) == "highlight":
|
||||
board_zone.fx.play_perfect_sweep()
|
||||
elif String(row.get("highlight", "")) == "flag_hit":
|
||||
board_zone.fx.play_flag_hit_projectile(true)
|
||||
_refresh_presentations()
|
||||
|
||||
|
||||
# 逐条结算标出(三轮 #3——用户裁决按结算顺序演出:先 Buff/Debuff 特效、再基础伤害数字;
|
||||
# 打中谁标谁、主角被打标主角;道具/奖励行逐条冒出)。目标解析失败兜底棋盘中心。
|
||||
func _play_settlement_mark(row: Dictionary) -> void:
|
||||
var mark_type := String(row.get("type", ""))
|
||||
if mark_type != "effect" and mark_type != "damage" and mark_type != "heal":
|
||||
return
|
||||
var target := String(row.get("target", ""))
|
||||
var center: Vector2
|
||||
if target == "hero":
|
||||
center = stage.hero_standee_center()
|
||||
else:
|
||||
var v = stage.enemy_standee_center(target) if target != "" and target != "all_enemies" else null
|
||||
center = v if v != null else board_view.get_global_rect().get_center()
|
||||
match mark_type:
|
||||
"effect":
|
||||
var col: Color = Color("7cc47c") if int(row.get("value", 0)) > 0 else Color("c47c7c")
|
||||
settlement_fx.play_status_pulse(center, col)
|
||||
"damage":
|
||||
settlement_fx.play_damage_number(center, "-%d" % int(row.get("value", 0)),
|
||||
Color("d9402a"), bool(row.get("crit", false)))
|
||||
"heal":
|
||||
settlement_fx.play_damage_number(center, "+%d" % int(row.get("value", 0)), Color("7cc47c"), false)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
# 排播泵(卡⑦——演出层帧驱动;队列空=零开销直返)
|
||||
if _row_queue.is_empty():
|
||||
_row_pace_wait = 0.0
|
||||
_pace_prev_segment = ""
|
||||
if _was_pumping:
|
||||
# 演播完边沿:结算视觉期由排播队列承载(_is_settling 排播项),队列排空的翻转
|
||||
# 无核心信号驱动——此处主动刷回挖掘态(去饱和解除/明细带收起/mode=dig)。
|
||||
_was_pumping = false
|
||||
_refresh_presentations.call_deferred()
|
||||
return
|
||||
_was_pumping = true
|
||||
_row_pace_wait -= delta
|
||||
if _row_pace_wait > 0.0:
|
||||
return
|
||||
var entry: Dictionary = _row_queue.pop_front()
|
||||
_consume_row(entry)
|
||||
var seg := String(entry.get("type", ""))
|
||||
_row_pace_wait = 0.0 if UiKit.motion_scale < 1.0 else (PACE_SEGMENT_GAP if seg != _pace_prev_segment else PACE_ROW_GAP)
|
||||
_pace_prev_segment = seg
|
||||
|
||||
|
||||
# 段间延迟取数面(减动效双态断言用——开=0 归零,行不跳过;常态=段间/段内两档)
|
||||
# 段间延迟取数面(减动效双态断言用——pacing_queue_test 直读;委托 row_pacer 同名面)
|
||||
func pace_delay_for(prev_seg: String, next_seg: String) -> float:
|
||||
if UiKit.motion_scale < 1.0:
|
||||
return 0.0
|
||||
return PACE_SEGMENT_GAP if prev_seg != next_seg else PACE_ROW_GAP
|
||||
return _row_pacer.pace_delay_for(prev_seg, next_seg)
|
||||
|
||||
|
||||
# 强制排空(模态开启前调用——明细回看/停点覆盖层/终局面所见明细带恒完整)
|
||||
# 强制排空(模态开启前调用——明细回看/停点覆盖层/终局面所见明细带恒完整;pacing_queue_test 直读)
|
||||
func _flush_row_queue() -> void:
|
||||
while not _row_queue.is_empty():
|
||||
_consume_row(_row_queue.pop_front())
|
||||
_row_pace_wait = 0.0
|
||||
_pace_prev_segment = ""
|
||||
|
||||
|
||||
# 跨区因果演出(§Transitions 结算逐行兑现——动画密度终稿归战斗表现样张批):
|
||||
# 伤害行 → ⚔ 弹道自棋盘飞向目标敌方站桩(crit 金档加重);治疗行 → ✚ 弹道飞向主角站桩;
|
||||
# 命中反馈(闪白/血条递减/灰化)由站桩侧 on_step_row_fx/set_data 承接
|
||||
func _play_cross_zone_fx(row: Dictionary) -> void:
|
||||
var from_v: Vector2 = board_view.get_global_rect().get_center()
|
||||
match String(row.get("type", "")):
|
||||
"damage":
|
||||
var target := String(row.get("target", ""))
|
||||
var crit := bool(row.get("crit", false))
|
||||
if target == "" or target == "hero":
|
||||
# 主角受击弹道(迭代 6 T5 拆「敌→主角零反馈」早退):⚔ 危险红飞向主角站桩;
|
||||
# crit 只加重字号不加金(金=收益/胜利语义位,不用于敌方对主角输出)
|
||||
settlement_fx.play_projectile(from_v, stage.hero_standee_center(), "⚔",
|
||||
UiKit.RED_MINE, crit)
|
||||
return
|
||||
var to_v = stage.enemy_standee_center(target)
|
||||
if to_v != null:
|
||||
settlement_fx.play_projectile(from_v, to_v, "⚔",
|
||||
UiKit.GOLD if crit else UiKit.RED_MINE, crit)
|
||||
"heal":
|
||||
if String(row.get("target", "")) != "hero" and String(row.get("target", "")) != "":
|
||||
return
|
||||
settlement_fx.play_projectile(from_v, stage.hero_standee_center(), "✚", Color("7cc47c"))
|
||||
_row_pacer.flush()
|
||||
|
||||
|
||||
func _on_round_started(ri: int) -> void:
|
||||
# 卡⑦:轮首分隔行随排播队列走(落在前轮各行之后——带序正确);轮横幅即轮切换时刻发射
|
||||
#(view 内判减动效=静态化变体)。减动效快路=分隔行同帧直落(不跳过)。
|
||||
#(view 内判减动效=静态化变体)。减动效快路=分隔行同帧直落(不跳过——consume_now 复用
|
||||
# _consume_row 轮首分隔行分支,与原直写逐字等价)。
|
||||
if UiKit.motion_scale < 1.0:
|
||||
log_view.finish_settlement_round()
|
||||
log_view.append_bb("[color=#e0b860]── 第 %d 轮 ──[/color]" % ri)
|
||||
_row_pacer.consume_now({"__round_line": ri})
|
||||
else:
|
||||
_row_queue.append({"__round_line": ri})
|
||||
_row_pacer.enqueue({"__round_line": ri})
|
||||
settlement_fx.play_round_banner(ri)
|
||||
_refresh_used_round = 0
|
||||
_refresh_presentations()
|
||||
@@ -1006,12 +952,13 @@ func _open_camp() -> void:
|
||||
return
|
||||
meta.refresh_workshop_unlocks()
|
||||
_start_overlay.visible = false
|
||||
_open_modal(camp_overlay) # 模态栈单口(T3 收口批——登记层开合一律走栈)
|
||||
camp_overlay.open_camp(_camp_data())
|
||||
_refresh_presentations()
|
||||
|
||||
|
||||
func _on_camp_closed() -> void:
|
||||
camp_overlay.visible = false
|
||||
_close_modal(camp_overlay) # 模态栈单口(T3 收口批)
|
||||
_start_overlay.visible = true
|
||||
_refresh_resume_button() # 迭代 8 卡②——回菜单按断点在档态刷新
|
||||
_refresh_presentations() # 关闭后刷新顶栏(工坊解锁可能变了余额侧读数面)
|
||||
@@ -1021,11 +968,11 @@ func _on_camp_closed() -> void:
|
||||
|
||||
func _open_settings() -> void:
|
||||
_start_overlay.visible = false
|
||||
settings_overlay.visible = true
|
||||
_open_modal(settings_overlay) # 模态栈单口(T3 收口批)
|
||||
|
||||
|
||||
func _on_settings_closed() -> void:
|
||||
settings_overlay.visible = false
|
||||
_close_modal(settings_overlay) # 模态栈单口(T3 收口批)
|
||||
_start_overlay.visible = true
|
||||
_refresh_resume_button()
|
||||
|
||||
@@ -1083,116 +1030,12 @@ func _on_shop_leave() -> void:
|
||||
runner.continue_after_node()
|
||||
|
||||
|
||||
# ---- 魔法道具 HUD 接线(迭代 7 T4——库存条+两步交互+叠加层呈现;数据源=runner 级库存只读面)----
|
||||
|
||||
# ---- 奖励节点三选一(迭代 7 T5——拍板②丙:奖励屏=小构筑点;「节点奖励=功能道具正式渠道」兑现)----
|
||||
# aux 解锁门控集组装(迭代 7 T5——拍板③甲:工坊解锁 ∩ 活行词表;罗盘退役/失义件不入集)
|
||||
func _aux_unlocked_set() -> Dictionary:
|
||||
var out := {}
|
||||
if meta == null:
|
||||
return out
|
||||
for row in db.aux_active_rows():
|
||||
var iid := String(row.get("item_id", ""))
|
||||
if meta.save.item_unlocked(iid):
|
||||
out[iid] = true
|
||||
return out
|
||||
|
||||
|
||||
# 槽点击(第一步):AP 类(target=none)点击即用(无目标);空间类进/出选中态(toggle 取消)。
|
||||
func _on_aux_slot_clicked(slot_handle: int) -> void:
|
||||
if settlement == null or runner == null or runner.aux_inventory == null:
|
||||
return
|
||||
if slot_handle == 0:
|
||||
_aux_use_selection = 0
|
||||
_refresh_aux_slots()
|
||||
return
|
||||
var rec: Dictionary = {}
|
||||
for r in _aux_inventory_projection():
|
||||
if int(r.get("handle", 0)) == slot_handle:
|
||||
rec = r
|
||||
break
|
||||
if rec.is_empty() or bool(rec.get("used", false)):
|
||||
return
|
||||
var arow: Dictionary = db.aux_item_row(String(rec.get("item_id", "")))
|
||||
if String(arow.get("target", "none")) == "none":
|
||||
_attempt_aux_use(slot_handle, Vector2i(-9, -9)) # AP 类:无目标即用
|
||||
return
|
||||
if not bool(rec.get("usable", true)):
|
||||
board_zone.toast.show_toast("当前无可预读的未开格", "warn")
|
||||
return
|
||||
_aux_use_selection = 0 if _aux_use_selection == slot_handle else slot_handle # toggle 取消
|
||||
_refresh_aux_slots()
|
||||
if _aux_use_selection != 0:
|
||||
board_zone.toast.show_toast("%s:点击目标格使用(9 宫窗口),再点道具取消" % String(rec.get("name", "")), "info")
|
||||
|
||||
|
||||
# 使用受理(第二步;core 侧 use_aux 四步编排——拒绝零消耗 toast 可辨认)
|
||||
func _attempt_aux_use(handle: int, cell: Vector2i) -> void:
|
||||
if settlement == null:
|
||||
return
|
||||
var v: Dictionary = settlement.use_aux(handle, cell)
|
||||
if not bool(v.ok):
|
||||
board_zone.toast.show_toast(String(v.get("reason", "无法使用")), "warn")
|
||||
return
|
||||
_aux_use_selection = 0
|
||||
_refresh_aux_slots()
|
||||
_refresh_aux_overlays()
|
||||
var arow: Dictionary = db.aux_item_row(String(v.get("item_id", "")))
|
||||
board_zone.toast.show_toast("已使用 %s" % String(arow.get("display_name", "")), "info")
|
||||
|
||||
|
||||
# 库存投影(C 区四槽取数面——字形=名首字;usable=板级零信息预判回调接排雷玩法只读面)
|
||||
func _aux_inventory_projection() -> Array:
|
||||
if runner == null or runner.aux_inventory == null:
|
||||
return []
|
||||
var ms = settlement.minesweeper if settlement != null else null
|
||||
var cb := Callable()
|
||||
if ms != null:
|
||||
cb = func(_c: Vector2i) -> bool:
|
||||
for y in ms.current_board().height:
|
||||
for x in ms.current_board().width:
|
||||
if not ms.is_revealed(Vector2i(x, y)):
|
||||
return true
|
||||
return false
|
||||
var out: Array = []
|
||||
for rec in runner.aux_inventory.query_inventory(cb):
|
||||
var arow: Dictionary = db.aux_item_row(String(rec.get("item_id", "")))
|
||||
var dn := String(arow.get("display_name", ""))
|
||||
out.append({
|
||||
"handle": int(rec.get("handle", 0)),
|
||||
"item_id": String(rec.get("item_id", "")),
|
||||
"glyph": dn.left(1) if dn != "" else "?",
|
||||
"name": dn,
|
||||
"used": bool(rec.get("used", false)),
|
||||
"usable": bool(rec.get("usable", true)),
|
||||
})
|
||||
return out
|
||||
|
||||
|
||||
func _refresh_aux_slots() -> void:
|
||||
info_panel.set_aux_inventory(_aux_inventory_projection(), _aux_use_selection)
|
||||
|
||||
|
||||
# 叠加层投影注入(use 成功/三清后——棋盘标记与预读徽章重算)
|
||||
func _refresh_aux_overlays() -> void:
|
||||
if runner == null or runner.aux_inventory == null or board_view == null:
|
||||
return
|
||||
var ov: Dictionary = runner.aux_inventory.query_overlays()
|
||||
board_view.set_aux_overlays(ov.get("mine_reveal", []), ov.get("reward_preview", []))
|
||||
|
||||
|
||||
func _on_wallet_changed(event: String, gp: int, extra: Dictionary) -> void:
|
||||
# wallet 行族转发(到账/购买/清零——试玩日志验收取数面)+ 常驻余额与在开商店覆盖层重刷
|
||||
logger.log_wallet(event, gp, extra)
|
||||
match String(event): # GP 影子账(迭代 6 T7——收束清零前取值,纯呈现零核心触碰=C1 呈现层方案)
|
||||
"gain", "buy":
|
||||
_adventure_gp_view = gp
|
||||
if String(event) == "gain":
|
||||
_last_gain_amount = int(extra.get("amount", 0)) # 卡⑥ GP 增量取数(每战恰一笔 gain)
|
||||
"clear":
|
||||
_adventure_gp_final = _adventure_gp_view
|
||||
_adventure_gp_view = 0
|
||||
_last_gain_amount = 0
|
||||
_gp_ledger.apply_event(event, gp) # GP 影子账(迭代 6 T7——收束清零前取值,纯呈现零核心触碰=C1;账本归 gp_shadow_ledger.gd)
|
||||
if String(event) == "gain":
|
||||
_last_gain_amount = int(extra.get("amount", 0)) # 卡⑥ GP 增量取数(每战恰一笔 gain)
|
||||
if shop_overlay.visible and runner != null:
|
||||
shop_overlay.refresh_state(runner.shop_products(), int(runner.gp))
|
||||
_refresh_presentations()
|
||||
@@ -1205,7 +1048,7 @@ func _on_board_action(cell: Vector2i, kind: String) -> void:
|
||||
if _input_locked():
|
||||
return
|
||||
# 结算演播期棋盘冻结(T17 用户明确令 v3——不可点/不可移/不可挖):
|
||||
# 锁面=排播队列活跃(纯队列判据;_row_pace_wait 有末行尾迹窗口不可作演播中标志)。
|
||||
# 锁面=排播队列活跃(纯队列判据;排播泵尾迹窗口不可作演播中标志——见 row_pacer._process 注)。
|
||||
# 队列排空即解锁、无状态残留;HUD 按钮不在锁面(停手/明细回看各自通路,开启前
|
||||
# _flush_row_queue 自行排空)。v1 撞收尾链的教训对策:ui_click_probe 的排播期 stall
|
||||
# 豁免随批(合法停顿态),Waiting 收尾等待期**不豁免**——probe 报卡即为真回归信号。
|
||||
@@ -1213,14 +1056,15 @@ func _on_board_action(cell: Vector2i, kind: String) -> void:
|
||||
return
|
||||
# 魔法道具选中态劫持(迭代 7 T4——两步交互第二步):点格=使用目标拾取,不落挖格;
|
||||
# 只劫持挖掘点击(move/flag 语义不冲突——选中期玩家仍可移动/插旗)
|
||||
if _aux_use_selection != 0 and kind == "dig":
|
||||
_attempt_aux_use(_aux_use_selection, cell)
|
||||
var aux_sel: int = _aux.active_selection()
|
||||
if aux_sel != 0 and kind == "dig":
|
||||
_aux.attempt_use(aux_sel, cell)
|
||||
return
|
||||
var outcomes: Array = settlement.minesweeper.settle_batch([{"action": kind, "cell": cell}])
|
||||
var accepted: bool = not outcomes.is_empty() and bool(outcomes[0].accepted)
|
||||
logger.log_action(kind, cell, accepted)
|
||||
if runner != null and runner.aux_inventory != null:
|
||||
_refresh_aux_slots() # 迭代 7 T4——授予发生在 settle_batch 结算段内(同步走完,行尾重查)
|
||||
_aux.refresh_slots() # 迭代 7 T4——授予发生在 settle_batch 结算段内(同步走完,行尾重查)
|
||||
_handle_outcome_feedback(outcomes[0] if not outcomes.is_empty() else {})
|
||||
# settle_batch 内部同步走完 结算→战斗结束→关卡推进(事件同步发射)——关卡终局时 settlement 已被
|
||||
# runner 置空,清图演出判定须先判 settlement(基线既有重入隐患,2026-09-13 加固)
|
||||
@@ -1306,34 +1150,32 @@ func _sync_settlement_visuals() -> void:
|
||||
# 结算阶段(round_end → 下一轮):D 区去饱和+金橙偏移锁输入(明度不变)+ 明细带展开 84px
|
||||
var settling := _is_settling()
|
||||
if settling and not _was_settling:
|
||||
settlement_fx.play_round_sweep(board_view.get_global_rect()) # 结算轮光带(演出链批)
|
||||
settlement_fx.play_round_sweep(board_view.get_global_rect()) # 结算轮光带(演出链批)——时序敏感演出触发,例外留在组合根
|
||||
_was_settling = settling
|
||||
board_view.set_desaturated(settling)
|
||||
board_view.set_dig_glow_enabled(not settling)
|
||||
stage.set_settle_focus(settling)
|
||||
log_view.set_mode("settle" if settling else "dig")
|
||||
stage.set_round_text(_phase_text())
|
||||
HudPresenter.sync_settlement_visuals(stage, board_view, log_view, settling, _phase_text())
|
||||
|
||||
|
||||
func _is_settling() -> bool:
|
||||
# T17 修复:结算视觉期跟随排播演播到放完。核心层同步走完(轮末结算后逻辑秒回 Active),
|
||||
# 旧判定让演播期棋盘提前恢复可挖观感=用户「没意识到在结算演出」感知缺口的根源;
|
||||
# 演播期维持去饱和+禁挖光(结算中语义),放完自然回挖掘认知。判据=纯队列(_row_pace_wait
|
||||
# 在末行消费后到下帧泵清零前有 >0 尾迹窗口,不可作演播中标志)。battle 收尾演播例外:
|
||||
# 演播期维持去饱和+禁挖光(结算中语义),放完自然回挖掘认知。判据=纯队列(排播泵尾迹
|
||||
# 窗口不可作演播中标志——row_pacer._process 注)。battle 收尾演播例外:
|
||||
# overlay 开启即 _flush_row_queue 排空,视觉随队列空同步交接结算框。
|
||||
# 相位取数经 presentation_snapshot 成文面——禁 settlement.action_economy 链式穿透(T3 收口批)。
|
||||
if settlement == null or not settlement.is_battle_live():
|
||||
return not _row_queue.is_empty()
|
||||
if settlement.action_economy.phase == settlement.action_economy.PHASE_WAITING:
|
||||
if String(settlement.presentation_snapshot().get("phase", "")) == ActionEconomy.PHASE_WAITING:
|
||||
return true
|
||||
return not _row_queue.is_empty()
|
||||
|
||||
|
||||
func _phase_text() -> String:
|
||||
# 文案装配归 narrator(settlement 缺位 = 非战斗态——组合根模式状态守卫)
|
||||
# 文案装配归 narrator(settlement 缺位 = 非战斗态——组合根模式状态守卫;相位取数经
|
||||
# presentation_snapshot 成文面——禁 settlement.action_economy 链式穿透,T3 收口批)
|
||||
if settlement == null:
|
||||
return "第 — 轮 · —"
|
||||
return Narrator.phase_text(int(settlement.round_index), String(settlement.battle_result),
|
||||
settlement.action_economy.phase == settlement.action_economy.PHASE_WAITING,
|
||||
String(settlement.presentation_snapshot().get("phase", "")) == ActionEconomy.PHASE_WAITING,
|
||||
_boss_active, Narrator.boss_name(runner, db))
|
||||
|
||||
|
||||
@@ -1368,11 +1210,11 @@ func _on_diagnose_pressed() -> void:
|
||||
|
||||
func _show_tune_panel() -> void:
|
||||
_start_overlay.visible = false
|
||||
tune_panel.visible = true
|
||||
_open_modal(tune_panel) # 模态栈单口(T3 收口批)
|
||||
|
||||
|
||||
func _close_tune_panel() -> void:
|
||||
tune_panel.visible = false
|
||||
_close_modal(tune_panel) # 模态栈单口(T3 收口批)
|
||||
_start_overlay.visible = true
|
||||
_refresh_resume_button() # 迭代 8 卡②——回菜单按断点在档态刷新
|
||||
|
||||
|
||||
@@ -32,3 +32,12 @@ func close(m: Control) -> void:
|
||||
if is_instance_valid(h):
|
||||
h.visible = true
|
||||
_occluded.erase(m)
|
||||
|
||||
|
||||
# 开态查询(T3 模态收口批 2026-10-02——组合根输入锁唯一判据:登记册上是否有任一层处于开态;
|
||||
# 可见性即开态,与 open/close 记账同源——组合根不再手工罗列各层)
|
||||
func has_open() -> bool:
|
||||
for m in _list:
|
||||
if is_instance_valid(m) and m.visible:
|
||||
return true
|
||||
return false
|
||||
@@ -0,0 +1,144 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# 结算行排播泵(迭代 8 卡⑦;T3 拆件批 2026-10-02 自 main.gd 整体迁出——规范 1.1 单一职责):
|
||||
# 演出层排播:逻辑先行零回压,行按段分组阶梯延迟进明细带。
|
||||
# 硬契约:核心层零 await 不破——本队列只做信号的"后置消费",结算顺序/结果/时序逻辑零改动(§3 契约 1);
|
||||
# 减动效开=延迟归零走同帧直落快路(行呈现不跳过——§3 契约 5);模态开启前强制排空(明细回看/停点
|
||||
# 覆盖层所见明细带恒完整——排空入口在组合根 _open_modal)。
|
||||
# 装配纪律:视图句柄与呈现刷新回调由组合根注入(ADR-0001——本类零信号连接,仅做信号后置消费);
|
||||
# 行消费与卡⑦前逐位一致(_consume_row 原文迁入,语义零改动)。
|
||||
extends Node
|
||||
|
||||
const PACE_SEGMENT_GAP := 0.3 # 段间阶梯延迟(代拍板:白名单 0.2–0.4s 内取值,追认清单呈报)
|
||||
const PACE_ROW_GAP := 0.22 # 段内间隔(三轮 #3 上调 0.06→0.22——逐条演出可感知,配合数字浮现/状态脉冲)
|
||||
|
||||
var _rows: Array = [] # 待排播行(step_row 载荷原样入队;__round_line 标记=轮首分隔行)
|
||||
var _pace_wait := 0.0 # 距下一行排播的剩余秒(_process 消耗)
|
||||
var _prev_segment := "" # 上一排播行的段型(段间/段内延迟判据)
|
||||
var _was_pumping := false # 排播活跃边沿(T17——末行消费帧触发结算视觉回挖掘态刷新)
|
||||
|
||||
# 组合根注入面(_build_ui 装配——呈现句柄 + 演播完边沿的呈现刷新回调)
|
||||
var log_view: BattleLogView
|
||||
var stage: BattleStageView
|
||||
var board_zone: BoardZoneView
|
||||
var settlement_fx: SettlementFxView
|
||||
var board_view: BoardView
|
||||
var refresh_request := Callable() # () → 组合根 _refresh_presentations(行尾直呼/演播完边沿 call_deferred)
|
||||
|
||||
|
||||
# 队列只读面(探针/测试经组合根委托属性读——共享同一 Array 引用,读数即实况)
|
||||
func pending_rows() -> Array:
|
||||
return _rows
|
||||
|
||||
|
||||
# 入队(常态路径——减动效关;step_row 载荷原样,零加工)
|
||||
func enqueue(row: Dictionary) -> void:
|
||||
_rows.append(row)
|
||||
|
||||
|
||||
# 同帧直落(减动效快路——延迟归零,行呈现不跳过;轮首分隔行减动效分支同用此口)
|
||||
func consume_now(row: Dictionary) -> void:
|
||||
_consume_row(row)
|
||||
|
||||
|
||||
# 段间延迟取数面(减动效双态断言用——开=0 归零,行不跳过;常态=段间/段内两档)
|
||||
func pace_delay_for(prev_seg: String, next_seg: String) -> float:
|
||||
if UiKit.motion_scale < 1.0:
|
||||
return 0.0
|
||||
return PACE_SEGMENT_GAP if prev_seg != next_seg else PACE_ROW_GAP
|
||||
|
||||
|
||||
# 强制排空(模态开启前调用——明细回看/停点覆盖层/终局面所见明细带恒完整)
|
||||
func flush() -> void:
|
||||
while not _rows.is_empty():
|
||||
_consume_row(_rows.pop_front())
|
||||
_pace_wait = 0.0
|
||||
_prev_segment = ""
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
# 排播泵(卡⑦——演出层帧驱动;队列空=零开销直返)
|
||||
if _rows.is_empty():
|
||||
_pace_wait = 0.0
|
||||
_prev_segment = ""
|
||||
if _was_pumping:
|
||||
# 演播完边沿:结算视觉期由排播队列承载(_is_settling 排播项),队列排空的翻转
|
||||
# 无核心信号驱动——此处主动刷回挖掘态(去饱和解除/明细带收起/mode=dig)。
|
||||
_was_pumping = false
|
||||
refresh_request.call_deferred()
|
||||
return
|
||||
_was_pumping = true
|
||||
_pace_wait -= delta
|
||||
if _pace_wait > 0.0:
|
||||
return
|
||||
var entry: Dictionary = _rows.pop_front()
|
||||
_consume_row(entry)
|
||||
var seg := String(entry.get("type", ""))
|
||||
_pace_wait = 0.0 if UiKit.motion_scale < 1.0 else (PACE_SEGMENT_GAP if seg != _prev_segment else PACE_ROW_GAP)
|
||||
_prev_segment = seg
|
||||
|
||||
|
||||
# 行消费(排播队列的出队步 / 减动效快路共用):明细带入带+演出链+呈现刷新——与卡⑦前排位逐位一致
|
||||
func _consume_row(row: Dictionary) -> void:
|
||||
if row.has("__round_line"):
|
||||
log_view.finish_settlement_round() # 上轮摘要行(明细带 pattern 3)随轮首分隔行落带
|
||||
log_view.append_bb("[color=#e0b860]── 第 %d 轮 ──[/color]" % int(row["__round_line"]))
|
||||
return
|
||||
log_view.append_row(row)
|
||||
stage.on_step_row_fx(row)
|
||||
_play_cross_zone_fx(row) # 跨区因果演出(结算演出链批):开格之因 → 站桩之果
|
||||
_play_settlement_mark(row) # 三轮 #3——逐条标出:Buff/Debuff 色环脉冲 → 伤害/治疗数字浮现
|
||||
# 高光演出(战斗结算×3①② 占位):完美排雷横幅 / 标中旗加重弹道
|
||||
if String(row.get("type", "")) == "highlight":
|
||||
board_zone.fx.play_perfect_sweep()
|
||||
elif String(row.get("highlight", "")) == "flag_hit":
|
||||
board_zone.fx.play_flag_hit_projectile(true)
|
||||
refresh_request.call()
|
||||
|
||||
|
||||
# 逐条结算标出(三轮 #3——用户裁决按结算顺序演出:先 Buff/Debuff 特效、再基础伤害数字;
|
||||
# 打中谁标谁、主角被打标主角;道具/奖励行逐条冒出)。目标解析失败兜底棋盘中心。
|
||||
func _play_settlement_mark(row: Dictionary) -> void:
|
||||
var mark_type := String(row.get("type", ""))
|
||||
if mark_type != "effect" and mark_type != "damage" and mark_type != "heal":
|
||||
return
|
||||
var target := String(row.get("target", ""))
|
||||
var center: Vector2
|
||||
if target == "hero":
|
||||
center = stage.hero_standee_center()
|
||||
else:
|
||||
var v = stage.enemy_standee_center(target) if target != "" and target != "all_enemies" else null
|
||||
center = v if v != null else board_view.get_global_rect().get_center()
|
||||
match mark_type:
|
||||
"effect":
|
||||
var col: Color = Color("7cc47c") if int(row.get("value", 0)) > 0 else Color("c47c7c")
|
||||
settlement_fx.play_status_pulse(center, col)
|
||||
"damage":
|
||||
settlement_fx.play_damage_number(center, "-%d" % int(row.get("value", 0)),
|
||||
Color("d9402a"), bool(row.get("crit", false)))
|
||||
"heal":
|
||||
settlement_fx.play_damage_number(center, "+%d" % int(row.get("value", 0)), Color("7cc47c"), false)
|
||||
|
||||
|
||||
# 跨区因果演出(§Transitions 结算逐行兑现——动画密度终稿归战斗表现样张批):
|
||||
# 伤害行 → ⚔ 弹道自棋盘飞向目标敌方站桩(crit 金档加重);治疗行 → ✚ 弹道飞向主角站桩;
|
||||
# 命中反馈(闪白/血条递减/灰化)由站桩侧 on_step_row_fx/set_data 承接
|
||||
func _play_cross_zone_fx(row: Dictionary) -> void:
|
||||
var from_v: Vector2 = board_view.get_global_rect().get_center()
|
||||
match String(row.get("type", "")):
|
||||
"damage":
|
||||
var target := String(row.get("target", ""))
|
||||
var crit := bool(row.get("crit", false))
|
||||
if target == "" or target == "hero":
|
||||
# 主角受击弹道(迭代 6 T5 拆「敌→主角零反馈」早退):⚔ 危险红飞向主角站桩;
|
||||
# crit 只加重字号不加金(金=收益/胜利语义位,不用于敌方对主角输出)
|
||||
settlement_fx.play_projectile(from_v, stage.hero_standee_center(), "⚔",
|
||||
UiKit.RED_MINE, crit)
|
||||
return
|
||||
var to_v = stage.enemy_standee_center(target)
|
||||
if to_v != null:
|
||||
settlement_fx.play_projectile(from_v, to_v, "⚔",
|
||||
UiKit.GOLD if crit else UiKit.RED_MINE, crit)
|
||||
"heal":
|
||||
if String(row.get("target", "")) != "hero" and String(row.get("target", "")) != "":
|
||||
return
|
||||
settlement_fx.play_projectile(from_v, stage.hero_standee_center(), "✚", Color("7cc47c"))
|
||||
@@ -0,0 +1 @@
|
||||
uid://68efaexi7rfr
|
||||
@@ -52,12 +52,9 @@ func open(text: String, confirm_label: String, cancel_label: String = "取消")
|
||||
_text.text = text
|
||||
_confirm_btn.text = confirm_label
|
||||
_cancel_btn.text = cancel_label
|
||||
# z 序自提(三轮修改批 #1——用户实机实证:本弹窗构建序早于开始覆盖层,而开始层是组合根
|
||||
# 尾部才 add_child 的(树序最末=绘制最上),弹窗被整层盖住——「看不见弹窗/离开菜单才显形」。
|
||||
# 每次打开强制提到宿主子级最末,绘制序恒最上。)
|
||||
var host := get_parent()
|
||||
if host != null:
|
||||
host.move_child(self, host.get_child_count() - 1)
|
||||
# z 序纪律(T3 收口批 2026-10-02):树序由组合根构建序保证——本弹窗在 main._build_ui 尾部
|
||||
# add_child(开始层/过场层/诊断钮之后 = 子级最末,登记序 = 树序 = 绘制序恒最上),
|
||||
# 「每次打开 move_child 运行时自提」创可贴随之删除。
|
||||
visible = true
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user