155 lines
7.1 KiB
GDScript
155 lines
7.1 KiB
GDScript
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||
# Date: 2026-09-21(GP+商店房实施批——商店房覆盖层呈现)
|
||
# 商店覆盖层(模态):标题「商店」/ 金币余额 / 商品卡陈列 / 购买按钮(余额不足禁用置灰)/ 离开按钮。
|
||
# 模态挂接方式仿既有覆盖层(legend_overlay 现成参照——dim + CenterContainer + box,组合根
|
||
# _register_modal 收口层级)。
|
||
# 双面承载(M-4 迭代 11 第②批 T4):legacy 面 = 三商品卡全陈列(open_shop/refresh_state——shop=off
|
||
# 对照承载原样保留);pool 面 = 共享池货架三选一同构卡(open_shelf——进店抽 3 + 买 1 全重抽反馈 +
|
||
# 库存余量就近呈现,买后组合根重投喂货架整架重建)。高内聚边界:纯视图 setter 面——商品/货架快照
|
||
# 与余额经组合根喂入(runner 投影现算现价,视图零复算定价公式),购买/离开意图经信号外发——
|
||
# 零核心写调用、零 runner 持有。
|
||
class_name ShopOverlay
|
||
extends Control
|
||
|
||
signal buy_requested(product_id: String) # legacy=product_id / pool=kind_id(组合根按模式分发)
|
||
signal leave_pressed
|
||
|
||
const ATTR_LABELS := {"atk": "攻击", "max_hp": "生命上限", "armor_class": "护甲"} # 档位文案轴(数据契约 attr 词表)
|
||
const KIND_LABELS := {"attack": "攻击", "max_hp": "生命上限", "armor": "护甲", "ap": "行动点"} # 货架件值文案轴(共享池 kind 词表——迭代 11 第②批)
|
||
|
||
var _gp_label: Label
|
||
var _cards_box: VBoxContainer
|
||
var _card_rows: Array[Dictionary] = [] # [{product_id, price, bought, btn}]——refresh_state 就地重刷(零重建回流)
|
||
var _shelf_rows: Array[Dictionary] = [] # [{kind_id, btn}]——pool 面卡片(买后整架重建=重抽反馈)
|
||
|
||
|
||
func _init() -> void:
|
||
var box: VBoxContainer = UiKit.modal_shell(self, Color(0, 0, 0, 0.6)).box
|
||
var title := UiKit.label("商店", 26, true, UiKit.WARM)
|
||
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
box.add_child(title)
|
||
_gp_label = UiKit.label("金币 0", 18, true, UiKit.GOLD)
|
||
_gp_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
box.add_child(_gp_label)
|
||
_cards_box = VBoxContainer.new()
|
||
_cards_box.add_theme_constant_override("separation", 8)
|
||
box.add_child(_cards_box)
|
||
var leave_btn := UiKit.button("离开商店")
|
||
leave_btn.pressed.connect(_emit_leave_pressed)
|
||
box.add_child(leave_btn)
|
||
visible = false
|
||
|
||
|
||
# 打开商店(products = runner.shop_products() 投影——[{product_id, display_name, attr, flat_value, price, purchased}])
|
||
func open_shop(products: Array, gp: int) -> void:
|
||
for child in _cards_box.get_children():
|
||
child.queue_free()
|
||
_card_rows.clear()
|
||
for p in products:
|
||
_card_rows.append(_build_card(p))
|
||
refresh_state(products, gp)
|
||
visible = true
|
||
|
||
|
||
# 状态重刷(购买/钱包变化后调用——就地改文案与按钮禁用态,零重建)
|
||
func refresh_state(products: Array, gp: int) -> void:
|
||
_gp_label.text = "金币 %d" % gp
|
||
var by_id := {}
|
||
for p in products:
|
||
by_id[String(p.product_id)] = p
|
||
for card in _card_rows:
|
||
var p: Dictionary = by_id.get(String(card.product_id), {})
|
||
if p.is_empty():
|
||
continue
|
||
card.price.text = "现价 %d 金币" % int(p.price)
|
||
card.bought.text = "已购 %d 档" % int(p.purchased)
|
||
card.btn.disabled = gp < int(p.price) # 余额不足禁用置灰
|
||
|
||
|
||
# 商品卡(显示名 + 档位文案 | 现价 | 已购档数 | 购买按钮)
|
||
func _build_card(p: 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, 6, 12, 6)))
|
||
var row := HBoxContainer.new()
|
||
row.add_theme_constant_override("separation", 14)
|
||
card.add_child(row)
|
||
var name_l := UiKit.label(_card_title(p), 15, true)
|
||
name_l.custom_minimum_size = Vector2(190, 0)
|
||
row.add_child(name_l)
|
||
var price_l := UiKit.label("", 14, false, UiKit.WARM_SOFT)
|
||
price_l.custom_minimum_size = Vector2(110, 0)
|
||
row.add_child(price_l)
|
||
var bought_l := UiKit.label("", 14, false, UiKit.WARM_FAINT)
|
||
row.add_child(bought_l)
|
||
var btn := UiKit.button("购买")
|
||
btn.pressed.connect(_emit_buy_requested.bind(p))
|
||
row.add_child(btn)
|
||
_cards_box.add_child(card)
|
||
return {"product_id": String(p.product_id), "price": price_l, "bought": bought_l, "btn": btn}
|
||
|
||
|
||
# 卡题 = 显示名 + 档位文案(attr →「攻击 +1」形)
|
||
func _card_title(p: Dictionary) -> String:
|
||
var attr := String(p.get("attr", ""))
|
||
return "%s(%s %+d)" % [String(p.get("display_name", "")), String(ATTR_LABELS.get(attr, attr)), int(p.get("flat_value", 0))]
|
||
|
||
|
||
func _emit_leave_pressed() -> void:
|
||
leave_pressed.emit()
|
||
|
||
|
||
func _emit_buy_requested(p: Dictionary) -> void:
|
||
buy_requested.emit(String(p.product_id))
|
||
|
||
|
||
# ---- 共享池货架面(M-4 迭代 11 第②批 T4——P3 甲程序占位,三选一同构卡;UiKit 既有件)----
|
||
|
||
# 打开/重刷货架(cards = runner.shop_shelf() 投影——[{kind_id, display_name, value, price, stock_left}])。
|
||
# 买后组合根重投喂新货架 = 整架重建呈现(三件全重抽反馈——未买不保留);空架 = 池空硬闸合法态。
|
||
func open_shelf(cards: Array, gp: int) -> void:
|
||
for child in _cards_box.get_children():
|
||
child.queue_free()
|
||
_shelf_rows.clear()
|
||
for c in cards:
|
||
_shelf_rows.append(_build_shelf_card(c))
|
||
if cards.is_empty():
|
||
var empty := UiKit.label("货架售罄——共享池已买空(属性件全冒险共 3+3+3 份 / AP 件 2 份)", 14, false, UiKit.WARM_FAINT)
|
||
empty.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
_cards_box.add_child(empty)
|
||
refresh_shelf_state(gp)
|
||
visible = true
|
||
|
||
|
||
# 货架余额重刷(就地改禁用态——买后重建路径经 open_shelf 覆盖,本口供 wallet 变化联动)
|
||
func refresh_shelf_state(gp: int) -> void:
|
||
_gp_label.text = "金币 %d" % gp
|
||
for card in _shelf_rows:
|
||
card.btn.disabled = gp < int(card.price)
|
||
|
||
|
||
# 货架卡(显示名+件值文案 | 现价 | 库存余量 | 购买钮)——三选一同构语言:买 1 = 一次真实购买
|
||
func _build_shelf_card(c: Dictionary) -> Dictionary:
|
||
var kind := String(c.get("kind_id", ""))
|
||
var card := PanelContainer.new()
|
||
card.add_theme_stylebox_override("panel", UiKit.panel_flat(UiKit.BG_OVERLAY, UiKit.GOLD_DIM, 4, 1, Vector4(12, 6, 12, 6)))
|
||
var row := HBoxContainer.new()
|
||
row.add_theme_constant_override("separation", 14)
|
||
card.add_child(row)
|
||
var name_l := UiKit.label("%s(%s %+d)" % [String(c.get("display_name", kind)), String(KIND_LABELS.get(kind, kind)), int(c.get("value", 0))], 15, true)
|
||
name_l.custom_minimum_size = Vector2(190, 0)
|
||
row.add_child(name_l)
|
||
var price_l := UiKit.label("现价 %d 金币" % int(c.get("price", 0)), 14, false, UiKit.WARM_SOFT)
|
||
price_l.custom_minimum_size = Vector2(110, 0)
|
||
row.add_child(price_l)
|
||
var stock_l := UiKit.label("库存余 %s" % str(int(c.get("stock_left", 0))) if int(c.get("stock_left", 0)) < (1 << 20) else "库存余 ∞", 14, false, UiKit.WARM_FAINT)
|
||
row.add_child(stock_l)
|
||
var btn := UiKit.button("购买")
|
||
btn.pressed.connect(_emit_shelf_buy.bind(kind))
|
||
row.add_child(btn)
|
||
_cards_box.add_child(card)
|
||
return {"kind_id": kind, "price": int(c.get("price", 0)), "btn": btn}
|
||
|
||
|
||
func _emit_shelf_buy(kind: String) -> void:
|
||
buy_requested.emit(kind)
|