198 lines
8.8 KiB
GDScript
198 lines
8.8 KiB
GDScript
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||
# Date: 2026-09-21(元进度实现批——营地覆盖层呈现)
|
||
# 营地覆盖层(模态):标题「营地 · 永久强化」+ 钥石余额 / 柜台一「属性锻造」(三行可购——显示名 ·
|
||
# 当前 Lv · 每级 +flat · 下级价格 · 购买按钮余额不足禁用)/ 柜台二「道具工坊」(六行——显示名 ·
|
||
# 解锁条件 · 进度 · 状态;局内效果排 Alpha 魔法道具重设计批——代拍板 3,注记一行)/ 柜台三
|
||
# 「奖励格图鉴」(占位锁定行——内容排 Alpha 地图战斗资源差异化批——代拍板 4)/「最近冒险」列表
|
||
# (recent_summaries 投影:结果/通关数/钥石合计/种子)/ 关闭按钮。
|
||
# 挂接方式:仿 ShopOverlay(dim + CenterContainer + box);开合与开始层互斥仿 TunePanel 先例
|
||
# (开始覆盖层非模态且树序在后——打开本层前由组合根隐藏开始层,规范 2.3 遮挡纪律)。
|
||
# 高内聚边界:纯视图 setter 面——账本快照经组合根喂入(MetaProgression 只读投影),购买/关闭
|
||
# 意图经信号外发——零核心写调用、零 MetaProgression 持有。视觉 = ui_kit 程序占位
|
||
#(代拍板 8——视觉终稿以表现层样张过审为前置,归 HUD 协作批)。
|
||
class_name CampOverlay
|
||
extends Control
|
||
|
||
signal buy_requested(attr: String)
|
||
signal closed
|
||
|
||
const ATTR_LABELS := {"atk": "攻击", "max_hp": "生命上限", "armor_class": "护甲"} # 档位文案轴(数据契约 attr 词表,与 ShopOverlay 同款)
|
||
|
||
var _balance_label: Label
|
||
var _forge_holder: VBoxContainer
|
||
var _workshop_holder: VBoxContainer
|
||
var _forge_rows: Array = [] # [{attr, level_l, price_l, btn}]——refresh_camp 就地重刷(零重建回流)
|
||
var _workshop_rows: Array = [] # [{item_id, progress_l, status_l}]
|
||
var _recent_list: Label # 最近冒险(逐行拼接 label——只读账面,随 open_camp 重建)
|
||
|
||
|
||
func _init() -> void:
|
||
var dim := ColorRect.new()
|
||
dim.color = Color(0, 0, 0, 0.72)
|
||
dim.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
add_child(dim)
|
||
var center := CenterContainer.new()
|
||
center.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
dim.add_child(center)
|
||
var box := VBoxContainer.new()
|
||
box.add_theme_constant_override("separation", 6)
|
||
center.add_child(box)
|
||
var title := UiKit.label("营地 · 永久强化", 24, true)
|
||
title.add_theme_color_override("font_color", UiKit.WARM)
|
||
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
box.add_child(title)
|
||
_balance_label = UiKit.label("钥石 0", 18, true)
|
||
_balance_label.add_theme_color_override("font_color", UiKit.GOLD)
|
||
_balance_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
box.add_child(_balance_label)
|
||
box.add_child(HSeparator.new())
|
||
|
||
box.add_child(_counter_header("属性锻造(钥石直购 · 每级 flat 整数 · 价格几何递增)"))
|
||
_forge_holder = VBoxContainer.new()
|
||
_forge_holder.add_theme_constant_override("separation", 4)
|
||
box.add_child(_forge_holder)
|
||
|
||
box.add_child(_counter_header("道具工坊(累计通关次数解锁 · 局内效果排 Alpha 魔法道具重设计批)"))
|
||
_workshop_holder = VBoxContainer.new()
|
||
_workshop_holder.add_theme_constant_override("separation", 4)
|
||
box.add_child(_workshop_holder)
|
||
|
||
box.add_child(_counter_header("奖励格图鉴"))
|
||
var lock_l := UiKit.label(" —— 柜台结构位预留:内容排 Alpha(地图战斗资源差异化批)", 13, false)
|
||
lock_l.add_theme_color_override("font_color", UiKit.WARM_FAINT)
|
||
box.add_child(lock_l)
|
||
box.add_child(HSeparator.new())
|
||
|
||
box.add_child(_counter_header("最近冒险"))
|
||
var scroll := ScrollContainer.new()
|
||
scroll.custom_minimum_size = Vector2(560, 96)
|
||
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||
box.add_child(scroll)
|
||
_recent_list = UiKit.label("", 13, false)
|
||
_recent_list.add_theme_color_override("font_color", UiKit.WARM_SOFT)
|
||
_recent_list.custom_minimum_size = Vector2(544, 0)
|
||
_recent_list.autowrap_mode = TextServer.AUTOWRAP_OFF
|
||
scroll.add_child(_recent_list)
|
||
|
||
var close_btn := UiKit.button("关闭营地(回开始界面)")
|
||
close_btn.pressed.connect(func(): closed.emit())
|
||
box.add_child(close_btn)
|
||
visible = false
|
||
|
||
|
||
func _counter_header(text: String) -> Label:
|
||
var l := UiKit.label(text, 15, true)
|
||
l.add_theme_color_override("font_color", UiKit.WARM_SOFT)
|
||
return l
|
||
|
||
|
||
# ---- 柜台行构建(open_camp 重建;行结构仿 ShopOverlay 商品卡)----
|
||
|
||
# 打开营地(全量重建三柜台行 + 最近冒险——数据 = 组合根 _camp_data() 投影)
|
||
func open_camp(data: Dictionary) -> void:
|
||
_rebuild_forge(data)
|
||
_rebuild_workshop(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))
|
||
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]
|
||
card.status_l.text = "已解锁" if unlocked else "未解锁"
|
||
card.status_l.add_theme_color_override("font_color", UiKit.GOLD if unlocked else UiKit.WARM_FAINT)
|
||
|
||
|
||
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(UiKit.BG_OVERLAY, UiKit.GOLD_DIM, 4, 1, Vector4(12, 4, 12, 4)))
|
||
var line := HBoxContainer.new()
|
||
line.add_theme_constant_override("separation", 12)
|
||
card.add_child(line)
|
||
var attr := String(row.get("attr", ""))
|
||
var name_l := UiKit.label("%s(每级 %+d flat)" % [String(row.get("display_name", "")), int(row.get("flat", 0))], 14, true)
|
||
name_l.custom_minimum_size = Vector2(210, 0)
|
||
line.add_child(name_l)
|
||
var level_l := UiKit.label("", 14, false)
|
||
level_l.custom_minimum_size = Vector2(64, 0)
|
||
line.add_child(level_l)
|
||
var price_l := UiKit.label("", 14, false)
|
||
price_l.add_theme_color_override("font_color", UiKit.WARM_SOFT)
|
||
price_l.custom_minimum_size = Vector2(150, 0)
|
||
line.add_child(price_l)
|
||
var btn := UiKit.button("锻造 +1 级")
|
||
btn.pressed.connect(func(): buy_requested.emit(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(UiKit.BG_OVERLAY, UiKit.WARM_FAINT, 4, 1, Vector4(12, 4, 12, 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", "")), 14, true)
|
||
name_l.custom_minimum_size = Vector2(210, 0)
|
||
line.add_child(name_l)
|
||
var progress_l := UiKit.label("", 13, false)
|
||
progress_l.add_theme_color_override("font_color", UiKit.WARM_SOFT)
|
||
progress_l.custom_minimum_size = Vector2(150, 0)
|
||
line.add_child(progress_l)
|
||
var status_l := UiKit.label("", 13, true)
|
||
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})
|
||
|
||
|
||
func _rebuild_recent(data: Dictionary) -> void:
|
||
var lines: Array = []
|
||
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 = "\n".join(lines)
|