## 秘宝匣子界面(自 CampOverlay 秘宝匣柜台抽出——承载面迁移,界面逻辑复用零重设计): ## 钥匙门(树上 ks_workshop 点亮前锁定态;点亮=12 件行板+同调装配段+Y 刷新升级行整体迁入)。 ## UI 只发意图信号(购买写口唯一归 MetaProgression.buy_treasure_unlock / toggle_attunement / buy_attune_refresh)。 class_name TreasureView extends CampSubViewBase signal buy_requested(attr: String) ## 秘宝购买/Y 刷新升级意图(presenter 按 attr 分派) signal attune_toggled(item_id: String) ## 同调装配切换意图(写口唯一归 MetaProgression.toggle_attunement) const ROW_PLATE_COL := UiKit.CAMP_ROW_PLATE var _ws_lock_l: Label # 锁定态行(ks_workshop 未点亮) var _ws_scroll: ScrollContainer # 秘宝匣内容容器(钥匙点亮后可见) var _workshop_holder: VBoxContainer # 秘宝匣行板(六行+同调装配段+Y 升级行;探针直读冻结) var _workshop_rows: Array[Dictionary] = [] # [{item_id, progress_l, status_l, buy_btn, price, unlocked, card}](探针消费) var _attune_header_l: Label # 同调装配段(Slots 点亮呈现) var _attune_btns: Array[Dictionary] = [] # [{item_id, btn, unlocked, equipped}]——装配/卸下切换钮(满位禁选随刷) var _attune_nodes: Array[Control] = [] # 段内节点册(open 重建时定向清理,不扰工坊行板) var _refresh_l: Label # Y 刷新升级行(双入口之一——树上 ks_refresh 同账) var _refresh_btn: Button func _init() -> void: var holder := VBoxContainer.new() holder.add_theme_constant_override("separation", 6) holder.custom_minimum_size = Vector2(0, 336) _ws_lock_l = UiKit.label("🔒 秘宝匣未开放\n\n点亮树上「秘宝匣」钥匙节点后展开。\n(前置=脊柱生命第 1 级)", UiKit.FS_HINT, false, UiKit.STONE_DEEP) _ws_lock_l.size_flags_vertical = Control.SIZE_SHRINK_CENTER _ws_lock_l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART _ws_lock_l.custom_minimum_size = Vector2(280, 0) _ws_scroll = ScrollContainer.new() _ws_scroll.custom_minimum_size = Vector2(300, 336) _ws_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL _ws_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED _workshop_holder = VBoxContainer.new() _workshop_holder.add_theme_constant_override("separation", 6) _ws_scroll.add_child(_workshop_holder) holder.add_child(_ws_lock_l) holder.add_child(_ws_scroll) var scroll := ScrollContainer.new() scroll.custom_minimum_size = Vector2(300, 336) scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED scroll.add_child(holder) _build_shell("秘宝匣", UiKit.TEX_ICON_WORKSHOP, scroll) func open_camp(data: Dictionary) -> void: _rebuild_workshop(data) # 行板+同调装配段+Y 尾行随开层全量重建(段尾链 _rebuild_attune) super.open_camp(data) func refresh_camp(data: Dictionary) -> void: var balance := int(data.get("balance", 0)) _balance_label.text = "钥石 %d" % balance _refresh_key_gate(data) _refresh_workshop_rows(data, balance) _refresh_attune_segment(data, balance) # 秘宝匣钥匙门(树上 ks_workshop 行 level 判据——零核心访问,纯视图读投影) func _refresh_key_gate(data: Dictionary) -> void: var lvl := 0 for row in (data.get("tree", []) as Array): if String(row.get("node_id", "")) == "ks_workshop": lvl = int(row.get("level", 0)) var open := lvl >= 1 _ws_lock_l.visible = not open _ws_scroll.visible = open func _rebuild_workshop(data: Dictionary) -> void: _workshop_rows.clear() for child in _workshop_holder.get_children(): child.queue_free() _attune_nodes.clear() _attune_btns.clear() _attune_header_l = null _refresh_l = null _refresh_btn = null for row in data.get("workshop", []): var card := _workshop_card(row) _workshop_holder.add_child(card["card"]) _workshop_rows.append(card) _rebuild_attune(data) ## 秘宝匣行板构建域:件名(分档后缀+效果摘要悬停)+进度列+状态列+钥石购买钮(购买件未解锁在列) func _workshop_card(row: Dictionary) -> Dictionary: var card := PanelContainer.new() card.add_theme_stylebox_override("panel", UiKit.panel_flat(ROW_PLATE_COL, UiKit.PANEL_BORDER, UiKit.RADIUS_OBJECT, 1, Vector4(10, 4, 10, 4))) var line := HBoxContainer.new() line.add_theme_constant_override("separation", 12) card.add_child(line) var tier_name := String(UiKit.TREASURE_TIER_LABELS.get(String(row.get("tier", "")), "")) var name_l := UiKit.label(String(row.get("display_name", "")) + ("·" + tier_name if tier_name != "" else ""), UiKit.FS_HINT, true, UiKit.INK) name_l.custom_minimum_size = Vector2(132, 0) name_l.clip_text = true name_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS name_l.tooltip_text = String(row.get("desc", "")) # 件效果摘要悬停可见(分档呈现契约——槽位档位=名后缀) line.add_child(name_l) var progress_l := UiKit.label("", UiKit.FS_HINT, false, UiKit.STONE_DEEP) progress_l.custom_minimum_size = Vector2(150, 0) progress_l.clip_text = true # 营地溢出修——文字全宽不撑柜 progress_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS line.add_child(progress_l) # 状态色用亮板双色墨制(深墨族替代暖白族)——色随刷新态条件赋值,非机械两行式 var status_l := UiKit.label("", UiKit.FS_HINT, true, UiKit.STONE_DEEP) status_l.clip_text = true # 「已解锁 · 能力封存中」不撑柜(余宽吸收者) status_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS status_l.size_flags_horizontal = Control.SIZE_EXPAND_FILL line.add_child(status_l) # 钥石购买钮(购买件且未解锁时在列;写口唯一=buy_treasure_unlock, # 经 buy_requested(item_id) 信号走 presenter 分支;禁用态=余额不足) var buy_btn: Button = null if not bool(row.get("unlocked", false)) and String(row.get("mode", "")) == "keystone": buy_btn = UiKit.button("购买 %d" % int(row.get("price", 0)), 0.0) # 行板紧凑钮豁免 buy_btn.pressed.connect(_emit_buy_requested.bind(String(row.get("item_id", "")))) line.add_child(buy_btn) return {"item_id": String(row.get("item_id", "")), "progress_l": progress_l, "status_l": status_l, "buy_btn": buy_btn, "price": int(row.get("price", 0)), "unlocked": bool(row.get("unlocked", false)), "card": card} # 同调装配段重建(秘宝匣尾部段:Slots 头行+九件行板+Y 升级尾行) func _rebuild_attune(data: Dictionary) -> void: var att: Dictionary[Variant, Variant] = data.get("attune", {}) _attune_header_l = UiKit.label("", UiKit.FS_HINT, true, UiKit.INK) _workshop_holder.add_child(_attune_header_l) _attune_nodes.append(_attune_header_l) for row in att.get("items", []): var line := _attune_item_line(row) _workshop_holder.add_child(line) _attune_nodes.append(line) _refresh_attune_tail() ## 同调件行板构建域:件名+形态+档位+装配切换钮(未解锁禁装——toggle 写口同守卫) func _attune_item_line(row: Dictionary) -> HBoxContainer: var iid := String(row.get("item_id", "")) var line := HBoxContainer.new() line.add_theme_constant_override("separation", 6) var name_l := UiKit.label(String(row.get("display_name", "")), UiKit.FS_HINT, true, UiKit.INK) name_l.custom_minimum_size = Vector2(96, 0) name_l.clip_text = true name_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS line.add_child(name_l) var form_l := UiKit.label("被动" if String(row.get("form", "")) == "passive" else "主动·充能", UiKit.FS_CAPTION, false, UiKit.STONE_DEEP) form_l.custom_minimum_size = Vector2(58, 0) line.add_child(form_l) # 档位标签(槽位档位显示:件档=普通/优秀/稀有,装入须件档≤格档契约 2 的呈现半边) var tier_l := UiKit.label(String(UiKit.TREASURE_TIER_LABELS.get(String(row.get("tier", "")), "—")), UiKit.FS_CAPTION, false, UiKit.STONE_DEEP) tier_l.custom_minimum_size = Vector2(36, 0) line.add_child(tier_l) var btn := UiKit.button("装配", 0.0) # 行板紧凑钮豁免 btn.pressed.connect(attune_toggled.emit.bind(iid)) btn.disabled = not bool(row.get("unlocked", false)) # 未解锁禁装(解锁为装配前置——toggle 写口同守卫) line.add_child(btn) _attune_btns.append({"item_id": iid, "btn": btn, "unlocked": bool(row.get("unlocked", false)), "equipped": bool(row.get("equipped", false))}) return line ## Y 升级尾行构建域(双入口之一——树上 ks_refresh 同账) func _refresh_attune_tail() -> void: var refresh_line := HBoxContainer.new() refresh_line.add_theme_constant_override("separation", 6) _refresh_l = UiKit.label("", UiKit.FS_CAPTION, false, UiKit.STONE_DEEP) refresh_line.add_child(_refresh_l) _refresh_btn = UiKit.button("升级", 0.0) # 行板紧凑钮豁免 _refresh_btn.pressed.connect(_emit_buy_requested.bind("attune_refresh")) refresh_line.add_child(_refresh_btn) _workshop_holder.add_child(refresh_line) _attune_nodes.append(refresh_line) ## 秘宝匣行板刷新域:进度列(解锁混合制三形态)与状态色(亮板双色墨制)与购买钮余额联动 func _refresh_workshop_rows(data: Dictionary, balance: int) -> void: var ws_by_id := {} for row in data.get("workshop", []): ws_by_id[String(row.get("item_id", ""))] = row for card in _workshop_rows: var row: Dictionary[Variant, Variant] = ws_by_id.get(String(card.item_id), {}) if row.is_empty(): continue var unlocked := bool(row.get("unlocked", false)) card.progress_l.text = _workshop_progress_text(row, unlocked) var st_text := "未解锁" var st_col: Color = UiKit.STONE_DEEP if unlocked: st_text = "已解锁" st_col = UiKit.GOLD_INK # GOLD_DEEP 对行板 3.00:1 越带,换深金墨 5.84:1 card.status_l.text = st_text card.status_l.add_theme_color_override("font_color", st_col) card.status_l.tooltip_text = st_text # 截断兜底(状态列 EXPAND_FILL 吸余宽——悬停看全文) # 购买钮余额联动(禁用态=余额不足,常态防线;pattern 1 兜底拒绝可见归 presenter) if card.get("buy_btn") != null: card.buy_btn.disabled = balance < int(card.get("price", 0)) ## 进度列文案域(解锁混合制三形态:钥石购买价/条件进度拼接/已入手) func _workshop_progress_text(row: Dictionary, unlocked: bool) -> String: if unlocked: return "已入手" var mode := String(row.get("mode", "")) if mode == "keystone": return "钥石 %d" % int(row.get("price", 0)) if mode == "condition": var parts: Array[String] = [] for req_v in (row.get("reqs", []) as Array): var req: Dictionary[Variant, Variant] = req_v as Dictionary var lbl := String(UiKit.TREASURE_REQ_LABELS.get(String(req.get("key", "")), "条件")) parts.append("%s %d/%d" % [lbl, int(req.get("progress", 0)), int(req.get("threshold", 0))]) return "、".join(parts) return "" ## 同调装配段刷新域(Slots 点亮/满位禁选/Y 升级行余额联动——就地刷) func _refresh_attune_segment(data: Dictionary, balance: int) -> void: var att: Dictionary[Variant, Variant] = data.get("attune", {}) if _attune_header_l != null: var slots_n := int(att.get("slots", 0)) var eq: Array[Variant] = att.get("equipped", []) _attune_header_l.text = "── 同调装配 Slots %d/%d ──" % [eq.size(), slots_n] var eq_set := {} for v in eq: eq_set[String(v)] = true for card2 in _attune_btns: var on: bool = eq_set.has(String(card2.item_id)) card2.btn.text = "卸下" if on else "装配" # 满位禁选+未解锁禁装(已装件恒可卸下;unlocked 快照随 rebuild 刷新) card2.btn.disabled = (not on and eq.size() >= slots_n) or (not on and not bool(card2.get("unlocked", false))) if _refresh_l != null: var rf: Dictionary[Variant, Variant] = att.get("refresh", {}) _refresh_l.text = "三选一刷新 +%d 次/冒险 · Lv%d" % [int(rf.get("charges", 0)), int(rf.get("level", 0))] _refresh_btn.text = "已满级" if bool(rf.get("maxed", false)) else "升级 %d 钥石" % int(rf.get("price", 0)) _refresh_btn.disabled = bool(rf.get("maxed", false)) or balance < int(rf.get("price", 0)) func _emit_buy_requested(attr: String) -> void: buy_requested.emit(attr)