迭代11第②批:GP商店房重构——三选一同构化(P1共享池账本+抽3买1全重抽+A/b乘法定价ceil+断点扩键+货架同构呈现+MC off/on分层;冒烟671/0+off对拍off_n60逐位PASS+探针三件绿)

This commit is contained in:
DKChan committed 2026-10-03 18:19:57 +08:00
1 parent bc17b66c00
commit d79afd2241
18 files changed
+557 -45

No files matched your search

+21 -5
View File
@@ -447,7 +447,7 @@ func _start_adventure() -> void:
# 永久强化注入(元进度实现批):冒险开始参数面——meta 现算 override 显式传参(全零 = 白板)
# 首次冒险脚本(关卡节点图生成 v0.4):存档布尔位 false = 全关固定模板 0(教学重放);置位后逐关抽签
var first_script: bool = not save_manager.is_first_adventure_done()
if not adventure.start_adventure(seed_val, {"hero_override": meta.hero_override() if meta != null else {}, "first_script": first_script, "aux_unlocked": _aux.unlocked_set()}):
if not adventure.start_adventure(seed_val, {"hero_override": meta.hero_override() if meta != null else {}, "first_script": first_script, "aux_unlocked": _aux.unlocked_set(), "shop_mode": "pool"}):
_fatal_screen("冒险启动失败——见输出日志")
return
@@ -496,10 +496,14 @@ func _on_runner_node(node: Dictionary, info: Dictionary) -> void:
node_overlay.show_rest(healed, int(info.get("hp", 0)), int(info.get("max_hp", 0)))
log_view.append_bb(Narrator.rest_line(healed, int(info.get("hp", 0)), int(info.get("max_hp", 0))))
elif t == "shop":
# 商店房兑付(GP+商店房实施批):到达即开店——商品快照/余额经 runner 只读投影喂入;
# 购买/离开归玩家意图(buy_requested/leave_pressed 接线),零自动推进
# 商店房兑付(GP+商店房实施批):到达即开店——商品/货架快照与余额经 runner 只读投影喂入;
# 购买/离开归玩家意图(buy_requested/leave_pressed 接线),零自动推进。
# pool 面 = 共享池货架三选一同构卡(迭代 11 第②批 T4);legacy 面 = 固定三件(off 对照承载)
_open_modal(shop_overlay)
shop_overlay.open_shop(runner.shop_products(), int(runner.gp))
if runner.is_shop_pool():
shop_overlay.open_shelf(runner.shop_shelf(), int(runner.gp))
else:
shop_overlay.open_shop(runner.shop_products(), int(runner.gp))
log_view.append_bb(Narrator.shop_line(int(runner.gp)))
logger.log_node(node, info, runner.node_index)
_refresh_presentations()
@@ -1084,6 +1088,15 @@ func _on_camp_attune_toggle(item_id: String) -> void:
func _on_shop_buy(product_id: String) -> void:
if runner == null:
return
if runner.is_shop_pool():
# pool 面:买 1 → 核心全重抽——货架整架重投喂(重抽反馈;余额不足核心拒绝零副作用)
if not runner.buy_pool_item(product_id):
logger.log_ux("ux_reject", {"source": "wallet", "sub_reason": "insufficient_gp"})
board_zone.toast.show_toast("金币不足——买不起这件商品", "warn")
return
shop_overlay.open_shelf(runner.shop_shelf(), int(runner.gp))
_refresh_presentations()
return
if not runner.buy_product(product_id):
# 余额不足(按钮禁用为常态防线——兜底拒绝可见,pattern 1)
logger.log_ux("ux_reject", {"source": "wallet", "sub_reason": "insufficient_gp"})
@@ -1107,7 +1120,10 @@ func _on_wallet_changed(event: String, gp: int, extra: Dictionary) -> void:
if String(event) == "gain":
_last_gain_amount = int(extra.get("amount", 0)) # 卡⑥ GP 增量取数(每战恰一笔 gain)
if shop_overlay.visible and runner != null:
shop_overlay.refresh_state(runner.shop_products(), int(runner.gp))
if runner.is_shop_pool():
shop_overlay.refresh_shelf_state(int(runner.gp)) # pool 面余额联动(货架内容仅买后重抽变)
else:
shop_overlay.refresh_state(runner.shop_products(), int(runner.gp))
_refresh_presentations()
+63 -6
View File
@@ -1,21 +1,26 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# Date: 2026-09-21(GP+商店房实施批——商店房覆盖层呈现)
# 商店覆盖层(模态):标题「商店」/ 金币余额 / 三商品卡全陈列(显示名·档位文案·现价·已购档数)/
# 购买按钮(余额不足禁用置灰)/ 离开按钮。模态挂接方式仿既有覆盖层(legend_overlay 现成参照——
# dim + CenterContainer + box,组合根 _register_modal 收口层级)。
# 高内聚边界:纯视图 setter 面——商品快照与余额经组合根喂入(runner.shop_products() 只读投影,
# 现价/已购档数由 runner 现算),购买/离开意图经信号外发——零核心写调用、零 runner 持有。
# 商店覆盖层(模态):标题「商店」/ 金币余额 / 商品卡陈列 / 购买按钮(余额不足禁用置灰)/ 离开按钮。
# 模态挂接方式仿既有覆盖层(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)
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:
@@ -95,3 +100,55 @@ func _emit_leave_pressed() -> void:
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)