Files
gplan-2/game/presentation/treasure_interact_controller.gd
T

140 lines
6.3 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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——拍板③甲:工坊解锁 ∩ 过渡活行词表;罗盘退役/失义件/同调行不入集
# ——迭代 11 第①批 T1 切 treasure_transitional_rows 口径,同调行永不入过渡渠道)
func unlocked_set() -> Dictionary:
var out := {}
if meta == null:
return out
for row in db.treasure_transitional_rows():
var iid := String(row.get("item_id", ""))
if meta.is_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.treasure_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()
board_view.refresh() # 迭代 13 T8/D6——移除格即时落图(无移除态的件 diff 零变化幂等)
var arow: Dictionary = db.treasure_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:
return ms.has_unrevealed() # 只读查询面(排雷玩法 has_unrevealed——手搓全板双循环收口)
var out: Array = []
for rec in runner.aux_inventory.query_inventory(cb):
var arow: Dictionary = db.treasure_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", []))