Files
gplan-2/game/presentation/views/camp_overlay_view.gd
T

277 lines
12 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
# 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}(探针依赖)。
# 挂接方式:开合与开始层互斥仿 TunePanel 先例(开始覆盖层非模态且树序在后——打开本层前由组合根
# 隐藏开始层,规范 2.3 遮挡纪律)。
# 高内聚边界:纯视图 setter 面——账本快照经组合根喂入(MetaProgression 只读投影),购买/关闭
# 意图经信号外发——零核心写调用、零 MetaProgression 持有。
class_name CampOverlay
extends Control
signal buy_requested(attr: String)
signal closed
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 _forge_rows: Array[Dictionary] = [] # [{attr, level_l, price_l, btn}]——refresh_camp 就地重刷(零重建回流)
var _workshop_rows: Array[Dictionary] = [] # [{item_id, progress_l, status_l}]
var _recent_list: Label # 最近冒险(逐行拼接 label——只读账面,随 open_camp 重建)
func _init() -> void:
var dim := UiKit.modal_dim(self, Color(0, 0, 0, 0.45)) # 方向稿=石纹底透出(浅 dim——石纹全屏底担当氛围)
# 石纹底噪(样张 v2 冻结件——start overlay 同款;缺位不挂即回退平 dim)
var bd_tex := UiKit.load_tex(UiKit.TEX_BACKDROP_STONE)
if bd_tex != null:
var bd := TextureRect.new()
bd.texture = bd_tex
bd.stretch_mode = TextureRect.STRETCH_TILE
bd.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
bd.set_anchors_preset(Control.PRESET_FULL_RECT)
bd.mouse_filter = Control.MOUSE_FILTER_IGNORE
dim.add_child(bd)
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, 2, 1, Vector4(14, 8, 14, 8)))
top.add_child(plaque)
var plaque_col := VBoxContainer.new()
plaque.add_child(plaque_col)
var title := UiKit.label("营地 · 永久强化", 22, true, UiKit.INK_TITLE)
plaque_col.add_child(title)
var sub := UiKit.label("跨局成长账本——钥石换永久属性", 13, false, UiKit.STONE)
plaque_col.add_child(sub)
var spacer := Control.new()
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
top.add_child(spacer)
var badge := PanelContainer.new()
badge.add_theme_stylebox_override("panel",
UiKit.panel_flat(BADGE_PANEL_COL, UiKit.PANEL_BORDER, 2, 1, Vector4(14, 8, 14, 8)))
top.add_child(badge)
var bal_row := HBoxContainer.new()
bal_row.add_theme_constant_override("separation", 6)
badge.add_child(bal_row)
var keystone_icon := UiKit.icon_rect(UiKit.TEX_ICON_KEYSTONE, 18) # 缺位 null → 纯文本回退
if keystone_icon != null:
bal_row.add_child(keystone_icon)
_balance_label = UiKit.label("钥石 0", 18, true, UiKit.GOLD)
bal_row.add_child(_balance_label)
# ── 三柜台列(方向稿同构:头带深棕+米色板身)──
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)
mid.add_child(_counter_panel("属性锻造", UiKit.TEX_ICON_FORGE, _forge_holder, 356))
_workshop_holder = VBoxContainer.new()
_workshop_holder.add_theme_constant_override("separation", 6)
mid.add_child(_counter_panel("道具工坊", UiKit.TEX_ICON_WORKSHOP, _workshop_holder, 356))
var atlas_holder := VBoxContainer.new()
mid.add_child(_counter_panel("奖励格图鉴", UiKit.TEX_ICON_ATLAS, atlas_holder, 356))
var lock_l := UiKit.label(" —— 柜台结构位预留:内容排 Alpha(地图战斗资源差异化批)", 13, 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)
# ── 底栏:关闭钮(左)/ 操作注记(右)+ 最近冒险条带 ──
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("每购得一件永久强化,对应柜台亮起一格暖光 · 价格为钥石", 13, false, UiKit.WARM_SOFT)
bottom.add_child(caption)
col.add_child(bottom)
_recent_list = UiKit.label("", 13, 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: VBoxContainer, 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, 3, 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, 2, 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, 16) # 缺位 null → 纯文本回退
if ic != null:
head.add_child(ic)
var l := UiKit.label(title_text, 16, true, UiKit.WARM)
head.add_child(l)
vbox.add_child(holder)
return panel
# ---- 柜台行构建(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]
# 迭代 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)
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, 2, 1, Vector4(10, 4, 10, 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/级" % [String(row.get("display_name", "")), int(row.get("flat", 0))], 14, true, UiKit.INK)
name_l.custom_minimum_size = Vector2(132, 0)
line.add_child(name_l)
var level_l := UiKit.label("", 14, false, UiKit.INK)
level_l.custom_minimum_size = Vector2(44, 0)
line.add_child(level_l)
var price_l := UiKit.label("", 14, false, UiKit.STONE)
price_l.custom_minimum_size = Vector2(64, 0)
line.add_child(price_l)
var btn := UiKit.button("锻造 +1 级")
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, 2, 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", "")), 14, true, UiKit.INK)
name_l.custom_minimum_size = Vector2(132, 0)
line.add_child(name_l)
var progress_l := UiKit.label("", 13, false, UiKit.STONE)
progress_l.custom_minimum_size = Vector2(110, 0)
line.add_child(progress_l)
# 状态色随卡⑭ 亮板双色墨制(深墨族替代暖白族)——色随刷新态条件赋值,非机械两行式(T4 收口保留)
var status_l := UiKit.label("", 13, true, UiKit.STONE)
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[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)