556 lines
30 KiB
GDScript
556 lines
30 KiB
GDScript
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||
## 营地覆盖层:树淬炼/锻造工坊/秘宝匣/奖励格图鉴四柜台只读投影,购买与切换意图经信号外发。
|
||
## 柜台锁定态(钥匙未点亮)开/关零副作用——零写档零报错、锁定行纯文案无可交互购买路径。
|
||
## 开合与开始层互斥归组合根承接(打开前隐藏开始层)。
|
||
class_name CampOverlay
|
||
extends Control
|
||
|
||
signal buy_requested(attr: String)
|
||
signal closed
|
||
signal attune_toggled(item_id: String) ## 同调装配切换意图(写口唯一归 MetaProgression.toggle_attunement)
|
||
signal tree_buy_requested(node_id: String) ## 天赋树购买意图(写口唯一归 MetaProgression.tree_buy)
|
||
signal tree_fork_toggled(group: String, side: String) ## 双态切换意图(一节点双态——零币切态)
|
||
signal forge_buy_requested(route_id: String) ## 锻造路线购买意图(写口唯一归 MetaProgression.forge_buy_level)
|
||
signal forge_unlock_requested(route_id: String) ## 锻造路线解锁意图(写口唯一归 MetaProgression.forge_buy_unlock)
|
||
signal forge_select_requested(route_id: String) ## 锻造路线选中意图(写口唯一归 MetaProgression.forge_select_weapon/armor)
|
||
|
||
# 方向稿取色(柜台头带深棕/行板暖米;双色墨=亮板配深墨)——值单点收 UiKit
|
||
const HEADER_BAND_COL := UiKit.CAMP_HEADER_BAND
|
||
const ROW_PLATE_COL := UiKit.CAMP_ROW_PLATE
|
||
|
||
var _balance_label: Label
|
||
var _tree_holder: VBoxContainer # 树淬炼柜台内容(主界面——脊柱两行+钥匙/socket 行板+Fork 双态钮)
|
||
var _tree_buy_btns: Array[Dictionary] = [] # [{node_id, btn}]——树购买/解锁钮登记面(open/refresh 重建时刷新;探针消费)
|
||
var _tree_fork_btns: Array[Dictionary] = [] # [{group, side, btn}]——Fork 双态切换钮登记面(探针消费)
|
||
var _forge_lock_l: Label # 锻造工坊柜台锁定态行(钥匙未点亮)
|
||
var _forge_workbench: VBoxContainer # 锻造工坊工作间(钥匙点亮后展开——两区六路线行板)
|
||
var _forge_zone_weapon: VBoxContainer # 武器锻造区行板容器(三路线)
|
||
var _forge_zone_armor: VBoxContainer # 护甲锻造区行板容器(三路线)
|
||
var _forge_rows: Array[Dictionary] = [] # [{route_id, name_l, lvl_l, status_l, buy_btn, unlock_btn, select_btn, next_price, unlock_price}](open/refresh 重建;探针消费)
|
||
var _ws_scroll: ScrollContainer # 秘宝匣柜台内容容器(钥匙点亮后可见)
|
||
var _ws_lock_l: Label # 秘宝匣柜台锁定态行
|
||
var _workshop_holder: VBoxContainer # 秘宝匣柜台行板(六行+同调装配段+Y 升级行)
|
||
var _workshop_rows: Array[Dictionary] = [] # [{item_id, progress_l, status_l}]
|
||
var _attune_header_l: Label # 同调装配段(Slots 点亮呈现)
|
||
var _attune_btns: Array[Dictionary] = [] # [{item_id, btn}]——装配/卸下切换钮(满位禁选随刷)
|
||
var _attune_nodes: Array[Control] = [] # 段内节点册(open_camp 重建时定向清理,不扰工坊六行)
|
||
var _refresh_l: Label # Y 刷新升级行(双入口之一——树上 ks_refresh 同账)
|
||
var _refresh_btn: Button
|
||
var _recent_list: Label # 最近冒险(逐行拼接 label——只读账面,随 open_camp 重建)
|
||
|
||
|
||
func _init() -> void:
|
||
# 底=不透明暖亮档(半透 dim 会透出底下棋盘/战斗提示条,故须实底;demo #v-camp=bg-bright 实底,
|
||
# 遮蔽语义由组合根输入锁兜底)
|
||
var dim := UiKit.modal_dim(self, UiKit.BG_BRIGHT)
|
||
# 石纹弱质感+暗角(demo .tilebg 0.14/.vignette;缺位不挂即回退平色)
|
||
var bd := UiKit.backdrop_tile()
|
||
if bd != null:
|
||
dim.add_child(bd)
|
||
dim.add_child(UiKit.vignette_rect())
|
||
|
||
var root := MarginContainer.new()
|
||
root.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
root.add_theme_constant_override("margin_left", 16)
|
||
root.add_theme_constant_override("margin_right", 16)
|
||
root.add_theme_constant_override("margin_top", 12)
|
||
root.add_theme_constant_override("margin_bottom", 12)
|
||
dim.add_child(root)
|
||
var col := VBoxContainer.new()
|
||
col.add_theme_constant_override("separation", 8)
|
||
root.add_child(col)
|
||
|
||
# ── 顶栏:左铭牌(标题+副题)/ 右钥石徽 ──
|
||
var top := HBoxContainer.new()
|
||
top.add_theme_constant_override("separation", 10)
|
||
col.add_child(top)
|
||
var plaque := PanelContainer.new()
|
||
plaque.add_theme_stylebox_override("panel",
|
||
UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER,UiKit.RADIUS_OBJECT, 1, Vector4(14, 8, 14, 8)))
|
||
top.add_child(plaque)
|
||
var plaque_col := VBoxContainer.new()
|
||
plaque.add_child(plaque_col)
|
||
var title := UiKit.label("营地 · 天赋树",UiKit.FS_TITLE, true, UiKit.INK_TITLE)
|
||
plaque_col.add_child(title)
|
||
var sub := UiKit.label("营地本身即树——钥石逐节点点亮,属性/规则/工作间都在树上",UiKit.FS_HINT, false, UiKit.STONE_DEEP) # STONE 对铭牌板 3.59:1 越带,换灰石深化 5.98:1
|
||
plaque_col.add_child(sub)
|
||
var spacer := Control.new()
|
||
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
top.add_child(spacer)
|
||
# 钥石徽章烛光黄底+深墨字(demo ②)。UiKit.keystone_badge 工厂单点
|
||
var badge := UiKit.keystone_badge("钥石 0")
|
||
top.add_child(badge)
|
||
_balance_label = badge.value_label
|
||
|
||
# ── 四柜台并排(程序占位重排)──
|
||
# 宽度不足横向滚动(四柜 300 min 宽 > 窄窗可用宽时横滚兜底;纵向撑满余高)
|
||
var camp_scroll := ScrollContainer.new()
|
||
camp_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
camp_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_AUTO
|
||
camp_scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||
col.add_child(camp_scroll)
|
||
var mid := HBoxContainer.new()
|
||
mid.add_theme_constant_override("separation", 14)
|
||
mid.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
camp_scroll.add_child(mid)
|
||
|
||
# 柜一·树淬炼(树脊柱界面形态=主界面:一线一节点行板内升级+Fork 双态切换钮)
|
||
_tree_holder = VBoxContainer.new()
|
||
_tree_holder.add_theme_constant_override("separation", 6)
|
||
var tree_scroll := ScrollContainer.new()
|
||
tree_scroll.custom_minimum_size = Vector2(300, 336)
|
||
tree_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
tree_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||
tree_scroll.add_child(_tree_holder)
|
||
var tree_panel := _counter_panel("天赋树 · 淬炼", "", tree_scroll, 300)
|
||
tree_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
tree_panel.size_flags_vertical = Control.SIZE_FILL # 四栏等高:任一栏长高时四栏同步拉伸
|
||
mid.add_child(tree_panel)
|
||
|
||
# 柜二·锻造工坊(钥匙门——forge_workshop 点亮前锁定态;点亮=工作间两区六路线行板)
|
||
_forge_lock_l = UiKit.label("🔒 锻造工坊未开放\n\n点亮树上「锻造工坊」钥匙节点后展开工作间。\n(钥匙免费、无前置——树上直接点亮)",UiKit.FS_HINT, false, UiKit.STONE_DEEP)
|
||
_forge_lock_l.size_flags_vertical = Control.SIZE_SHRINK_CENTER # 锁文在等高栏内垂直居中
|
||
_forge_lock_l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
_forge_lock_l.custom_minimum_size = Vector2(280, 0)
|
||
_forge_workbench = VBoxContainer.new()
|
||
_forge_workbench.add_theme_constant_override("separation", 6)
|
||
_forge_workbench.visible = false # 默认锁定态(钥匙门未判前零行板——门开才建)
|
||
_forge_zone_weapon = VBoxContainer.new()
|
||
_forge_zone_weapon.add_theme_constant_override("separation", 4)
|
||
_forge_zone_armor = VBoxContainer.new()
|
||
_forge_zone_armor.add_theme_constant_override("separation", 4)
|
||
var wb_wh := UiKit.label("── 武器锻造区 ──",UiKit.FS_HINT, true, UiKit.GOLD_INK) # 同款深金墨(对柜台板 5.21:1)
|
||
_forge_workbench.add_child(wb_wh)
|
||
_forge_workbench.add_child(_forge_zone_weapon)
|
||
var wb_ah := UiKit.label("── 护甲锻造区 ──",UiKit.FS_HINT, true, UiKit.GOLD_INK)
|
||
_forge_workbench.add_child(wb_ah)
|
||
_forge_workbench.add_child(_forge_zone_armor)
|
||
var forge_holder := VBoxContainer.new()
|
||
forge_holder.add_theme_constant_override("separation", 6)
|
||
forge_holder.custom_minimum_size = Vector2(0, 336) # 四栏等高(与树/秘宝匣 scroll 336 min 对齐——锁定态仅锁文也撑住栏高)
|
||
forge_holder.add_child(_forge_lock_l)
|
||
forge_holder.add_child(_forge_workbench)
|
||
var forge_panel := _counter_panel("锻造工坊", UiKit.TEX_ICON_FORGE, forge_holder, 300)
|
||
forge_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
forge_panel.size_flags_vertical = Control.SIZE_FILL # 四栏等高:任一栏长高时四栏同步拉伸
|
||
mid.add_child(forge_panel)
|
||
|
||
# 柜三·秘宝匣(钥匙门——ks_workshop 点亮前锁定态;点亮=现工坊内容整体迁入:六行+同调装配段+Y 升级行)
|
||
_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)
|
||
var ws_holder := VBoxContainer.new()
|
||
ws_holder.add_theme_constant_override("separation", 6)
|
||
ws_holder.custom_minimum_size = Vector2(0, 336) # 四栏等高(锁定态 scroll 隐、仅锁文也撑住栏高)
|
||
ws_holder.add_child(_ws_lock_l)
|
||
ws_holder.add_child(_ws_scroll)
|
||
var ws_panel := _counter_panel("秘宝匣", UiKit.TEX_ICON_WORKSHOP, ws_holder, 300)
|
||
ws_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
ws_panel.size_flags_vertical = Control.SIZE_FILL # 四栏等高:任一栏长高时四栏同步拉伸
|
||
mid.add_child(ws_panel)
|
||
|
||
# 柜四·奖励格图鉴(常开独立位——结构位预留,内容排 Alpha)
|
||
var atlas_holder := VBoxContainer.new()
|
||
atlas_holder.custom_minimum_size = Vector2(0, 336) # 四栏等高(图鉴仅锁文也撑住栏高)
|
||
var atlas_panel := _counter_panel("奖励格图鉴", UiKit.TEX_ICON_ATLAS, atlas_holder, 300)
|
||
atlas_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
atlas_panel.size_flags_vertical = Control.SIZE_FILL # 四栏等高:任一栏长高时四栏同步拉伸
|
||
mid.add_child(atlas_panel)
|
||
var lock_l := UiKit.label(" —— 柜台结构位预留:内容排 Alpha(地图战斗资源差异化批)",UiKit.FS_HINT, false, UiKit.STONE_DEEP)
|
||
lock_l.size_flags_vertical = Control.SIZE_SHRINK_CENTER # 锁文在等高栏内垂直居中
|
||
lock_l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
lock_l.custom_minimum_size = Vector2(280, 0)
|
||
atlas_holder.add_child(lock_l)
|
||
|
||
# ── 底栏:关闭钮(左)/ 操作注记(右)+ 最近冒险条带 ──
|
||
var close_btn := UiKit.button("关闭营地(回开始界面)")
|
||
close_btn.pressed.connect(_emit_closed)
|
||
var bottom := HBoxContainer.new()
|
||
bottom.add_theme_constant_override("separation", 10)
|
||
bottom.add_child(close_btn)
|
||
var spacer2 := Control.new()
|
||
spacer2.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
bottom.add_child(spacer2)
|
||
var caption := UiKit.label("树上点亮 · 工作间随钥匙展开 · 图鉴常开",UiKit.FS_HINT, false, UiKit.WARM_SOFT)
|
||
bottom.add_child(caption)
|
||
col.add_child(bottom)
|
||
_recent_list = UiKit.label("",UiKit.FS_HINT, false, UiKit.WARM_SOFT)
|
||
_recent_list.clip_text = true
|
||
_recent_list.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||
col.add_child(_recent_list)
|
||
visible = false
|
||
|
||
|
||
# 柜台列面板(方向稿同构):PanelContainer 米板身 + 头带深棕(图标+标题暖白)
|
||
func _counter_panel(title_text: String, icon_path: String, holder: Control, min_w: int) -> PanelContainer:
|
||
var panel := PanelContainer.new()
|
||
panel.custom_minimum_size = Vector2(min_w, 0)
|
||
panel.add_theme_stylebox_override("panel",
|
||
UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER,UiKit.RADIUS_OBJECT, 1, Vector4(10, 10, 10, 10)))
|
||
var vbox := VBoxContainer.new()
|
||
vbox.add_theme_constant_override("separation", 8)
|
||
panel.add_child(vbox)
|
||
var band := PanelContainer.new()
|
||
band.add_theme_stylebox_override("panel", UiKit.panel_flat(HEADER_BAND_COL, UiKit.PANEL_BORDER,UiKit.RADIUS_OBJECT, 1, Vector4(10, 5, 10, 5)))
|
||
vbox.add_child(band)
|
||
var head := HBoxContainer.new()
|
||
head.add_theme_constant_override("separation", 6)
|
||
band.add_child(head)
|
||
if icon_path != "":
|
||
var ic := UiKit.icon_rect(icon_path, 32) # 柜台头带 64 源 0.5×;缺位 null → 纯文本回退
|
||
if ic != null:
|
||
head.add_child(ic)
|
||
var l := UiKit.label(title_text,UiKit.FS_BODY, true, UiKit.WARM)
|
||
head.add_child(l)
|
||
vbox.add_child(holder)
|
||
return panel
|
||
|
||
|
||
# ---- 柜台行构建(open_camp 重建;行结构仿 ShopOverlay 商品卡——亮板双色墨制)----
|
||
|
||
# 天赋树段重建(树淬炼柜台主界面:脉分组行板:节点名/等级/价/购买钮 + Fork 双态切换钮;
|
||
# 树数据 = 组合根 _camp_data().tree 投影 [{node_id, display_name, vein, kind, level, price, can_buy,
|
||
# currency}...] + fork_groups [{group, node_id, active_side, owned, can_buy, price, sides:[{side, display_name, effects}]}])
|
||
func _rebuild_tree(data: Dictionary) -> void:
|
||
for c in _tree_holder.get_children():
|
||
c.queue_free()
|
||
_tree_buy_btns.clear()
|
||
_tree_fork_btns.clear()
|
||
var tree: Array[Variant] = data.get("tree", []) as Array
|
||
var forks: Array[Variant] = data.get("fork_groups", []) as Array
|
||
var vein_names := {"spine": "·脊柱淬炼·", "forge": "·锻造脉·", "keystone": "·钥石脉·", "attune": "·同调脉·"}
|
||
var last_vein := ""
|
||
for row in tree:
|
||
var vein := String(row.get("vein", ""))
|
||
if vein != last_vein:
|
||
last_vein = vein
|
||
var vh := UiKit.label(String(vein_names.get(vein, vein)),UiKit.FS_HINT, true, UiKit.GOLD_INK) # GOLD_DEEP 对柜台板 2.67:1 越带,换深金墨 5.21:1
|
||
_tree_holder.add_child(vh)
|
||
var hb := HBoxContainer.new()
|
||
hb.add_theme_constant_override("separation", 6)
|
||
var lvl := int(row.get("level", 0))
|
||
var cap := int(row.get("cap", 1))
|
||
var name_l := UiKit.label("%s Lv%d/%d" % [String(row.get("display_name", "")), lvl, cap], 13, false, UiKit.INK_LABEL)
|
||
name_l.custom_minimum_size = Vector2(150, 0)
|
||
name_l.clip_text = true
|
||
var gate := String(row.get("gate_text", ""))
|
||
if gate != "":
|
||
name_l.text += " 🔒" # 等级阈值前置未达标标记(R1 机制位——悬停看门槛;达标后随 refresh 消失)
|
||
name_l.tooltip_text = "等级门槛:%s" % gate
|
||
hb.add_child(name_l)
|
||
if String(row.get("kind", "")) == "fork":
|
||
continue # Fork 行去行板化——由 fork_groups 段承载双态切换钮
|
||
var btn := UiKit.button("购买 %d 钥石" % int(row.get("price", 0)), 0.0) # 柜台行板紧凑钮豁免宽度基准
|
||
btn.pressed.connect(func(): tree_buy_requested.emit(String(row.get("node_id", ""))))
|
||
btn.disabled = not bool(row.get("can_buy", false))
|
||
hb.add_child(btn)
|
||
_tree_buy_btns.append({"node_id": String(row.get("node_id", "")), "btn": btn})
|
||
_tree_holder.add_child(hb)
|
||
for fg in forks:
|
||
var group := String(fg.get("group", ""))
|
||
var active := String(fg.get("active_side", ""))
|
||
var owned := bool(fg.get("owned", false))
|
||
var fb := HBoxContainer.new()
|
||
fb.add_theme_constant_override("separation", 6)
|
||
var state_txt := "未解锁"
|
||
if owned:
|
||
state_txt = "激活:%s" % active
|
||
var gl := UiKit.label("%s[%s]" % [String(fg.get("display_name", group)), state_txt], 13, false, UiKit.INK_LABEL)
|
||
fb.add_child(gl)
|
||
if not owned:
|
||
var bb := UiKit.button("解锁 %d 钥石" % int(fg.get("price", 0)), 0.0) # 行板紧凑钮豁免
|
||
bb.pressed.connect(func(): tree_buy_requested.emit(String(fg.get("node_id", ""))))
|
||
bb.disabled = not bool(fg.get("can_buy", false))
|
||
fb.add_child(bb)
|
||
_tree_buy_btns.append({"node_id": String(fg.get("node_id", "")), "btn": bb})
|
||
else:
|
||
for side_row in (fg.get("sides", []) as Array):
|
||
var sd := String(side_row.get("side", ""))
|
||
var sb := UiKit.button(String(side_row.get("display_name", sd)), 0.0) # 行板紧凑钮豁免
|
||
sb.pressed.connect(func(): tree_fork_toggled.emit(group, sd))
|
||
fb.add_child(sb)
|
||
_tree_fork_btns.append({"group": group, "side": sd, "btn": sb})
|
||
_tree_holder.add_child(fb)
|
||
|
||
|
||
# 工作间钥匙门(锻造工坊/秘宝匣柜台锁定态切换:节点点亮=展开、未点亮=锁定行;
|
||
# 图鉴常开无门槛。判据读树投影行 level——零核心访问,纯视图)
|
||
func _refresh_key_gates(data: Dictionary) -> void:
|
||
var lvl_by_id := {}
|
||
for row in (data.get("tree", []) as Array):
|
||
lvl_by_id[String(row.get("node_id", ""))] = int(row.get("level", 0))
|
||
var forge_open := int(lvl_by_id.get("forge_workshop", 0)) >= 1
|
||
var ws_open := int(lvl_by_id.get("ks_workshop", 0)) >= 1
|
||
_forge_lock_l.visible = not forge_open
|
||
_forge_workbench.visible = forge_open
|
||
_ws_lock_l.visible = not ws_open
|
||
_ws_scroll.visible = ws_open
|
||
|
||
|
||
# 锻造工坊行板重建(两区六路线;open_camp/refresh_camp 调用。行结构仿秘宝匣行板:
|
||
# 件名+等级+解锁态+购买/解锁/选中钮。UI 只发意图信号〔契约 9——零核心写调用、零 MetaProgression
|
||
# 持有〕;钮禁用=常态防线(余额/未解锁/满级/已选中),兜底拒绝可见归 presenter pattern 1)
|
||
func _rebuild_forge(data: Dictionary) -> void:
|
||
for zone in [_forge_zone_weapon, _forge_zone_armor]:
|
||
for c in zone.get_children():
|
||
c.queue_free()
|
||
_forge_rows.clear()
|
||
if not _forge_workbench.visible:
|
||
return # 锁定态(钥匙未点亮):零行板零购买路径——占位纪律延续
|
||
var bal := int(data.get("balance", 0))
|
||
for row in data.get("forge", []):
|
||
var rid := String(row.get("route_id", ""))
|
||
var kind := String(row.get("kind", ""))
|
||
var zone: VBoxContainer = _forge_zone_weapon if kind == "weapon" else _forge_zone_armor
|
||
var unlocked := bool(row.get("unlocked", false))
|
||
var initial := bool(row.get("initial_owned", false))
|
||
var lvl := int(row.get("level", 0))
|
||
var cap_v := int(row.get("cap", 0))
|
||
var next_price := int(row.get("next_price", 0))
|
||
var unlock_price := int(row.get("unlock_price", 0))
|
||
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", 6)
|
||
card.add_child(line)
|
||
var name_l := UiKit.label(String(row.get("display_name", "")), UiKit.FS_HINT, true, UiKit.INK)
|
||
name_l.custom_minimum_size = Vector2(104, 0)
|
||
name_l.clip_text = true
|
||
name_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||
line.add_child(name_l)
|
||
var lvl_l := UiKit.label("Lv%d/%d" % [lvl, cap_v], 13, false, UiKit.INK_LABEL)
|
||
lvl_l.custom_minimum_size = Vector2(44, 0)
|
||
line.add_child(lvl_l)
|
||
# 解锁态/解锁通道呈现(并联双通道:练满前一档 or 一次性解锁价直购)
|
||
var st_text := "已解锁"
|
||
if not unlocked:
|
||
if initial:
|
||
st_text = "未解锁"
|
||
else:
|
||
st_text = "未解锁 · 练满%s 或 %d 钥石直购" % [String(row.get("prereq_name", "")), unlock_price]
|
||
elif lvl >= cap_v:
|
||
st_text = "已满级"
|
||
var status_l := UiKit.label(st_text, UiKit.FS_HINT, false, 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)
|
||
# 购买钮(已解锁未满级——几何价显示;余额不足禁用)
|
||
var buy_btn: Button = null
|
||
if unlocked and lvl < cap_v:
|
||
buy_btn = UiKit.button("锻造 %d" % next_price, 0.0) # 行板紧凑钮豁免
|
||
buy_btn.pressed.connect(forge_buy_requested.emit.bind(rid))
|
||
buy_btn.disabled = bal < next_price
|
||
line.add_child(buy_btn)
|
||
# 解锁钮(待解锁非初始——一次性解锁价)
|
||
var unlock_btn: Button = null
|
||
if not unlocked and not initial:
|
||
unlock_btn = UiKit.button("解锁 %d" % unlock_price, 0.0) # 行板紧凑钮豁免
|
||
unlock_btn.pressed.connect(forge_unlock_requested.emit.bind(rid))
|
||
unlock_btn.disabled = bal < unlock_price
|
||
line.add_child(unlock_btn)
|
||
# 选中钮(已解锁——免费切换;当前=禁用态标记)
|
||
var select_btn: Button = null
|
||
if unlocked:
|
||
var sel := bool(row.get("selected", false))
|
||
select_btn = UiKit.button("当前" if sel else "选中", 0.0) # 行板紧凑钮豁免
|
||
select_btn.pressed.connect(forge_select_requested.emit.bind(rid))
|
||
select_btn.disabled = sel
|
||
line.add_child(select_btn)
|
||
zone.add_child(card)
|
||
_forge_rows.append({"route_id": rid, "name_l": name_l, "lvl_l": lvl_l, "status_l": status_l,
|
||
"buy_btn": buy_btn, "unlock_btn": unlock_btn, "select_btn": select_btn,
|
||
"next_price": next_price, "unlock_price": unlock_price})
|
||
|
||
|
||
# 打开营地(全量重建四柜台行 + 同调装配段 + 最近冒险;树段与钥匙门随 refresh_camp 重建——
|
||
# 数据 = 组合根 _camp_data() 投影)
|
||
func open_camp(data: Dictionary) -> void:
|
||
_rebuild_workshop(data) # 段尾链 _rebuild_attune(同调装配段+Y 升级行随秘宝匣柜台内容)
|
||
_rebuild_forge(data) # 锻造工坊两区六路线行板
|
||
_rebuild_recent(data)
|
||
refresh_camp(data)
|
||
visible = true
|
||
|
||
|
||
# 状态重刷(购买后调用——余额/树行板/钥匙门/工坊状态就地重建或改文案;树行板随刷重建=按钮
|
||
# 禁用态与门槛/cap 同步,12 行重建开销可忽略)
|
||
func refresh_camp(data: Dictionary) -> void:
|
||
var balance := int(data.get("balance", 0))
|
||
_balance_label.text = "钥石 %d" % balance
|
||
_rebuild_tree(data)
|
||
_refresh_key_gates(data)
|
||
_rebuild_forge(data) # 行板随刷重建(购买/解锁/选中后等级/价/禁用态同步)
|
||
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))
|
||
# 秘宝匣进度列(解锁混合制三形态:钥石购买价/条件进度拼接/已入手)
|
||
var prog_text := ""
|
||
var mode := String(row.get("mode", ""))
|
||
if unlocked:
|
||
prog_text = "已入手"
|
||
elif mode == "keystone":
|
||
prog_text = "钥石 %d" % int(row.get("price", 0))
|
||
elif 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))])
|
||
prog_text = "、".join(parts)
|
||
card.progress_l.text = prog_text
|
||
# 状态色用亮板双色墨制(深墨族替代暖白族——亮板上暖白不可读)
|
||
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))
|
||
# 同调装配段状态就地刷(Slots 点亮/满位禁选/Y 升级行余额联动)
|
||
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 _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 := 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)
|
||
_workshop_holder.add_child(card)
|
||
_workshop_rows.append({"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))})
|
||
_rebuild_attune(data)
|
||
|
||
|
||
# 同调装配段重建(秘宝匣柜台尾部段;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 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)
|
||
_workshop_holder.add_child(line)
|
||
_attune_nodes.append(line)
|
||
_attune_btns.append({"item_id": iid, "btn": btn, "unlocked": bool(row.get("unlocked", false)), "equipped": bool(row.get("equipped", false))})
|
||
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 _rebuild_recent(data: Dictionary) -> void:
|
||
var lines: Array[String] = []
|
||
var recent: Array[Variant] = data.get("recent", [])
|
||
if recent.is_empty():
|
||
lines.append(" 最近冒险:(暂无记录——完成或结束一次冒险后入账)")
|
||
for s in recent:
|
||
var verdict := "失败"
|
||
if String(s.get("result", "")) == "completed":
|
||
verdict = "完成"
|
||
elif String(s.get("result", "")) == "abandoned":
|
||
verdict = "放弃"
|
||
lines.append("最近冒险:%s · 完成 %d 关 · 钥石 +%d · 种子 %d" % [verdict,
|
||
int(s.get("levels_cleared", 0)), int(s.get("keystone_total", 0)), int(s.get("master_seed", 0))])
|
||
_recent_list.text = " | ".join(lines)
|
||
|
||
|
||
func _emit_closed() -> void:
|
||
closed.emit()
|
||
|
||
|
||
func _emit_buy_requested(attr: String) -> void:
|
||
buy_requested.emit(attr)
|