Files
gplan-2/game/presentation/camp_shop_presenter.gd
T
DKChan 3661689a56 iter33 卡② T1+T2: 主菜单三入口化(6 钮→3 钮)+F10 位点全量迁移
钮集:build_start_overlay 六钮两组帽→三钮(进入存档 primary/设置/退出——裁决 1;组帽双撤追认单留痕);
menu_screen 句柄面撤 4 留 3 增 entersavebtn;main.gd 接线撤四线接 _open_save_select(mn3 点击链闭环)。
_refresh_resume_button 净删除+消费点全清(main.gd 五处+camp ctx 载体+presenter 三处+probe_ui_util
参数+pacing_queue+V2d 族);断点确认链主菜单侧摘除(mn6——分派器 new_adventure 臂随迁退役,
机制保留卡④第三消费方)。_start_adventure_from_menu/_resume_from_menu/_open_career 保留为探针
直调中间态(mn10——卡④ 大门/笔记本换代点,已知中间态登记)。
F10 迁移:smoke V2 三钮/V2d 族废止三钮承接/V3g camp 经进档开层(槽模板隔离+恢复 attach 写盘目标)/
V3i 钮断言摘除+_stage_faces.resume_vis 键退役;ui_probe/ui_struct/screen_flow/shot/fx/t17 营地与
冒险驱动换代(open_camp_via_savesel 助手——槽模板清场防真实槽污染);ui_click 营地购买链换代
(swap=true 整链落槽文件+持久化核验同源)+断点段②③按 §7.2 申报删除移交卡④+生涯直调;
pacing_queue 钮依赖摘除;isolate_save 签名换代(refresh_resume→swap_save,全调用点核对)。
golden 基线随 T2 重制(mn12——三钮形态)。验证:冒烟 1212/0、单元 40 套件全绿、
struct/click/flow/ui_probe/tune/layout/input/t17 全绿。
2026-10-22 22:32:02 +08:00

213 lines
9.1 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
## 营地/商店投影控制器:营地开合/快照投影/锻造·同调·天赋树·Fork 购买切换 + 商店房购买/离开(pool 与 product 双面)。
## 信号连接只归组合根(camp_overlay/shop_overlay 信号接至本类 on_* 入口);模态栈/呈现刷新与 runner 现值经组合根回调注入(续玩刷新已随主菜单三入口化退役——卡② mn7)。
## 写调用唯一经 MetaProgression 购买原语与 LevelRunner.buy_*——本类零直写,拒绝兜底 toast 保留原地。
extends RefCounted
var db
var _meta_get: Callable # () → 元进度账本现值(组合根 lambda——探针 isolate_save 换注面)
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
func _init(ctx: CampFlowContext) -> void:
# 构造参数收拢 context 对象(13 参长签名收一处命名面)
db = ctx.db
_meta_get = ctx.meta_get
_save_get = ctx.save_get
camp_overlay = ctx.camp_overlay
shop_overlay = ctx.shop_overlay
toast = ctx.toast
start_overlay = ctx.start_overlay
logger = ctx.logger
_runner_get = ctx.runner_get
_open_modal = ctx.open_modal
_close_modal = ctx.close_modal
_refresh_presentations = ctx.refresh_presentations
# 打开营地(主菜单入口——开合与开始层互斥仿设置面先例:开始覆盖层非模态且树序在后,
# 规范 2.3 遮挡纪律;打开时先刷条件解锁——达标未解锁就地入账)
func open_camp() -> void:
var meta = _meta_get.call()
if meta == null:
return
meta.refresh_treasure_unlocks() # 条件解锁刷新
start_overlay.visible = false
_open_modal.call(camp_overlay) # 模态栈单口(登记层开合一律走栈)
camp_overlay.open_camp(camp_data())
_refresh_presentations.call()
func on_camp_closed() -> void:
_close_modal.call(camp_overlay) # 模态栈单口
start_overlay.visible = true
_refresh_presentations.call() # 关闭后刷新顶栏(工坊解锁可能变了余额侧读数面)
# 营地快照投影(纯读——秘宝匣 12 件/同调装配/树面/锻造工坊两区六路线/余额/最近冒险;拼句归
# CampOverlay,账面归 MetaProgression。锻造三行投影不在本投影内;
# workshop 键=秘宝匣 12 件;forge 键=锻造工坊路线行板投影)
func camp_data() -> Dictionary:
var meta = _meta_get.call()
return {
"balance": meta.keystone_balance() if meta != null else 0,
"workshop": meta.treasure_shop_status() if meta != null else [], # 键名沿 workshop(视图消费键不变——行内容=秘宝匣 12 件解锁投影)
"forge": meta.forge_shop_status() if meta != null else [], # 锻造工坊两区六路线投影
"attune": meta.attune_camp_status() if meta != null else {}, # 同调装配面投影
"tree": meta.tree_camp_rows() if meta != null else [], # 树面投影(含 forge_workshop/ks_workshop 钥匙门判据行)
"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
# 秘宝钥石购买(秘宝匣柜台购买钮意图;写口唯一=buy_treasure_unlock,
# 余额闸/重复购/条件件误购三重拒绝零副作用)
if attr != "attune_refresh":
if not meta.buy_treasure_unlock(attr):
toast.show_toast("购买被拒(余额不足或不可购买)", "warn")
return
camp_overlay.refresh_camp(camp_data())
_refresh_presentations.call()
return
# Y 刷新升级行购买(双入口之一——树上 ks_refresh 同账;
# 按钮禁用为常态防线,兜底拒绝可见 pattern 1。本函数无属性锻造分支)
if not meta.buy_attune_refresh():
toast.show_toast("购买被拒", "warn")
return
camp_overlay.refresh_camp(camp_data())
_refresh_presentations.call()
# 同调装配切换(装配关系唯一写口经 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())
# 天赋树购买(写口唯一归 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 免费切换(同组换侧零币;未激活组走购买面)
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()
# ---- 锻造工坊三意图(契约 9:写口唯一归 MetaProgression 锻造写口族;UI 只发意图)----
# 路线购买(几何价;余额不足/未解锁/满级=核心拒绝零副作用,兜底拒绝可见 pattern 1)
func on_camp_forge_buy(route_id: String) -> void:
var meta = _meta_get.call()
if meta == null:
return
if not meta.forge_buy_level(route_id):
toast.show_toast("锻造被拒——钥石不足或路线不可锻造", "warn")
return
camp_overlay.refresh_camp(camp_data())
_refresh_presentations.call()
# 一次性解锁价直购(并联双通道②;已解锁=拒绝零收费)
func on_camp_forge_unlock(route_id: String) -> void:
var meta = _meta_get.call()
if meta == null:
return
if not meta.forge_buy_unlock(route_id):
toast.show_toast("解锁被拒——钥石不足或已解锁", "warn")
return
camp_overlay.refresh_camp(camp_data())
_refresh_presentations.call()
# 当前武器/甲选中切换(免费;未解锁拒选)
func on_camp_forge_select(route_id: String) -> void:
var meta = _meta_get.call()
if meta == null:
return
var ok: bool = meta.forge_select_weapon(route_id) if String(db.forge_route(route_id).get("kind", "")) == "weapon" \
else meta.forge_select_armor(route_id)
if not ok:
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("GP不足——买不起这件商品", "warn")
return
shop_overlay.open_shelf(runner.shop_shelf(), int(runner.gp))
shop_overlay.set_visit_info(int(runner.shop_bp_visit_buys()), float(db.shop_price_a())) # T6 爬价规则行
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("GP不足——买不起这件商品", "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("GP不足——刷新失败", "warn")
return
shop_overlay.open_shelf(runner.shop_shelf(), int(runner.gp))
shop_overlay.set_visit_info(int(runner.shop_bp_visit_buys()), float(db.shop_price_a())) # T6 爬价规则行
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()