## VERTICAL SLICE - NOT FOR PRODUCTION ## 秘宝两步交互控制器:C 区槽位选中/取消(toggle)→ 棋盘点格使用目标拾取的状态机 + 库存/叠加层只读投影装配。 ## 写调用分家(三触点):使用受理走 grant(RewardGrant.use_treasure——授予件面)、排雷玩法只读读与 ## 积点进度读走 settlement(编排器面);写调用唯一经现值回调注入的协作件,控制器零核心写调用本体。 ## 信号连接只归组合根(info_panel.treasure_slot_clicked 接至 on_slot_clicked)。 ## settlement/runner/grant 随关卡/战斗逐次更替——经组合根现值回调注入(grant 每战新建编排器, ## 现值取件防悬挂)。AP 类(target=none)调用面一律 attempt_use(handle)(cell 缺省=null=显式无目标) ## ——哨兵坐标收口本类,不出控制器。 ## ctx 引用束封闭键集(8 项,缺一键 assert):db/meta/info_panel:HeroInfoPanelView/toast:ToastStack/ ## board_view:BoardView/settlement_get:Callable(编排器现值——minesweeper 读与 attune_convert_state ## 双取值面之一)/runner_get:Callable/grant_get:Callable(授予件现值——use_treasure 直注)。 ## 禁实现期增删键。 extends RefCounted const NO_TARGET := Vector2i(-9, -9) # core use_treasure 形参占位(仅 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 _grant_get: Callable # () → 当前奖励授予件现值(组合根 lambda——每战新建,防悬挂) var _use_selection := 0 # 选中句柄(0=未选中;选中期棋盘点击=使用目标拾取) func _init(ctx: Dictionary) -> void: for k in ["db", "meta", "info_panel", "toast", "board_view", "settlement_get", "runner_get", "grant_get"]: assert(ctx.has(k), "[秘宝交互] 引用束缺键: %s" % k) db = ctx.db meta = ctx.meta info_panel = ctx.info_panel toast = ctx.toast board_view = ctx.board_view _settlement_get = ctx.settlement_get _runner_get = ctx.runner_get _grant_get = ctx.grant_get # aux 解锁门控集组装:工坊解锁 ∩ 过渡活行词表(treasure_transitional_rows 口径); # 罗盘/失义件/同调行不入集(同调行永不入过渡渠道) func unlocked_set() -> Dictionary: var out := {} if meta == null: return out for row in db.crossrun.treasure_items.transitional_rows(): var iid := String(row.get("item_id", "")) if meta.meta_state.item_unlocked(iid): out[iid] = true return out # 选中态读数(组合根棋盘点击劫持判据——0=未选中) func active_selection() -> int: return _use_selection # 槽点击(第一步):AP 类(target=none)点击即用(无目标);空间类进/出选中态(toggle 取消); # 自动兑付件展示行(handle<0)无使用动作——点击给说明提示。 func on_slot_clicked(slot_handle: int) -> void: var settlement = _settlement_get.call() var runner = _runner_get.call() var grant = _grant_get.call() # 三现值同查守卫(settlement/runner/grant 任一未就绪即静默 return——现值回调随更替时序) if settlement == null or runner == null or grant == null or runner.treasure_inventory == null: return if slot_handle == 0: _use_selection = 0 refresh_slots() return if slot_handle < 0: toast.show_toast("自动兑付件:满足条件自动生效,无需手动使用", "info") return var rec: Dictionary[Variant, Variant] = {} 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[Variant, Variant] = db.crossrun.treasure_items.row_by_id(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_treasure 四步编排——拒绝零消耗 toast 可辨认) func attempt_use(handle: int, cell: Variant = null) -> void: var grant = _grant_get.call() if grant == null: return var target: Vector2i = cell if cell != null else NO_TARGET var v: Dictionary[Variant, Variant] = grant.use_treasure(handle, target) if not bool(v.ok): toast.show_toast(String(v.get("reason", "无法使用")), "warn") return _use_selection = 0 refresh_slots() refresh_overlays() board_view.refresh() # 移除格即时落图(无移除态的件 diff 零变化幂等) var arow: Dictionary[Variant, Variant] = db.crossrun.treasure_items.row_by_id(String(v.get("item_id", ""))) toast.show_toast("已使用 %s" % String(arow.get("display_name", "")), "info") # 库存投影(C 区四槽取数面——逐库存行装配 + 末尾自动兑付件合成行) func inventory_projection() -> Array: var runner = _runner_get.call() if runner == null or runner.treasure_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: return ms.has_unrevealed() # 只读查询面(排雷玩法 has_unrevealed——手搓全板双循环收口) var out: Array[Variant] = [] var cs: Dictionary[Variant, Variant] = settlement.attune_convert_state() if settlement != null else {} for rec in runner.treasure_inventory.query_inventory(cb): out.append(_projection_row(rec, cs)) # 自动兑付件展示行(charges=0 不入骑库存〔_reseed_attune_charges 跳过注入〕, # C 区四槽末尾补合成行承载悬停进度;handle=-1 非库存句柄:槽点击走说明 toast 分支不进两步交互)。 # 库存内已见同 id 行(恒否——charges=0 不入骑库存)则不重复补行,守合成行重复 var auto_seen := false if bool(cs.get("assembled", false)): for row_v in out: if String(cs.get("item_id", "")) == String((row_v as Dictionary).get("item_id", "")): auto_seen = true break if bool(cs.get("assembled", false)) and not auto_seen: var auto := _auto_row(cs) if not auto.is_empty(): out.append(auto) return out # 库存投影行(字形=名首字;usable=整图零信息预判回调接排雷玩法只读面)+ # 积点进度浮窗行(自动兑付件 C 区悬停 tooltip 补 N/M 进度;件 id 锁定=装配行唯一) func _projection_row(rec: Dictionary, cs: Dictionary) -> Dictionary: var arow: Dictionary[Variant, Variant] = db.crossrun.treasure_items.row_by_id(String(rec.get("item_id", ""))) var dn := String(arow.get("display_name", "")) var note := "" if bool(cs.get("assembled", false)) and String(cs.get("item_id", "")) == String(rec.get("item_id", "")): note = "积点 %d/%d" % [int(cs.get("points", 0)), int(cs.get("need", 0))] return { "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)), "note": note, } # 自动兑付件合成展示行(未装配/无名行=空字典不补行) func _auto_row(cs: Dictionary) -> Dictionary: if not bool(cs.get("assembled", false)): return {} var adn := String(cs.get("display_name", "")) if adn == "": return {} return { "handle": -1, "item_id": String(cs.get("item_id", "")), "glyph": adn.left(1), "name": adn, "used": false, "usable": true, "note": "积点 %d/%d" % [int(cs.get("points", 0)), int(cs.get("need", 0))], } func refresh_slots() -> void: info_panel.set_treasure_inventory(inventory_projection(), _use_selection) # 叠加层投影注入(use 成功/三清后——棋盘标记与预读徽章重算) func refresh_overlays() -> void: var runner = _runner_get.call() if runner == null or runner.treasure_inventory == null or board_view == null: return var ov: Dictionary[Variant, Variant] = runner.treasure_inventory.query_overlays() board_view.set_treasure_overlays(ov.get("mine_reveal", []), ov.get("reward_preview", []))