187 lines
8.1 KiB
GDScript
187 lines
8.1 KiB
GDScript
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||
# 营地/商店投影控制器(迭代 13 T4/D4——二期家族③ 自 main.gd 迁出;规范 1.1 单一职责):
|
||
# 营地面开合/快照投影/锻造·同调·天赋树·Fork 购买与切换 + 商店房购买/离开(pool 与 product 双面)。
|
||
# 装配纪律(ADR-0001,仿 treasure_interact_controller 先例):信号连接不在此发生——campbtn/
|
||
# camp_overlay 四信号/shop_overlay 两信号由组合根接至本类 on_* 入口;模态栈/呈现刷新/续玩按钮
|
||
# 刷新经组合根回调注入;runner 随局更替——现值回调注入;写调用唯一经 MetaProgression 购买原语
|
||
# 与 LevelRunner.buy_*(core 侧单口,本类零直写——拒绝可见 pattern 1 兜底 toast 保留原地)。
|
||
extends RefCounted
|
||
|
||
var db
|
||
var _meta_get: Callable # () → 元进度账本现值(组合根 lambda——探针 isolate_save 换注面,迭代13 T4 实证)
|
||
var _save_get: Callable # () → 存档承载现值(同上——营地 recent 摘要读数面)
|
||
var camp_overlay: CampOverlay
|
||
var shop_overlay: ShopOverlay
|
||
var toast: ToastStack
|
||
var start_overlay: Control # 开始层互斥面(开合仿设置面先例——非模态且树序在后,规范 2.3)
|
||
var logger # 试玩日志(ux_reject 取数面)
|
||
var _runner_get: Callable # () → 关卡推进器现值(组合根 lambda——随局更替)
|
||
var _open_modal: Callable
|
||
var _close_modal: Callable
|
||
var _refresh_presentations: Callable
|
||
var _refresh_resume_button: Callable
|
||
|
||
|
||
func _init(db_ref, meta_get: Callable, save_get: Callable, camp_overlay_ref: CampOverlay,
|
||
shop_overlay_ref: ShopOverlay, toast_ref: ToastStack, start_overlay_ref: Control,
|
||
logger_ref, runner_get: Callable, open_modal: Callable, close_modal: Callable,
|
||
refresh_presentations: Callable, refresh_resume_button: Callable) -> void:
|
||
db = db_ref
|
||
_meta_get = meta_get
|
||
_save_get = save_get
|
||
camp_overlay = camp_overlay_ref
|
||
shop_overlay = shop_overlay_ref
|
||
toast = toast_ref
|
||
start_overlay = start_overlay_ref
|
||
logger = logger_ref
|
||
_runner_get = runner_get
|
||
_open_modal = open_modal
|
||
_close_modal = close_modal
|
||
_refresh_presentations = refresh_presentations
|
||
_refresh_resume_button = refresh_resume_button
|
||
|
||
|
||
# 打开营地(主菜单入口——开合与开始层互斥仿设置面先例:开始覆盖层非模态且树序在后,
|
||
# 规范 2.3 遮挡纪律;打开时先刷工坊解锁——达标未解锁就地入账)
|
||
func open_camp() -> void:
|
||
var meta = _meta_get.call()
|
||
if meta == null:
|
||
return
|
||
meta.refresh_workshop_unlocks()
|
||
start_overlay.visible = false
|
||
_open_modal.call(camp_overlay) # 模态栈单口(T3 收口批——登记层开合一律走栈)
|
||
camp_overlay.open_camp(camp_data())
|
||
_refresh_presentations.call()
|
||
|
||
|
||
func on_camp_closed() -> void:
|
||
_close_modal.call(camp_overlay) # 模态栈单口(T3 收口批)
|
||
start_overlay.visible = true
|
||
_refresh_resume_button.call() # 迭代 8 卡②——回菜单按断点在档态刷新
|
||
_refresh_presentations.call() # 关闭后刷新顶栏(工坊解锁可能变了余额侧读数面)
|
||
|
||
|
||
# 营地快照投影(纯读——锻造三行/工坊六行/余额/最近冒险;拼句归 CampOverlay,账面归 MetaProgression)
|
||
func camp_data() -> Dictionary:
|
||
var meta = _meta_get.call()
|
||
var forge_rows: Array[Dictionary] = []
|
||
if meta != null:
|
||
for attr in MetaTable.FORGE_ATTRS:
|
||
var row: Dictionary = db.meta_forge_row(attr)
|
||
forge_rows.append({
|
||
"attr": attr,
|
||
"display_name": String(row.get("display_name", "")),
|
||
"level": meta.forge_level(attr),
|
||
"flat": int(row.get("flat_value", 0)),
|
||
"price": meta.forge_price(attr),
|
||
})
|
||
return {
|
||
"balance": meta.keystone_balance() if meta != null else 0,
|
||
"forge": forge_rows,
|
||
"workshop": meta.workshop_status() if meta != null else [],
|
||
"attune": meta.attune_camp_status() if meta != null else {}, # 迭代 11 第①批 T4——同调装配面投影
|
||
"tree": meta.tree_camp_rows() if meta != null else [], # 迭代 12 T9——天赋树面投影
|
||
"fork_groups": meta.tree_fork_groups() if meta != null else [],
|
||
"recent": _save_get.call().recent_summaries() if _save_get.call() != null else [],
|
||
}
|
||
|
||
|
||
func on_camp_buy(attr: String) -> void:
|
||
var meta = _meta_get.call()
|
||
if meta == null:
|
||
return
|
||
if attr == "attune_refresh":
|
||
# Y 刷新升级行购买(迭代 11 第①批 T4——P1 甲;按钮禁用为常态防线,兜底拒绝可见 pattern 1)
|
||
if not meta.buy_attune_refresh():
|
||
toast.show_toast("钥石不足——买不起这次升级", "warn")
|
||
return
|
||
camp_overlay.refresh_camp(camp_data())
|
||
_refresh_presentations.call()
|
||
return
|
||
if not meta.buy_forge(attr):
|
||
# 余额不足(按钮禁用为常态防线——兜底拒绝可见,pattern 1)
|
||
toast.show_toast("钥石不足——买不起这件强化", "warn")
|
||
return
|
||
camp_overlay.refresh_camp(camp_data())
|
||
_refresh_presentations.call()
|
||
|
||
|
||
# 同调装配切换(迭代 11 第①批 T4——装配关系唯一写口经 MetaProgression.toggle_attunement;满位兜底拒绝可见)
|
||
func on_camp_attune_toggle(item_id: String) -> void:
|
||
var meta = _meta_get.call()
|
||
if meta == null:
|
||
return
|
||
if not meta.toggle_attunement(item_id):
|
||
toast.show_toast("同调位已满——先卸下再装配", "warn")
|
||
return
|
||
camp_overlay.refresh_camp(camp_data())
|
||
|
||
|
||
# 天赋树购买(迭代 12 T9——写口唯一归 MetaProgression.tree_buy/tree_buy_fork;Fork 节点购=激活侧)
|
||
func on_camp_tree_buy(node_id: String) -> void:
|
||
var meta = _meta_get.call()
|
||
if meta == null:
|
||
return
|
||
var ok: bool = meta.tree_buy_fork(node_id) if String(db.tree_node(node_id).get("kind", "")) == "fork" else meta.tree_buy(node_id)
|
||
if not ok:
|
||
toast.show_toast("购买被拒——前置/行门槛/余额不足", "warn")
|
||
return
|
||
camp_overlay.refresh_camp(camp_data())
|
||
_refresh_presentations.call()
|
||
|
||
|
||
# A/B 免费切换(迭代 12 T8——同组换侧零币;未激活组走购买面)
|
||
func on_camp_fork_toggle(group: String, side: String) -> void:
|
||
var meta = _meta_get.call()
|
||
if meta == null:
|
||
return
|
||
if not meta.tree_toggle_fork(group, side):
|
||
toast.show_toast("切换被拒——未激活组请先购买", "warn")
|
||
return
|
||
camp_overlay.refresh_camp(camp_data())
|
||
_refresh_presentations.call()
|
||
|
||
|
||
func on_shop_buy(product_id: String) -> void:
|
||
var runner = _runner_get.call()
|
||
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"})
|
||
toast.show_toast("金币不足——买不起这件商品", "warn")
|
||
return
|
||
shop_overlay.open_shelf(runner.shop_shelf(), int(runner.gp))
|
||
shop_overlay.set_refresh_display(runner.shop_refresh_free_left(), runner.shop_refresh_cost(), int(runner.gp) >= runner.shop_refresh_cost())
|
||
_refresh_presentations.call()
|
||
return
|
||
if not runner.buy_product(product_id):
|
||
# 余额不足(按钮禁用为常态防线——兜底拒绝可见,pattern 1)
|
||
logger.log_ux("ux_reject", {"source": "wallet", "sub_reason": "insufficient_gp"})
|
||
toast.show_toast("金币不足——买不起这件商品", "warn")
|
||
return
|
||
shop_overlay.refresh_state(runner.shop_products(), int(runner.gp))
|
||
_refresh_presentations.call()
|
||
|
||
|
||
# MC-1:刷新意图(shop_overlay.refresh_requested——核心 shop_refresh 受理后整架重投喂=重抽反馈)
|
||
func on_shop_refresh() -> void:
|
||
var runner = _runner_get.call()
|
||
if runner == null or not runner.is_shop_pool():
|
||
return
|
||
if not runner.shop_refresh():
|
||
toast.show_toast("金币不足——刷新失败", "warn")
|
||
return
|
||
shop_overlay.open_shelf(runner.shop_shelf(), int(runner.gp))
|
||
shop_overlay.set_refresh_display(runner.shop_refresh_free_left(), runner.shop_refresh_cost(), int(runner.gp) >= runner.shop_refresh_cost())
|
||
_refresh_presentations.call()
|
||
|
||
|
||
func on_shop_leave() -> void:
|
||
_close_modal.call(shop_overlay)
|
||
var runner = _runner_get.call()
|
||
if runner != null:
|
||
# 复用兑付确认纪律(接口漂移清偿):离开 ≠ 推进——确认后经 choices_presented 弹出选路口
|
||
runner.continue_after_node()
|