Files
gplan-2/game/presentation/main_ui_build.gd
T

299 lines
22 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
## 组合根 UI 装配分步承接件:三屏壳实例化、视图句柄接线、覆盖层装配与模态编排注入按步拆开。
## 本件是 main._build_ui 的逐字分步搬运(成员访问改 root 前缀)——main 侧留同名薄委托,装配序与 z 序零改动。
## 层序唯一事实源=root.ui.LAYER_CONTRACT + root.ui.apply_layer_contract(ui_registry.gd——本件不持有层序逻辑)。
extends RefCounted
const UiAssembler := preload("res://presentation/ui_assembler.gd")
const RowPacer := preload("res://presentation/row_pacer.gd")
const TreasureInteractController := preload("res://presentation/treasure_interact_controller.gd")
const CurioInteractController := preload("res://presentation/curio_interact_controller.gd")
const CampShopPresenter := preload("res://presentation/camp_shop_presenter.gd")
const BoardResolversScript := preload("res://presentation/board_resolvers.gd")
const DebugSessionScript := preload("res://presentation/debug_session.gd")
const TuneSimController := preload("res://presentation/tune_sim_controller.gd")
const MenuScreenScene := preload("res://presentation/scenes/menu_screen.tscn")
const CampScreenScene := preload("res://presentation/scenes/camp_screen.tscn")
const AdventureScreenScene := preload("res://presentation/scenes/adventure_screen.tscn")
static func build(root: Variant) -> void:
_build_screens(root)
_build_battle_wiring(root)
_build_overlays(root)
_build_aux_panels(root)
_build_curio(root)
_build_menu_wiring(root)
_build_debug_and_resolvers(root)
_build_modal_flow(root)
static func _build_screens(root: Variant) -> void:
# 三屏壳实例化:冒险屏先挂、菜单屏后挂(启动态菜单在顶层=玩家可见——
# 装配序纪律:菜单先挂会被冒险屏盖死,F5 见战斗界面);最终树序+登记序以 LAYER_CONTRACT
# 为唯一事实源,_apply_layer_contract 在装配尾统一落位。
# 屏内 level_screen 与 battle_root 无同时可见态(屏级互斥+模态栈遮挡),层内先后零视觉差;
# 视觉共存对逐对验算:legend/battle(图例战斗中开——legend 在冒险屏壳之后=其上 ✓)、
# debug/level_screen(浮窗最后子=其上 ✓)、shop/level_screen(商店节点域弹出——模态均在屏壳上 ✓)。
root.ui.screens.adventure_screen = AdventureScreenScene.instantiate()
root.add_child(root.ui.screens.adventure_screen)
root.ui.screens.menu_screen = MenuScreenScene.instantiate()
root.add_child(root.ui.screens.menu_screen)
root.ui.screens.start_overlay = root.ui.screens.menu_screen.start_overlay
root.ui.screens.battle_root = root.ui.screens.adventure_screen.battle_root
root.ui.battle.stage = root.ui.screens.adventure_screen.stage
root.ui.battle.log_view = root.ui.screens.adventure_screen.log_view
root.ui.battle.info_panel = root.ui.screens.adventure_screen.info_panel
root.ui.battle.board_zone = root.ui.screens.adventure_screen.board_zone
root.ui.battle.settlement_fx = root.ui.screens.adventure_screen.settlement_fx
root.ui.modals.run.level_screen = root.ui.screens.adventure_screen.level_screen
static func _build_battle_wiring(root: Variant) -> void:
# 结算行排播泵(本体在 row_pacer.gd;本类注入视图句柄与呈现刷新回调,零信号连接)
root.ui.screens.row_pacer = RowPacer.new()
root.ui.screens.row_pacer.log_view = root.ui.battle.log_view
root.ui.screens.row_pacer.stage = root.ui.battle.stage
root.ui.screens.row_pacer.board_zone = root.ui.battle.board_zone
root.ui.screens.row_pacer.settlement_fx = root.ui.battle.settlement_fx
root.ui.screens.row_pacer.board_view = root.ui.battle.board_zone.board_view
root.ui.screens.row_pacer.refresh_request = root._refresh_presentations
root.ui.screens.row_pacer.pump_ended = root.ui.flow.pump_ended # 演播完边沿统一派发(挂起 modal 优先,其次锁期点击重放)
root.add_child(root.ui.screens.row_pacer)
# 秘宝两步交互(本体在 treasure_interact_controller.gd;settlement/runner/grant 随关卡/战斗更替,
# 经现值回调注入——仿 identity_resolver 只读回调先例;grant 每战新建编排器,现值取件防悬挂)
root._treasure_ui = TreasureInteractController.new({
"db": root.db, "meta": root.meta, "info_panel": root.ui.battle.info_panel, "toast": root.ui.battle.board_zone.toast,
"board_view": root.ui.battle.board_zone.board_view,
"settlement_get": func() -> Variant: return root.run_scope.settlement,
"runner_get": func() -> Variant: return root.run_scope.runner,
"grant_get": func() -> Variant: return root.run_scope.settlement.reward_grant() if root.run_scope.settlement != null else null,
})
root.ui.battle.board_zone.action_requested.connect(root._on_board_action)
root.ui.battle.board_zone.status_badge.pressed.connect(root._on_status_badge_pressed) # 状态面板入口(战斗屏 HUD 角标)
root.ui.battle.info_panel.treasure_slot_clicked.connect(root._treasure_ui.on_slot_clicked) # 两步交互第一步
root.ui.battle.board_zone.stop_pressed.connect(root._on_stop_pressed)
root.ui.battle.board_zone.refresh_confirmed.connect(root._on_refresh_confirmed)
root.ui.battle.board_zone.legend_toggled.connect(root._on_legend_toggled)
root.ui.battle.board_zone.hover_forbidden.connect(root._on_hover_forbidden)
root.ui.battle.board_zone.chord_attempted.connect(root._on_chord_attempted)
root.logger = PlaytestLogger.new()
root.logger.ap_per_round = int(root.db.run.action_economy.ap_per_round)
static func _build_overlays(root: Variant) -> void:
# 模态覆盖层(登记序 = 树序 = z 遮挡序:图例 < 明细回看 < 节点 < 关卡画面 < 三选一 < 终局
# < 商店 < 营地场景根 < 四子界面 < 设置 < 状态面板(只读) < 生涯页(只读) < 确认弹窗 < 调试浮窗;
# 关卡画面住冒险屏壳(树序在模态之下)——登记序仍夹在节点层与三选一层之间,遮挡语义零改动)
var overlays: Dictionary[Variant, Variant] = UiAssembler.build_overlays(root, root.db)
root.ui.modals.run.legend_overlay = overlays.legend_overlay
root.ui.modals.run.node_overlay = overlays.node_overlay
root.ui.modals.run.end_overlay = overlays.end_overlay
root.ui.modals.menu.shop_overlay = overlays.shop_overlay
root.ui.screens.camp_screen = CampScreenScene.instantiate()
root.add_child(root.ui.screens.camp_screen)
# 营地场景根装配(布局表经 ConfigDB 单点注入——视图禁自载表路径;CampOverlay 随四柜台拆解退役)
var camp_layout: CampSceneLayoutTable = root.db.presentation.camp_scene_layout
root.ui.screens.camp_screen.camp_scene.setup_layers(camp_layout)
root.ui.screens.camp_screen.camp_scene.bind_hotspots(camp_layout)
root.ui.modals.camp.scene = root.ui.screens.camp_screen.camp_scene
# 营地四子界面(独立全屏页——场景根留底形态;句柄落 ui.modals.camp 子容器——禁平铺回 ui_modals)
var forge := ForgeView.new()
root.add_child(forge)
root.ui.modals.camp.forge = forge
var treasure := TreasureView.new()
root.add_child(treasure)
root.ui.modals.camp.treasure = treasure
var tree_view := TreeView.new()
root.add_child(tree_view)
root.ui.modals.camp.tree = tree_view
var codex := CodexView.new()
root.add_child(codex)
root.ui.modals.camp.codex = codex
# Esc 浮窗(卡⑤——登记于设置层之前的模态;三钮意图经 MainEscFlow 路由)
var esc_overlay := CampEscOverlay.new()
esc_overlay.setup_buttons()
esc_overlay.bind_signals()
root.add_child(esc_overlay)
root.ui.modals.camp.esc_overlay = esc_overlay
# 营地顶栏 HUD 栏(卡⑤——钥石余额常驻;挂卡④场景根顶栏预留位+入刷新单口目标册)
var hud_band := CampHudBand.new()
root.ui.modals.camp.scene.set_hud_band(hud_band)
# 屏壳/子界面插位由 LAYER_CONTRACT 槽统一落位——禁散落 move_child
root.ui.modals.run.legend_overlay.closed.connect(root.ui.flow.close.bind(root.ui.modals.run.legend_overlay))
root.ui.modals.run.node_overlay.continue_pressed.connect(root._on_node_continue)
root.ui.modals.run.level_screen.route_chosen.connect(root._on_route_chosen)
root.ui.modals.run.level_screen.status_badge.pressed.connect(root._on_status_badge_pressed) # 状态面板入口(地图屏顶栏角标)
root.ui.modals.run.level_screen.abandon_requested.connect(root._on_abandon_requested) # 放弃入口
root.ui.modals.run.end_overlay.continue_pressed.connect(root._on_battle_end_continue)
static func _build_aux_panels(root: Variant) -> void:
root.ui.modals.menu.settings_overlay = SettingsOverlay.new(root.settings_store) # 最小设置面(音量/全屏/减动效)
root.add_child(root.ui.modals.menu.settings_overlay)
root.ui.modals.menu.settings_overlay.closed.connect(root._on_settings_closed)
# 局内状态面板·只读(登记设置层同档带 dim 遮罩,确认弹窗之前;
# 数据=组合根开层实时读 MetaProgression 访问器喂入,本层零写通路)
root.ui.modals.run.status_panel = StatusPanel.new()
root.add_child(root.ui.modals.run.status_panel)
root.ui.modals.run.status_panel.closed.connect(root._on_status_closed)
# 生涯页·只读全屏页(登记序=营地同档,设置层旁、确认弹窗之前;
# 树序同步=addChild 同位(登记序=树序=z 序纪律);数据=组合根开层实时读 SaveManager 计数
# 访问器+MetaProgression 访问器喂入,本层零写通路零跳转;入口=开始层「营地与系统」组生涯钮)
root.ui.modals.menu.career_overlay = CareerOverlay.new()
root.add_child(root.ui.modals.menu.career_overlay)
root.ui.modals.menu.career_overlay.closed.connect(root._on_career_closed)
# 选档界面(营地交互重做卡③):登记序=生涯同档(树序同步=addChild 同位);三动作只发意图信号
#(激活/创建/删除+确认分派归组合根——视图零数据层依赖);入口=「进入存档」钮(卡② mn3 接线)。
root.ui.modals.menu.save_select_overlay = SaveSelectOverlay.new()
root.add_child(root.ui.modals.menu.save_select_overlay)
root.ui.modals.menu.save_select_overlay.closed.connect(root._on_save_select_closed)
root.ui.modals.menu.save_select_overlay.new_slot_requested.connect(root._on_save_select_new)
root.ui.modals.menu.save_select_overlay.load_slot_requested.connect(root._on_save_select_load)
root.ui.modals.menu.save_select_overlay.delete_slot_requested.connect(root._on_save_select_delete)
static func _build_curio(root: Variant) -> void:
# 提升三选一覆盖层(z 序 = 节点层之上、终局层之下;确认后同拍推进;
# 插位由 LAYER_CONTRACT curio 槽统一落位——禁散落 move_child)
root.ui.modals.run.curio_overlay = CurioPickOverlay.new()
root.add_child(root.ui.modals.run.curio_overlay)
# 提升三选一与替换子交互(本体在 curio_interact_controller.gd;本类只接线与回调注入,
# 模态栈/演播后排窗/呈现刷新/fatal 经回调注入,adventure/runner 现值回调)
root._bonus = CurioInteractController.new(CurioFlowContext.new(root.db,
{"curio_overlay": root.ui.modals.run.curio_overlay, "end_overlay": root.ui.modals.run.end_overlay,
"board_view": root.ui.battle.board_zone.board_view, "toast": root.ui.battle.board_zone.toast},
{"meta_get": func() -> Variant: return root.meta, "save_get": func() -> Variant: return root.save_manager,
"adventure_get": func() -> Variant: return root.run_scope.adventure,
"runner_get": func() -> Variant: return root.run_scope.runner,
"adventure_mode_get": func() -> Variant: return root.run_scope.is_adventure},
{"open_modal": root.ui.flow.open, "open_modal_after_playback": root.ui.flow.open_after_playback,
"close_modal": root.ui.flow.close, "refresh_presentations": root._refresh_presentations,
"fatal_screen": root._fatal_screen,
"gp_summary": _gp_summary_cb(root)})) # 构造参数收拢 context(u20 三束)
root.ui.modals.run.curio_overlay.pick_confirmed.connect(root._bonus.on_pick_confirmed)
root.ui.modals.run.curio_overlay.continue_pressed.connect(root._bonus.on_continue)
root.ui.modals.run.curio_overlay.refresh_requested.connect(root._bonus.on_refresh)
static func _build_menu_wiring(root: Variant) -> void:
# 菜单屏接线(句柄经屏壳暴露;三入口化——进入存档/设置/退出游戏,卡② mn3:
# 进入存档钮落点=卡③选档层 _open_save_select,点击链在此闭环)
root.ui.screens.menu_screen.entersavebtn.pressed.connect(root._open_save_select)
# 调试入口=UI 最顶层全局浮层角标(任何 UI 不遮挡——菜单/战斗/地图三枚
# 屏内角标由全局浮层承载;连接归组合根,树序/登记面见 LAYER_CONTRACT 末位)
root.ui.screens.debug_badge = UiAssembler.build_debug_badge(root)
root.add_child(root.ui.screens.debug_badge)
root.ui.screens.debug_badge.pressed.connect(root._on_debug_badge_pressed)
# 营地/商店投影(本体在 camp_shop_presenter.gd;本类只接线、探针缝保缝转发;
# 购买写调用唯一经 MetaProgression/LevelRunner core 单口——组合根 lambda 现值注入)
# 营地刷新单口(各写口尾→现开界面+场景根 HUD 一闸完成——卡④ T3;卡⑤ HUD band 随卡⑤挂册)
var camp_bus := CampRefreshBus.new()
camp_bus.bind_targets([root.ui.modals.camp.forge, root.ui.modals.camp.treasure,
root.ui.modals.camp.tree, root.ui.modals.camp.codex,
root.ui.screens.camp_screen.camp_scene.hud_band] as Array[Control])
root._camp_pres = CampShopPresenter.new(CampFlowContext.new(root.db,
{"camp_scene": root.ui.modals.camp.scene, "forge_view": root.ui.modals.camp.forge,
"treasure_view": root.ui.modals.camp.treasure, "tree_view": root.ui.modals.camp.tree,
"codex_view": root.ui.modals.camp.codex, "shop_overlay": root.ui.modals.menu.shop_overlay,
"toast": root.ui.battle.board_zone.toast, "start_overlay": root.ui.screens.start_overlay,
"save_select_overlay": root.ui.modals.menu.save_select_overlay, "logger": root.logger,
"refresh_bus": camp_bus},
{"meta_get": func() -> Variant: return root.meta, "save_get": func() -> Variant: return root.save_manager,
"runner_get": func() -> Variant: return root.run_scope.runner},
{"open_modal": root.ui.flow.open, "close_modal": root.ui.flow.close,
"refresh_presentations": root._refresh_presentations})) # 构造参数收拢 context(u21 三束)
root.ui.modals.menu.shop_overlay.buy_requested.connect(root._camp_pres.on_shop_buy)
root.ui.modals.menu.shop_overlay.refresh_requested.connect(root._camp_pres.on_shop_refresh) # MC-1
root.ui.modals.menu.shop_overlay.leave_pressed.connect(root._camp_pres.on_shop_leave)
# 营地信号接线(CampOverlay 退役——四子界面意图信号+场景点选组合根路由+closed 经 bind 携视图)
root.ui.modals.camp.forge.forge_buy_requested.connect(root._camp_pres.on_camp_forge_buy)
root.ui.modals.camp.forge.forge_unlock_requested.connect(root._camp_pres.on_camp_forge_unlock)
root.ui.modals.camp.forge.forge_select_requested.connect(root._camp_pres.on_camp_forge_select)
root.ui.modals.camp.treasure.buy_requested.connect(root._camp_pres.on_camp_buy)
root.ui.modals.camp.treasure.attune_toggled.connect(root._camp_pres.on_camp_attune_toggle)
root.ui.modals.camp.tree.tree_buy_requested.connect(root._camp_pres.on_camp_tree_buy)
root.ui.modals.camp.tree.tree_fork_toggled.connect(root._camp_pres.on_camp_fork_toggle)
root.ui.modals.camp.forge.closed.connect(root._camp_pres.on_camp_sub_closed.bind(root.ui.modals.camp.forge))
root.ui.modals.camp.treasure.closed.connect(root._camp_pres.on_camp_sub_closed.bind(root.ui.modals.camp.treasure))
root.ui.modals.camp.tree.closed.connect(root._camp_pres.on_camp_sub_closed.bind(root.ui.modals.camp.tree))
root.ui.modals.camp.codex.closed.connect(root._camp_pres.on_camp_sub_closed.bind(root.ui.modals.camp.codex))
root.ui.modals.camp.scene.object_pressed.connect(root._on_camp_object_pressed)
root.ui.screens.menu_screen.settingsbtn.pressed.connect(root._open_settings) # 最小设置面入口
root.ui.screens.menu_screen.quitbtn.pressed.connect(root._quit_game) # 退出游戏提级一级菜单
static func _build_debug_and_resolvers(root: Variant) -> void:
var inter: Dictionary[Variant, Variant] = UiAssembler.build_inter_overlay(root)
root.ui.screens.inter_overlay = inter.overlay
root.ui.screens.inter_label = inter.label
inter.next_btn.pressed.connect(root._on_inter_next_pressed)
# 确认弹窗(新冒险互斥确认弹窗化):树序落在开始层/过场层之后 = 子级最末
#(纪律「登记序 = 树序 = z 序」——confirm_dialog.open 禁运行时 move_child 自提抬层;
# 弹窗只在开始菜单域弹出,绘制序恒最上;
# 诊断转储入口在调试浮窗)
root.ui.modals.menu.confirm_dialog = ConfirmDialog.new()
root.add_child(root.ui.modals.menu.confirm_dialog)
# 消费方编排(卡③ 契约 3/sl8——禁双常驻静态接线):open 前设 _confirm_consumer,
# confirmed/cancelled 后按该状态量分派;分派面可容纳后续消费方(卡④ 到位直接接入不返工)
root.ui.modals.menu.confirm_dialog.confirmed.connect(root._on_confirm_accepted)
root.ui.modals.menu.confirm_dialog.secondary_confirmed.connect(root._on_confirm_secondary) # 三钮次确认(卡④ 大门「开始新冒险」出口)
root.ui.modals.menu.confirm_dialog.cancelled.connect(root._on_confirm_declined)
# Esc 浮窗三出口接线(卡⑤——意图信号经薄委托归 MainEscFlow 动线;输入入口=main._unhandled_input)
root.ui.modals.camp.esc_overlay.return_requested.connect(root._on_esc_return)
root.ui.modals.camp.esc_overlay.settings_requested.connect(root._on_esc_settings)
root.ui.modals.camp.esc_overlay.quit_requested.connect(root._on_esc_quit)
# 调试浮窗(主菜单三散件收一:随机种子/数值调整/批量模拟/两层调试入口;
# 全局可开浮窗:调试会话恒开=启动即开〔_ready〕+两屏角标再开+当局数据只读区块;
# 诊断转储入口在本浮窗,玩家画面零常驻调试件)。
# 树序在开始层与确认弹窗之后 = 浮于菜单之上的 Dialog(不隐藏开始层——与确认弹窗同款形态);
# 开窗唯一入口 = _show_debug_overlay(C6 单口——角标/探针/启动路径全收口)
root.ui.modals.menu.debug_overlay = DebugOverlay.new(root.db, int(root.db.combat.hero.atk), int(root.db.combat.hero.max_hp))
root.add_child(root.ui.modals.menu.debug_overlay)
root.ui.modals.menu.debug_overlay.closed.connect(root.ui.flow.close.bind(root.ui.modals.menu.debug_overlay))
root.ui.modals.menu.debug_overlay.apply_requested.connect(root._on_tune_apply)
root.ui.modals.menu.debug_overlay.simulate_requested.connect(root._on_tune_simulate)
root.ui.modals.menu.debug_overlay.debug_levels_requested.connect(root._start_debug_levels)
# 棋盘解析回调族(有态绑定件——board_resolvers.gd 承接;组合根只接线;局部持有不占类成员位)
var resolvers = BoardResolversScript.new(root.db, root.ui.modals.menu.debug_overlay, func() -> Variant: return root.run_scope.settlement)
# 奖励徽章数据源:地图战斗资源 身份解析只读回调(已开格身份 icon——挖掘期 Active 相位内有效)
root.ui.battle.board_zone.board_view.set_identity_resolver(resolvers.identity_lookup)
# 数字遮蔽数据源:地形变异 hide_number 解析只读回调(信息型呈现——board_view 渲染面读取)
root.ui.battle.board_zone.board_view.set_variant_mask_resolver(resolvers.variant_mask_lookup)
root.ui.battle.board_zone.board_view.set_variant_mark_resolver(resolvers.variant_mark_lookup) # flat_damage 未开格角标
# 调试会话(英雄生效值预览回调绑同一解析件——创建晚于浮窗、随 _build_ui 一次接线)
root._debug = DebugSessionScript.new(root.db, root.get_node(root.AUTOLOAD_PATH_RNG),
func() -> Dictionary: return resolvers.debug_hero_override(), root._seed_value)
root._debug.battle_created.connect(root._on_debug_battle_created)
root._debug.battle_ready.connect(root._on_debug_battle_ready)
static func _build_modal_flow(root: Variant) -> void:
# 模态登记(缺陷⑤纪律——登记序 = 树序 = z 序):层序唯一事实源=LAYER_CONTRACT,
# _apply_layer_contract 统一落位(关卡画面宿住冒险屏壳、营地屏壳夹商店与设置层之间、
# 三选一对位终局层之下等特例均在合同数据内表达,禁散落 move_child)
# 模态编排注入(ModalFlow 纯逻辑类:依赖仅 ModalStack+查询 Callable——实例随 ui.modals 容器构造建立)
root.ui.flow.start_visible_get = func() -> bool: return root.ui.screens.start_overlay.visible
root.ui.flow.inter_visible_get = func() -> bool: return root.ui.screens.inter_overlay.visible
root.ui.flow.settlement_get = func() -> Variant: return root.run_scope.settlement
root.ui.flow.rows_empty_get = func() -> bool: return root.ui.screens.row_pacer.pending_rows().is_empty()
root.ui.flow.flush_rows = root.ui.screens.row_pacer.flush
root.ui.flow.on_open_sync = func() -> void:
root._sync_status_entry() # 状态面板入口角标可见性/置灰随开态同步
root._sync_debug_overlay() # 浮窗当局数据/按钮态随开态同步
root.ui.flow.on_close_sync = func() -> void:
root._sync_status_entry() # 关闭恢复角标可点态(无残留——同源口径)
root._sync_debug_overlay() # 他层关闭复活浮窗时同步刷新(关后自动恢复语义配套)
root.ui.flow.replay_click = root._replay_locked_click
# 视觉层合同装配(唯一事实源=LAYER_CONTRACT:树序+登记序同源派生——改序只改合同表)
root.ui.apply_layer_contract(root)
root._tune_sim = TuneSimController.new(root.db, root.ui.modals.menu.debug_overlay, int(root.db.combat.hero.max_hp))
static func fatal_screen(root: Variant, msg: String) -> void:
var l := UiKit.label(msg,UiKit.FS_SUB, true, UiKit.RED_ALERT)
l.set_anchors_preset(Control.PRESET_CENTER)
root.add_child(l)
# GP 总结行回调(gp_summary 束键——单行 lambda 内嵌字典实参收尾歧义,提函数承载)
static func _gp_summary_cb(root: Variant) -> Callable:
return func() -> String: return root._gp_summary_text({"gp_dropped": root.run_scope.last_gain_amount})