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

192 lines
8.7 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
## 秘宝两步交互控制器: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", []))