- ui_kit.gd 新增 KeystoneBadge 徽章工厂件(Q10 甲裁决收编):钥石徽形制参数化 (烛光黄底 CANDLE+木描边 CAMP_HEADER_BAND+icon_rect 守卫装载缺位纯文本回退+INK_ON_GOLD 深墨字,value_label/set_text 面),消费方=营地覆盖层+暂停层——零新素材零新色值,值全取现行 token - camp_overlay_view.gd 钥石徽本地拼装(原 :79-92 panel_flat+icon_rect+label 三段)迁 UiKit.keystone_badge 工厂调用,形制参数逐项同原装配=观感零变化,消单点重复 - 新增 presentation/views/pause_overlay.gd:局内暂停层·只读——标题区+钥石余额行(badge 工厂件) +属性锻造三行已购等级/道具工坊六行解锁态(文案判据同营地柜台:退役>封存>已解锁>未解锁) /天赋树已购概览(已购节点等级+Fork 激活侧聚合文案,空态句)+「继续」关闭钮(代拍板:紧凑 90 返回钮形制沿用设置面先例);全部经 UiKit 工厂拼装(modal_shell/panel_skinned/label/button/ keystone_badge)零裸色值零自建样式(R1);零 tween 零 AnimationPlayer=零动效直开直关(R5/P4 甲) - main.gd 接线:PauseOverlay 实例化+登记 modal_stack 登记册设置层同档(确认弹窗之前——P2 甲, 登记序=树序=z 序纪律段同步换代)+closed 接 _on_pause_closed(模态栈单口收口)+ _pause_data() 快照投影(R4:实时读 MetaProgression 访问器 keystone_balance/forge_level/ workshop_status/tree_camp_rows/tree_fork_groups,行序复用营地屏同序〔MetaTable.FORGE_ATTRS〕, 快照仅单次开层生命周期、每次开层重建)+_open_pause_layer() 开层口(_open_modal 单口) - 纯呈现零数值改动=零 MC;核心层零改动(meta_progression/save_manager 未触碰) - 轻闸:冒烟 783/0 + ui_struct_probe PASS(基线对拍 T0:783/0 零漂移)
445 lines
23 KiB
GDScript
445 lines
23 KiB
GDScript
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||
# Date: 2026-09-21(元进度实现批——营地覆盖层呈现;2026-09-30 迭代 9 卡⑭ 视觉终稿——对照营地屏
|
||
# 方向稿 artwork/mockups/v3-draft-2026-09-29/4-camp.png 重排:石纹全屏底 + 顶栏铭牌/钥石徽 +
|
||
# 三柜台列(属性锻造/道具工坊/奖励格图鉴,头带深棕+米色板身+内嵌行板,暗底→亮板双色墨制)+
|
||
# 底栏关闭钮/操作注记;最近冒险收底栏条带。信号面/投影契约零变化:buy_requested(attr)/closed、
|
||
# open_camp(data)/refresh_camp(data)、_forge_rows 行结构 {attr,level_l,price_l,btn}(探针依赖)。
|
||
# 挂接方式:开合与开始层互斥仿设置面先例(开始覆盖层非模态且树序在后——打开本层前由组合根
|
||
# 隐藏开始层,规范 2.3 遮挡纪律)。
|
||
# 高内聚边界:纯视图 setter 面——账本快照经组合根喂入(MetaProgression 只读投影),购买/关闭
|
||
# 意图经信号外发——零核心写调用、零 MetaProgression 持有。
|
||
class_name CampOverlay
|
||
extends Control
|
||
|
||
signal buy_requested(attr: String)
|
||
signal closed
|
||
signal attune_toggled(item_id: String) # 同调装配切换意图(迭代 11 第①批 T4——写口唯一归 MetaProgression.toggle_attunement)
|
||
signal tree_buy_requested(node_id: String) # 天赋树购买意图(迭代 12 T9——写口唯一归 MetaProgression.tree_buy)
|
||
signal tree_fork_toggled(group: String, side: String) # A/B 免费切换意图(同组换侧零币)
|
||
|
||
const ATTR_LABELS := {"atk": "攻击", "max_hp": "生命上限", "armor_class": "护甲"} # 档位文案轴(数据契约 attr 词表,与 ShopOverlay 同款)
|
||
# 方向稿取色(4-camp.png——柜台头带深棕/行板暖米;双色墨=亮板配深墨)——值单点收 UiKit(T4 裸色收口批)
|
||
const HEADER_BAND_COL := UiKit.CAMP_HEADER_BAND
|
||
const ROW_PLATE_COL := UiKit.CAMP_ROW_PLATE
|
||
const BADGE_PANEL_COL := UiKit.CAMP_BADGE_PANEL
|
||
|
||
var _balance_label: Label
|
||
var _forge_holder: VBoxContainer
|
||
var _workshop_holder: VBoxContainer
|
||
var _tree_holder: VBoxContainer # 天赋树面板(迭代 12 T9 程序占位——行板复用行板结构)
|
||
var _forge_rows: Array[Dictionary] = [] # [{attr, level_l, price_l, btn}]——refresh_camp 就地重刷(零重建回流)
|
||
var _workshop_rows: Array[Dictionary] = [] # [{item_id, progress_l, status_l}]
|
||
var _attune_header_l: Label # 同调装配段(迭代 11 第①批 T4 工坊扩容——Slots 点亮呈现)
|
||
var _attune_btns: Array[Dictionary] = [] # [{item_id, btn}]——装配/卸下切换钮(满位禁选随刷)
|
||
var _attune_nodes: Array[Control] = [] # 段内节点册(open_camp 重建时定向清理,不扰工坊六行)
|
||
var _refresh_l: Label # Y 刷新升级行(P1 甲——每冒险开局给 Y 次、冒险周期池)
|
||
var _refresh_btn: Button
|
||
var _recent_list: Label # 最近冒险(逐行拼接 label——只读账面,随 open_camp 重建)
|
||
|
||
|
||
func _init() -> void:
|
||
# 底=不透明暖亮档(2026-10-06 用户实测缺陷修——原形制=0.45 半透 dim+满铺石纹遮底,T5 石纹降 0.14
|
||
# 后底下屏幕透出〔用户实机:对局中开营地见棋盘/战斗提示条〕;demo #v-camp=bg-bright 实底,
|
||
# 遮蔽语义由组合根输入锁兜底)
|
||
var dim := UiKit.modal_dim(self, UiKit.BG_BRIGHT)
|
||
# 石纹弱质感+暗角(UI 对齐批 T5 2026-10-06——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)
|
||
plaque_col.add_child(sub)
|
||
var spacer := Control.new()
|
||
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
top.add_child(spacer)
|
||
# M3(UI 落码批):钥石徽章烛光黄底+深墨字(demo ②——旧暗底 CAMP_BADGE_PANEL 退役;
|
||
# BADGE_PANEL_COL 常量保留他用不动)。Q10 甲(2026-10-06):本地拼装迁 UiKit.keystone_badge
|
||
# 工厂调用(badge 单点化——暂停层同款消费);形制参数逐项同原装配,观感零变化
|
||
var badge := UiKit.keystone_badge("钥石 0")
|
||
top.add_child(badge)
|
||
_balance_label = badge.value_label
|
||
|
||
# ── 三柜台列(方向稿同构:头带深棕+米色板身)──
|
||
# 三柜等宽分配(2026-10-06 用户实测缺陷修——原定宽 356+行标签按文字全宽算 min,进度行文案
|
||
# 「累计通关 28/2 次·能力封存中」随档变长把面板撑爆,三柜总宽溢出 1248 可用宽=右柜裁出屏;
|
||
# 改=EXPAND_FILL 等比分屏宽+行标签截断兜底,min 恒 ≤ 定宽)
|
||
var mid := HBoxContainer.new()
|
||
mid.add_theme_constant_override("separation", 18)
|
||
mid.alignment = BoxContainer.ALIGNMENT_CENTER
|
||
mid.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
col.add_child(mid)
|
||
_forge_holder = VBoxContainer.new()
|
||
_forge_holder.add_theme_constant_override("separation", 6)
|
||
var forge_panel := _counter_panel("属性锻造", UiKit.TEX_ICON_FORGE, _forge_holder, 356)
|
||
forge_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
mid.add_child(forge_panel)
|
||
# 迭代 11 第①批 T4 工坊扩容(P2 甲程序占位——同调装配段+Y 升级行入工坊柜台,行板复用;行数增长以
|
||
# 柜台内滚承载,冻结三柜台版式与另两柜台零触碰,视觉终稿归营地批)
|
||
var ws_scroll := ScrollContainer.new()
|
||
ws_scroll.custom_minimum_size = Vector2(340, 336)
|
||
ws_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL # 2026-10-06 用户实测修——纵向撑满面板余高(固定 336=行断流但面板下半空着,滚动条提前出现)
|
||
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_panel := _counter_panel("道具工坊", UiKit.TEX_ICON_WORKSHOP, ws_scroll, 356)
|
||
ws_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
mid.add_child(ws_panel)
|
||
var atlas_holder := VBoxContainer.new()
|
||
var atlas_panel := _counter_panel("奖励格图鉴", UiKit.TEX_ICON_ATLAS, atlas_holder, 356)
|
||
atlas_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
mid.add_child(atlas_panel)
|
||
var lock_l := UiKit.label(" —— 柜台结构位预留:内容排 Alpha(地图战斗资源差异化批)",UiKit.FS_HINT, false, UiKit.STONE)
|
||
lock_l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
lock_l.custom_minimum_size = Vector2(300, 0)
|
||
atlas_holder.add_child(lock_l)
|
||
# 天赋树面板(迭代 12 T9——程序占位:图鉴柜台下段承载树行〔脉分组行板+购买/切换钮〕;
|
||
# 「三柜台→一树」字面重构归营地视觉批披露,本批功能重构程序占位先行)
|
||
_tree_holder = VBoxContainer.new()
|
||
_tree_holder.add_theme_constant_override("separation", 6)
|
||
var tree_scroll := ScrollContainer.new()
|
||
tree_scroll.custom_minimum_size = Vector2(300, 200)
|
||
tree_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL # 2026-10-06 同修——树段内滚撑满图鉴柜余高
|
||
tree_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||
tree_scroll.add_child(_tree_holder)
|
||
atlas_holder.add_child(tree_scroll)
|
||
|
||
# ── 底栏:关闭钮(左)/ 操作注记(右)+ 最近冒险条带 ──
|
||
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) # P6 甲②(2026-10-06):柜台头带 64 源 0.5×(原 20 违 §8.2);缺位 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 商品卡——亮板双色墨制)----
|
||
|
||
# 天赋树段重建(迭代 12 T9 程序占位——脉分组行板:节点名/等级/价/购买钮 + Fork 组侧切换钮;
|
||
# 树数据 = 组合根 _camp_data().tree 投影 [{node_id, display_name, vein, kind, level, price, can_buy,
|
||
# currency}...] + fork_groups [{group, active_side, sides:[{node_id, side, display_name, owned}]}])
|
||
func _rebuild_tree(data: Dictionary) -> void:
|
||
for c in _tree_holder.get_children():
|
||
c.queue_free()
|
||
var tree: Array = data.get("tree", []) as Array
|
||
var forks: Array = 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_DEEP)
|
||
_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
|
||
hb.add_child(name_l)
|
||
if String(row.get("kind", "")) == "fork":
|
||
continue # Fork 行去 UI 化——由 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_holder.add_child(hb)
|
||
for fg in forks:
|
||
var group := String(fg.get("group", ""))
|
||
var active := String(fg.get("active_side", ""))
|
||
var fb := HBoxContainer.new()
|
||
fb.add_theme_constant_override("separation", 6)
|
||
var gl := UiKit.label("%s[激活:%s]" % [String(fg.get("display_name", group)), active if active != "" else "未激活"], 13, false, UiKit.INK_LABEL)
|
||
fb.add_child(gl)
|
||
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))
|
||
sb.disabled = not bool(side_row.get("owned", false))
|
||
fb.add_child(sb)
|
||
_tree_holder.add_child(fb)
|
||
|
||
|
||
# 打开营地(全量重建三柜台行 + 同调装配段 + 天赋树段 + 最近冒险——数据 = 组合根 _camp_data() 投影)
|
||
func open_camp(data: Dictionary) -> void:
|
||
_rebuild_forge(data)
|
||
_rebuild_workshop(data)
|
||
_rebuild_attune(data)
|
||
_rebuild_tree(data)
|
||
_rebuild_recent(data)
|
||
refresh_camp(data)
|
||
visible = true
|
||
|
||
|
||
# 状态重刷(购买后调用——余额/价格/禁用态/工坊状态就地改文案,零重建)
|
||
func refresh_camp(data: Dictionary) -> void:
|
||
var balance := int(data.get("balance", 0))
|
||
_balance_label.text = "钥石 %d" % balance
|
||
var forge_by_attr := {}
|
||
for row in data.get("forge", []):
|
||
forge_by_attr[String(row.get("attr", ""))] = row
|
||
for card in _forge_rows:
|
||
var row: Dictionary = forge_by_attr.get(String(card.attr), {})
|
||
if row.is_empty():
|
||
continue
|
||
card.level_l.text = "Lv %d" % int(row.get("level", 0))
|
||
card.price_l.text = "%d 钥石" % int(row.get("price", 0))
|
||
# M3:满级态文案(demo ②——已满级替代价签与按钮动词;数据无 maxed 字段时零影响)
|
||
if bool(row.get("maxed", false)):
|
||
card.btn.text = "已满级"
|
||
card.btn.disabled = true
|
||
else:
|
||
card.btn.text = "锻造 +1 级"
|
||
card.btn.disabled = balance < int(row.get("price", 0)) # 余额不足禁用置灰
|
||
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 = ws_by_id.get(String(card.item_id), {})
|
||
if row.is_empty():
|
||
continue
|
||
var th := int(row.get("threshold", 0))
|
||
var prog := int(row.get("progress", 0))
|
||
var unlocked := bool(row.get("unlocked", false))
|
||
card.progress_l.text = "累计通关 %d / %d 次" % [prog, th]
|
||
# 迭代 7 T5——失义件(显影尘/地穴全图卷 Alpha 重定)与退役件(探宝罗盘 2026-09-29 拍板①乙)呈现口径;
|
||
# 状态色随卡⑭ 亮板双色墨制(深墨族替代暖白族——亮板上暖白不可读)
|
||
var st_text := "未解锁"
|
||
var st_col: Color = UiKit.STONE
|
||
if bool(row.get("retired", false)):
|
||
st_text = "已退役"
|
||
st_col = UiKit.STONE
|
||
elif bool(row.get("deferred", false)) and unlocked:
|
||
st_text = "已解锁 · 能力封存中"
|
||
st_col = UiKit.GOLD_DEEP
|
||
elif unlocked:
|
||
st_text = "已解锁"
|
||
st_col = UiKit.GOLD_DEEP
|
||
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 吸余宽——悬停看全文)
|
||
# 同调装配段状态就地刷(迭代 11 第①批 T4——Slots 点亮/满位禁选/Y 升级行余额联动)
|
||
var att: Dictionary = data.get("attune", {})
|
||
if _attune_header_l != null:
|
||
var slots_n := int(att.get("slots", 0))
|
||
var eq: Array = 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 "装配"
|
||
card2.btn.disabled = (not on) and eq.size() >= slots_n # 满位禁选
|
||
if _refresh_l != null:
|
||
var rf: Dictionary = 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_forge(data: Dictionary) -> void:
|
||
_forge_rows.clear()
|
||
for child in _forge_holder.get_children():
|
||
child.queue_free()
|
||
for row in data.get("forge", []):
|
||
_forge_rows.append(_build_forge_row(_forge_holder, row))
|
||
|
||
|
||
func _build_forge_row(holder: VBoxContainer, 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", 8) # 2026-10-06 营地溢出修(12→8——行 min 压回定宽内)
|
||
card.add_child(line)
|
||
var attr := String(row.get("attr", ""))
|
||
# M3(UI 落码批):行首 20px 语义图标(图标+形状+颜色三通道——demo ②;子串映射容错 attr 键名演进)
|
||
var icon_key := ""
|
||
var attr_l := attr.to_lower()
|
||
if attr_l.contains("attack"):
|
||
icon_key = UiKit.TEX_ICON_SWORD
|
||
elif attr_l.contains("armor") or attr_l.contains("ac"):
|
||
icon_key = UiKit.TEX_ICON_SHIELD
|
||
elif attr_l.contains("hp") or attr_l.contains("life"):
|
||
icon_key = UiKit.TEX_ICON_HEART
|
||
elif attr_l.contains("dig"):
|
||
icon_key = UiKit.TEX_ICON_SHOVEL
|
||
if icon_key != "":
|
||
var ic := UiKit.icon_rect(icon_key, 20)
|
||
if ic != null:
|
||
line.add_child(ic)
|
||
var name_l := UiKit.label("%s %+d/级" % [String(row.get("display_name", "")), int(row.get("flat", 0))], 14, true, UiKit.INK)
|
||
name_l.custom_minimum_size = Vector2(112, 0)
|
||
name_l.clip_text = true # 2026-10-06 营地溢出修——文字全宽不入 min(三柜等宽分配前提)
|
||
name_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||
line.add_child(name_l)
|
||
var level_l := UiKit.label("",UiKit.FS_HINT, true, UiKit.INK_ON_DEEP)
|
||
# M3:Lv 章化(木底小章——demo lvtag;Label normal 槽挂样式盒)
|
||
level_l.add_theme_stylebox_override("normal", UiKit.panel_flat(UiKit.CAMP_HEADER_BAND, Color(0, 0, 0, 0),UiKit.RADIUS_OBJECT, 0, Vector4(6, 0, 6, 0)))
|
||
level_l.custom_minimum_size = Vector2(44, 0)
|
||
line.add_child(level_l)
|
||
var price_l := UiKit.label("",UiKit.FS_HINT, false, UiKit.STONE)
|
||
price_l.custom_minimum_size = Vector2(64, 0)
|
||
price_l.clip_text = true
|
||
price_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||
line.add_child(price_l)
|
||
var btn := UiKit.button("锻造 +1 级", 0.0) # 柜台行板紧凑钮豁免宽度基准
|
||
btn.pressed.connect(_emit_buy_requested.bind(attr))
|
||
line.add_child(btn)
|
||
holder.add_child(card)
|
||
return {"attr": attr, "level_l": level_l, "price_l": price_l, "btn": btn}
|
||
|
||
|
||
func _rebuild_workshop(data: Dictionary) -> void:
|
||
_workshop_rows.clear()
|
||
for child in _workshop_holder.get_children():
|
||
child.queue_free()
|
||
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 name_l := UiKit.label(String(row.get("display_name", "")),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
|
||
line.add_child(name_l)
|
||
var progress_l := UiKit.label("",UiKit.FS_HINT, false, UiKit.STONE)
|
||
progress_l.custom_minimum_size = Vector2(150, 0) # 2026-10-06 修——110 截掉阈值数字(「累计通关 28 / 2 次」≈154px)
|
||
progress_l.clip_text = true # 营地溢出修——文字全宽不撑柜
|
||
progress_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||
line.add_child(progress_l)
|
||
# 状态色随卡⑭ 亮板双色墨制(深墨族替代暖白族)——色随刷新态条件赋值,非机械两行式(T4 收口保留)
|
||
var status_l := UiKit.label("",UiKit.FS_HINT, true, UiKit.STONE)
|
||
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)
|
||
_workshop_holder.add_child(card)
|
||
_workshop_rows.append({"item_id": String(row.get("item_id", "")), "progress_l": progress_l, "status_l": status_l})
|
||
|
||
|
||
# 同调装配段重建(迭代 11 第①批 T4——工坊柜台尾部追加;Slots 点亮+九件行板+Y 升级行,程序占位面)
|
||
func _rebuild_attune(data: Dictionary) -> void:
|
||
for n in _attune_nodes:
|
||
n.queue_free()
|
||
_attune_nodes.clear()
|
||
_attune_btns.clear()
|
||
var att: Dictionary = 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)
|
||
form_l.custom_minimum_size = Vector2(58, 0)
|
||
line.add_child(form_l)
|
||
var btn := UiKit.button("装配", 0.0) # 行板紧凑钮豁免
|
||
btn.pressed.connect(attune_toggled.emit.bind(iid))
|
||
line.add_child(btn)
|
||
_workshop_holder.add_child(line)
|
||
_attune_nodes.append(line)
|
||
_attune_btns.append({"item_id": iid, "btn": btn})
|
||
var refresh_line := HBoxContainer.new()
|
||
refresh_line.add_theme_constant_override("separation", 6)
|
||
_refresh_l = UiKit.label("",UiKit.FS_CAPTION, false, UiKit.STONE)
|
||
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 = 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)
|