iter52-卡3:迭代52卡③(presentation层封装修复): A1三类42字段公开化(280处改址)+A2拆件子件公开面(vitals14/motion14/board_hover七口)+B族组合根交接面(18服务+12句柄口,束参两处,on_*保私有)+C族杂项(UiKit三工厂/构造交付)——presentation私取sweep恒零(V1-V4,105checks),main.gd例外键32与ui_kit例外键23落基线(§3.2误测0/0反哺点),探针测试22处引用迁移,渲染golden与探针套件零转红; 验收档中
This commit is contained in:
1 parent
f2c3a3887d
commit
67024cb9ec
42 files changed
+1391
-679
No files matched your search
+189
-96
@@ -49,7 +49,57 @@ var settings_store # 系统级设置独立存储(音量
|
||||
var _tune_sim = null # 调试浮窗批量模拟控制器(tune_sim_controller.gd)
|
||||
|
||||
|
||||
func _replay_locked_click() -> void:
|
||||
# ---- 句柄读投影/设置口(pres封装 B 规则 4——成员保持私有,分文件经此读写;BreakpointStore 方法对先例)----
|
||||
|
||||
func set_treasure_ui(v: Variant) -> void:
|
||||
_treasure_ui = v
|
||||
|
||||
|
||||
func treasure_ui() -> Variant:
|
||||
return _treasure_ui
|
||||
|
||||
|
||||
func set_bonus(v: Variant) -> void:
|
||||
_bonus = v
|
||||
|
||||
|
||||
func bonus() -> Variant:
|
||||
return _bonus
|
||||
|
||||
|
||||
func set_camp_pres(v: Variant) -> void:
|
||||
_camp_pres = v
|
||||
|
||||
|
||||
func camp_pres() -> Variant:
|
||||
return _camp_pres
|
||||
|
||||
|
||||
func set_debug(v: Variant) -> void:
|
||||
_debug = v
|
||||
|
||||
|
||||
func debug() -> Variant:
|
||||
return _debug
|
||||
|
||||
|
||||
func set_tune_sim(v: Variant) -> void:
|
||||
_tune_sim = v
|
||||
|
||||
|
||||
func tune_sim() -> Variant:
|
||||
return _tune_sim
|
||||
|
||||
|
||||
func set_confirm_consumer(v: String) -> void:
|
||||
_confirm_consumer = v
|
||||
|
||||
|
||||
func set_confirm_consumer_slot(v: int) -> void:
|
||||
_confirm_consumer_slot = v
|
||||
|
||||
|
||||
func replay_locked_click() -> void:
|
||||
if run_scope.locked_click.is_empty() or not ui.screens.row_pacer.pending_rows().is_empty():
|
||||
return
|
||||
var click: Dictionary[Variant, Variant] = run_scope.locked_click
|
||||
@@ -59,7 +109,7 @@ func _replay_locked_click() -> void:
|
||||
|
||||
# 会话生命周期:_ready 建闲时态(永非 null——全组合根零空判责),
|
||||
# 开局/续玩/调试两层各建新会话,旧会话整节点 queue_free()(帧末回收;新节点先挂树无真空窗)。
|
||||
func _begin_session(adventure_session: bool) -> void:
|
||||
func begin_session(adventure_session: bool) -> void:
|
||||
ui.screens.row_pacer.flush() # 会话更替即清队(待播行不住局态——机制保证,不靠时序约定)
|
||||
if run_scope != null:
|
||||
run_scope.queue_free()
|
||||
@@ -73,7 +123,7 @@ func _ready() -> void:
|
||||
if not db.is_ready:
|
||||
var ok: bool = db.load_tables("res://data/tables", false)
|
||||
if not ok:
|
||||
_fatal_screen("数值表加载失败——见输出错误清单(fail-fast)")
|
||||
fatal_screen("数值表加载失败——见输出错误清单(fail-fast)")
|
||||
return
|
||||
# SaveManager/MetaProgression 升格 autoload:读盘已在各自 _ready 完成
|
||||
#(autoload 先于主场景就绪——读盘时序等价),此处零动作。
|
||||
@@ -83,7 +133,7 @@ func _ready() -> void:
|
||||
settings_store.apply()
|
||||
ui = UiRegistry.new() # 注册面先于装配建(_build_ui 八步向容器登记——装配时序零变化)
|
||||
_build_ui()
|
||||
_begin_session(false) # 闲时会话(gp_ledger 等探针注入面自 _ready 起恒在——ui_probe 合成段时序)
|
||||
begin_session(false) # 闲时会话(gp_ledger 等探针注入面自 _ready 起恒在——ui_probe 合成段时序)
|
||||
ui.screens.start_overlay.visible = true
|
||||
ui.screens.debug_badge.visible = _debug_entry # 浮层角标可见性须门控落定后赋值(构建/连接在 _build_ui);_debug_entry 缺省 false=发布态恒隐
|
||||
# boot 菜单优先——启动不自动开浮窗(开始屏不得被调试面板盖住);
|
||||
@@ -93,11 +143,44 @@ func _ready() -> void:
|
||||
# ==================== 视图接线(三屏壳 + 模态层装配;构建归屏壳/UiAssembler,本类只接线) ====================
|
||||
|
||||
func _build_ui() -> void:
|
||||
MainUiBuild.build(self)
|
||||
# 处理器束(pres封装 B 规则 2——on_* 处理器保私有,接线语句经束消费;键=用途名,main 内自引合法)
|
||||
MainUiBuild.build(self, {
|
||||
"board_action": _on_board_action,
|
||||
"status_badge_pressed": _on_status_badge_pressed,
|
||||
"stop_pressed": _on_stop_pressed,
|
||||
"refresh_confirmed": _on_refresh_confirmed,
|
||||
"legend_toggled": _on_legend_toggled,
|
||||
"hover_forbidden": _on_hover_forbidden,
|
||||
"chord_attempted": _on_chord_attempted,
|
||||
"node_continue": _on_node_continue,
|
||||
"route_chosen": _on_route_chosen,
|
||||
"abandon_requested": _on_abandon_requested,
|
||||
"battle_end_continue": _on_battle_end_continue,
|
||||
"settings_closed": _on_settings_closed,
|
||||
"status_closed": _on_status_closed,
|
||||
"career_closed": _on_career_closed,
|
||||
"save_select_closed": _on_save_select_closed,
|
||||
"save_select_new": _on_save_select_new,
|
||||
"save_select_load": _on_save_select_load,
|
||||
"save_select_delete": _on_save_select_delete,
|
||||
"debug_badge_pressed": _on_debug_badge_pressed,
|
||||
"camp_object_pressed": _on_camp_object_pressed,
|
||||
"inter_next_pressed": _on_inter_next_pressed,
|
||||
"confirm_accepted": _on_confirm_accepted,
|
||||
"confirm_secondary": _on_confirm_secondary,
|
||||
"confirm_declined": _on_confirm_declined,
|
||||
"esc_return": _on_esc_return,
|
||||
"esc_settings": _on_esc_settings,
|
||||
"esc_quit": _on_esc_quit,
|
||||
"tune_apply": _on_tune_apply,
|
||||
"tune_simulate": _on_tune_simulate,
|
||||
"debug_battle_created": _on_debug_battle_created,
|
||||
"debug_battle_ready": _on_debug_battle_ready,
|
||||
})
|
||||
ui.modals.menu.debug_overlay.diagnose_requested.connect(_on_diagnose_pressed) # 诊断转储入口
|
||||
|
||||
|
||||
func _fatal_screen(msg: String) -> void:
|
||||
func fatal_screen(msg: String) -> void:
|
||||
MainUiBuild.fatal_screen(self, msg)
|
||||
|
||||
|
||||
@@ -122,13 +205,13 @@ func _on_confirm_accepted() -> void:
|
||||
save_manager.slots.delete_slot(_confirm_consumer_slot)
|
||||
ui.modals.menu.save_select_overlay.open_save_select(HudPresenter.save_select_data(save_manager)) # 就地刷新(该槽回空态)
|
||||
"gate":
|
||||
_camp_pres.on_camp_closed() # 冒险发起后营地场景屏隐藏(选档层同收——卡④ T4 关层收闭)
|
||||
camp_pres().on_camp_closed() # 冒险发起后营地场景屏隐藏(选档层同收——卡④ T4 关层收闭)
|
||||
_resume_from_menu() # 继续上一段冒险(现役续玩编排同源——卡④ 契约 4)
|
||||
"gate_new":
|
||||
_camp_pres.on_camp_closed()
|
||||
camp_pres().on_camp_closed()
|
||||
_start_adventure() # 开始新冒险(现役开局编排同源)
|
||||
"campfire_leave":
|
||||
_camp_pres.on_camp_closed() # 篝火确认离场=营地与选档层同收+开始层恢复(回主菜单)
|
||||
camp_pres().on_camp_closed() # 篝火确认离场=营地与选档层同收+开始层恢复(回主菜单)
|
||||
_confirm_consumer = ""
|
||||
|
||||
|
||||
@@ -137,7 +220,7 @@ func _on_confirm_secondary() -> void:
|
||||
ui.flow.close(ui.modals.menu.confirm_dialog) # 模态栈单口
|
||||
match _confirm_consumer:
|
||||
"gate":
|
||||
_camp_pres.on_camp_closed()
|
||||
camp_pres().on_camp_closed()
|
||||
_start_adventure()
|
||||
_confirm_consumer = ""
|
||||
|
||||
@@ -148,7 +231,7 @@ func _on_confirm_declined() -> void:
|
||||
|
||||
|
||||
# 退出游戏(一级菜单入口;设置即时落盘后退出)
|
||||
func _quit_game() -> void:
|
||||
func quit_game() -> void:
|
||||
if settings_store != null:
|
||||
settings_store.save_settings()
|
||||
get_tree().quit()
|
||||
@@ -161,23 +244,26 @@ func _resume_from_menu() -> void:
|
||||
var bp: Dictionary[Variant, Variant] = save_manager.breakpoint_snapshot()
|
||||
ui.flow.close(ui.modals.menu.debug_overlay) # 开冒险路径显式关浮窗(续玩同口径)
|
||||
ui.screens.start_overlay.visible = false
|
||||
_begin_session(true)
|
||||
begin_session(true)
|
||||
_wire_adventure()
|
||||
run_scope.level_active = true
|
||||
_sync_status_entry() # 续玩恢复点亮状态面板入口角标
|
||||
sync_status_entry() # 续玩恢复点亮状态面板入口角标
|
||||
ui.modals.run.level_screen.set_abandon_available(true)
|
||||
ui.battle.log_view.clear()
|
||||
logger.start_session(int(bp.get("master_seed", 0)))
|
||||
run_scope.adventure.set_hero_override(ui.modals.menu.debug_overlay.atk_pct(), ui.modals.menu.debug_overlay.hp_pct())
|
||||
# 同调三键随续玩下传(build_adventure_params 统一构造,与开新局同源同口径)
|
||||
if not run_scope.adventure.resume_adventure(bp, build_adventure_params(save_manager, meta,
|
||||
{"treasure_unlocked": _treasure_ui.unlocked_set()})):
|
||||
_fatal_screen("续玩恢复失败——见输出日志")
|
||||
{"treasure_unlocked": treasure_ui().unlocked_set()})):
|
||||
fatal_screen("续玩恢复失败——见输出日志")
|
||||
return
|
||||
|
||||
|
||||
func _wire_adventure() -> void:
|
||||
MainAdventureFlow._wire_adventure(self)
|
||||
MainAdventureFlow.wire_adventure(self, {
|
||||
"adventure_finished": _on_adventure_finished,
|
||||
"level_started": _on_adventure_level_started,
|
||||
})
|
||||
|
||||
|
||||
# 冒险开局参数面构造(主菜单开新/续玩两入口共用:同调三键随开局下传。
|
||||
@@ -205,59 +291,66 @@ static func build_adventure_params(save_manager_ref: SaveManagerCore, meta_ref:
|
||||
# 信号连接只在组合根)
|
||||
func _start_adventure() -> void:
|
||||
ui.flow.close(ui.modals.menu.debug_overlay) # 开冒险路径显式关浮窗(调试浮窗默认开态下开局不带窗;对局中经角标再开)
|
||||
_begin_session(true)
|
||||
begin_session(true)
|
||||
_wire_adventure()
|
||||
run_scope.level_active = true
|
||||
_sync_status_entry() # 进对局点亮状态面板入口角标
|
||||
sync_status_entry() # 进对局点亮状态面板入口角标
|
||||
ui.modals.run.level_screen.set_abandon_available(true) # 放弃入口挂选路停点(天然非战斗期)
|
||||
ui.battle.log_view.clear()
|
||||
var seed_val := _seed_value()
|
||||
var seed_val := seed_value()
|
||||
logger.start_session(seed_val)
|
||||
run_scope.adventure.set_hero_override(ui.modals.menu.debug_overlay.atk_pct(), ui.modals.menu.debug_overlay.hp_pct())
|
||||
# 永久强化注入:冒险开始参数面——meta 现算 override 显式传参(全零 = 白板)
|
||||
# 开局参数面(同调三键同源构造——确定性承载=固定种子+参数档,首局教学图由种子域三分判定给 42)
|
||||
if not run_scope.adventure.start_adventure(seed_val, build_adventure_params(save_manager, meta,
|
||||
{"treasure_unlocked": _treasure_ui.unlocked_set(), "shop_mode": "pool"})):
|
||||
_fatal_screen("冒险启动失败——见输出日志")
|
||||
{"treasure_unlocked": treasure_ui().unlocked_set(), "shop_mode": "pool"})):
|
||||
fatal_screen("冒险启动失败——见输出日志")
|
||||
return
|
||||
|
||||
|
||||
func _on_adventure_level_started(level_index: int, display_name: String)-> void:
|
||||
MainAdventureFlow._on_adventure_level_started(self, level_index, display_name)
|
||||
MainAdventureFlow.on_adventure_level_started(self, level_index, display_name, {
|
||||
"step_row": _on_step_row,
|
||||
"round_started": _on_round_started,
|
||||
"board_changed": _on_board_changed,
|
||||
"runner_node": _on_runner_node,
|
||||
"choices_presented": _on_choices_presented,
|
||||
"wallet_changed": _on_wallet_changed,
|
||||
})
|
||||
|
||||
|
||||
func _on_runner_node(node: Dictionary, info: Dictionary)-> void:
|
||||
MainAdventureFlow._on_runner_node(self, node, info)
|
||||
MainAdventureFlow.on_runner_node(self, node, info)
|
||||
|
||||
|
||||
func _on_adventure_finished(summary: Dictionary)-> void:
|
||||
MainAdventureFlow._on_adventure_finished(self, summary)
|
||||
MainAdventureFlow.on_adventure_finished(self, summary)
|
||||
|
||||
|
||||
func _on_battle_end_continue() -> void:
|
||||
MainAdventureFlow._on_battle_end_continue(self)
|
||||
MainAdventureFlow.on_battle_end_continue(self)
|
||||
|
||||
|
||||
func _start_debug_levels() -> void:
|
||||
MainDebugFlow._start_debug_levels(self)
|
||||
func start_debug_levels() -> void:
|
||||
MainDebugFlow.start_debug_levels(self)
|
||||
|
||||
|
||||
func _on_debug_battle_created(settlement_ref: BattleOrchestrator, _level: int, _carry: int)-> void:
|
||||
MainDebugFlow._on_debug_battle_created(self, settlement_ref, _level, _carry)
|
||||
MainDebugFlow.on_debug_battle_created(self, settlement_ref, _level, _carry)
|
||||
|
||||
|
||||
func _on_debug_battle_ready(settlement_ref: BattleOrchestrator, _level: int, _carry: int)-> void:
|
||||
MainDebugFlow._on_debug_battle_ready(self, settlement_ref, _level, _carry)
|
||||
MainDebugFlow.on_debug_battle_ready(self, settlement_ref, _level, _carry)
|
||||
|
||||
|
||||
func _on_inter_next_pressed() -> void:
|
||||
MainAdventureFlow._on_inter_next_pressed(self)
|
||||
MainAdventureFlow.on_inter_next_pressed(self)
|
||||
|
||||
|
||||
# 加成生效值供给 / 奖励徽章身份解析 / 地形变异两种解析 —— 见 board_resolvers.gd(有态绑定族判离)
|
||||
|
||||
|
||||
func _show_board_fx_for_node(node_type: String = "") -> void:
|
||||
func show_board_fx_for_node(node_type: String = "") -> void:
|
||||
# Boss 遭遇变体:紫赤影子爬棋盘一次性占位演出(§2.6)
|
||||
if node_type == "boss":
|
||||
ui.battle.board_zone.fx.play_boss_shadow()
|
||||
@@ -266,15 +359,15 @@ func _show_board_fx_for_node(node_type: String = "") -> void:
|
||||
func _on_battle_finished(payload: Dictionary) -> void:
|
||||
# 仅调试模式接线(冒险的关卡结束归 AdventureRunner._on_level_finished——大奖励/冒险终局分派)
|
||||
var result: String = String(payload.get("result", ""))
|
||||
_sync_settlement_visuals() # → 全关(battle_over 反馈面)
|
||||
sync_settlement_visuals() # → 全关(battle_over 反馈面)
|
||||
HudPresenter.battle_end_feedback(ui.battle.stage, ui.battle.board_zone.ap_bar, true) # 灰化/idle 回正/RAP 清零/状态全清——HUD 单通道
|
||||
var decision: Dictionary[Variant, Variant] = _debug.on_battle_finished(payload)
|
||||
var decision: Dictionary[Variant, Variant] = debug().on_battle_finished(payload)
|
||||
match String(decision.outcome):
|
||||
"next_layer":
|
||||
ui.screens.inter_label.text = "第 %d 层通过!\nHP %d/%d 带入下一层" % [
|
||||
int(decision.layer), int(decision.carry_hp), int(db.combat.hero.max_hp)]
|
||||
ui.screens.inter_overlay.visible = true
|
||||
_refresh_presentations()
|
||||
refresh_presentations()
|
||||
return
|
||||
"level_end":
|
||||
ui.modals.run.end_overlay.open_victory(Narrator.battle_end_text("victory", true),
|
||||
@@ -287,7 +380,7 @@ func _on_battle_finished(payload: Dictionary) -> void:
|
||||
ui.modals.run.end_overlay.open_defeat(Narrator.battle_end_text("defeat", false),
|
||||
"返回", ui.battle.board_zone.board_view.get_global_rect())
|
||||
ui.flow.open_after_playback(ui.modals.run.end_overlay)
|
||||
_refresh_presentations()
|
||||
refresh_presentations()
|
||||
|
||||
|
||||
# 战利品行装配(battle.md OQ14——胜利总结结算展示 GP 总量+增量;纯呈现读数):
|
||||
@@ -297,7 +390,7 @@ func _on_battle_finished(payload: Dictionary) -> void:
|
||||
# resume_adventure 的 bonus_pending 分支在 level_started 之前同步 curio_choice_presented.emit
|
||||
# 〔resume 提前 return〕,本 lambda 求值时 runner 尚未赋值;合计改读断点快照 gp 兜底——恢复源快照
|
||||
# 此刻必在档,语义=续玩恢复面的合计即存档合计;承接体见 HudPresenter.gp_summary_text)
|
||||
func _gp_summary_text(payload: Dictionary) -> String:
|
||||
func gp_summary_text(payload: Dictionary) -> String:
|
||||
return HudPresenter.gp_summary_text(payload, run_scope.runner, save_manager)
|
||||
|
||||
|
||||
@@ -306,12 +399,12 @@ func _gp_summary_text(payload: Dictionary) -> String:
|
||||
|
||||
## 战末账行束(open_victory opts 键装配单点——GP/钥石/加成三行与增量合计)
|
||||
func _end_victory_opts(payload: Dictionary) -> Dictionary:
|
||||
return {"gp_summary": _gp_summary_text(payload),
|
||||
return {"gp_summary": gp_summary_text(payload),
|
||||
"ks_summary": "钥石 · 累计 %d" % run_scope.runner.keystone,
|
||||
"bonus_summary": HudPresenter.bonus_summary_text(run_scope.runner),
|
||||
"gp_gain": int(payload.get("gp_dropped", 0)), "gp_total": int(run_scope.runner.gp)}
|
||||
|
||||
func _wire_battle(settlement_ref: BattleOrchestrator) -> void:
|
||||
func wire_battle(settlement_ref: BattleOrchestrator) -> void:
|
||||
settlement_ref.step_row.connect(_on_step_row)
|
||||
settlement_ref.battle_finished.connect(_on_battle_finished)
|
||||
settlement_ref.round_started.connect(_on_round_started)
|
||||
@@ -363,7 +456,7 @@ func _on_round_started(ri: int) -> void:
|
||||
var vlines: Array[String] = HudPresenter.board_variant_lines(db, bvs)
|
||||
if not vlines.is_empty():
|
||||
ui.battle.settlement_fx.play_board_variant_banner("地形变异 · " + " · ".join(vlines))
|
||||
_refresh_presentations()
|
||||
refresh_presentations()
|
||||
|
||||
|
||||
func _on_board_changed() -> void:
|
||||
@@ -376,8 +469,8 @@ func _on_board_changed() -> void:
|
||||
ui.battle.board_zone.board_view.rebuild(board, run_scope.settlement.minesweeper)
|
||||
if board != null:
|
||||
ui.battle.stage.set_board_type(String(board.board_type)) # 舞台背景选图随板(C8——既有重建点单行同步)
|
||||
_check_board_hints()
|
||||
_refresh_presentations()
|
||||
check_board_hints()
|
||||
refresh_presentations()
|
||||
|
||||
|
||||
# 秘宝视野呈现桥:阔视野带 light_radius_bonus
|
||||
@@ -392,7 +485,7 @@ func _sync_treasure_glow() -> void:
|
||||
|
||||
# ---- 一次性非模态提示(每 run 一次——pattern 10)与诚实提示 ----
|
||||
|
||||
func _check_board_hints() -> void:
|
||||
func check_board_hints() -> void:
|
||||
var board = run_scope.settlement.minesweeper.current_board()
|
||||
if board == null:
|
||||
return
|
||||
@@ -423,15 +516,15 @@ func _show_hint(text: String, hint_id: String) -> void:
|
||||
# ---- 节点覆盖层"继续"(奖励/恢复点兑付确认)与节点图选路覆盖层 ----
|
||||
|
||||
func _on_node_continue() -> void:
|
||||
MainAdventureFlow._on_node_continue(self)
|
||||
MainAdventureFlow.on_node_continue(self)
|
||||
|
||||
|
||||
func _on_choices_presented(candidates: Array)-> void:
|
||||
MainAdventureFlow._on_choices_presented(self, candidates)
|
||||
MainAdventureFlow.on_choices_presented(self, candidates)
|
||||
|
||||
|
||||
func _on_route_chosen(node_id: int)-> void:
|
||||
MainAdventureFlow._on_route_chosen(self, node_id)
|
||||
MainAdventureFlow.on_route_chosen(self, node_id)
|
||||
|
||||
|
||||
# ---- 独立屏屏级互斥(单一出口:进屏隐战斗屏 / 出屏显战斗屏)----
|
||||
@@ -439,86 +532,86 @@ func _on_route_chosen(node_id: int)-> void:
|
||||
# 关闭自动恢复本屏(遮挡语义复用),战斗屏不在此期间回显。
|
||||
|
||||
func _enter_level_screen() -> void:
|
||||
MainAdventureFlow._enter_level_screen(self)
|
||||
MainAdventureFlow.enter_level_screen(self)
|
||||
|
||||
|
||||
func _exit_level_screen() -> void:
|
||||
MainAdventureFlow._exit_level_screen(self)
|
||||
MainAdventureFlow.exit_level_screen(self)
|
||||
|
||||
|
||||
func _on_abandon_requested() -> void:
|
||||
MainAdventureFlow._on_abandon_requested(self)
|
||||
MainAdventureFlow.on_abandon_requested(self)
|
||||
|
||||
|
||||
# ---- 最小设置面(入口/开合仿营地先例:开始层互斥;生效与持久化归 SettingsStore)----
|
||||
|
||||
func _open_settings() -> void:
|
||||
MainOverlayFlow._open_settings(self)
|
||||
func open_settings() -> void:
|
||||
MainOverlayFlow.open_settings(self)
|
||||
|
||||
|
||||
func _on_settings_closed() -> void:
|
||||
MainOverlayFlow._on_settings_closed(self)
|
||||
MainOverlayFlow.on_settings_closed(self)
|
||||
|
||||
|
||||
func _status_data() -> Dictionary:
|
||||
return MainOverlayFlow._status_data(self)
|
||||
func status_data() -> Dictionary:
|
||||
return MainOverlayFlow.status_data(self)
|
||||
|
||||
|
||||
func _open_status_panel() -> void:
|
||||
MainOverlayFlow._open_status_panel(self)
|
||||
MainOverlayFlow.open_status_panel(self)
|
||||
|
||||
|
||||
func _on_status_closed() -> void:
|
||||
MainOverlayFlow._on_status_closed(self)
|
||||
MainOverlayFlow.on_status_closed(self)
|
||||
|
||||
|
||||
# ---- 生涯页·只读(入口=开始层生涯钮;开合仿设置面先例:开始层互斥;
|
||||
# 数据快照随开随建零写通路,页内零跳转)----
|
||||
|
||||
func _open_career() -> void:
|
||||
MainOverlayFlow._open_career(self)
|
||||
MainOverlayFlow.open_career(self)
|
||||
|
||||
|
||||
func _on_career_closed() -> void:
|
||||
MainOverlayFlow._on_career_closed(self)
|
||||
MainOverlayFlow.on_career_closed(self)
|
||||
|
||||
|
||||
func _open_save_select() -> void:
|
||||
MainOverlayFlow._open_save_select(self)
|
||||
func open_save_select() -> void:
|
||||
MainOverlayFlow.open_save_select(self)
|
||||
|
||||
|
||||
func _on_save_select_closed() -> void:
|
||||
MainOverlayFlow._on_save_select_closed(self)
|
||||
MainOverlayFlow.on_save_select_closed(self)
|
||||
|
||||
|
||||
func _on_save_select_new(slot: int)-> void:
|
||||
MainOverlayFlow._on_save_select_new(self, slot)
|
||||
MainOverlayFlow.on_save_select_new(self, slot)
|
||||
|
||||
|
||||
func _on_save_select_load(slot: int)-> void:
|
||||
MainOverlayFlow._on_save_select_load(self, slot)
|
||||
MainOverlayFlow.on_save_select_load(self, slot)
|
||||
|
||||
|
||||
func _on_save_select_delete(slot: int)-> void:
|
||||
MainOverlayFlow._on_save_select_delete(self, slot)
|
||||
MainOverlayFlow.on_save_select_delete(self, slot)
|
||||
|
||||
|
||||
func _on_status_badge_pressed() -> void:
|
||||
MainOverlayFlow._on_status_badge_pressed(self)
|
||||
MainOverlayFlow.on_status_badge_pressed(self)
|
||||
|
||||
|
||||
func _sync_status_entry() -> void:
|
||||
MainOverlayFlow._sync_status_entry(self)
|
||||
func sync_status_entry() -> void:
|
||||
MainOverlayFlow.sync_status_entry(self)
|
||||
|
||||
|
||||
# ---- 探针缝保缝(click/ui_probe 营地段直呼组合根面;实现归 camp_shop_presenter.gd / main_overlay_flow.gd)----
|
||||
|
||||
func _on_camp_closed() -> void:
|
||||
MainOverlayFlow._on_camp_closed(self)
|
||||
MainOverlayFlow.on_camp_closed(self)
|
||||
|
||||
|
||||
func _on_camp_object_pressed(key: StringName) -> void:
|
||||
MainOverlayFlow._on_camp_object_pressed(self, key) # 场景点选路由(卡④ T3——薄委托,实现归覆盖层流程承接件)
|
||||
MainOverlayFlow.on_camp_object_pressed(self, key) # 场景点选路由(卡④ T3——薄委托,实现归覆盖层流程承接件)
|
||||
|
||||
|
||||
# Esc 输入入口(产品码首个键盘入口——卡⑤ T1 契约 9 接线定稿:事件驱动 _unhandled_input、
|
||||
@@ -529,23 +622,23 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||
|
||||
|
||||
func _on_esc_return() -> void:
|
||||
MainEscFlow._on_return_to_menu(self)
|
||||
MainEscFlow.on_return_to_menu(self)
|
||||
|
||||
|
||||
func _on_esc_settings() -> void:
|
||||
MainEscFlow._on_settings(self)
|
||||
MainEscFlow.on_settings(self)
|
||||
|
||||
|
||||
func _on_esc_quit() -> void:
|
||||
MainEscFlow._on_quit(self)
|
||||
MainEscFlow.on_quit(self)
|
||||
|
||||
|
||||
func _camp_data() -> Dictionary:
|
||||
return MainOverlayFlow._camp_data(self)
|
||||
return MainOverlayFlow.camp_data(self)
|
||||
|
||||
|
||||
func _on_wallet_changed(event: String, gp: int, extra: Dictionary)-> void:
|
||||
MainOverlayFlow._on_wallet_changed(self, event, gp, extra)
|
||||
MainOverlayFlow.on_wallet_changed(self, event, gp, extra)
|
||||
|
||||
|
||||
# ==================== 玩家意图 → 核心写调用(唯一出口)====================
|
||||
@@ -565,9 +658,9 @@ func _on_board_action(cell: Vector2i, kind: String) -> void:
|
||||
return
|
||||
# 秘宝选中态劫持(两步交互第二步):点格=使用目标拾取,不落挖格;
|
||||
# 只劫持挖掘点击(move/seal 语义不冲突——选中期玩家仍可移动/封印)
|
||||
var treasure_sel: int = _treasure_ui.active_selection()
|
||||
var treasure_sel: int = treasure_ui().active_selection()
|
||||
if treasure_sel != 0 and kind == "dig":
|
||||
_treasure_ui.attempt_use(treasure_sel, cell)
|
||||
treasure_ui().attempt_use(treasure_sel, cell)
|
||||
return
|
||||
if run_scope.settlement == null:
|
||||
return # 入口显式空防护(双保险——锁谓词正常态已挡,防锁判据异常时崩于空引用)
|
||||
@@ -575,7 +668,7 @@ func _on_board_action(cell: Vector2i, kind: String) -> void:
|
||||
var accepted: bool = not outcomes.is_empty() and bool(outcomes[0].accepted)
|
||||
logger.log_action(kind, cell, accepted)
|
||||
if run_scope.runner != null and run_scope.runner.treasure_inventory != null:
|
||||
_treasure_ui.refresh_slots() # 授予发生在 settle_batch 结算段内(同步走完,行尾重查)
|
||||
treasure_ui().refresh_slots() # 授予发生在 settle_batch 结算段内(同步走完,行尾重查)
|
||||
_handle_outcome_feedback(outcomes[0] if not outcomes.is_empty() else {})
|
||||
# settle_batch 内部同步走完 结算→战斗结束→关卡推进(事件同步发射)——关卡终局时 settlement 已被
|
||||
# runner 置空,清图演出判定须先判 settlement(重入隐患守卫)
|
||||
@@ -591,7 +684,7 @@ func _on_board_action(cell: Vector2i, kind: String) -> void:
|
||||
elif accepted and kind == "move":
|
||||
ui.battle.board_zone.board_view.notify_hero_move() # 原地移动(目标=当前格幂等受理)帧闪回执——refresh diff 前置告知
|
||||
ui.battle.board_zone.board_view.refresh()
|
||||
_refresh_presentations()
|
||||
refresh_presentations()
|
||||
|
||||
|
||||
func _on_stop_pressed() -> void:
|
||||
@@ -607,7 +700,7 @@ func _on_stop_pressed() -> void:
|
||||
ui.battle.board_zone.toast.show_toast("本轮已清图,停手未执行 · 剩余 AP 已作废", "warn")
|
||||
logger.log_ux("ux_reject", {"source": "voided", "sub_reason": "stop_voided"})
|
||||
ui.battle.board_zone.board_view.refresh()
|
||||
_refresh_presentations()
|
||||
refresh_presentations()
|
||||
|
||||
|
||||
func _on_refresh_confirmed() -> void:
|
||||
@@ -635,7 +728,7 @@ func _on_refresh_confirmed() -> void:
|
||||
else:
|
||||
ui.battle.board_zone.toast.show_toast("本轮刷新次数已用完(每轮 %d 次)" % int(db.run.action_economy.refresh_cap_per_round), "warn")
|
||||
ui.battle.board_zone.board_view.refresh()
|
||||
_refresh_presentations()
|
||||
refresh_presentations()
|
||||
|
||||
|
||||
func _handle_outcome_feedback(outcome: Dictionary) -> void:
|
||||
@@ -659,15 +752,15 @@ func _refresh_used_snapshot() -> int:
|
||||
return int(run_scope.settlement.presentation_snapshot().get("refresh_used", 0))
|
||||
|
||||
|
||||
func _refresh_presentations() -> void:
|
||||
func refresh_presentations() -> void:
|
||||
# 每个核心信号/写调用返回后的全量重查装配(更新触发注记——无推送信号,命令式重查)
|
||||
var refresh_remaining: int = maxi(0, int(db.run.action_economy.refresh_cap_per_round) - _refresh_used_snapshot())
|
||||
HudPresenter.refresh(ui.battle.stage, ui.battle.info_panel, ui.battle.board_zone.ap_bar, {"settlement": run_scope.settlement, "runner": run_scope.runner, "level_active": run_scope.level_active, "db": db, "refresh_remaining": refresh_remaining})
|
||||
_treasure_ui.refresh_slots() # C 区秘宝槽重查(试炼镐积点进度随挖格刷新悬停浮窗)
|
||||
_sync_settlement_visuals()
|
||||
treasure_ui().refresh_slots() # C 区秘宝槽重查(试炼镐积点进度随挖格刷新悬停浮窗)
|
||||
sync_settlement_visuals()
|
||||
|
||||
|
||||
func _sync_settlement_visuals() -> void:
|
||||
func sync_settlement_visuals() -> void:
|
||||
# 结算阶段(round_end → 下一轮):D 区去饱和+金橙偏移锁输入(明度不变)+ 明细带展开(高度读 battle_layout)
|
||||
var settling := _is_settling()
|
||||
if settling and not run_scope.was_settling:
|
||||
@@ -697,56 +790,56 @@ func _is_settling() -> bool:
|
||||
# ==================== B 区明细带 / 图例(模态入口)====================
|
||||
|
||||
func _on_legend_toggled() -> void:
|
||||
MainOverlayFlow._on_legend_toggled(self)
|
||||
MainOverlayFlow.on_legend_toggled(self)
|
||||
|
||||
|
||||
# ==================== 诊断转储(卡死时点击——全量核心状态 + 环形缓冲)====================
|
||||
|
||||
func _on_diagnose_pressed() -> void:
|
||||
MainDebugFlow._on_diagnose_pressed(self)
|
||||
MainDebugFlow.on_diagnose_pressed(self)
|
||||
|
||||
|
||||
# ==================== 调试浮窗(开合/应用/模拟编排由组合根承接)====================
|
||||
|
||||
func _show_debug_overlay() -> void:
|
||||
MainDebugFlow._show_debug_overlay(self)
|
||||
MainDebugFlow.show_debug_overlay(self)
|
||||
|
||||
|
||||
func _on_debug_badge_pressed() -> void:
|
||||
MainDebugFlow._on_debug_badge_pressed(self)
|
||||
MainDebugFlow.on_debug_badge_pressed(self)
|
||||
|
||||
|
||||
func _sync_debug_overlay() -> void:
|
||||
MainDebugFlow._sync_debug_overlay(self)
|
||||
func sync_debug_overlay() -> void:
|
||||
MainDebugFlow.sync_debug_overlay(self)
|
||||
|
||||
|
||||
func _debug_run_data() -> Dictionary:
|
||||
return MainDebugFlow._debug_run_data(self)
|
||||
return MainDebugFlow.debug_run_data(self)
|
||||
|
||||
|
||||
# 本局种子现值(只读单点——当局数据区块与诊断转储共用):冒险=adventure.master_seed 访问器现值;
|
||||
# 调试两层=RNG 服务 master_seed。禁 _seed_value() 重算冒充本局种子。
|
||||
func _current_master_seed() -> int:
|
||||
# 调试两层=RNG 服务 master_seed。禁 seed_value() 重算冒充本局种子。
|
||||
func current_master_seed() -> int:
|
||||
if run_scope.adventure != null:
|
||||
return int(run_scope.adventure.master_seed)
|
||||
return int(get_node(AUTOLOAD_PATH_RNG).master_seed)
|
||||
|
||||
|
||||
func _close_debug_overlay() -> void:
|
||||
MainDebugFlow._close_debug_overlay(self)
|
||||
MainDebugFlow.close_debug_overlay(self)
|
||||
|
||||
|
||||
func _on_tune_apply(atk_pct: int, hp_pct: int)-> void:
|
||||
MainDebugFlow._on_tune_apply(self, atk_pct, hp_pct)
|
||||
MainDebugFlow.on_tune_apply(self, atk_pct, hp_pct)
|
||||
|
||||
|
||||
func _on_tune_simulate(n: int)-> void:
|
||||
MainDebugFlow._on_tune_simulate(self, n)
|
||||
MainDebugFlow.on_tune_simulate(self, n)
|
||||
|
||||
|
||||
# ==================== 工具 ====================
|
||||
|
||||
func _seed_value() -> int:
|
||||
func seed_value() -> int:
|
||||
# 种子三分判定单点(全工程唯一种子框读取处,禁第二处读框):
|
||||
# ①显式合法整数全场景优先(含首局——测试/调试接口显式指定;第五路 DebugSession Callable
|
||||
# 经本函数=调试对局与玩家同口径);
|
||||
|
||||
@@ -7,22 +7,22 @@ extends RefCounted
|
||||
# 冒险编排构建+接线(开新局/续玩共用——信号连接只在组合根;断点注入同口)。
|
||||
# 信号直连(拆件批 T3):runner 六信号在 level_started 重取点重连(旧实例连接随引用释放消亡——
|
||||
# is_connected 守卫恰好一次);三选一信号面归停点件(每冒险一次——接线恰一次)。
|
||||
static func _wire_adventure(root: Variant) -> void:
|
||||
static func wire_adventure(root: Variant, handlers: Dictionary) -> void:
|
||||
root.run_scope.adventure = AdventureRunner.new(root.db, root.get_node(root.AUTOLOAD_PATH_RNG))
|
||||
root.run_scope.adventure.adventure_finished.connect(root._on_adventure_finished)
|
||||
root.run_scope.adventure.level_started.connect(root._on_adventure_level_started)
|
||||
root.run_scope.adventure.stop().curio_choice_presented.connect(root._bonus.on_presented)
|
||||
root.run_scope.adventure.stop().curio_resolved.connect(root._bonus.on_resolved)
|
||||
root.run_scope.adventure.adventure_finished.connect(handlers["adventure_finished"])
|
||||
root.run_scope.adventure.level_started.connect(handlers["level_started"])
|
||||
root.run_scope.adventure.stop().curio_choice_presented.connect(root.bonus().on_presented)
|
||||
root.run_scope.adventure.stop().curio_resolved.connect(root.bonus().on_resolved)
|
||||
root.run_scope.adventure.set_save_manager(root.save_manager) # 断点承载注入(停点快照写盘;调试/模拟不经过此路径)
|
||||
|
||||
|
||||
|
||||
# 每关开始(冒险模式):重取 runner 实例 + 层名行(L1 与单关模式同序——层名行随首战横幅后到达;
|
||||
# 本回调返回后才有 choices_presented 到达——入口选路层随即弹出)
|
||||
static func _on_adventure_level_started(root: Variant, level_index: int, display_name: String) -> void:
|
||||
static func on_adventure_level_started(root: Variant, level_index: int, display_name: String, handlers: Dictionary) -> void:
|
||||
root.run_scope.runner = root.run_scope.adventure.runner()
|
||||
# runner 信号直连(拆件批 T3——六转发族退役后的重取点重连;恰好一次断言 sc1 守卫承载)
|
||||
_connect_runner_once(root)
|
||||
_connect_runner_once(root, handlers)
|
||||
# 开场行传当前生效值(runner.effective_atk/effective_max_hp 与战斗面板同源:
|
||||
# 表值+永久强化 flat+加算区 flat 后乘调试浮窗 pct,本回调时点 runner 已带全部加成);「含面板加成」
|
||||
# 后缀判断认任一加成源——永久强化 atk/max_hp flat 或调试浮窗 pct 或局内加算区 atk/max_hp
|
||||
@@ -37,22 +37,22 @@ static func _on_adventure_level_started(root: Variant, level_index: int, display
|
||||
root.run_scope.runner.effective_atk(), root.run_scope.runner.effective_max_hp(), boosted))
|
||||
|
||||
# runner 六信号重连(is_connected 守卫——未连即接;守卫失败 push_error=重连恰好一次契约承载)
|
||||
static func _connect_runner_once(root: Variant) -> void:
|
||||
static func _connect_runner_once(root: Variant, handlers: Dictionary) -> void:
|
||||
var r = root.run_scope.runner
|
||||
if r == null:
|
||||
push_error("[冒险流程] runner 信号重连失败——level_started 后 runner 为空")
|
||||
return
|
||||
var pairs := [
|
||||
[r.step_row, root._on_step_row], [r.round_started, root._on_round_started],
|
||||
[r.board_changed, root._on_board_changed], [r.node_entered, root._on_runner_node],
|
||||
[r.choices_presented, root._on_choices_presented], [r.wallet_changed, root._on_wallet_changed],
|
||||
[r.step_row, handlers["step_row"]], [r.round_started, handlers["round_started"]],
|
||||
[r.board_changed, handlers["board_changed"]], [r.node_entered, handlers["runner_node"]],
|
||||
[r.choices_presented, handlers["choices_presented"]], [r.wallet_changed, handlers["wallet_changed"]],
|
||||
]
|
||||
for pair in pairs:
|
||||
if not (pair[0] as Signal).is_connected(pair[1]):
|
||||
(pair[0] as Signal).connect(pair[1])
|
||||
|
||||
|
||||
static func _on_runner_node(root: Variant, node: Dictionary, info: Dictionary) -> void:
|
||||
static func on_runner_node(root: Variant, node: Dictionary, info: Dictionary) -> void:
|
||||
# 冒险模式:runner 逐关更替——每次节点事件重取(单关模式幂等);须先于 board_changed 回退读面
|
||||
if root.run_scope.is_adventure and root.run_scope.adventure != null:
|
||||
root.run_scope.runner = root.run_scope.adventure.runner()
|
||||
@@ -61,12 +61,12 @@ static func _on_runner_node(root: Variant, node: Dictionary, info: Dictionary) -
|
||||
root.run_scope.settlement = root.run_scope.runner.current_battle() # 呈现层统一读 settlement(与调试模式共用接线)
|
||||
root.logger.attach_battle(root.run_scope.settlement)
|
||||
root.run_scope.boss_active = t == "boss"
|
||||
root._show_board_fx_for_node(t)
|
||||
root.show_board_fx_for_node(t)
|
||||
root.ui.battle.log_view.append_bb(root.Narrator.node_banner(root.run_scope.runner, node, t))
|
||||
elif t == "reward":
|
||||
# 奖励节点三选一(与关卡完成三选一共享同池同权重同上限;CurioPickOverlay 共用面)。
|
||||
# 呈现编排归 curio_interact_controller.present_node_curio。
|
||||
root._bonus.present_node_curio()
|
||||
root.bonus().present_node_curio()
|
||||
root.ui.battle.log_view.append_bb(root.Narrator.reward_line())
|
||||
elif t == "rest":
|
||||
var healed: int = int(info.get("healed", 0))
|
||||
@@ -87,20 +87,20 @@ static func _on_runner_node(root: Variant, node: Dictionary, info: Dictionary) -
|
||||
root.ui.modals.menu.shop_overlay.set_visit_info(-1, 1.0) # legacy 面无爬价行(off 对照承载)
|
||||
root.ui.battle.log_view.append_bb(root.Narrator.shop_line(int(root.run_scope.runner.gp)))
|
||||
root.logger.log_node(node, info, root.run_scope.runner.node_index)
|
||||
root._refresh_presentations()
|
||||
root.refresh_presentations()
|
||||
|
||||
|
||||
# ---- 冒险模式终局面(三选一选择面 / 冒险结算屏)----
|
||||
|
||||
|
||||
# 冒险结算(completed/failed/abandoned):复用 BattleEndOverlay——摘要五件事 + 钥石入账明细进文案
|
||||
static func _on_adventure_finished(root: Variant, summary: Dictionary) -> void:
|
||||
static func on_adventure_finished(root: Variant, summary: Dictionary) -> void:
|
||||
root.run_scope.level_active = false
|
||||
var result: String = String(summary.get("result", ""))
|
||||
root.run_scope.settlement = null
|
||||
_exit_level_screen(root) # 终局兜底:独立屏在开态(放弃入口)也先回战斗屏呈现摘要——屏级互斥单一出口
|
||||
root._sync_status_entry() # 离局熄灭状态面板入口角标(防菜单误现)
|
||||
root._sync_settlement_visuals() # → 全关
|
||||
exit_level_screen(root) # 终局兜底:独立屏在开态(放弃入口)也先回战斗屏呈现摘要——屏级互斥单一出口
|
||||
root.sync_status_entry() # 离局熄灭状态面板入口角标(防菜单误现)
|
||||
root.sync_settlement_visuals() # → 全关
|
||||
HudPresenter.battle_end_feedback(root.ui.battle.stage, root.ui.battle.board_zone.ap_bar, false) # 灰化/idle 回正/状态全清——HUD 单通道
|
||||
# 存档入账:settle = Boss 两档入账 + 工坊解锁 + 摘要入档 + 写盘(唯一写档点不变——
|
||||
# 调试两层不经过本处理器,天然不写档不污染生涯数据)。
|
||||
@@ -108,7 +108,7 @@ static func _on_adventure_finished(root: Variant, summary: Dictionary) -> void:
|
||||
detail.assign(root.meta.settle_adventure(summary))
|
||||
var text: String = root.Narrator.adventure_summary_text(summary)
|
||||
if not detail.is_empty():
|
||||
text += "\n" + root.Narrator.keystone_settle_text(detail, root._bonus.item_name_lookup())
|
||||
text += "\n" + root.Narrator.keystone_settle_text(detail, root.bonus().item_name_lookup())
|
||||
if result == "completed":
|
||||
root.ui.modals.run.end_overlay.open_victory(text, "返回冒险", root.ui.battle.board_zone.board_view.get_global_rect(),
|
||||
{"gp_summary": "本局 GP 入账 · 合计 %d" % int(summary.get("gp_final", 0))}) # GP 行(取数=summary gp_final 清账前快照);钥石明细已在 text(keystone_settle_text)——三行账
|
||||
@@ -118,52 +118,52 @@ static func _on_adventure_finished(root: Variant, summary: Dictionary) -> void:
|
||||
if root.run_scope.runner != null:
|
||||
root.run_scope.runner.restore_hero_base()
|
||||
root.ui.flow.open_after_playback(root.ui.modals.run.end_overlay)
|
||||
root._refresh_presentations()
|
||||
root.refresh_presentations()
|
||||
|
||||
static func _on_battle_end_continue(root: Variant) -> void:
|
||||
static func on_battle_end_continue(root: Variant) -> void:
|
||||
# 终局出口:返回开始界面(冒险摘要留档试玩日志;钥石在收束时入存档)
|
||||
root.ui.flow.close(root.ui.modals.run.end_overlay)
|
||||
root.ui.modals.run.end_overlay.close()
|
||||
# 三选一停点挂起分支:战斗总结结算(Boss 胜利钥石增量行)确认后进三选一面
|
||||
if root._bonus.resume_if_pending(): # 三选一停点在途恢复入口(curio_interact_controller)
|
||||
if root.bonus().resume_if_pending(): # 三选一停点在途恢复入口(curio_interact_controller)
|
||||
return
|
||||
# 挂起分派:战斗胜利总结结算框的「下一节点」≠ 终局——进选路页(暂存候选恢复呈现)
|
||||
if root.run_scope.settlement_stop_timer.is_armed():
|
||||
root.run_scope.settlement_stop_timer.disarm()
|
||||
_enter_level_screen(root)
|
||||
enter_level_screen(root)
|
||||
root.ui.modals.run.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held())))
|
||||
root.ui.modals.run.level_screen.open_choices(root.run_scope.pending_candidates, root.run_scope.runner)
|
||||
root.ui.modals.run.level_screen.apply_status(level_map_status_payload(root.run_scope.runner))
|
||||
root.run_scope.pending_candidates = []
|
||||
root._refresh_presentations()
|
||||
root.refresh_presentations()
|
||||
return
|
||||
# 会话收束:run_scope 整节点 queue_free()(帧末回收)——新闲时会话即建,
|
||||
# 局态封印/账本/模态挂起全复位(局态字段清单见 run_scope.gd)
|
||||
root._debug.reset()
|
||||
root._begin_session(false)
|
||||
root.debug().reset()
|
||||
root.begin_session(false)
|
||||
root.ui.modals.menu.debug_overlay.reset_sliders()
|
||||
root.ui.screens.start_overlay.visible = true
|
||||
root._refresh_presentations()
|
||||
root.refresh_presentations()
|
||||
|
||||
static func _on_inter_next_pressed(root: Variant) -> void:
|
||||
static func on_inter_next_pressed(root: Variant) -> void:
|
||||
root.ui.screens.inter_overlay.visible = false
|
||||
if root.run_scope.is_adventure:
|
||||
# 冒险模式:大奖励屏确认 → 进下一关(AdventureRunner 序列推进——零自动推进的冒险级停点)
|
||||
if root.run_scope.adventure != null and not root.run_scope.adventure.continue_to_next_level():
|
||||
root._fatal_screen("关卡推进失败——见输出日志")
|
||||
root.fatal_screen("关卡推进失败——见输出日志")
|
||||
return
|
||||
if not root._debug.next_layer():
|
||||
root._fatal_screen("战斗启动失败——见输出日志")
|
||||
if not root.debug().next_layer():
|
||||
root.fatal_screen("战斗启动失败——见输出日志")
|
||||
|
||||
|
||||
|
||||
static func _on_node_continue(root: Variant) -> void:
|
||||
static func on_node_continue(root: Variant) -> void:
|
||||
root.ui.flow.close(root.ui.modals.run.node_overlay)
|
||||
if root.run_scope.runner != null:
|
||||
# 确认 ≠ 推进——兑付确认后经 choices_presented 弹出选路口
|
||||
root.run_scope.runner.continue_after_node()
|
||||
|
||||
static func _on_choices_presented(root: Variant, candidates: Array) -> void:
|
||||
static func on_choices_presented(root: Variant, candidates: Array) -> void:
|
||||
# 零自动推进的停点呈现:整幅节点图 + 图上点击选路(战斗胜利/兑付确认统一到达此处)
|
||||
if root.run_scope.runner == null:
|
||||
return
|
||||
@@ -180,38 +180,38 @@ static func _on_choices_presented(root: Variant, candidates: Array) -> void:
|
||||
root.ui.battle.board_zone.board_view.get_global_rect(),
|
||||
{"gp_summary": "战利品 GP +%d · 合计 %d" % [root.run_scope.last_gain_amount, int(root.run_scope.runner.gp)]})
|
||||
root.ui.flow.open_after_playback(root.ui.modals.run.end_overlay)
|
||||
root._refresh_presentations()
|
||||
root.refresh_presentations()
|
||||
return
|
||||
_enter_level_screen(root)
|
||||
enter_level_screen(root)
|
||||
root.ui.modals.run.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held())))
|
||||
root.ui.modals.run.level_screen.open_choices(candidates, root.run_scope.runner)
|
||||
root.ui.modals.run.level_screen.apply_status(level_map_status_payload(root.run_scope.runner))
|
||||
root._refresh_presentations()
|
||||
root.refresh_presentations()
|
||||
|
||||
static func _on_route_chosen(root: Variant, node_id: int) -> void:
|
||||
static func on_route_chosen(root: Variant, node_id: int) -> void:
|
||||
# 视图已按节点类型定去留——战斗/Boss 出屏(visible=false,此处收栈
|
||||
# 记录一致性+回战斗屏);奖励/恢复/商店本屏保持激活(兑付覆盖层由节点事件域内弹出,模态栈
|
||||
# 打开低层时自动隐藏本屏、关闭自动恢复)
|
||||
if not root.ui.modals.run.level_screen.visible:
|
||||
root.ui.flow.close(root.ui.modals.run.level_screen)
|
||||
_exit_level_screen(root)
|
||||
exit_level_screen(root)
|
||||
if root.run_scope.runner != null and not root.run_scope.runner.pick_next_node(node_id):
|
||||
push_error("[组合根] 选路被拒(编号 %d 不在候选)" % node_id)
|
||||
|
||||
|
||||
|
||||
static func _enter_level_screen(root: Variant) -> void:
|
||||
static func enter_level_screen(root: Variant) -> void:
|
||||
root.run_scope.level_screen_active = true
|
||||
root.ui.screens.battle_root.visible = false
|
||||
root.ui.flow.open(root.ui.modals.run.level_screen)
|
||||
root._sync_status_entry() # 屏级互斥切换同步(_open_modal 单口不识屏级状态)
|
||||
root.sync_status_entry() # 屏级互斥切换同步(_open_modal 单口不识屏级状态)
|
||||
|
||||
static func _exit_level_screen(root: Variant) -> void:
|
||||
static func exit_level_screen(root: Variant) -> void:
|
||||
root.run_scope.level_screen_active = false
|
||||
root.ui.screens.battle_root.visible = true
|
||||
if root.ui.modals.run.level_screen.visible:
|
||||
root.ui.flow.close(root.ui.modals.run.level_screen)
|
||||
root._sync_status_entry() # 同上
|
||||
root.sync_status_entry() # 同上
|
||||
|
||||
|
||||
# 选路屏状态栏装配(视图零核心态拼装——architecture §1.3:读核心态→拼展示文案集中于装配器)。
|
||||
@@ -243,7 +243,7 @@ static func level_map_status_payload(runner: LevelRunner) -> Dictionary:
|
||||
"bonus_row_visible": not parts.is_empty(),
|
||||
}
|
||||
|
||||
static func _on_abandon_requested(root: Variant) -> void:
|
||||
static func on_abandon_requested(root: Variant) -> void:
|
||||
# 放弃入口(冒险结构.md 已放开「MVP 无界面入口」后置条款;
|
||||
# 入口挂选路停点 = 天然非战斗期,拒绝分支仅防御在案):受理 → 冒险放弃结算(同失败型,
|
||||
# 已获大奖励不追回)→ adventure_finished 同步到达 → 终局面收层
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
## 调试会话状态机归 debug_session.gd;本件只接线与出 UI,零成员态。
|
||||
extends RefCounted
|
||||
|
||||
static func _on_diagnose_pressed(root: Variant) -> void:
|
||||
static func on_diagnose_pressed(root: Variant) -> void:
|
||||
# 本局种子现值走 _current_master_seed 只读单点
|
||||
var ctx := {"mode": "level" if root.run_scope.level_active else "debug", "seed": root._current_master_seed()}
|
||||
var ctx := {"mode": "level" if root.run_scope.level_active else "debug", "seed": root.current_master_seed()}
|
||||
if root.run_scope.level_active and root.run_scope.runner != null:
|
||||
ctx["runner"] = {"node_index": root.run_scope.runner.node_index, "level_result": String(root.run_scope.runner.level_result),
|
||||
"hero_hp": root.run_scope.runner.hero_hp, "settlement_is_null": root.run_scope.runner.current_battle() == null}
|
||||
@@ -17,33 +17,33 @@ static func _on_diagnose_pressed(root: Variant) -> void:
|
||||
|
||||
|
||||
|
||||
static func _show_debug_overlay(root: Variant) -> void:
|
||||
static func show_debug_overlay(root: Variant) -> void:
|
||||
# 门禁:对局中可开;本函数仍是唯一开窗入口,门禁逻辑收函数内:
|
||||
# 棋盘输入锁/状态角标判据排除浮窗,
|
||||
# 遮挡语义=ModalStack 登记制(他模态开时暂隐、关后自动恢复),此处零场景限制
|
||||
if root.ui.modals.menu.debug_overlay == null:
|
||||
return
|
||||
root.ui.flow.open(root.ui.modals.menu.debug_overlay)
|
||||
_sync_debug_overlay(root)
|
||||
sync_debug_overlay(root)
|
||||
|
||||
|
||||
|
||||
# 调试浮窗角标点击(两屏同款处理器——与状态角标同形制守卫;仍收口 _show_debug_overlay 单口)
|
||||
static func _on_debug_badge_pressed(root: Variant) -> void:
|
||||
# 调试浮窗角标点击(两屏同款处理器——与状态角标同形制守卫;仍收口 show_debug_overlay 单口)
|
||||
static func on_debug_badge_pressed(root: Variant) -> void:
|
||||
var map_on: bool = root.ui.modals.run.level_screen != null and root.ui.modals.run.level_screen.visible
|
||||
if (map_on and root.ui.flow.badge_greyed_map()) or (not map_on and root.ui.flow.badge_greyed_battle()):
|
||||
return
|
||||
_show_debug_overlay(root)
|
||||
show_debug_overlay(root)
|
||||
|
||||
|
||||
|
||||
# 浮窗上下文同步(当局数据刷新+对局中按钮禁用单点):调用面=开窗口与模态开合
|
||||
# 单口(凡影响开态/对局态的路径必经其一——与 _sync_status_entry 同款纪律)
|
||||
static func _sync_debug_overlay(root: Variant) -> void:
|
||||
static func sync_debug_overlay(root: Variant) -> void:
|
||||
if root.ui.modals.menu.debug_overlay == null:
|
||||
return
|
||||
root.ui.modals.menu.debug_overlay.set_context_locked(root.run_scope.settlement != null or root.run_scope.is_adventure) # 对局中批量模拟/两层调试入口禁用(长程异步模拟与活跃结算并存态不开放)
|
||||
root.ui.modals.menu.debug_overlay.update_run_data(_debug_run_data(root)) # 当局数据只读区块喂入(开窗+模态开合信号驱动)
|
||||
root.ui.modals.menu.debug_overlay.update_run_data(debug_run_data(root)) # 当局数据只读区块喂入(开窗+模态开合信号驱动)
|
||||
|
||||
|
||||
|
||||
@@ -51,9 +51,9 @@ static func _sync_debug_overlay(root: Variant) -> void:
|
||||
# (非 _seed_value() 重算);调试两层/非对局无冒险对象=RNG 服务 master_seed 现值(只读出口,
|
||||
# 未播种 -1 显「—」);关卡/节点/HP/GP=runner 成文出口、钥石=adventure 冒险账本(LevelRunner 无此域);
|
||||
# 缺位=「—」占位。零动效(纯标签重查)。
|
||||
static func _debug_run_data(root: Variant) -> Dictionary:
|
||||
static func debug_run_data(root: Variant) -> Dictionary:
|
||||
var r = root.run_scope.adventure.runner() if root.run_scope.adventure != null else null
|
||||
var seed_now: int = root._current_master_seed()
|
||||
var seed_now: int = root.current_master_seed()
|
||||
var seed_txt: String = str(seed_now) if seed_now >= 0 else "—"
|
||||
if r == null:
|
||||
return {"seed": seed_txt, "level": "—", "node": "—", "hp": "—", "gp": "—", "keystone": "—"}
|
||||
@@ -62,47 +62,47 @@ static func _debug_run_data(root: Variant) -> Dictionary:
|
||||
|
||||
|
||||
|
||||
static func _close_debug_overlay(root: Variant) -> void:
|
||||
static func close_debug_overlay(root: Variant) -> void:
|
||||
root.ui.flow.close(root.ui.modals.menu.debug_overlay) # 模态栈单口——开始层全程未隐藏,无需恢复
|
||||
|
||||
static func _on_tune_apply(root: Variant, atk_pct: int, hp_pct: int) -> void:
|
||||
static func on_tune_apply(root: Variant, atk_pct: int, hp_pct: int) -> void:
|
||||
# 加成无持久态可"落"——各战斗开局经 board_resolvers.debug_hero_override/runner 现算生效(应用=关闭浮窗)
|
||||
_close_debug_overlay(root)
|
||||
close_debug_overlay(root)
|
||||
|
||||
static func _on_tune_simulate(root: Variant, n: int) -> void:
|
||||
static func on_tune_simulate(root: Variant, n: int) -> void:
|
||||
# 批量模拟编排 = 控制器承接(tune_sim_controller.gd);E4 后无表态需复位——模拟走 runner 覆盖参数。
|
||||
# 对局中禁用兜底(长程异步模拟与活跃冒险/结算并存态不开放;探针 emit 绕过 disabled 时由此守住)
|
||||
if root.run_scope.settlement != null or root.run_scope.is_adventure:
|
||||
return
|
||||
root._tune_sim.run(n)
|
||||
root.tune_sim().run(n)
|
||||
|
||||
|
||||
|
||||
static func _start_debug_levels(root: Variant) -> void:
|
||||
static func start_debug_levels(root: Variant) -> void:
|
||||
# 守卫:调试对局与活跃冒险/结算/批量模拟并存不开放——并发会清写
|
||||
# settlement/编排态=状态机破坏;模拟在跑时 reset_sliders 会作废测量读数(d5 置灰之外的信号绕过闸)
|
||||
if root.run_scope.is_adventure or root.run_scope.settlement != null or root._tune_sim.is_running():
|
||||
if root.run_scope.is_adventure or root.run_scope.settlement != null or root.tune_sim().is_running():
|
||||
return
|
||||
root.ui.modals.menu.debug_overlay.reset_sliders()
|
||||
root.ui.flow.close(root.ui.modals.menu.debug_overlay) # 浮窗内入口发起——随开随收(对局期不可再开:门禁在 _show_debug_overlay)
|
||||
root.ui.flow.close(root.ui.modals.menu.debug_overlay) # 浮窗内入口发起——随开随收(对局期不可再开:门禁在 show_debug_overlay)
|
||||
root.ui.screens.start_overlay.visible = false
|
||||
root.ui.battle.log_view.clear()
|
||||
root.ui.battle.log_view.append_bb(root.Narrator.debug_open_line(root._seed_value(), 1, true))
|
||||
root._begin_session(false)
|
||||
if not root._debug.start_debug_levels():
|
||||
root._fatal_screen("战斗启动失败——见输出日志")
|
||||
root.ui.battle.log_view.append_bb(root.Narrator.debug_open_line(root.seed_value(), 1, true))
|
||||
root.begin_session(false)
|
||||
if not root.debug().start_debug_levels():
|
||||
root.fatal_screen("战斗启动失败——见输出日志")
|
||||
|
||||
static func _on_debug_battle_created(root: Variant, settlement_ref: BattleOrchestrator, _level: int, _carry: int) -> void:
|
||||
static func on_debug_battle_created(root: Variant, settlement_ref: BattleOrchestrator, _level: int, _carry: int) -> void:
|
||||
# begin_battle 前接手 + 接线(signal.connect 唯一归组合根)。settlement 必须先于 begin_battle
|
||||
# 就位——begin 期间发出的 board_changed 要用新板重建棋盘并触发一次性提示(缺陷①同款时序义务)
|
||||
root.run_scope.settlement = settlement_ref
|
||||
root._wire_battle(settlement_ref)
|
||||
root.wire_battle(settlement_ref)
|
||||
|
||||
static func _on_debug_battle_ready(root: Variant, settlement_ref: BattleOrchestrator, _level: int, _carry: int) -> void:
|
||||
static func on_debug_battle_ready(root: Variant, settlement_ref: BattleOrchestrator, _level: int, _carry: int) -> void:
|
||||
root.run_scope.settlement = settlement_ref
|
||||
var board = root.run_scope.settlement.minesweeper.current_board()
|
||||
root.ui.battle.board_zone.board_view.rebuild(board, root.run_scope.settlement.minesweeper)
|
||||
if board != null:
|
||||
root.ui.battle.stage.set_board_type(String(board.board_type)) # 舞台背景选图随板(C8——调试就绪重建点单行同步)
|
||||
root._check_board_hints()
|
||||
root._refresh_presentations()
|
||||
root.check_board_hints()
|
||||
root.refresh_presentations()
|
||||
@@ -41,16 +41,16 @@ static func _topmost_camp_window(root: Variant) -> Control:
|
||||
|
||||
|
||||
## 返回主菜单动线(契约 4):首步 close 浮窗(缺此步浮窗残留主菜单上方)→营地与选档层同收+开始层恢复
|
||||
static func _on_return_to_menu(root: Variant) -> void:
|
||||
static func on_return_to_menu(root: Variant) -> void:
|
||||
root.ui.flow.close(root.ui.modals.camp.esc_overlay)
|
||||
root._camp_pres.on_camp_closed() # 关层收闭单口(卡④ T3——营地层与选档层同收+开始层恢复)
|
||||
root.camp_pres().on_camp_closed() # 关层收闭单口(卡④ T3——营地层与选档层同收+开始层恢复)
|
||||
|
||||
|
||||
## 设置出口(浮窗登记在设置层之下——open 只隐上方层,浮窗由树序遮挡非属性隐藏;关后重现=栈语义)
|
||||
static func _on_settings(root: Variant) -> void:
|
||||
static func on_settings(root: Variant) -> void:
|
||||
root.ui.flow.open(root.ui.modals.menu.settings_overlay)
|
||||
|
||||
|
||||
## 退出出口(_quit_game 原样复用——settings_store.save_settings+quit,零语义副作用)
|
||||
static func _on_quit(root: Variant) -> void:
|
||||
root._quit_game()
|
||||
static func on_quit(root: Variant) -> void:
|
||||
root.quit_game()
|
||||
@@ -4,11 +4,11 @@
|
||||
## 全部开合走组合根模态编排口(root.ui.flow.open/close);本件零成员态。
|
||||
extends RefCounted
|
||||
|
||||
static func _open_settings(root: Variant) -> void:
|
||||
static func open_settings(root: Variant) -> void:
|
||||
root.ui.screens.start_overlay.visible = false
|
||||
root.ui.flow.open(root.ui.modals.menu.settings_overlay) # 模态栈单口
|
||||
|
||||
static func _on_settings_closed(root: Variant) -> void:
|
||||
static func on_settings_closed(root: Variant) -> void:
|
||||
root.ui.flow.close(root.ui.modals.menu.settings_overlay) # 模态栈单口
|
||||
# 语境分支(卡⑤ 契约 3——谓词=camp_esc_overlay.visible,谁开的设置谁收):
|
||||
# 营地浮窗路径关设置回浮窗不回主菜单(栈语义:浮窗登记在下、由树序遮挡非属性隐藏);
|
||||
@@ -25,24 +25,24 @@ static func _on_settings_closed(root: Variant) -> void:
|
||||
# 八键换代(迭代 36 卡③):跨关解锁概览退役(营地/生涯承载)——只留本局信息;
|
||||
# ledger 直取下标(键缺失=致命,禁 .get 缺省留门——fail-fast 红线);禁 null 守卫软回退
|
||||
# (两入口开层时 runner 恒在役——状态面板卡 F3 证据链)
|
||||
static func _status_data(root: Variant) -> Dictionary:
|
||||
return HudPresenter.status_data(root.db, root.meta, root.run_scope.runner, root._treasure_ui)
|
||||
static func status_data(root: Variant) -> Dictionary:
|
||||
return HudPresenter.status_data(root.db, root.meta, root.run_scope.runner, root.treasure_ui())
|
||||
|
||||
static func _open_status_panel(root: Variant) -> void:
|
||||
static func open_status_panel(root: Variant) -> void:
|
||||
root.ui.flow.open(root.ui.modals.run.status_panel) # 模态栈单口(开层前排空排播队列——既有层同款行为,目检确认项)
|
||||
root.ui.modals.run.status_panel.open_status(_status_data(root))
|
||||
root.ui.modals.run.status_panel.open_status(status_data(root))
|
||||
|
||||
static func _on_status_closed(root: Variant) -> void:
|
||||
static func on_status_closed(root: Variant) -> void:
|
||||
root.ui.flow.close(root.ui.modals.run.status_panel) # 模态栈单口
|
||||
|
||||
|
||||
|
||||
static func _open_career(root: Variant) -> void:
|
||||
static func open_career(root: Variant) -> void:
|
||||
root.ui.screens.start_overlay.visible = false
|
||||
root.ui.flow.open(root.ui.modals.menu.career_overlay) # 模态栈单口
|
||||
root.ui.modals.menu.career_overlay.open_career(HudPresenter.career_data(root.db, root.save_manager, root.meta))
|
||||
|
||||
static func _on_career_closed(root: Variant) -> void:
|
||||
static func on_career_closed(root: Variant) -> void:
|
||||
root.ui.flow.close(root.ui.modals.menu.career_overlay) # 模态栈单口
|
||||
if not root.ui.modals.camp.scene.visible: # 营地语境关生涯不回开始层(笔记本入口——卡④ T3;栈已复现场景根)
|
||||
root.ui.screens.start_overlay.visible = true
|
||||
@@ -54,32 +54,32 @@ static func _on_career_closed(root: Variant) -> void:
|
||||
# 篝火回主菜单必须二次确认(camp.md v2.2 裁定);确认后营地层与选档层同收+开始层恢复(关层收闭)。
|
||||
|
||||
|
||||
static func _on_camp_object_pressed(root: Variant, key: StringName) -> void:
|
||||
static func on_camp_object_pressed(root: Variant, key: StringName) -> void:
|
||||
match key:
|
||||
&"forge":
|
||||
root._camp_pres.open_camp_forge()
|
||||
root.camp_pres().open_camp_forge()
|
||||
&"chest":
|
||||
root._camp_pres.open_camp_treasure()
|
||||
root.camp_pres().open_camp_treasure()
|
||||
&"dummy":
|
||||
root._camp_pres.open_camp_tree()
|
||||
root.camp_pres().open_camp_tree()
|
||||
&"board":
|
||||
root._camp_pres.open_camp_codex()
|
||||
root.camp_pres().open_camp_codex()
|
||||
&"notebook":
|
||||
_open_career(root) # 生涯页入口改挂帐篷旁笔记本(CareerOverlay 本体零改——卡④ T3)
|
||||
open_career(root) # 生涯页入口改挂帐篷旁笔记本(CareerOverlay 本体零改——卡④ T3)
|
||||
&"gate":
|
||||
_open_gate_confirm(root)
|
||||
&"campfire":
|
||||
root._confirm_consumer = "campfire_leave"
|
||||
root.set_confirm_consumer("campfire_leave")
|
||||
root.ui.modals.menu.confirm_dialog.open("确定离开营地回到主菜单?", "离开", "留下")
|
||||
|
||||
|
||||
static func _open_gate_confirm(root: Variant) -> void:
|
||||
if root.save_manager.has_breakpoint():
|
||||
root._confirm_consumer = "gate"
|
||||
root.set_confirm_consumer("gate")
|
||||
root.ui.modals.menu.confirm_dialog.open_three("有未完成的冒险——继续上一段冒险,还是开始新冒险?",
|
||||
"继续上一段冒险", "开始新冒险", "取消")
|
||||
return
|
||||
root._confirm_consumer = "gate_new"
|
||||
root.set_confirm_consumer("gate_new")
|
||||
root.ui.modals.menu.confirm_dialog.open("是否开始新冒险?", "是", "否")
|
||||
|
||||
|
||||
@@ -91,12 +91,12 @@ static func _open_gate_confirm(root: Variant) -> void:
|
||||
|
||||
# 开层(模态栈单口;与开始层互斥=career 同款先例)。「进入存档」钮归卡② mn3——本函数为
|
||||
# 该钮的唯一落点(卡③ 批内探针函数直调=中间态,实现报告「已知中间态」登记)。
|
||||
static func _open_save_select(root: Variant) -> void:
|
||||
static func open_save_select(root: Variant) -> void:
|
||||
root.ui.screens.start_overlay.visible = false
|
||||
root.ui.flow.open(root.ui.modals.menu.save_select_overlay) # 模态栈单口
|
||||
root.ui.modals.menu.save_select_overlay.open_save_select(HudPresenter.save_select_data(root.save_manager))
|
||||
|
||||
static func _on_save_select_closed(root: Variant) -> void:
|
||||
static func on_save_select_closed(root: Variant) -> void:
|
||||
root.ui.flow.close(root.ui.modals.menu.save_select_overlay) # 模态栈单口
|
||||
root.ui.screens.start_overlay.visible = true
|
||||
|
||||
@@ -107,24 +107,24 @@ static func _on_save_select_closed(root: Variant) -> void:
|
||||
|
||||
# 空槽新档链(卡④ T3 换代:落点=营地场景根——CampOverlay 随四柜台拆解退役):卡① 创建=激活=白板落盘,
|
||||
# 落点开层。动线序(禁先关后开):先落点开层(开始层保持隐藏),选档层被模态栈遮挡隐藏、不关闭。
|
||||
static func _on_save_select_new(root: Variant, slot: int) -> void:
|
||||
static func on_save_select_new(root: Variant, slot: int) -> void:
|
||||
root.save_manager.slots.create_slot(slot)
|
||||
root._camp_pres.open_camp()
|
||||
root.camp_pres().open_camp()
|
||||
|
||||
|
||||
|
||||
# 有档槽进档链(契约 8 进档即激活):卡① 激活+读盘(兜底/拒读链按槽成立)→落点=营地场景根(卡④ T3)。
|
||||
static func _on_save_select_load(root: Variant, slot: int) -> void:
|
||||
static func on_save_select_load(root: Variant, slot: int) -> void:
|
||||
root.save_manager.slots.activate_slot(slot)
|
||||
root._camp_pres.open_camp()
|
||||
root.camp_pres().open_camp()
|
||||
|
||||
|
||||
|
||||
# 删档链(契约 3):第一次点击只出确认窗(消费方编排——open 前设消费方=delete_slot),
|
||||
# 确认才删、取消不删;禁长按删/一次点击即删/撤销式软删。
|
||||
static func _on_save_select_delete(root: Variant, slot: int) -> void:
|
||||
root._confirm_consumer = "delete_slot"
|
||||
root._confirm_consumer_slot = slot
|
||||
static func on_save_select_delete(root: Variant, slot: int) -> void:
|
||||
root.set_confirm_consumer("delete_slot")
|
||||
root.set_confirm_consumer_slot(slot)
|
||||
root.ui.flow.open(root.ui.modals.menu.confirm_dialog) # 模态栈单口(显示编排/挂起兜底/遮挡记账——与其余覆盖层同口)
|
||||
root.ui.modals.menu.confirm_dialog.open("删除存档位 %d?删除后该档不可恢复。" % slot, "删除", "取消")
|
||||
|
||||
@@ -132,20 +132,20 @@ static func _on_save_select_delete(root: Variant, slot: int) -> void:
|
||||
|
||||
# 入口角标点击(两屏同款处理器):P1 守卫兜底——其他模态开着即拒开(常态防线=角标置灰,
|
||||
# 探针 pressed.emit 绕过 disabled 时由此兜住;地图屏自身的登记开态不计——has_open_excluding)
|
||||
static func _on_status_badge_pressed(root: Variant) -> void:
|
||||
static func on_status_badge_pressed(root: Variant) -> void:
|
||||
var map_on: bool = root.ui.modals.run.level_screen != null and root.ui.modals.run.level_screen.visible
|
||||
# P1 守卫兜底——其他模态开着即拒开(常态防线=角标置灰,探针 pressed.emit 绕过 disabled 时由此兜住;
|
||||
# 地图屏自身的登记开态不计;判据排除调试浮窗:对局中浮窗开着时状态角标仍可点)
|
||||
if (map_on and root.ui.flow.badge_greyed_map()) or (not map_on and root.ui.flow.badge_greyed_battle()):
|
||||
return
|
||||
_open_status_panel(root)
|
||||
open_status_panel(root)
|
||||
|
||||
|
||||
|
||||
# 入口角标可见性/置灰同步(P1 丁+守卫——单一同步点):战斗屏角标=对局内且地图屏未激活;
|
||||
# 地图屏角标=随屏;置灰=登记册上除地图屏自身外任一层开着(防「看得见点不着」态)。
|
||||
# 调用面=模态开合单口与屏级互斥口(凡影响开态/屏态的路径必经其一)。
|
||||
static func _sync_status_entry(root: Variant) -> void:
|
||||
static func sync_status_entry(root: Variant) -> void:
|
||||
if root.ui.modals.run.status_panel == null or root.ui.flow == null or root.ui.battle.board_zone == null or root.ui.modals.run.level_screen == null:
|
||||
return
|
||||
var map_on: bool = root.ui.modals.run.level_screen.visible
|
||||
@@ -160,13 +160,13 @@ static func _sync_status_entry(root: Variant) -> void:
|
||||
|
||||
|
||||
|
||||
static func _on_camp_closed(root: Variant) -> void:
|
||||
root._camp_pres.on_camp_closed()
|
||||
static func on_camp_closed(root: Variant) -> void:
|
||||
root.camp_pres().on_camp_closed()
|
||||
|
||||
static func _camp_data(root: Variant) -> Dictionary:
|
||||
return HudPresenter.camp_data(root._camp_pres)
|
||||
static func camp_data(root: Variant) -> Dictionary:
|
||||
return HudPresenter.camp_data(root.camp_pres())
|
||||
|
||||
static func _on_wallet_changed(root: Variant, event: String, gp: int, extra: Dictionary) -> void:
|
||||
static func on_wallet_changed(root: Variant, event: String, gp: int, extra: Dictionary) -> void:
|
||||
# wallet 行族转发(到账/购买/清零——试玩日志验收取数面)+ 常驻余额与在开商店覆盖层重刷
|
||||
root.logger.log_wallet(event, gp, extra)
|
||||
if String(event) == "gain":
|
||||
@@ -176,11 +176,11 @@ static func _on_wallet_changed(root: Variant, event: String, gp: int, extra: Dic
|
||||
root.ui.modals.menu.shop_overlay.refresh_shelf_state(int(root.run_scope.runner.gp)) # pool 面余额联动(货架内容仅买后重抽变)
|
||||
else:
|
||||
root.ui.modals.menu.shop_overlay.refresh_state(root.run_scope.runner.shop.shop_products(), int(root.run_scope.runner.gp))
|
||||
root._refresh_presentations()
|
||||
root.refresh_presentations()
|
||||
|
||||
|
||||
|
||||
static func _on_legend_toggled(root: Variant) -> void:
|
||||
static func on_legend_toggled(root: Variant) -> void:
|
||||
if root.ui.modals.run.legend_overlay.is_open():
|
||||
root.ui.flow.close(root.ui.modals.run.legend_overlay)
|
||||
else:
|
||||
|
||||
@@ -18,14 +18,14 @@ const AdventureScreenScene := preload("res://presentation/scenes/adventure_scree
|
||||
|
||||
|
||||
|
||||
static func build(root: Variant) -> void:
|
||||
static func build(root: Variant, handlers: Dictionary) -> void:
|
||||
_build_screens(root)
|
||||
_build_battle_wiring(root)
|
||||
_build_overlays(root)
|
||||
_build_aux_panels(root)
|
||||
_build_battle_wiring(root, handlers)
|
||||
_build_overlays(root, handlers)
|
||||
_build_aux_panels(root, handlers)
|
||||
_build_curio(root)
|
||||
_build_menu_wiring(root)
|
||||
_build_debug_and_resolvers(root)
|
||||
_build_menu_wiring(root, handlers)
|
||||
_build_debug_and_resolvers(root, handlers)
|
||||
_build_modal_flow(root)
|
||||
|
||||
static func _build_screens(root: Variant) -> void:
|
||||
@@ -48,7 +48,7 @@ static func _build_screens(root: Variant) -> void:
|
||||
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:
|
||||
static func _build_battle_wiring(root: Variant, handlers: Dictionary) -> void:
|
||||
# 结算行排播泵(本体在 row_pacer.gd;本类注入视图句柄与呈现刷新回调,零信号连接)
|
||||
root.ui.screens.row_pacer = RowPacer.new()
|
||||
root.ui.screens.row_pacer.log_view = root.ui.battle.log_view
|
||||
@@ -56,32 +56,32 @@ static func _build_battle_wiring(root: Variant) -> void:
|
||||
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.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({
|
||||
root.set_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.ui.battle.board_zone.action_requested.connect(handlers["board_action"])
|
||||
root.ui.battle.board_zone.status_badge.pressed.connect(handlers["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(handlers["stop_pressed"])
|
||||
root.ui.battle.board_zone.refresh_confirmed.connect(handlers["refresh_confirmed"])
|
||||
root.ui.battle.board_zone.legend_toggled.connect(handlers["legend_toggled"])
|
||||
root.ui.battle.board_zone.hover_forbidden.connect(handlers["hover_forbidden"])
|
||||
root.ui.battle.board_zone.chord_attempted.connect(handlers["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:
|
||||
static func _build_overlays(root: Variant, handlers: Dictionary) -> void:
|
||||
# 模态覆盖层(登记序 = 树序 = z 遮挡序:图例 < 明细回看 < 节点 < 关卡画面 < 三选一 < 终局
|
||||
# < 商店 < 营地场景根 < 四子界面 < 设置 < 状态面板(只读) < 生涯页(只读) < 确认弹窗 < 调试浮窗;
|
||||
# 关卡画面住冒险屏壳(树序在模态之下)——登记序仍夹在节点层与三选一层之间,遮挡语义零改动)
|
||||
@@ -121,35 +121,35 @@ static func _build_overlays(root: Variant) -> void:
|
||||
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)
|
||||
root.ui.modals.run.node_overlay.continue_pressed.connect(handlers["node_continue"])
|
||||
root.ui.modals.run.level_screen.route_chosen.connect(handlers["route_chosen"])
|
||||
root.ui.modals.run.level_screen.status_badge.pressed.connect(handlers["status_badge_pressed"]) # 状态面板入口(地图屏顶栏角标)
|
||||
root.ui.modals.run.level_screen.abandon_requested.connect(handlers["abandon_requested"]) # 放弃入口
|
||||
root.ui.modals.run.end_overlay.continue_pressed.connect(handlers["battle_end_continue"])
|
||||
|
||||
static func _build_aux_panels(root: Variant) -> void:
|
||||
static func _build_aux_panels(root: Variant, handlers: Dictionary) -> 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)
|
||||
root.ui.modals.menu.settings_overlay.closed.connect(handlers["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)
|
||||
root.ui.modals.run.status_panel.closed.connect(handlers["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)
|
||||
root.ui.modals.menu.career_overlay.closed.connect(handlers["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)
|
||||
root.ui.modals.menu.save_select_overlay.closed.connect(handlers["save_select_closed"])
|
||||
root.ui.modals.menu.save_select_overlay.new_slot_requested.connect(handlers["save_select_new"])
|
||||
root.ui.modals.menu.save_select_overlay.load_slot_requested.connect(handlers["save_select_load"])
|
||||
root.ui.modals.menu.save_select_overlay.delete_slot_requested.connect(handlers["save_select_delete"])
|
||||
|
||||
static func _build_curio(root: Variant) -> void:
|
||||
# 提升三选一覆盖层(z 序 = 节点层之上、终局层之下;确认后同拍推进;
|
||||
@@ -158,7 +158,7 @@ static func _build_curio(root: Variant) -> void:
|
||||
root.add_child(root.ui.modals.run.curio_overlay)
|
||||
# 提升三选一与替换子交互(本体在 curio_interact_controller.gd;本类只接线与回调注入,
|
||||
# 模态栈/演播后排窗/呈现刷新/fatal 经回调注入,adventure/runner 现值回调)
|
||||
root._bonus = CurioInteractController.new(CurioFlowContext.new(root.db,
|
||||
root.set_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,
|
||||
@@ -166,22 +166,22 @@ static func _build_curio(root: Variant) -> void:
|
||||
"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)
|
||||
"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:
|
||||
static func _build_menu_wiring(root: Variant, handlers: Dictionary) -> void:
|
||||
# 菜单屏接线(句柄经屏壳暴露;三入口化——进入存档/设置/退出游戏,卡② mn3:
|
||||
# 进入存档钮落点=卡③选档层 _open_save_select,点击链在此闭环)
|
||||
root.ui.screens.menu_screen.entersavebtn.pressed.connect(root._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)
|
||||
root.ui.screens.debug_badge.pressed.connect(handlers["debug_badge_pressed"])
|
||||
# 营地/商店投影(本体在 camp_shop_presenter.gd;本类只接线、探针缝保缝转发;
|
||||
# 购买写调用唯一经 MetaProgression/LevelRunner core 单口——组合根 lambda 现值注入)
|
||||
# 营地刷新单口(各写口尾→现开界面+场景根 HUD 一闸完成——卡④ T3;卡⑤ HUD band 随卡⑤挂册)
|
||||
@@ -189,7 +189,7 @@ static func _build_menu_wiring(root: Variant) -> void:
|
||||
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,
|
||||
root.set_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,
|
||||
@@ -199,31 +199,31 @@ static func _build_menu_wiring(root: Variant) -> void:
|
||||
{"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)
|
||||
"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) # 退出游戏提级一级菜单
|
||||
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(handlers["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:
|
||||
static func _build_debug_and_resolvers(root: Variant, handlers: Dictionary) -> 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)
|
||||
inter.next_btn.pressed.connect(handlers["inter_next_pressed"])
|
||||
|
||||
# 确认弹窗(新冒险互斥确认弹窗化):树序落在开始层/过场层之后 = 子级最末
|
||||
#(纪律「登记序 = 树序 = z 序」——confirm_dialog.open 禁运行时 move_child 自提抬层;
|
||||
@@ -233,13 +233,13 @@ static func _build_debug_and_resolvers(root: Variant) -> void:
|
||||
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)
|
||||
root.ui.modals.menu.confirm_dialog.confirmed.connect(handlers["confirm_accepted"])
|
||||
root.ui.modals.menu.confirm_dialog.secondary_confirmed.connect(handlers["confirm_secondary"]) # 三钮次确认(卡④ 大门「开始新冒险」出口)
|
||||
root.ui.modals.menu.confirm_dialog.cancelled.connect(handlers["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)
|
||||
root.ui.modals.camp.esc_overlay.return_requested.connect(handlers["esc_return"])
|
||||
root.ui.modals.camp.esc_overlay.settings_requested.connect(handlers["esc_settings"])
|
||||
root.ui.modals.camp.esc_overlay.quit_requested.connect(handlers["esc_quit"])
|
||||
|
||||
# 调试浮窗(主菜单三散件收一:随机种子/数值调整/批量模拟/两层调试入口;
|
||||
# 全局可开浮窗:调试会话恒开=启动即开〔_ready〕+两屏角标再开+当局数据只读区块;
|
||||
@@ -249,9 +249,9 @@ static func _build_debug_and_resolvers(root: Variant) -> void:
|
||||
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)
|
||||
root.ui.modals.menu.debug_overlay.apply_requested.connect(handlers["tune_apply"])
|
||||
root.ui.modals.menu.debug_overlay.simulate_requested.connect(handlers["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)
|
||||
@@ -261,10 +261,10 @@ static func _build_debug_and_resolvers(root: Variant) -> void:
|
||||
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)
|
||||
root.set_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(handlers["debug_battle_created"])
|
||||
root.debug().battle_ready.connect(handlers["debug_battle_ready"])
|
||||
|
||||
static func _build_modal_flow(root: Variant) -> void:
|
||||
# 模态登记(缺陷⑤纪律——登记序 = 树序 = z 序):层序唯一事实源=LAYER_CONTRACT,
|
||||
@@ -277,15 +277,15 @@ static func _build_modal_flow(root: Variant) -> void:
|
||||
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.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
|
||||
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))
|
||||
root.set_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)
|
||||
@@ -295,4 +295,4 @@ static func fatal_screen(root: Variant, msg: String) -> void:
|
||||
|
||||
# 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})
|
||||
return func() -> String: return root.gp_summary_text({"gp_dropped": root.run_scope.last_gain_amount})
|
||||
@@ -103,7 +103,7 @@ static func build_start_overlay(host: Control, db: ConfigDb = null) -> Dictionar
|
||||
var bd := UiKit.backdrop_tile()
|
||||
if bd != null:
|
||||
overlay.add_child(bd)
|
||||
overlay.add_child(UiKit._beams_rect())
|
||||
overlay.add_child(UiKit.beams_rect())
|
||||
overlay.add_child(UiKit.vignette_rect())
|
||||
# 双列骨架:左=操作列 56% / 右=舞台(立绘看台)
|
||||
var cols := _start_columns(overlay)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
## 战斗/冒险终局 overlay(模态):判定行+三行账+明细回看入口+继续/返回,失败侧暮蓝氛围。
|
||||
## 摘要文本经 open_victory/open_defeat 由组合根装配喂入——本类零文案装配。
|
||||
## 内部数据类 _EndOverlayState=宿主画面登记表(只被本文件填读,文件外零消费者——字段为公开数据成员)。
|
||||
class_name BattleEndOverlay
|
||||
extends Control
|
||||
|
||||
@@ -13,20 +14,20 @@ const COL_WARM := UiKit.WARM
|
||||
## 内部状态件(u6 子域拆件——契约 9 职责语句「战末呈现与演出态」):单一变化原因=终局画面呈现与
|
||||
## 演出计数。探针直读冻结成员(_btn/_gp_name/_gp_row/_gp_val)住宿主不进状态件。
|
||||
class _EndOverlayState extends RefCounted:
|
||||
var _dim: ColorRect
|
||||
var _seal: Panel
|
||||
var _verdict: Label
|
||||
var _rows_box: VBoxContainer # 三行账(GP/钥石/本关加成——demo ⑧两列对齐;失败侧整行隐藏)
|
||||
var _ks_row: HBoxContainer
|
||||
var _ks_name: Label
|
||||
var _ks_val: Label
|
||||
var _bonus_row_l: HBoxContainer
|
||||
var _bonus_name: Label
|
||||
var _bonus_val: Label
|
||||
var _cele: TextureRect # 庆祝立绘(idle 占位浮动)
|
||||
var _board_rect := Rect2()
|
||||
var _crest_bricks: Array[ColorRect] = [] # 纹章碎砖驻留面(close 时回收)
|
||||
var _fx_counts := {"crest": 0, "dust": 0, "seal": 0} # 演出计数(fx_probe 断言面——crest/dust/seal 三键)
|
||||
var dim: ColorRect
|
||||
var seal: Panel
|
||||
var verdict: Label
|
||||
var rows_box: VBoxContainer # 三行账(GP/钥石/本关加成——demo ⑧两列对齐;失败侧整行隐藏)
|
||||
var ks_row: HBoxContainer
|
||||
var ks_name: Label
|
||||
var ks_val: Label
|
||||
var bonus_row_l: HBoxContainer
|
||||
var bonus_name: Label
|
||||
var bonus_val: Label
|
||||
var cele: TextureRect # 庆祝立绘(idle 占位浮动)
|
||||
var board_rect := Rect2()
|
||||
var crest_bricks: Array[ColorRect] = [] # 纹章碎砖驻留面(close 时回收)
|
||||
var fx_counts := {"crest": 0, "dust": 0, "seal": 0} # 演出计数(fx_probe 断言面——crest/dust/seal 三键)
|
||||
|
||||
|
||||
var st := _EndOverlayState.new()
|
||||
@@ -40,10 +41,10 @@ func _init() -> void:
|
||||
# 半透明浮层文字不可读 → 实底石板皮面板形态
|
||||
#(与营地/设置/图例同族)。半透明氛围 dim 保留(胜利暖纱/失败暮蓝——open_* 就地换色),信息与
|
||||
# 按钮全部上板;金蜡封 = 面板头部静态徽记(坠落演出落点被面板遮挡)。
|
||||
# 骨架经 UiKit.modal_shell;_dim 引用摘出供 open_* 换氛围色。
|
||||
# 骨架经 UiKit.modal_shell;dim 引用摘出供 open_* 换氛围色。
|
||||
var shell := UiKit.modal_shell(self, Color(COL_WARM, 0.0), Vector2(520, 0), 12,
|
||||
UiKit.panel_skinned(Vector4(16, 14, 16, 14)))
|
||||
st._dim = shell.dim
|
||||
st.dim = shell.dim
|
||||
var box: VBoxContainer = shell.box
|
||||
_build_header(box)
|
||||
_build_ledger(box)
|
||||
@@ -62,20 +63,20 @@ func _build_header(box: VBoxContainer) -> void:
|
||||
header.add_theme_constant_override("separation", 12)
|
||||
header.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
box.add_child(header)
|
||||
st._seal = Panel.new()
|
||||
st._seal.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
st.seal = Panel.new()
|
||||
st.seal.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
var sb := UiKit.panel_flat(COL_GOLD, UiKit.GOLD_DEEP, 200, 4)
|
||||
st._seal.add_theme_stylebox_override("panel", sb)
|
||||
st._seal.custom_minimum_size = Vector2(52, 52)
|
||||
st._seal.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
header.add_child(st._seal)
|
||||
st._seal.add_child(_seal_img())
|
||||
st._verdict = UiKit.label("",UiKit.FS_TITLE, true, UiKit.INK_TITLE)
|
||||
st._verdict.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
st._verdict.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
header.add_child(st._verdict)
|
||||
st._cele = _cele_rect()
|
||||
header.add_child(st._cele)
|
||||
st.seal.add_theme_stylebox_override("panel", sb)
|
||||
st.seal.custom_minimum_size = Vector2(52, 52)
|
||||
st.seal.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
header.add_child(st.seal)
|
||||
st.seal.add_child(_seal_img())
|
||||
st.verdict = UiKit.label("",UiKit.FS_TITLE, true, UiKit.INK_TITLE)
|
||||
st.verdict.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
st.verdict.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
header.add_child(st.verdict)
|
||||
st.cele = _cele_rect()
|
||||
header.add_child(st.cele)
|
||||
|
||||
|
||||
## 蜡封图域(32px 像素过滤居中)
|
||||
@@ -110,30 +111,30 @@ func _cele_rect() -> TextureRect:
|
||||
|
||||
## 三行账域(两列对齐账行——审计差距④对齐 demo ⑧:项目名左/数值右,定宽 360)
|
||||
func _build_ledger(box: VBoxContainer) -> void:
|
||||
st._rows_box = VBoxContainer.new()
|
||||
st._rows_box.add_theme_constant_override("separation", 6)
|
||||
st._rows_box.visible = false
|
||||
box.add_child(st._rows_box)
|
||||
st.rows_box = VBoxContainer.new()
|
||||
st.rows_box.add_theme_constant_override("separation", 6)
|
||||
st.rows_box.visible = false
|
||||
box.add_child(st.rows_box)
|
||||
_gp_row = _ledger_row()
|
||||
_gp_name = _ledger_name(UiKit.FS_SUB, UiKit.GOLD_INK, true) # GOLD_DEEP 对结算板 2.67:1 越带,换深金墨 5.21:1
|
||||
_gp_val = _ledger_value(UiKit.FS_SUB, UiKit.GOLD_INK, true) # 同上
|
||||
_gp_row.add_child(_gp_name)
|
||||
_gp_row.add_child(_gp_val)
|
||||
st._rows_box.add_child(_gp_row)
|
||||
st._ks_row = _ledger_row()
|
||||
st._ks_name = _ledger_name(UiKit.FS_BODY, UiKit.INK_LABEL, false)
|
||||
st._ks_val = _ledger_value(UiKit.FS_BODY, UiKit.INK_LABEL, true)
|
||||
st._ks_row.add_child(st._ks_name)
|
||||
st._ks_row.add_child(st._ks_val)
|
||||
st._ks_row.visible = false
|
||||
st._rows_box.add_child(st._ks_row)
|
||||
st._bonus_row_l = _ledger_row()
|
||||
st._bonus_name = _ledger_name(UiKit.FS_HINT, UiKit.INK_LABEL, false)
|
||||
st._bonus_val = _ledger_value(UiKit.FS_HINT, UiKit.INK_LABEL, true)
|
||||
st._bonus_row_l.add_child(st._bonus_name)
|
||||
st._bonus_row_l.add_child(st._bonus_val)
|
||||
st._bonus_row_l.visible = false
|
||||
st._rows_box.add_child(st._bonus_row_l)
|
||||
st.rows_box.add_child(_gp_row)
|
||||
st.ks_row = _ledger_row()
|
||||
st.ks_name = _ledger_name(UiKit.FS_BODY, UiKit.INK_LABEL, false)
|
||||
st.ks_val = _ledger_value(UiKit.FS_BODY, UiKit.INK_LABEL, true)
|
||||
st.ks_row.add_child(st.ks_name)
|
||||
st.ks_row.add_child(st.ks_val)
|
||||
st.ks_row.visible = false
|
||||
st.rows_box.add_child(st.ks_row)
|
||||
st.bonus_row_l = _ledger_row()
|
||||
st.bonus_name = _ledger_name(UiKit.FS_HINT, UiKit.INK_LABEL, false)
|
||||
st.bonus_val = _ledger_value(UiKit.FS_HINT, UiKit.INK_LABEL, true)
|
||||
st.bonus_row_l.add_child(st.bonus_name)
|
||||
st.bonus_row_l.add_child(st.bonus_val)
|
||||
st.bonus_row_l.visible = false
|
||||
st.rows_box.add_child(st.bonus_row_l)
|
||||
box.add_child(HSeparator.new())
|
||||
|
||||
|
||||
@@ -147,19 +148,19 @@ func open_victory(verdict_line: String, continue_label: String, board_rect: Rect
|
||||
var bonus_summary: String = opts.get("bonus_summary", "")
|
||||
var gp_gain: int = opts.get("gp_gain", -1)
|
||||
var gp_total: int = opts.get("gp_total", -1)
|
||||
st._dim.color = Color(UiKit.DIM_NIGHT, 0.62) # 全屏压暗对齐 demo
|
||||
st._verdict.text = verdict_line
|
||||
st._seal.visible = true # 胜利徽记(面板头部静态)
|
||||
st._seal.modulate = Color.WHITE
|
||||
st.dim.color = Color(UiKit.DIM_NIGHT, 0.62) # 全屏压暗对齐 demo
|
||||
st.verdict.text = verdict_line
|
||||
st.seal.visible = true # 胜利徽记(面板头部静态)
|
||||
st.seal.modulate = Color.WHITE
|
||||
_set_ledger_row(_gp_name, _gp_val, gp_summary, true) # GP 行=固定名「战利品 GP」(滚动/整串均只入值列)
|
||||
_set_ledger_row(st._ks_name, st._ks_val, ks_summary)
|
||||
st._ks_row.visible = ks_summary != ""
|
||||
_set_ledger_row(st._bonus_name, st._bonus_val, bonus_summary)
|
||||
st._bonus_row_l.visible = bonus_summary != ""
|
||||
st._rows_box.visible = gp_summary != "" or ks_summary != "" or bonus_summary != ""
|
||||
_set_ledger_row(st.ks_name, st.ks_val, ks_summary)
|
||||
st.ks_row.visible = ks_summary != ""
|
||||
_set_ledger_row(st.bonus_name, st.bonus_val, bonus_summary)
|
||||
st.bonus_row_l.visible = bonus_summary != ""
|
||||
st.rows_box.visible = gp_summary != "" or ks_summary != "" or bonus_summary != ""
|
||||
_btn.text = continue_label
|
||||
st._board_rect = board_rect
|
||||
st._cele.visible = true
|
||||
st.board_rect = board_rect
|
||||
st.cele.visible = true
|
||||
_play_celebrate()
|
||||
visible = true
|
||||
if UiKit.victory_fx_style == "dust":
|
||||
@@ -173,23 +174,23 @@ func open_victory(verdict_line: String, continue_label: String, board_rect: Rect
|
||||
|
||||
func open_defeat(verdict_line: String, continue_label: String, board_rect: Rect2) -> void:
|
||||
# 明度不变的暮蓝色相氛围(全屏唯一暮蓝档——§2.5 最静一拍)
|
||||
st._dim.color = Color(COL_DUSK, 0.6) # 同族压暗
|
||||
st._verdict.text = verdict_line
|
||||
st._board_rect = board_rect
|
||||
st.dim.color = Color(COL_DUSK, 0.6) # 同族压暗
|
||||
st.verdict.text = verdict_line
|
||||
st.board_rect = board_rect
|
||||
visible = true
|
||||
st._seal.visible = true
|
||||
st._seal.modulate = Color(0.4, 0.4, 0.4) # 失败徽记灰化(demo ⑧)
|
||||
st._cele.visible = false
|
||||
st._rows_box.visible = false # 失败侧不呈现账行
|
||||
st.seal.visible = true
|
||||
st.seal.modulate = Color(0.4, 0.4, 0.4) # 失败徽记灰化(demo ⑧)
|
||||
st.cele.visible = false
|
||||
st.rows_box.visible = false # 失败侧不呈现账行
|
||||
_play_dust_sadle() # 坐砖掸灰(§2.5——终稿)
|
||||
_play_seal_recall(8)
|
||||
|
||||
|
||||
func close() -> void:
|
||||
visible = false
|
||||
st._seal.visible = false
|
||||
st._seal.modulate = Color.WHITE
|
||||
st._cele.visible = false
|
||||
st.seal.visible = false
|
||||
st.seal.modulate = Color.WHITE
|
||||
st.cele.visible = false
|
||||
var tw: Tween = get_meta(&"celebrate_tw", null) # 循环句柄随节点存(跨 open/close,close 必杀)
|
||||
if tw != null and tw.is_valid():
|
||||
tw.kill()
|
||||
@@ -202,7 +203,7 @@ func is_open() -> bool:
|
||||
|
||||
|
||||
func verdict_text() -> String:
|
||||
return st._verdict.text
|
||||
return st.verdict.text
|
||||
|
||||
|
||||
# 两列账行工厂(审计差距④——名左/值右,定宽 360)
|
||||
@@ -249,8 +250,8 @@ func _fx_center() -> Vector2:
|
||||
|
||||
# 封印化光点收回(失败——插过的封印逐面化光点收回占位,§2.5)
|
||||
func _play_seal_recall(count: int) -> void:
|
||||
st._fx_counts["seal"] = count
|
||||
var base := st._board_rect.get_center() if st._board_rect.size != Vector2.ZERO else get_viewport_rect().size * 0.5
|
||||
st.fx_counts["seal"] = count
|
||||
var base := st.board_rect.get_center() if st.board_rect.size != Vector2.ZERO else get_viewport_rect().size * 0.5
|
||||
for i in count:
|
||||
_spawn_seal_dot(i, base)
|
||||
|
||||
@@ -260,8 +261,8 @@ func _spawn_seal_dot(i: int, base: Vector2) -> void:
|
||||
var dot := ColorRect.new()
|
||||
dot.color = COL_GOLD
|
||||
dot.size = Vector2(6, 6)
|
||||
dot.position = base + Vector2((float(i % 5) - 2.0) * st._board_rect.size.x * 0.14 if st._board_rect.size.x > 0 else 0.0,
|
||||
(float(i % 3) - 1.0) * st._board_rect.size.y * 0.18)
|
||||
dot.position = base + Vector2((float(i % 5) - 2.0) * st.board_rect.size.x * 0.14 if st.board_rect.size.x > 0 else 0.0,
|
||||
(float(i % 3) - 1.0) * st.board_rect.size.y * 0.18)
|
||||
dot.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(dot)
|
||||
var tw := dot.create_tween()
|
||||
@@ -277,7 +278,7 @@ func _spawn_seal_dot(i: int, base: Vector2) -> void:
|
||||
# 碎砖拼纹章(胜利 §2.4——蜡封落定后十枚碎砖自四周向蜡封外环收拢拼成纹章环,驻留至 close)
|
||||
func _play_crest_assemble() -> void:
|
||||
_clear_crest_bricks()
|
||||
st._fx_counts["crest"] = 10
|
||||
st.fx_counts["crest"] = 10
|
||||
var c := _fx_center()
|
||||
for i in 10:
|
||||
_spawn_crest_brick(i, c)
|
||||
@@ -293,7 +294,7 @@ func _spawn_crest_brick(i: int, c: Vector2) -> void:
|
||||
var ring := c + Vector2(cos(ang), sin(ang)) * 64.0
|
||||
brick.position = c + Vector2(cos(ang), sin(ang)) * 200.0 # 自四周场外入场
|
||||
add_child(brick)
|
||||
st._crest_bricks.append(brick)
|
||||
st.crest_bricks.append(brick)
|
||||
var btw := brick.create_tween()
|
||||
btw.tween_interval(UiKit.motion(0.52)) # 待蜡封落定(seal 0.42+0.22 弹跳中段)
|
||||
btw.tween_property(brick, "position", ring - brick.size * 0.5, UiKit.motion(0.4)).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||
@@ -302,7 +303,7 @@ func _spawn_crest_brick(i: int, c: Vector2) -> void:
|
||||
|
||||
# 坐砖掸灰(失败 §2.5——暖灰尘絮自棋盘中带缓缓飘落,最静一拍的运动承载)
|
||||
func _play_dust_sadle() -> void:
|
||||
st._fx_counts["dust"] = 8
|
||||
st.fx_counts["dust"] = 8
|
||||
var base := _fx_center()
|
||||
for i in 8:
|
||||
_spawn_dust_mote(i, base)
|
||||
@@ -330,16 +331,16 @@ func _play_celebrate() -> void:
|
||||
var prev: Tween = get_meta(&"celebrate_tw", null)
|
||||
if prev != null and prev.is_valid():
|
||||
prev.kill()
|
||||
var tw := st._cele.create_tween()
|
||||
var tw := st.cele.create_tween()
|
||||
set_meta(&"celebrate_tw", tw)
|
||||
tw.set_loops()
|
||||
tw.tween_property(st._cele, "position:y", st._cele.position.y - 4.0, UiKit.motion(0.6)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
||||
tw.tween_property(st._cele, "position:y", st._cele.position.y, UiKit.motion(0.6)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
||||
tw.tween_property(st.cele, "position:y", st.cele.position.y - 4.0, UiKit.motion(0.6)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
||||
tw.tween_property(st.cele, "position:y", st.cele.position.y, UiKit.motion(0.6)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
||||
|
||||
|
||||
# 尘金粒子(备选演出——金屑自面板上带飘落闪灭;确定性散布守 S2)
|
||||
func _play_victory_dust() -> void:
|
||||
st._fx_counts["dust"] += 10
|
||||
st.fx_counts["dust"] += 10
|
||||
var c := _fx_center()
|
||||
for i in 10:
|
||||
_spawn_victory_mote(i, c)
|
||||
@@ -372,15 +373,15 @@ func _roll_gp(gp_gain: int, gp_total: int) -> void:
|
||||
|
||||
|
||||
func _clear_crest_bricks() -> void:
|
||||
for b in st._crest_bricks:
|
||||
for b in st.crest_bricks:
|
||||
if is_instance_valid(b):
|
||||
b.queue_free()
|
||||
st._crest_bricks.clear()
|
||||
st.crest_bricks.clear()
|
||||
|
||||
|
||||
# 演出探针出口(fx_probe——胜利纹章/失败掸灰/封印光收回三断言面)
|
||||
func fx_probe() -> Dictionary:
|
||||
return {"crest_shards": st._fx_counts["crest"], "dust_motes": st._fx_counts["dust"], "seal_dots": st._fx_counts["seal"]}
|
||||
return {"crest_shards": st.fx_counts["crest"], "dust_motes": st.fx_counts["dust"], "seal_dots": st.fx_counts["seal"]}
|
||||
|
||||
|
||||
func _emit_continue_pressed() -> void:
|
||||
|
||||
@@ -10,12 +10,14 @@ extends Node2D
|
||||
const _GLOW_LAYER := preload("res://presentation/views/cell_type_glow_layer.gd")
|
||||
const _BASE_LAYER := preload("res://presentation/views/cell_base_layer.gd")
|
||||
const _MARK_OVERLAY := preload("res://presentation/views/cell_mark_overlay.gd")
|
||||
const _SEAL_LAYER := preload("res://presentation/views/seal_mark_layer.gd")
|
||||
|
||||
var edge := 46.0: # 格边长(BoardView.set_cell_area 单点驱动)
|
||||
set(v):
|
||||
edge = v
|
||||
if _glow_layer != null and is_instance_valid(_glow_layer):
|
||||
_glow_layer.setup(edge, glow_group)
|
||||
_sync_seal_layer()
|
||||
queue_redraw()
|
||||
var cell := Vector2i.ZERO # 板坐标(诊断观测面)
|
||||
# ── 未开格类型微光(英雄 8 向相邻未开格的组粒度类型提示)
|
||||
@@ -41,16 +43,49 @@ var fog_state := 0: # 探针直读冻结
|
||||
set(v):
|
||||
if fog_state != v:
|
||||
fog_state = v
|
||||
_sync_seal_layer() # 封印随格隐没(§6.5——雾态 !=0 整层隐藏)
|
||||
queue_redraw()
|
||||
|
||||
# ── 封印印记(卡④ 法阵化——着色器按格边参数化绘制,替代 tex_seal 贴图整格铺放)──
|
||||
# BoardView paint 按格态写 seal_on(封印格=未开且未移除);子件 on 才建、off 撤、edge 变更重注入。
|
||||
var seal_on := false:
|
||||
set(v):
|
||||
if seal_on != v:
|
||||
seal_on = v
|
||||
_sync_seal_layer()
|
||||
|
||||
var _glow_layer: Node2D = null
|
||||
var _seal_layer: Node2D = null # 封印印记子件(seal_on 才建——作用集=封印格)
|
||||
var base = null # 格面基座层绘制域件(_init 自建)
|
||||
var face = null # 格面标识与叠加层绘制域件(_init 自建)
|
||||
|
||||
|
||||
# 封印子件生命周期单点:on 才建、off 撤;visible = seal_on 且非雾态(§6.5 随格隐没)
|
||||
func _sync_seal_layer() -> void:
|
||||
if seal_on and _seal_layer == null:
|
||||
_seal_layer = _SEAL_LAYER.new()
|
||||
add_child(_seal_layer)
|
||||
if _seal_layer != null and not is_instance_valid(_seal_layer):
|
||||
_seal_layer = null
|
||||
return
|
||||
if _seal_layer == null:
|
||||
return
|
||||
if not seal_on:
|
||||
_seal_layer.queue_free()
|
||||
_seal_layer = null
|
||||
return
|
||||
_seal_layer.setup(edge, fog_state == 0)
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
base = _BASE_LAYER.new(self)
|
||||
face = _MARK_OVERLAY.new(self)
|
||||
face = _MARK_OVERLAY.new(self, _set_badge_mask)
|
||||
|
||||
|
||||
# 徽章遮罩下发口(构造交付 Callable——glow 层引用自持本件,face 零私取回引)
|
||||
func _set_badge_mask(on: bool) -> void:
|
||||
if _glow_layer != null and is_instance_valid(_glow_layer):
|
||||
_glow_layer.set_badge_mask(on)
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
|
||||
@@ -33,7 +33,24 @@ func _init(bundle: Dictionary) -> void:
|
||||
_cb_chord = bundle["chord"]
|
||||
|
||||
|
||||
func _hover_at(pos: Vector2) -> void:
|
||||
# ---- 公开操作面(pres封装 A2——宿主经此驱动,字段保持私有)----
|
||||
|
||||
# 每战重绑(排雷玩法对象随战更替——宿主 rebuild 同拍调用)
|
||||
func rebind(minesweeper: Variant) -> void:
|
||||
_minesweeper = minesweeper
|
||||
|
||||
|
||||
# 硬复位悬停锚(无 unhover 视觉清理——宿主 rebuild 前提下视觉随重建清零)
|
||||
func reset() -> void:
|
||||
_hover = Vector2i(-9, -9)
|
||||
|
||||
|
||||
# 悬停现值读口(宿主呈现查询)
|
||||
func hover_pos() -> Vector2i:
|
||||
return _hover
|
||||
|
||||
|
||||
func hover_at(pos: Vector2) -> void:
|
||||
var c: Vector2i = _cb_cell_at.call(pos)
|
||||
if c.x < 0:
|
||||
if _hover.x >= 0:
|
||||
@@ -44,13 +61,13 @@ func _hover_at(pos: Vector2) -> void:
|
||||
_on_cell_hover(c)
|
||||
|
||||
|
||||
func _on_shell_mouse_exited() -> void:
|
||||
func on_shell_mouse_exited() -> void:
|
||||
if _hover.x >= 0:
|
||||
_on_cell_unhover(_hover)
|
||||
|
||||
|
||||
# chord 肌肉记忆首遇锚:同格双左击快开尝试(350ms 窗)——每 run 一次提示由组合根去重
|
||||
func _detect_chord(c: Vector2i) -> void:
|
||||
func detect_chord(c: Vector2i) -> void:
|
||||
var now := Time.get_ticks_msec()
|
||||
if _last_click.cell == c and now - int(_last_click.ms) <= CHORD_WINDOW_MS:
|
||||
_cb_chord.call(c)
|
||||
@@ -70,7 +87,7 @@ func _on_cell_unhover(c: Vector2i) -> void:
|
||||
_clear_hover_visuals(c)
|
||||
|
||||
|
||||
func _refresh_hover_after_paint() -> void:
|
||||
func refresh_after_paint() -> void:
|
||||
# 状态随 refresh 变化后光标/边框语义需重算(悬停格四格态可能已变)
|
||||
if _hover.x >= 0 and _cells.has(_hover):
|
||||
_apply_hover_visuals(_hover)
|
||||
|
||||
@@ -143,7 +143,7 @@ func board() -> BoardData:
|
||||
|
||||
func _ready() -> void:
|
||||
_apply_custom_cursors()
|
||||
mouse_exited.connect(_hover_comp._on_shell_mouse_exited)
|
||||
mouse_exited.connect(_hover_comp.on_shell_mouse_exited)
|
||||
|
||||
|
||||
# 结算期/道具窗口态:D 区去饱和 + 金橙偏移锁输入(明度不变——art-bible §2);
|
||||
@@ -246,7 +246,7 @@ func _refresh_slab() -> void:
|
||||
func rebuild(board: BoardData, minesweeper: MinesweeperRules) -> void:
|
||||
_board = board
|
||||
_minesweeper = minesweeper
|
||||
_hover_comp._minesweeper = _minesweeper # 件内只读引用随宿主重绑同拍回写(每战新排雷玩法对象)
|
||||
_hover_comp.rebind(_minesweeper) # 件内只读引用随宿主重绑同拍回写(每战新排雷玩法对象)
|
||||
_palette.bind_board(_board, _minesweeper) # 查表族棋盘态随板同步(换板后首拍 refresh 前置)
|
||||
for child in _world.get_children():
|
||||
child.queue_free()
|
||||
@@ -257,7 +257,7 @@ func rebuild(board: BoardData, minesweeper: MinesweeperRules) -> void:
|
||||
_contact_unit = null
|
||||
_hero_sprite.last = Vector2i(-99, -99)
|
||||
_r0.clear()
|
||||
_hover_comp._hover = Vector2i(-9, -9)
|
||||
_hover_comp.reset()
|
||||
for c in _board.compute_r0():
|
||||
_r0[c] = true
|
||||
# 瓦片套随板图型切换(board_gen 词表 v1.1 dungeon/underdark/deep_underdark;
|
||||
@@ -306,7 +306,7 @@ func refresh() -> void:
|
||||
for y in _board.height:
|
||||
for x in _board.width:
|
||||
_paint_cell(Vector2i(x, y), hero)
|
||||
_hover_comp._refresh_hover_after_paint()
|
||||
_hover_comp.refresh_after_paint()
|
||||
_apply_contact_bands()
|
||||
_update_hero(hero) # 英雄精灵 diff 驱动
|
||||
|
||||
@@ -461,7 +461,7 @@ func _gui_input(ev: InputEvent) -> void:
|
||||
if _minesweeper == null:
|
||||
return
|
||||
if ev is InputEventMouseMotion:
|
||||
_hover_comp._hover_at(ev.position)
|
||||
_hover_comp.hover_at(ev.position)
|
||||
elif ev is InputEventMouseButton and ev.pressed:
|
||||
var c := _cell_at(ev.position)
|
||||
if c.x >= 0:
|
||||
@@ -476,7 +476,7 @@ func _on_cell_input(c: Vector2i, ev: InputEvent) -> void:
|
||||
action_requested.emit(c, "move")
|
||||
else:
|
||||
action_requested.emit(c, "dig")
|
||||
_hover_comp._detect_chord(c)
|
||||
_hover_comp.detect_chord(c)
|
||||
elif ev.button_index == MOUSE_BUTTON_RIGHT:
|
||||
if not _minesweeper.has_seal(c):
|
||||
action_requested.emit(c, "seal_place")
|
||||
@@ -536,13 +536,13 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void:
|
||||
var seal: bool = _minesweeper.has_seal(c)
|
||||
var removed: bool = _minesweeper.is_removed(c) # 中立移除格(退色降档)
|
||||
var is_hero: bool = c == hero
|
||||
# 封印槽统一维护:封印格=封印印记纹理;其余格清 null(封印格挖开后残留在 tex_seal
|
||||
# 会让 _draw 无条件把封印印画在已开格上——tex_seal 绘制无 revealed 条件)。缺盘走字形回退。
|
||||
u.face.tex_seal = _palette.seal_tex if (seal and not revealed and _palette.seal_tex != null) else null
|
||||
# 封印印记下发(卡④ 法阵化):封印格=未开且未移除 → CellUnit 封印子件绘制(着色器参数化);
|
||||
# 其余格撤子件。层序与迷雾门控在 CellUnit 侧承载(seal_mark_layer/fog_state 联动)。
|
||||
u.seal_on = seal and not revealed
|
||||
if removed:
|
||||
revealed = false # 移除格固定未开呈现基座(退色暗幕层承载;危险格本无数字,周边数字冻结)
|
||||
seal = false
|
||||
u.face.tex_seal = null
|
||||
u.seal_on = false
|
||||
# 绘制层五段(字形与色牌→基底瓦片→立面投影→秘宝/变异叠加→迷雾)——段间交换经单元状态面
|
||||
#(u.base/u.face 域件与 u.glyph/u.glyph_color),唯一例外=基底色 bg 经 ink 段返回值交 base 段(无单元场域承载)
|
||||
var bg: Color = _paint_cell_ink(c, u, revealed, seal)
|
||||
@@ -577,10 +577,7 @@ func _paint_cell_ink(c: Vector2i, u: Node2D, revealed: bool, seal: bool) -> Colo
|
||||
digit_col = _digit_ink(d) # 数字墨三档色温(§4.1)
|
||||
else:
|
||||
if seal:
|
||||
bg = COL_BRICK
|
||||
if _palette.seal_tex == null:
|
||||
glyph = "封"
|
||||
digit_col = COL_SEAL
|
||||
bg = COL_BRICK # 封印印记由 CellUnit 封印子件绘制(卡④ 法阵化——缺图字形回退随贴图路径退役)
|
||||
else:
|
||||
bg = COL_BRICK
|
||||
u.glyph = glyph
|
||||
@@ -825,7 +822,7 @@ func diggable_probe() -> Dictionary:
|
||||
|
||||
# 悬停格只读出口(case① 悬停到达断言消费面——读件投影,返回语义零变化)
|
||||
func hover_probe() -> Vector2i:
|
||||
return _hover_comp._hover
|
||||
return _hover_comp.hover_pos()
|
||||
|
||||
|
||||
# 迷雾参数只读快照(case⑮ 参数漂移断言消费面)
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
## 变异标记/数字色牌与字形/奖励徽章/封印/路径点迹/焦点高亮/中立移除退色的全部绘制入参与绘制命令。
|
||||
## 无 class_name(类名缓存面纪律)——经宿主 board_cell_unit preload 引用;宿主 _draw 按层序调
|
||||
## draw_marks/draw_glyph_layer/draw_overlay_under/draw_removed_dim(雾层由宿主夹在 overlay_under 与 removed_dim 之间);
|
||||
## setter 经 _owner 回引触发宿主重绘语义逐字(badge_glyph 的 glow mask 回引 _owner._glow_layer 原样)。
|
||||
## setter 经 _owner 回引触发宿主重绘语义逐字(badge_glyph 的 glow mask 经构造交付 Callable 下发——pres封装 C)。
|
||||
extends RefCounted
|
||||
|
||||
var _owner: Node2D = null
|
||||
var _mask_set: Callable # 徽章遮罩下发口(构造交付——宿主 CellUnit 自持 glow 层引用,本件零回引宿主私有面)
|
||||
var badge_glyph := "": # 奖励徽章文字(右下角金——封印之下、变异纹之上、不进数字区)
|
||||
set(v):
|
||||
badge_glyph = v
|
||||
if _owner._glow_layer != null and is_instance_valid(_owner._glow_layer):
|
||||
_owner._glow_layer.set_badge_mask(v != "")
|
||||
if _mask_set.is_valid():
|
||||
_mask_set.call(v != "")
|
||||
_owner.queue_redraw()
|
||||
var dot_visible := false: # BFS 路径预览点迹(中心 6×6 小人蓝)
|
||||
set(v):
|
||||
@@ -30,11 +31,6 @@ var font: Font = null # 字体槽位(null = ThemeDB.fallback_font
|
||||
var tex_mutation: Texture2D = null # ② 变异纹带
|
||||
var tex_glyph: Texture2D = null # ③ 数字
|
||||
var tex_badge: Texture2D = null # ③' 徽章
|
||||
var tex_seal: Texture2D = null: # ④ 封印(槽位可运行时换装〔封印〕——同族 facade/treasure_mark
|
||||
set(v): # 均 setter 触发重绘(普通 var 赋值不重绘=封印插上不显示))
|
||||
if tex_seal != v:
|
||||
tex_seal = v
|
||||
_owner.queue_redraw()
|
||||
# 变异角标(程序化,tex_mutation 纹带槽暂留):
|
||||
# "flat_damage" = 未开格伤害型变异,左下角危险红三角(开格有代价的策略纹理标记)
|
||||
# 叠加层标记(秘宝整图叠加层的格面呈现;派生信息层零回写):
|
||||
@@ -56,8 +52,9 @@ var removed_dim := false: # 中立移除退色层(BoardView paint
|
||||
_owner.queue_redraw()
|
||||
|
||||
|
||||
func _init(owner: Node2D) -> void:
|
||||
func _init(owner: Node2D, mask_set: Callable) -> void:
|
||||
_owner = owner
|
||||
_mask_set = mask_set
|
||||
|
||||
|
||||
## 变异标记域:真视格=危险红内缩框/未开格伤害型变异=左下角危险红三角
|
||||
@@ -91,9 +88,7 @@ func draw_glyph_layer(canvas: CanvasItem, rect: Rect2, glyph: String, glyph_colo
|
||||
canvas.draw_texture_rect(tex_badge, rect, false)
|
||||
else:
|
||||
_draw_badge(canvas, rect, badge_glyph)
|
||||
# ④ 封印(现行视觉封印文字在 ③ 文字位承载——独立槽位留给封印图)
|
||||
if tex_seal != null:
|
||||
canvas.draw_texture_rect(tex_seal, rect, false)
|
||||
# ④ 封印:印记由 CellUnit 封印子件(seal_mark_layer)参数化绘制——本域件零封印绘制(卡④ 法阵化)
|
||||
|
||||
|
||||
## 数字色牌后备域(圣经 v3.5 冻结④:数字亮墨直画已开格底须垫牌;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
## 开发调试浮窗:全局可开(组合根启动即开+两屏角标再开),当局数据只读区块为本局种子唯一查看位。
|
||||
## 扩展调试项按区块往下追加(超高滚动);模拟报告文案装配归 HudPresenter——本类零动效零文案装配。
|
||||
## 内部数据类 _DebugState=宿主画面登记表(只被本文件填读,文件外零消费者——字段为公开数据成员)。
|
||||
class_name DebugOverlay
|
||||
extends Control
|
||||
|
||||
@@ -12,22 +13,22 @@ signal closed
|
||||
|
||||
## 内部状态件(u7 子域拆件——契约 9 职责语句「调试浮窗运行态」):单一变化原因=调试面板状态与
|
||||
## 当局数据投影。探针直读冻结成员(seed_edit/_sim_btn/_dbg_btn)住宿主;
|
||||
## st._run_data/st._run_labels 经 run_data_probe()/update_run_data() 方法出口(字段直访面零外部依赖)。
|
||||
## st.run_data/st.run_labels 经 run_data_probe()/update_run_data() 方法出口(字段直访面零外部依赖)。
|
||||
class _DebugState extends RefCounted:
|
||||
var _db
|
||||
var _base_atk := 0
|
||||
var _base_hp := 0
|
||||
var _atk: HSlider
|
||||
var _hp: HSlider
|
||||
var _atk_val: Label
|
||||
var _hp_val: Label
|
||||
var _eff: Label
|
||||
var _sim_count: SpinBox
|
||||
var _sim_out: RichTextLabel
|
||||
var _sim_running := false # 批量模拟进行中(is_running 判据——与对局中禁用态分离防互扰)
|
||||
var _ctx_locked := false # 对局中上下文锁定(组合根 _sync_debug_overlay 喂入——批量模拟/两层调试入口禁用)
|
||||
var _run_labels := {} # 当局数据六项标签(只读投影,组合根 update_run_data 喂入)
|
||||
var _run_data := {} # 最近一次投喂快照(探针值对面 run_data_probe)
|
||||
var db
|
||||
var base_atk := 0
|
||||
var base_hp := 0
|
||||
var atk: HSlider
|
||||
var hp: HSlider
|
||||
var atk_val: Label
|
||||
var hp_val: Label
|
||||
var eff: Label
|
||||
var sim_count: SpinBox
|
||||
var sim_out: RichTextLabel
|
||||
var sim_running := false # 批量模拟进行中(is_running 判据——与对局中禁用态分离防互扰)
|
||||
var ctx_locked := false # 对局中上下文锁定(组合根 _sync_debug_overlay 喂入——批量模拟/两层调试入口禁用)
|
||||
var run_labels := {} # 当局数据六项标签(只读投影,组合根 update_run_data 喂入)
|
||||
var run_data := {} # 最近一次投喂快照(探针值对面 run_data_probe)
|
||||
|
||||
|
||||
var st := _DebugState.new()
|
||||
@@ -37,9 +38,9 @@ var _dbg_btn: Button # 两层调试入口钮(对局中禁用承
|
||||
|
||||
|
||||
func _init(db_ref, base_atk: int, base_hp: int) -> void:
|
||||
st._db = db_ref
|
||||
st._base_atk = base_atk
|
||||
st._base_hp = base_hp
|
||||
st.db = db_ref
|
||||
st.base_atk = base_atk
|
||||
st.base_hp = base_hp
|
||||
_build()
|
||||
_sync_labels()
|
||||
visible = false # 显式初始化——不靠树序巧合(缺陷⑤家族纪律)
|
||||
@@ -48,78 +49,78 @@ func _init(db_ref, base_atk: int, base_hp: int) -> void:
|
||||
# ---- 对外语义面(组合根/探针)----
|
||||
|
||||
func atk_pct() -> int:
|
||||
return int(st._atk.value)
|
||||
return int(st.atk.value)
|
||||
|
||||
|
||||
func hp_pct() -> int:
|
||||
return int(st._hp.value)
|
||||
return int(st.hp.value)
|
||||
|
||||
|
||||
func set_sliders(atk: int, hp: int) -> void:
|
||||
st._atk.value = atk
|
||||
st._hp.value = hp
|
||||
st.atk.value = atk
|
||||
st.hp.value = hp
|
||||
|
||||
|
||||
func reset_sliders() -> void:
|
||||
st._atk.value = 0
|
||||
st._hp.value = 0
|
||||
st.atk.value = 0
|
||||
st.hp.value = 0
|
||||
_sync_labels()
|
||||
|
||||
|
||||
func effect_text() -> String:
|
||||
return st._eff.text
|
||||
return st.eff.text
|
||||
|
||||
|
||||
func set_sim_count(n: int) -> void:
|
||||
st._sim_count.value = n
|
||||
st.sim_count.value = n
|
||||
|
||||
|
||||
func is_running() -> bool:
|
||||
return st._sim_running # 批量模拟进行中(与对局中禁用态分离,置灰 ≠ 在跑)
|
||||
return st.sim_running # 批量模拟进行中(与对局中禁用态分离,置灰 ≠ 在跑)
|
||||
|
||||
|
||||
func set_sim_running(running: bool) -> void:
|
||||
st._sim_running = running
|
||||
st.sim_running = running
|
||||
_sync_sim_button()
|
||||
|
||||
|
||||
# 对局中上下文锁定(组合根 _sync_debug_overlay 喂入):批量模拟与两层调试入口
|
||||
# 在活跃对局并存态不开放(长程异步模拟/并发对局编排与活跃结算并存态破坏状态机)
|
||||
func set_context_locked(locked: bool) -> void:
|
||||
st._ctx_locked = locked
|
||||
st.ctx_locked = locked
|
||||
_sync_sim_button()
|
||||
|
||||
|
||||
# 当局数据只读投影喂入(六项——组合根 _debug_run_data 构造;纯标签重查零动效)
|
||||
func update_run_data(data: Dictionary) -> void:
|
||||
st._run_data = data.duplicate()
|
||||
for key in st._run_labels:
|
||||
(st._run_labels[key] as Label).text = str(data.get(key, "—"))
|
||||
st.run_data = data.duplicate()
|
||||
for key in st.run_labels:
|
||||
(st.run_labels[key] as Label).text = str(data.get(key, "—"))
|
||||
|
||||
|
||||
# 当局数据快照读面(探针值对面——ui_tune_probe 断言六项与只读访问器逐项一致)
|
||||
func run_data_probe() -> Dictionary:
|
||||
return st._run_data.duplicate()
|
||||
return st.run_data.duplicate()
|
||||
|
||||
|
||||
# 两钮同判据(模拟运行 / 对局上下文锁定对「模拟」「两层调试」两入口等价)——
|
||||
# 判据单点:两 setter 都经此同步,禁在调用点各写一份
|
||||
func _sync_sim_button() -> void:
|
||||
_sim_btn.disabled = st._sim_running or st._ctx_locked
|
||||
_sim_btn.text = "模拟中…" if st._sim_running else "运行模拟"
|
||||
_dbg_btn.disabled = st._sim_running or st._ctx_locked
|
||||
_sim_btn.disabled = st.sim_running or st.ctx_locked
|
||||
_sim_btn.text = "模拟中…" if st.sim_running else "运行模拟"
|
||||
_dbg_btn.disabled = st.sim_running or st.ctx_locked
|
||||
|
||||
|
||||
func set_sim_progress(text: String) -> void:
|
||||
st._sim_out.text = text
|
||||
st.sim_out.text = text
|
||||
|
||||
|
||||
func set_sim_report(text: String) -> void:
|
||||
st._sim_out.text = text
|
||||
st.sim_out.text = text
|
||||
|
||||
|
||||
func output_text() -> String:
|
||||
return st._sim_out.get_parsed_text()
|
||||
return st.sim_out.get_parsed_text()
|
||||
|
||||
|
||||
# ---- 探针接缝(真实翻译出口——按钮 pressed 信号路径,非直调组合根)----
|
||||
@@ -147,11 +148,11 @@ func _reset_btn_pressed() -> void:
|
||||
|
||||
|
||||
func effective_atk() -> int:
|
||||
return UiKit.scaled(st._base_atk, atk_pct())
|
||||
return UiKit.scaled(st.base_atk, atk_pct())
|
||||
|
||||
|
||||
func effective_max_hp() -> int:
|
||||
return UiKit.scaled(st._base_hp, hp_pct())
|
||||
return UiKit.scaled(st.base_hp, hp_pct())
|
||||
|
||||
|
||||
# ---- 内部构建(标题/关闭固定于浮窗骨架,可扩展列表区居中滚动)----
|
||||
@@ -185,7 +186,7 @@ func _build_intro(box: VBoxContainer) -> void:
|
||||
var title := UiKit.label("调试",UiKit.FS_TITLE, true, UiKit.WARM)
|
||||
box.add_child(title)
|
||||
var desc := UiKit.label(
|
||||
"开发调试专用(UI 最顶层「调试」浮层角标随时可开——任何界面不遮挡)。当局数据=本局只读投影,种子即本局种子唯一查看位。\n百分比叠加基础值:攻击 %d / 生命 %d。影响所有模式(手动与模拟),不落盘。" % [st._base_atk, st._base_hp],
|
||||
"开发调试专用(UI 最顶层「调试」浮层角标随时可开——任何界面不遮挡)。当局数据=本局只读投影,种子即本局种子唯一查看位。\n百分比叠加基础值:攻击 %d / 生命 %d。影响所有模式(手动与模拟),不落盘。" % [st.base_atk, st.base_hp],
|
||||
13, false, UiKit.WARM_SOFT)
|
||||
box.add_child(desc)
|
||||
box.add_child(HSeparator.new())
|
||||
@@ -207,7 +208,7 @@ func _build_run_data(list: VBoxContainer) -> void:
|
||||
cap.custom_minimum_size = Vector2(52, 0)
|
||||
run_grid.add_child(cap)
|
||||
var val := UiKit.label("—",UiKit.FS_HINT, false, UiKit.WARM)
|
||||
st._run_labels[item[0]] = val
|
||||
st.run_labels[item[0]] = val
|
||||
run_grid.add_child(val)
|
||||
list.add_child(HSeparator.new())
|
||||
|
||||
@@ -241,13 +242,13 @@ func _build_seed_row(list: VBoxContainer) -> void:
|
||||
## 区块②角色数值调整板(攻击/生命两滑轨+生效值行+应用/重置钮)
|
||||
func _build_slider_block(list: VBoxContainer) -> void:
|
||||
var atk_row := _slider_row(list, "攻击加成")
|
||||
st._atk = atk_row.slider
|
||||
st._atk_val = atk_row.value_label
|
||||
st.atk = atk_row.slider
|
||||
st.atk_val = atk_row.value_label
|
||||
var hp_row := _slider_row(list, "生命加成")
|
||||
st._hp = hp_row.slider
|
||||
st._hp_val = hp_row.value_label
|
||||
st._eff = UiKit.label("",UiKit.FS_HINT, false)
|
||||
list.add_child(st._eff)
|
||||
st.hp = hp_row.slider
|
||||
st.hp_val = hp_row.value_label
|
||||
st.eff = UiKit.label("",UiKit.FS_HINT, false)
|
||||
list.add_child(st.eff)
|
||||
var btn_row := HBoxContainer.new()
|
||||
list.add_child(btn_row)
|
||||
var apply_btn := UiKit.button("应用并关闭")
|
||||
@@ -264,21 +265,21 @@ func _build_sim_block(list: VBoxContainer) -> void:
|
||||
var sim_row := HBoxContainer.new()
|
||||
list.add_child(sim_row)
|
||||
sim_row.add_child(UiKit.label("批量模拟第一关",UiKit.FS_BODY, false))
|
||||
st._sim_count = SpinBox.new()
|
||||
st._sim_count.min_value = 10
|
||||
st._sim_count.max_value = 200
|
||||
st._sim_count.step = 10
|
||||
st._sim_count.value = 30
|
||||
st._sim_count.custom_minimum_size = Vector2(90, 0)
|
||||
sim_row.add_child(st._sim_count)
|
||||
st.sim_count = SpinBox.new()
|
||||
st.sim_count.min_value = 10
|
||||
st.sim_count.max_value = 200
|
||||
st.sim_count.step = 10
|
||||
st.sim_count.value = 30
|
||||
st.sim_count.custom_minimum_size = Vector2(90, 0)
|
||||
sim_row.add_child(st.sim_count)
|
||||
sim_row.add_child(UiKit.label("次(理性玩家策略,含面板当前加成)",UiKit.FS_HINT, false))
|
||||
_sim_btn = UiKit.button("运行模拟")
|
||||
_sim_btn.pressed.connect(_emit_simulate_requested)
|
||||
sim_row.add_child(_sim_btn)
|
||||
st._sim_out = RichTextLabel.new()
|
||||
st._sim_out.bbcode_enabled = true
|
||||
st._sim_out.custom_minimum_size = Vector2(0, 300)
|
||||
list.add_child(st._sim_out)
|
||||
st.sim_out = RichTextLabel.new()
|
||||
st.sim_out.bbcode_enabled = true
|
||||
st.sim_out.custom_minimum_size = Vector2(0, 300)
|
||||
list.add_child(st.sim_out)
|
||||
list.add_child(HSeparator.new())
|
||||
|
||||
|
||||
@@ -316,14 +317,14 @@ func _slider_row(box: VBoxContainer, title: String) -> Dictionary:
|
||||
|
||||
|
||||
func _sync_labels() -> void:
|
||||
st._atk_val.text = "+%d%%" % atk_pct()
|
||||
st._hp_val.text = "+%d%%" % hp_pct()
|
||||
st._eff.text = "生效值:攻击 %d → %d · 生命 %d → %d" % [
|
||||
st._base_atk, effective_atk(), st._base_hp, effective_max_hp()]
|
||||
st.atk_val.text = "+%d%%" % atk_pct()
|
||||
st.hp_val.text = "+%d%%" % hp_pct()
|
||||
st.eff.text = "生效值:攻击 %d → %d · 生命 %d → %d" % [
|
||||
st.base_atk, effective_atk(), st.base_hp, effective_max_hp()]
|
||||
|
||||
|
||||
func _emit_simulate_requested() -> void:
|
||||
simulate_requested.emit(int(st._sim_count.value))
|
||||
simulate_requested.emit(int(st.sim_count.value))
|
||||
|
||||
|
||||
func _emit_diagnose_requested() -> void:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
## 关卡画面·独立屏:全屏不透明独占呈现(激活期战斗屏宿主由组合根整体隐藏),节点图选路呈现。
|
||||
## 前进唯一通道=图上点候选节点(route_chosen 外发)——零自动推进;放弃入口可见性归组合根(仅冒险模式开)。
|
||||
## 内部数据类 _MapState=宿主画面登记表(只被本文件填读,文件外零消费者——字段为公开数据成员)。
|
||||
class_name LevelMapScreen
|
||||
extends Control
|
||||
|
||||
@@ -32,23 +33,23 @@ const CONFIRM_WINDOW_S := 3.0 # 放弃确认态窗口(
|
||||
const COL_HP_BAR_BACK := UiKit.HP_BAR_BACK # 迷你血条深底(对暗面板可辨;值单点收 UiKit)
|
||||
|
||||
## 内部状态件(u2 子域拆件——契约 9 职责轴):_MapState=选路屏运行态与图绘制态(单一变化原因=
|
||||
## 选路呈现)。_layer_decor 域字典承载换层装饰三件(图标贴图/石纹面/tint 层号——装饰域归并形态)。
|
||||
## 选路呈现)。layer_decor 域字典承载换层装饰三件(图标贴图/石纹面/tint 层号——装饰域归并形态)。
|
||||
## 探针直读冻结成员(_cand_ids/_hover_id/_content_size/_layout_cache/abandon_btn/status_badge)住宿主。
|
||||
class _MapState extends RefCounted:
|
||||
var _runner = null
|
||||
var _title: Label
|
||||
var _canvas: Control
|
||||
var _level_label: Label
|
||||
var _gp_value: Label
|
||||
var _hp_value: Label
|
||||
var _hp_bar: Control
|
||||
var _attr_value: Label
|
||||
var _bonus_row: HBoxContainer
|
||||
var _bonus_value: Label
|
||||
var _feat_value: Label # 特性持有行(组合根投喂逐件文案)
|
||||
var _abandon_timer := ArmedTimer.new() # 放弃确认窗(_process 帧驱动倒计时机制收口;窗口=CONFIRM_WINDOW_S)
|
||||
var _drawn_disp: Dictionary[int, Dictionary] = {} # 本停点实际绘制记录(_draw_graph 逐帧在实际绘制点重建——display_probe 消费面)
|
||||
var _layer_decor := {"node_icons": {}, "noise_rect": null, "tint_level": 0} # 换层装饰域(节点图标贴图 load_tex 守卫缺位 null→字形回退/屏底石纹面/当前已应用 tint 层号 0=未应用)
|
||||
var runner = null
|
||||
var title: Label
|
||||
var canvas: Control
|
||||
var level_label: Label
|
||||
var gp_value: Label
|
||||
var hp_value: Label
|
||||
var hp_bar: Control
|
||||
var attr_value: Label
|
||||
var bonus_row: HBoxContainer
|
||||
var bonus_value: Label
|
||||
var feat_value: Label # 特性持有行(组合根投喂逐件文案)
|
||||
var abandon_timer := ArmedTimer.new() # 放弃确认窗(_process 帧驱动倒计时机制收口;窗口=CONFIRM_WINDOW_S)
|
||||
var drawn_disp: Dictionary[int, Dictionary] = {} # 本停点实际绘制记录(_draw_graph 逐帧在实际绘制点重建——display_probe 消费面)
|
||||
var layer_decor := {"node_icons": {}, "noise_rect": null, "tint_level": 0} # 换层装饰域(节点图标贴图 load_tex 守卫缺位 null→字形回退/屏底石纹面/当前已应用 tint 层号 0=未应用)
|
||||
|
||||
var _cand_ids: Array[int] = [] # 本停点候选 id 集(图上命中合法集;探针直读冻结——住宿主)
|
||||
var _content_size := Vector2(620, 240) # 图内容包络(open_choices 按图拟合——画布 min 尺寸随它;探针直读冻结)
|
||||
@@ -60,18 +61,18 @@ var _map := _MapState.new()
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
_map._abandon_timer.expired.connect(_reset_abandon_confirm)
|
||||
_map.abandon_timer.expired.connect(_reset_abandon_confirm)
|
||||
# 节点图标贴图装载(load_tex 守卫,缺位回退现行字形)
|
||||
for t in TEX_NODE_ICONS:
|
||||
_map._layer_decor.node_icons[t] = UiKit.load_tex(TEX_NODE_ICONS[t])
|
||||
_map.layer_decor.node_icons[t] = UiKit.load_tex(TEX_NODE_ICONS[t])
|
||||
# 独立屏底(spec Layout——demo #v-map 暖棕竖向
|
||||
# 渐变(#4A3B2A 0%→#3A2E22 40%)+石纹弱质感 0.14+暗角聚焦;底纹缺位回退纯色)
|
||||
var base := UiKit._gradient_base(UiKit.BG_MAP_TOP, UiKit.BG_ROOT)
|
||||
var base := UiKit.gradient_base(UiKit.BG_MAP_TOP, UiKit.BG_ROOT)
|
||||
add_child(base)
|
||||
var tex := UiKit.backdrop_tile()
|
||||
if tex != null:
|
||||
base.add_child(tex)
|
||||
_map._layer_decor.noise_rect = tex # 世界侧 tint 承载面(换层色板渐变挂点——§2.7 切片档)
|
||||
_map.layer_decor.noise_rect = tex # 世界侧 tint 承载面(换层色板渐变挂点——§2.7 切片档)
|
||||
base.add_child(UiKit.vignette_rect())
|
||||
var center := CenterContainer.new()
|
||||
center.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
@@ -97,8 +98,8 @@ func _build_top_bar(box: VBoxContainer) -> void:
|
||||
lvl_plate.add_theme_stylebox_override("panel",
|
||||
UiKit.panel_flat(UiKit.DARK_UPCOMING, UiKit.PANEL_BORDER, 0, 1, Vector4(12, 3, 12, 3)))
|
||||
top.add_child(lvl_plate)
|
||||
_map._level_label = UiKit.label("",UiKit.FS_BODY, true, UiKit.WARM)
|
||||
lvl_plate.add_child(_map._level_label)
|
||||
_map.level_label = UiKit.label("",UiKit.FS_BODY, true, UiKit.WARM)
|
||||
lvl_plate.add_child(_map.level_label)
|
||||
var top_sp := Control.new()
|
||||
top_sp.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
top.add_child(top_sp)
|
||||
@@ -109,31 +110,31 @@ func _build_top_bar(box: VBoxContainer) -> void:
|
||||
var gp_row := HBoxContainer.new()
|
||||
gp_row.add_theme_constant_override("separation", 6)
|
||||
gp_box.add_child(gp_row)
|
||||
_map._gp_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_ON_GOLD)
|
||||
gp_row.add_child(_map._gp_value)
|
||||
_map.gp_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_ON_GOLD)
|
||||
gp_row.add_child(_map.gp_value)
|
||||
status_badge = UiKit.button("状态", 0.0)
|
||||
status_badge.add_theme_font_size_override("font_size", UiKit.FS_HINT)
|
||||
status_badge.tooltip_text = "查看本局冒险进度(只读)"
|
||||
status_badge.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
top.add_child(status_badge)
|
||||
_map._title = UiKit.label(TITLE_FORK,UiKit.FS_SUB, true, UiKit.WARM)
|
||||
_map._title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
box.add_child(_map._title)
|
||||
_map.title = UiKit.label(TITLE_FORK,UiKit.FS_SUB, true, UiKit.WARM)
|
||||
_map.title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
box.add_child(_map.title)
|
||||
|
||||
|
||||
## 节点图画布域(图上点击 = 唯一提交通道——布局决策 2;mouse_filter STOP 接悬停/点击)
|
||||
func _build_canvas(box: VBoxContainer) -> void:
|
||||
_map._canvas = Control.new()
|
||||
_map._canvas.custom_minimum_size = Vector2(620, 240)
|
||||
_map._canvas.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
_map._canvas.draw.connect(_draw_graph.bind(_map._canvas))
|
||||
_map._canvas.gui_input.connect(_on_canvas_input)
|
||||
box.add_child(_map._canvas)
|
||||
_map.canvas = Control.new()
|
||||
_map.canvas.custom_minimum_size = Vector2(620, 240)
|
||||
_map.canvas.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
_map.canvas.draw.connect(_draw_graph.bind(_map.canvas))
|
||||
_map.canvas.gui_input.connect(_on_canvas_input)
|
||||
box.add_child(_map.canvas)
|
||||
|
||||
|
||||
## 底部状态栏域(合并单条:亮米面板一站读齐=头像+生命+属性+本关加成/流派件——demo ④;
|
||||
## 道具槽不摆=道具读数无关卡画面成文出口(读快照私域越权);
|
||||
## _hp_value/_attr_value/_bonus_value/_feat_value 投喂键全部保留。
|
||||
## hp_value/attr_value/bonus_value/feat_value 投喂键全部保留。
|
||||
## 底栏定高纪律(对齐 demo——单条 ~85px 紧凑亮条;任何子件不再能把栏撑爆:
|
||||
## 特性持有行限宽截断+tooltip 全文))
|
||||
func _build_status_bar(box: VBoxContainer) -> void:
|
||||
@@ -150,18 +151,18 @@ func _build_status_bar(box: VBoxContainer) -> void:
|
||||
var life_box := VBoxContainer.new()
|
||||
strip.add_child(life_box)
|
||||
life_box.add_child(UiKit.label("生命",UiKit.FS_CAPTION, false, UiKit.INK_HINT))
|
||||
_map._hp_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_TITLE)
|
||||
life_box.add_child(_map._hp_value)
|
||||
_map._hp_bar = Control.new()
|
||||
_map._hp_bar.custom_minimum_size = Vector2(130, 7)
|
||||
_map._hp_bar.draw.connect(_draw_hp_bar.bind(_map._hp_bar))
|
||||
life_box.add_child(_map._hp_bar)
|
||||
_map.hp_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_TITLE)
|
||||
life_box.add_child(_map.hp_value)
|
||||
_map.hp_bar = Control.new()
|
||||
_map.hp_bar.custom_minimum_size = Vector2(130, 7)
|
||||
_map.hp_bar.draw.connect(_draw_hp_bar.bind(_map.hp_bar))
|
||||
life_box.add_child(_map.hp_bar)
|
||||
strip.add_child(_vdiv())
|
||||
var attr_box := VBoxContainer.new()
|
||||
strip.add_child(attr_box)
|
||||
attr_box.add_child(UiKit.label("属性",UiKit.FS_CAPTION, false, UiKit.INK_HINT))
|
||||
_map._attr_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_TITLE)
|
||||
attr_box.add_child(_map._attr_value)
|
||||
_map.attr_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_TITLE)
|
||||
attr_box.add_child(_map.attr_value)
|
||||
strip.add_child(_vdiv())
|
||||
_build_bonus_group(strip)
|
||||
var spacer := Control.new()
|
||||
@@ -193,21 +194,21 @@ func _build_face_rect() -> TextureRect:
|
||||
|
||||
## 本关加成框域(零加成隐藏——spec 布局决策 4)+ 流派件持有行(组合根投喂;限宽单行截断+tooltip 全文)
|
||||
func _build_bonus_group(strip: HBoxContainer) -> void:
|
||||
_map._bonus_row = HBoxContainer.new()
|
||||
strip.add_child(_map._bonus_row)
|
||||
_map.bonus_row = HBoxContainer.new()
|
||||
strip.add_child(_map.bonus_row)
|
||||
var bonus_box := VBoxContainer.new()
|
||||
_map._bonus_row.add_child(bonus_box)
|
||||
_map.bonus_row.add_child(bonus_box)
|
||||
var bonus_inner := VBoxContainer.new()
|
||||
bonus_box.add_child(bonus_inner)
|
||||
var bonus_cap := UiKit.label("属性值提升",UiKit.FS_CAPTION, false, UiKit.INK_HINT) # 深墨(亮板可读)
|
||||
bonus_inner.add_child(bonus_cap)
|
||||
_map._bonus_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_TITLE) # 淡墨→深墨(同上)
|
||||
bonus_inner.add_child(_map._bonus_value)
|
||||
_map._feat_value = UiKit.label("",UiKit.FS_HINT, false, UiKit.INK_HINT)
|
||||
_map._feat_value.custom_minimum_size = Vector2(240, 0)
|
||||
_map._feat_value.clip_text = true
|
||||
_map._feat_value.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
bonus_box.add_child(_map._feat_value)
|
||||
_map.bonus_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_TITLE) # 淡墨→深墨(同上)
|
||||
bonus_inner.add_child(_map.bonus_value)
|
||||
_map.feat_value = UiKit.label("",UiKit.FS_HINT, false, UiKit.INK_HINT)
|
||||
_map.feat_value.custom_minimum_size = Vector2(240, 0)
|
||||
_map.feat_value.clip_text = true
|
||||
_map.feat_value.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
bonus_box.add_child(_map.feat_value)
|
||||
|
||||
|
||||
# 竖分隔线(底栏分组线——亮面板上低对比细线)
|
||||
@@ -221,10 +222,10 @@ static func _vdiv() -> ColorRect:
|
||||
func _draw_hp_bar(bar: Control) -> void:
|
||||
# 迷你血条(深底 UiKit.HP_BAR_BACK #28221C + 小人蓝填充——与战斗站桩血条同语言,
|
||||
# 「蓝只说己方」§4.2)。比例 = hero_hp / effective_max_hp
|
||||
if _map._runner == null:
|
||||
if _map.runner == null:
|
||||
return
|
||||
var max_hp := maxi(1, _map._runner.effective_max_hp())
|
||||
var ratio := clampf(float(_map._runner.hero_hp) / float(max_hp), 0.0, 1.0)
|
||||
var max_hp := maxi(1, _map.runner.effective_max_hp())
|
||||
var ratio := clampf(float(_map.runner.hero_hp) / float(max_hp), 0.0, 1.0)
|
||||
var full := Rect2(Vector2.ZERO, bar.size)
|
||||
bar.draw_rect(full, COL_HP_BAR_BACK)
|
||||
if ratio > 0.0:
|
||||
@@ -234,8 +235,8 @@ func _draw_hp_bar(bar: Control) -> void:
|
||||
# 放弃入口可见性(组合根在冒险启动时开、默认关——语义对象 = 整局冒险)
|
||||
# 特性持有投影(组合根投喂逐件文案;空文案=行隐藏)
|
||||
func set_feat_holdings(text: String) -> void:
|
||||
_map._feat_value.text = text
|
||||
_map._feat_value.tooltip_text = text # 截断全量兜底(限宽单行省略——悬停看全文)
|
||||
_map.feat_value.text = text
|
||||
_map.feat_value.tooltip_text = text # 截断全量兜底(限宽单行省略——悬停看全文)
|
||||
|
||||
|
||||
func set_abandon_available(v: bool) -> void:
|
||||
@@ -245,8 +246,8 @@ func set_abandon_available(v: bool) -> void:
|
||||
# 放弃二次确认(布局决策 3):首击转确认态(文案+危险红脉冲约 3 秒超时回落),二击才提交。
|
||||
# 单击不提交——ui_probe 战斗中 pressed.emit() 单击路径保持「未受理」语义(战斗中拒绝归组合根)。
|
||||
func _on_abandon_pressed() -> void:
|
||||
if not _map._abandon_timer.is_armed():
|
||||
_map._abandon_timer.arm(CONFIRM_WINDOW_S)
|
||||
if not _map.abandon_timer.is_armed():
|
||||
_map.abandon_timer.arm(CONFIRM_WINDOW_S)
|
||||
abandon_btn.text = "再点一次确认放弃——不可逆"
|
||||
if ArmedTimer.motion_still():
|
||||
abandon_btn.modulate = Color(1, 1, 1, 0.75) # 减动效静态基色(脉冲层不再驱动)
|
||||
@@ -256,45 +257,45 @@ func _on_abandon_pressed() -> void:
|
||||
|
||||
|
||||
func _reset_abandon_confirm() -> void:
|
||||
_map._abandon_timer.disarm()
|
||||
_map.abandon_timer.disarm()
|
||||
abandon_btn.modulate = Color(1, 1, 1, 1)
|
||||
abandon_btn.text = "放弃" # 文案收敛 level-map 4 字上限(确认态转义面见 _on_abandon_pressed)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not _map._abandon_timer.is_armed():
|
||||
if not _map.abandon_timer.is_armed():
|
||||
return
|
||||
_map._abandon_timer.tick(delta)
|
||||
if not _map._abandon_timer.is_armed():
|
||||
_map.abandon_timer.tick(delta)
|
||||
if not _map.abandon_timer.is_armed():
|
||||
return # 窗口到点回落(expired 信号已驱动 _reset_abandon_confirm)
|
||||
# 危险红边框脉冲(单段呼吸约 1Hz——闪烁基线 ≤3 次/秒内;减动效 = 静态半透不闪——档判据单点 motion_still)
|
||||
if ArmedTimer.motion_still():
|
||||
abandon_btn.modulate = Color(1, 1, 1, 0.75)
|
||||
else:
|
||||
var k := 0.5 + 0.5 * sin(_map._abandon_timer.elapsed * TAU)
|
||||
var k := 0.5 + 0.5 * sin(_map.abandon_timer.elapsed * TAU)
|
||||
abandon_btn.modulate = Color(1, 1, 1, 0.65 + 0.35 * k)
|
||||
|
||||
|
||||
# 打开选路口(cands = 候选节点引用数组;runner 只读用于整图渲染与状态栏刷新)
|
||||
func open_choices(cands: Array, runner: LevelRunner) -> void:
|
||||
var was_visible := visible # 进入转场判据(就地刷新/模态栈恢复不重放淡入)
|
||||
_map._runner = runner
|
||||
_map.runner = runner
|
||||
_hover_id = -1
|
||||
_reset_abandon_confirm()
|
||||
var visited: Array[int] = []
|
||||
if runner != null:
|
||||
visited = runner.visited_ids()
|
||||
if cands.size() > 1:
|
||||
_map._title.text = TITLE_FORK
|
||||
_map.title.text = TITLE_FORK
|
||||
elif visited.is_empty():
|
||||
_map._title.text = TITLE_ENTRY # 入口停点(尚未走出第一步)
|
||||
_map.title.text = TITLE_ENTRY # 入口停点(尚未走出第一步)
|
||||
else:
|
||||
_map._title.text = TITLE_SINGLE
|
||||
_map.title.text = TITLE_SINGLE
|
||||
_cand_ids = []
|
||||
for cand in cands:
|
||||
_cand_ids.append(_id_of(cand))
|
||||
_fit_canvas(_map._runner.nodes())
|
||||
_apply_level_tint(int(_map._runner.level_row.get("level_index", 0)))
|
||||
_fit_canvas(_map.runner.nodes())
|
||||
_apply_level_tint(int(_map.runner.level_row.get("level_index", 0)))
|
||||
visible = true
|
||||
if not was_visible:
|
||||
# 独立屏淡入(spec Transitions:约 200ms;alpha 淡入非明度变化——art-bible §2 明度恒定)
|
||||
@@ -321,7 +322,7 @@ func _fit_canvas(nodes: Array) -> void:
|
||||
_content_size = Vector2(LAYER_BASE_X + maxl * LAYER_STEP_X + NODE_HALF.x + 8.0,
|
||||
SLOT_BASE_Y + maxslot * SLOT_STEP_Y + NODE_HALF.y + 24.0) # +24 = 类型文字标签行
|
||||
_content_size = _content_size.max(Vector2(620, 256)) # 下限(抬高容纳标签行)
|
||||
_map._canvas.custom_minimum_size = _content_size
|
||||
_map.canvas.custom_minimum_size = _content_size
|
||||
|
||||
|
||||
# 换层色板渐变挂点(art-bible §2.7 下探过渡切片档——关卡结算确认→下一关→独立屏淡入链路上的
|
||||
@@ -332,39 +333,39 @@ const BOARD_TINTS := {1: Color(1, 1, 1), 2: Color(0.84, 0.99, 1.28), 3: Color(0.
|
||||
|
||||
|
||||
func _apply_level_tint(level_index: int) -> void:
|
||||
if _map._layer_decor.noise_rect == null or level_index == _map._layer_decor.tint_level:
|
||||
if _map.layer_decor.noise_rect == null or level_index == _map.layer_decor.tint_level:
|
||||
return
|
||||
_map._layer_decor.tint_level = level_index
|
||||
_map.layer_decor.tint_level = level_index
|
||||
var target: Color = BOARD_TINTS.get(level_index, Color(1, 1, 1))
|
||||
var tw := create_tween()
|
||||
tw.tween_property(_map._layer_decor.noise_rect, "modulate",
|
||||
tw.tween_property(_map.layer_decor.noise_rect, "modulate",
|
||||
Color(target.r, target.g, target.b, UiKit.BACKDROP_TILE_ALPHA), UiKit.motion(0.45)) # 0.14 弱质感档随 tint 保持
|
||||
|
||||
|
||||
# 状态栏投喂(命令式纪律——组合根经 level_map_status_payload 取成品后喂入;零推送信号订阅、零核心态拼装)
|
||||
func apply_status(payload: Dictionary) -> void:
|
||||
_map._level_label.text = String(payload["level_label"])
|
||||
_map._gp_value.text = String(payload["gp_text"])
|
||||
_map._hp_value.text = String(payload["hp_text"])
|
||||
_map.level_label.text = String(payload["level_label"])
|
||||
_map.gp_value.text = String(payload["gp_text"])
|
||||
_map.hp_value.text = String(payload["hp_text"])
|
||||
if bool(payload["hp_bar_redraw"]):
|
||||
_map._hp_bar.queue_redraw()
|
||||
_map._attr_value.text = String(payload["attr_text"])
|
||||
_map._bonus_value.text = String(payload["bonus_text"])
|
||||
_map.hp_bar.queue_redraw()
|
||||
_map.attr_value.text = String(payload["attr_text"])
|
||||
_map.bonus_value.text = String(payload["bonus_text"])
|
||||
# 特性持有行可见性(与加成行并框——任一非空整框显示;文案归组合根 set_feat_holdings)
|
||||
_map._bonus_row.visible = bool(payload["bonus_row_visible"]) or _map._feat_value.text != ""
|
||||
_map.bonus_row.visible = bool(payload["bonus_row_visible"]) or _map.feat_value.text != ""
|
||||
|
||||
|
||||
# ---- 图上命中(布局决策 2——唯一提交通道;探针 press_candidate 同路径) ----
|
||||
|
||||
func _on_canvas_input(ev: InputEvent) -> void:
|
||||
if _map._runner == null or not visible:
|
||||
if _map.runner == null or not visible:
|
||||
return
|
||||
if ev is InputEventMouseMotion:
|
||||
var nid := _candidate_at(ev.position) # gui_input 事件坐标 = 接收控件局部坐标(push_input 同成立)
|
||||
if nid != _hover_id:
|
||||
_hover_id = nid
|
||||
_map._canvas.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND if nid >= 0 else Control.CURSOR_ARROW
|
||||
_map._canvas.queue_redraw()
|
||||
_map.canvas.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND if nid >= 0 else Control.CURSOR_ARROW
|
||||
_map.canvas.queue_redraw()
|
||||
elif ev is InputEventMouseButton and ev.button_index == MOUSE_BUTTON_LEFT and ev.pressed:
|
||||
var nid2 := _candidate_at(ev.position)
|
||||
if nid2 >= 0:
|
||||
@@ -373,10 +374,10 @@ func _on_canvas_input(ev: InputEvent) -> void:
|
||||
|
||||
# 候选命中:位置 = 布局公式(与 _draw_graph 同源),矩形 = 节点方块 + 外扩容差;非候选格不响应
|
||||
func _candidate_at(local_pos: Vector2) -> int:
|
||||
if _map._runner == null:
|
||||
if _map.runner == null:
|
||||
return -1
|
||||
var nodes: Array[Variant] = _map._runner.nodes()
|
||||
var pos := _layout(nodes, _map._canvas)
|
||||
var nodes: Array[Variant] = _map.runner.nodes()
|
||||
var pos := _layout(nodes, _map.canvas)
|
||||
for nid in _cand_ids:
|
||||
var node := _node_by_id(nid, nodes)
|
||||
if node.is_empty():
|
||||
@@ -391,7 +392,7 @@ func _candidate_at(local_pos: Vector2) -> int:
|
||||
func _submit_choice(nid: int) -> void:
|
||||
# 独立屏分支:战斗/Boss 节点 = 出屏切回战斗屏;奖励/恢复/商店节点 =
|
||||
# 本屏保持激活——兑付覆盖层由节点事件在本屏域内弹出(模态栈遮挡恢复语义复用),兑付确认后回图刷新
|
||||
var node := _node_by_id(nid, _map._runner.nodes())
|
||||
var node := _node_by_id(nid, _map.runner.nodes())
|
||||
var t := String(node.get("type", ""))
|
||||
if t != "reward" and t != "rest" and t != "shop":
|
||||
visible = false
|
||||
@@ -400,13 +401,13 @@ func _submit_choice(nid: int) -> void:
|
||||
|
||||
# 探针复用:按候选序号走图上命中同路径(候选节点中心坐标 → 命中检测 → 提交——布局决策 2 探针面改造)
|
||||
func press_candidate(index: int) -> void:
|
||||
if index < 0 or index >= _cand_ids.size() or _map._runner == null:
|
||||
if index < 0 or index >= _cand_ids.size() or _map.runner == null:
|
||||
return
|
||||
var nodes: Array[Variant] = _map._runner.nodes()
|
||||
var nodes: Array[Variant] = _map.runner.nodes()
|
||||
var node := _node_by_id(int(_cand_ids[index]), nodes)
|
||||
if node.is_empty():
|
||||
return
|
||||
var pos: Vector2 = _layout(nodes, _map._canvas)[node]
|
||||
var pos: Vector2 = _layout(nodes, _map.canvas)[node]
|
||||
var nid := _candidate_at(pos) # 节点中心必命中自身(命中面含节点方块)
|
||||
if nid >= 0:
|
||||
_submit_choice(nid)
|
||||
@@ -414,7 +415,7 @@ func press_candidate(index: int) -> void:
|
||||
|
||||
func _id_of(node: Dictionary) -> int:
|
||||
# 编号显示经行进器读面(编号空间解析单一出口——单链 = 下标 / 图 = node_depth)
|
||||
return _map._runner.node_id_of(node)
|
||||
return _map.runner.node_id_of(node)
|
||||
|
||||
|
||||
# ---- 节点呈现决策面(信息迷雾 v2.1:类型仅在可见集内出示,Boss 标记不入雾)----
|
||||
@@ -440,9 +441,9 @@ static func node_display(node: Dictionary, nid: int, type_vis: Array,
|
||||
# ---- 整图自绘(程序化占位——分层布局:层 = 入口 BFS 深度,同层垂直排布)----
|
||||
|
||||
func _draw_graph(canvas: Control) -> void:
|
||||
if _map._runner == null or not visible:
|
||||
if _map.runner == null or not visible:
|
||||
return
|
||||
_map._drawn_disp.clear() # 绘制记录随本帧重建——只反映实际发生的绘制(接线坏的判据面)
|
||||
_map.drawn_disp.clear() # 绘制记录随本帧重建——只反映实际发生的绘制(接线坏的判据面)
|
||||
var ctx := _draw_context(canvas)
|
||||
_draw_passage_layer(canvas, ctx)
|
||||
for node in ctx["nodes"]:
|
||||
@@ -451,16 +452,16 @@ func _draw_graph(canvas: Control) -> void:
|
||||
|
||||
## 绘制上下文束(单次取数——布局/字体/编号空间/可见集,绘制域共享只读面)
|
||||
func _draw_context(canvas: Control) -> Dictionary:
|
||||
var nodes: Array[Variant] = _map._runner.nodes()
|
||||
var nodes: Array[Variant] = _map.runner.nodes()
|
||||
return {
|
||||
"nodes": nodes,
|
||||
"pos": _layout(nodes, canvas),
|
||||
"font": get_theme_default_font(), # 绘制路径字体单次取用(三处 draw_string 共享——零语义差)
|
||||
"current_id": _map._runner.current_node_id(),
|
||||
"current_id": _map.runner.current_node_id(),
|
||||
# 当前节点 id(单链=下标 / 图=编号)——visited_ids() 已按编号空间解析
|
||||
"visited": _map._runner.visited_ids(),
|
||||
"cand_ids": _map._runner.next_candidate_ids(),
|
||||
"type_vis": _map._runner.type_visible_ids(), # 本停点类型可见集(每帧一取——纯函数零状态)
|
||||
"visited": _map.runner.visited_ids(),
|
||||
"cand_ids": _map.runner.next_candidate_ids(),
|
||||
"type_vis": _map.runner.type_visible_ids(), # 本停点类型可见集(每帧一取——纯函数零状态)
|
||||
}
|
||||
|
||||
|
||||
@@ -525,7 +526,7 @@ func _draw_room(canvas: Control, node: Dictionary, ctx: Dictionary) -> void:
|
||||
var label_rec := _draw_room_label(canvas, rect, disp, nid, ctx)
|
||||
rec["draw_label"] = bool(label_rec.get("draw_label", false))
|
||||
rec["label_text"] = String(label_rec.get("label_text", ""))
|
||||
_map._drawn_disp[nid] = rec
|
||||
_map.drawn_disp[nid] = rec
|
||||
|
||||
|
||||
## 节点图标域(20px 烘焙 1:1 消费;双色墨制=乘法调制——当前金底深墨,暗底系 WARM 原色;
|
||||
@@ -536,7 +537,7 @@ func _draw_room_icon(canvas: Control, node: Dictionary, disp: Dictionary, ctx: D
|
||||
var p: Vector2 = ctx["pos"][node]
|
||||
var col := _room_color(node, ctx)
|
||||
var t := String(node.get("type", ""))
|
||||
var icon: Texture2D = _map._layer_decor.node_icons.get(t)
|
||||
var icon: Texture2D = _map.layer_decor.node_icons.get(t)
|
||||
if icon != null:
|
||||
var tint := UiKit.ICON_TINT_ON_GOLD if col == COL_ROOM_CURRENT else Color(1.0, 1.0, 1.0)
|
||||
canvas.draw_texture_rect(icon, Rect2(p - Vector2(NODE_ICON_PX, NODE_ICON_PX) / 2.0,
|
||||
@@ -580,20 +581,20 @@ func _layout(nodes: Array, canvas: Control) -> Dictionary:
|
||||
|
||||
# ---- 探针只读出口(形态仿 fx_probe 先例:只读快照) ----
|
||||
|
||||
# 选路布局只读快照(node → 局部位置;画布固定取现行 _map._canvas,调用侧零私有域触达)
|
||||
# 选路布局只读快照(node → 局部位置;画布固定取现行 _map.canvas,调用侧零私有域触达)
|
||||
func layout_probe(nodes: Array) -> Dictionary:
|
||||
return _layout(nodes, _map._canvas)
|
||||
return _layout(nodes, _map.canvas)
|
||||
|
||||
|
||||
# 路由画布只读快照(origin=全局原点;min_size=最小拟合尺寸——画布拟合断言消费面)
|
||||
func canvas_probe() -> Dictionary:
|
||||
return {"origin": _map._canvas.get_global_position(), "min_size": _map._canvas.custom_minimum_size}
|
||||
return {"origin": _map.canvas.get_global_position(), "min_size": _map.canvas.custom_minimum_size}
|
||||
|
||||
|
||||
# 类型可见性绘制只读记录(id → 本帧实际绘制事实:图标/文字/描边/文案;_draw_graph 在实际绘制点写入——
|
||||
# 接线层断言消费点:绕开决策面直接画,这里就露出「雾下也画了」)
|
||||
func display_probe() -> Dictionary:
|
||||
return _map._drawn_disp
|
||||
return _map.drawn_disp
|
||||
|
||||
|
||||
# 分层(layout/fit 共享件):layer = 入口 BFS 深度;同层 slot 垂直排布;未达节点兜底排末层
|
||||
@@ -637,7 +638,7 @@ func _entry(nodes: Array) -> Dictionary:
|
||||
|
||||
|
||||
func _out_ids(node: Dictionary, _nodes: Array) -> Array:
|
||||
return _map._runner.node_out_ids(node)
|
||||
return _map.runner.node_out_ids(node)
|
||||
|
||||
|
||||
func _node_by_id(nid: int, nodes: Array) -> Dictionary:
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
// 封印印记(卡④ 法阵化批)——环+十字+环外压暗光圈,canvas_item 距离场实时绘制(无贴图缩放,
|
||||
// 格边 26–46px 连续可变下任意格边清晰)。
|
||||
// 事实源:art-bible v3.41 §3.2「封印印记」(形/画法/光圈/层序纪律)+ §8.2「封印印记参数表」;
|
||||
// gdd/ux/battle.md「棋盘格尺寸公式」(格边 clampi 26–46)。
|
||||
// 纪律:参数五枚+色全由 gd 侧逐次注入(参数单点 = ui_kit SEAL_* 五常量——禁在此改值,
|
||||
// 下方默认值仅为兜底且与 §8.2 同值);光圈不出格(半径夹 cell_px/2);
|
||||
// 零动效(不呼吸不脉冲——§2.1 挖掘期唯一在动 = 接触带微光);印记与光圈同用一色(光圈只改 alpha)。
|
||||
// 坐标口径:VERTEX 为格件本件局部像素坐标(0..cell_px),不依赖 UV(同 cell_type_glow 口径)。
|
||||
shader_type canvas_item;
|
||||
render_mode blend_mix;
|
||||
|
||||
uniform float cell_px : hint_range(8.0, 128.0) = 46.0;
|
||||
uniform float ring_frac = 0.82;
|
||||
uniform float wall_frac = 0.11;
|
||||
uniform float arm_frac = 0.156;
|
||||
uniform float halo_frac = 0.09;
|
||||
uniform float halo_alpha : hint_range(0.0, 1.0) = 0.50;
|
||||
uniform float halo_gamma = 1.6;
|
||||
uniform vec4 mark_col : source_color = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
|
||||
varying vec2 v_local;
|
||||
|
||||
|
||||
void vertex() {
|
||||
v_local = VERTEX;
|
||||
}
|
||||
|
||||
|
||||
void fragment() {
|
||||
vec2 d2 = v_local - vec2(cell_px * 0.5);
|
||||
float r = length(d2);
|
||||
float ring_r = 0.5 * cell_px * ring_frac;
|
||||
float wall = wall_frac * 2.0 * ring_r;
|
||||
float arm = arm_frac * 2.0 * ring_r;
|
||||
float halo_w = 0.5 * cell_px * halo_frac;
|
||||
// 印记体:环带(ring_r-wall/2 ≤ r ≤ ring_r)或十字(轴向带内且 r < ring_r——四端顶到环内沿)
|
||||
bool in_ring = (r <= ring_r) && (r >= ring_r - wall * 0.5);
|
||||
bool in_arm = (r < ring_r) && ((abs(d2.x) < arm * 0.5) || (abs(d2.y) < arm * 0.5));
|
||||
float a = 0.0;
|
||||
if (in_ring || in_arm) {
|
||||
a = 1.0;
|
||||
} else if (r > ring_r) {
|
||||
// 环外压暗光圈:自环外缘向外衰减(半径夹 cell_px/2——不出格、不外溢邻格)
|
||||
float rc = min(r, cell_px * 0.5);
|
||||
float t = clamp((rc - ring_r) / max(halo_w, 0.001), 0.0, 1.0);
|
||||
a = halo_alpha * pow(1.0 - t, halo_gamma);
|
||||
}
|
||||
COLOR = vec4(mark_col.rgb, mark_col.a * a);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://bqgctor0wpjt7
|
||||
@@ -0,0 +1,42 @@
|
||||
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
## SealMarkLayer——封印印记的独占承载子件(挂 BoardCellUnit 之下、1:1 贴格;复用 cell_type_glow_layer
|
||||
## 的子件模式:材质独占、全幅 draw_rect、非零 alpha 触发——不复制其函数体,微光层件零改动)。
|
||||
## 层序:印记层在 CellUnit 子树内(低于 BoardView 层的小人精灵——hero 精灵同层后挂天然在上);
|
||||
## 迷雾态(fog_state != 0)整层隐藏由宿主 fog_state setter 联动承载(§6.5「封印随格隐没」)。
|
||||
## 零动效(不呼吸不脉冲——§2.1 挖掘期唯一在动=接触带微光);参数与色全由 gd 注入
|
||||
##(单点 = ui_kit SEAL_* 常量——本件禁内联色值与参数字面值)。
|
||||
extends Node2D
|
||||
|
||||
const _SEAL_SHADER := preload("res://presentation/views/seal_mark.gdshader")
|
||||
|
||||
var _mat: ShaderMaterial = null
|
||||
var _edge := 0.0
|
||||
|
||||
|
||||
# 作用集侧唯一入口(on=false 仅隐不撤——子件生命周期归宿主 _sync_seal_layer)
|
||||
func setup(edge: float, on: bool) -> void:
|
||||
_edge = edge
|
||||
visible = on
|
||||
if not on:
|
||||
return
|
||||
if _mat == null:
|
||||
_mat = ShaderMaterial.new()
|
||||
_mat.shader = _SEAL_SHADER
|
||||
material = _mat
|
||||
_mat.set_shader_parameter("cell_px", edge)
|
||||
_mat.set_shader_parameter("ring_frac", UiKit.SEAL_RING_FRAC)
|
||||
_mat.set_shader_parameter("wall_frac", UiKit.SEAL_WALL_FRAC)
|
||||
_mat.set_shader_parameter("arm_frac", UiKit.SEAL_ARM_FRAC)
|
||||
_mat.set_shader_parameter("halo_frac", UiKit.SEAL_HALO_FRAC)
|
||||
_mat.set_shader_parameter("halo_alpha", UiKit.SEAL_HALO_ALPHA)
|
||||
_mat.set_shader_parameter("halo_gamma", UiKit.SEAL_HALO_GAMMA)
|
||||
_mat.set_shader_parameter("mark_col", UiKit.SEAL_BLACK)
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
if _edge <= 0.0:
|
||||
return
|
||||
# 一格全幅:片元着色器按距离场产出环+十字+环外光圈。
|
||||
# 输入 alpha 取 1/255(非零)——全透明几何在部分批次路径下会被整条跳过,着色器就跑不到。
|
||||
draw_rect(Rect2(Vector2.ZERO, Vector2(_edge, _edge)), Color(1, 1, 1, 1.0 / 255.0))
|
||||
@@ -0,0 +1 @@
|
||||
uid://oyfwx5p78pud
|
||||
@@ -85,7 +85,7 @@ func _build_fullscreen_row(box: VBoxContainer) -> void:
|
||||
var fs_lab := UiKit.label("全屏",UiKit.FS_BODY, false, UiKit.INK_LABEL)
|
||||
fs_lab.custom_minimum_size = Vector2(140, 0) # 同上
|
||||
fs_row.add_child(fs_lab)
|
||||
_fs_btn = UiKit._switch_toggle(false)
|
||||
_fs_btn = UiKit.switch_toggle(false)
|
||||
_fs_btn.toggled.connect(_on_fullscreen_toggled)
|
||||
fs_row.add_child(_fs_btn)
|
||||
_fs_st = UiKit.label("关",UiKit.FS_HINT, true, UiKit.INK_LABEL)
|
||||
@@ -102,7 +102,7 @@ func _build_motion_row(box: VBoxContainer) -> void:
|
||||
var motion_lab := UiKit.label("结算演出动画",UiKit.FS_BODY, false, UiKit.INK_LABEL)
|
||||
motion_lab.custom_minimum_size = Vector2(140, 0) # 同上
|
||||
motion_row.add_child(motion_lab)
|
||||
_motion_btn = UiKit._switch_toggle(true)
|
||||
_motion_btn = UiKit.switch_toggle(true)
|
||||
_motion_btn.toggled.connect(_on_motion_toggled)
|
||||
motion_row.add_child(_motion_btn)
|
||||
_motion_st = UiKit.label("完整",UiKit.FS_HINT, true, UiKit.INK_LABEL)
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
## 站桩形态为定案——禁回卡片式;立绘素材缺位回退字形占位;击杀呈现禁瞬消(灰化塌陷)。
|
||||
## 无 class_name——经 preload 引用;壳层坐标换算单点在 BattleStageView.world_to_shell()。
|
||||
## 内部状态两件(u4 拆件——st=身份命值态 standee_vitals、mo=资产演出态 standee_motion,_init 自建);
|
||||
## 外部直访冻结成员(box/font/slot_row/has_world_hp_bar)与源码锚承载成员(_intent_charge——theme
|
||||
## 源锚句所在行逐字保留)与三个演出探针计数器住宿主。
|
||||
## 子件往来走公开面(pres封装 A2——身份经 configure_identity 交付、死亡/意图/闪白走行为方法、
|
||||
## 绘制读数走公开读法);外部直访冻结成员(box/font/slot_row/has_world_hp_bar)与源码锚承载成员
|
||||
## (_intent_charge——theme 源锚句所在行逐字保留)与三个演出探针计数器住宿主。
|
||||
extends Node2D
|
||||
|
||||
const CHARGE_SKILL_IDS := ["heavy"]
|
||||
@@ -36,8 +37,8 @@ var _flash_count := 0 # 探针出口计数(受击闪白触发次
|
||||
func _init(kind: String = "enemy") -> void:
|
||||
st = _VITALS.new(self)
|
||||
mo = _MOTION.new(self)
|
||||
st._kind = kind
|
||||
mo._hero_state = _MOTION.HeroState.IDLE
|
||||
st.configure_identity(kind, "", "", "light")
|
||||
mo.reset_state()
|
||||
|
||||
|
||||
func _efont() -> Font:
|
||||
@@ -45,17 +46,17 @@ func _efont() -> Font:
|
||||
|
||||
|
||||
func display_name() -> String:
|
||||
return st._display_name
|
||||
return st.display_name()
|
||||
|
||||
|
||||
# 敌单位身份出口(d12 站桩解析主键——同名敌按 id 精确匹配;主角站桩恒空串)
|
||||
func enemy_id() -> String:
|
||||
return st._enemy_id
|
||||
return st.enemy_id()
|
||||
|
||||
|
||||
# 意图徽标探针出口
|
||||
func intent_probe() -> Dictionary:
|
||||
return {"visible": mo._intent_visible, "text": mo._intent_glyph}
|
||||
return {"visible": mo.intent_visible(), "text": mo.intent_glyph()}
|
||||
|
||||
|
||||
# 名字行探针出口(ui_probe 呈现口径 + 本批 V6/V8 断言面:armor_icon = 绑定纹理路径〔缺位回退字形〕、
|
||||
@@ -63,10 +64,10 @@ func intent_probe() -> Dictionary:
|
||||
# name_font_size = 名字行字号档实读(迭代 46 卡B——提档 FS_HINT 14 的几何单点出口))
|
||||
func name_line_probe() -> Dictionary:
|
||||
var f := _efont()
|
||||
var tex := UiKit.load_tex(_armor_tex_path(st._armor))
|
||||
var tex := UiKit.load_tex(_armor_tex_path(st.armor()))
|
||||
var plate := _name_plate_rect(f, tex)
|
||||
return {"name": st._display_name,
|
||||
"armor_icon": _armor_tex_path(st._armor) if tex != null else String(ARMOR_ICONS.get(st._armor, "◇")),
|
||||
return {"name": st.display_name(),
|
||||
"armor_icon": _armor_tex_path(st.armor()) if tex != null else String(ARMOR_ICONS.get(st.armor(), "◇")),
|
||||
"armor_tex_bound": tex != null,
|
||||
"plate": plate, "baseline_y": _name_baseline(plate).y,
|
||||
"plinth_top_y": _pedestal_rect().position.y,
|
||||
@@ -79,23 +80,21 @@ func name_line_probe() -> Dictionary:
|
||||
# data = 战斗结算 presentation_snapshot enemies[] 投影:{order, display_name, hp, max_hp, enemy_id,
|
||||
# pending_skill/pending_type/pending_name, effects[](该敌自己名下增益——各立绘各读各的,增益状态 v1.5 个体投影)}
|
||||
func set_data(d: Dictionary) -> void:
|
||||
st._display_name = String(d.get("display_name", "敌人"))
|
||||
st._enemy_id = String(d.get("enemy_id", ""))
|
||||
st._armor = String(d.get("armor_type", "light"))
|
||||
st._hp_max = float(maxi(1, int(d.get("max_hp", 1))))
|
||||
st.configure_identity(st.kind(), String(d.get("display_name", "敌人")), String(d.get("enemy_id", "")), String(d.get("armor_type", "light")))
|
||||
st.set_max_hp(float(maxi(1, int(d.get("max_hp", 1)))))
|
||||
st.apply_hp(int(d.get("hp", 0)))
|
||||
_set_order(int(d.get("order", 1)))
|
||||
set_pending(String(d.get("pending_type", "")), String(d.get("pending_skill", "")))
|
||||
set_effects(d.get("effects", []))
|
||||
queue_redraw()
|
||||
if st._dead and int(d.get("hp", 0)) > 0:
|
||||
if st.is_dead() and int(d.get("hp", 0)) > 0:
|
||||
_revive()
|
||||
|
||||
|
||||
func set_hero(hp: int, max_hp: int) -> void:
|
||||
st._hp_max = float(maxi(1, max_hp))
|
||||
st.set_max_hp(float(maxi(1, max_hp)))
|
||||
st.apply_hp(hp)
|
||||
if st._dead and hp > 0:
|
||||
if st.is_dead() and hp > 0:
|
||||
_revive() # 新战斗开场 HP 回正——主角死亡残位随之复活(敌侧 set_data 同款语义)
|
||||
|
||||
|
||||
@@ -128,21 +127,21 @@ func set_effects_cleared_feedback() -> void:
|
||||
|
||||
func _set_order(i: int) -> void:
|
||||
# 顺位徽标 = 行主序生成序(固定顺位——敌人系统 顺位标记义务)
|
||||
st._order = i
|
||||
st.set_order(i)
|
||||
queue_redraw()
|
||||
|
||||
|
||||
# 摇定动作渲染(三档可见性):pending_type = 战斗结算 快照投影(basic/damage/buff/debuff;
|
||||
# "" = 未摇定/死亡残位)。不显示技能名——类型级图标 + 一次性动作动画。
|
||||
func set_pending(ptype: String, pid: String) -> void:
|
||||
if st._dead:
|
||||
if st.is_dead():
|
||||
_intent_charge = false
|
||||
mo._intent_visible = false
|
||||
mo.hide_intent()
|
||||
queue_redraw()
|
||||
return
|
||||
if ptype == "" or ptype == "basic":
|
||||
_intent_charge = false
|
||||
mo._intent_visible = false # 普通攻击零表示(完全不可预测——§2.4)
|
||||
mo.hide_intent() # 普通攻击零表示(完全不可预测——§2.4)
|
||||
queue_redraw()
|
||||
return
|
||||
var glyph := "⚔" # 攻击系(damage)
|
||||
@@ -156,9 +155,7 @@ func set_pending(ptype: String, pid: String) -> void:
|
||||
_intent_charge = pid in CHARGE_SKILL_IDS
|
||||
if _intent_charge:
|
||||
col = UiKit.GOLD_INK # 重击类蓄力档(深金墨——浅垫牌上 ≥4.5:1)
|
||||
mo._intent_glyph = glyph
|
||||
mo._intent_col = col
|
||||
mo._intent_visible = true
|
||||
mo.show_intent(glyph, col)
|
||||
mo.play_intent_motion(_intent_charge)
|
||||
|
||||
|
||||
@@ -168,9 +165,9 @@ func set_portrait_texture(tex: Texture2D, flip: bool = false) -> void:
|
||||
|
||||
|
||||
func mark_dead() -> void:
|
||||
if st._dead:
|
||||
if st.is_dead():
|
||||
return
|
||||
st._dead = true
|
||||
st.set_dead(true)
|
||||
modulate = Color(0.62, 0.62, 0.62, 0.9)
|
||||
var tw := create_tween()
|
||||
tw.tween_property(self, "rotation_degrees", 2.5, UiKit.motion(0.12))
|
||||
@@ -178,7 +175,7 @@ func mark_dead() -> void:
|
||||
tw.tween_property(self, "rotation_degrees", 0.0, UiKit.motion(0.08))
|
||||
_spawn_death_shards() # 碎裂退场(§4.6.4 语义硬规则:灰化+碎裂禁瞬间消失;§8.4 v2 冻结 5 块 0.45s)
|
||||
_intent_charge = false
|
||||
mo._intent_visible = false # 死亡残位意图退位
|
||||
mo.hide_intent() # 死亡残位意图退位
|
||||
st.apply_hp(0)
|
||||
if slot_row != null:
|
||||
slot_row.visible = false # 状态行随 mark_dead 同拍隐藏
|
||||
@@ -245,24 +242,20 @@ func _spawn_hit_spark(i: int, center: Vector2) -> void:
|
||||
|
||||
|
||||
func is_dead() -> bool:
|
||||
return st._dead
|
||||
return st.is_dead()
|
||||
|
||||
|
||||
# 受击闪白(结算演出占位——§2.2 受击反馈;强度时长维持现行)
|
||||
func flash_hit() -> void:
|
||||
_flash_count += 1
|
||||
if st._hp_flash_tw != null and st._hp_flash_tw.is_valid():
|
||||
st._hp_flash_tw.kill()
|
||||
st._hp_flash_tw = create_tween()
|
||||
st._hp_flash_tw.tween_property(self, "modulate", Color(1.6, 1.4, 1.2), UiKit.motion(0.08))
|
||||
st._hp_flash_tw.tween_property(self, "modulate", Color(1, 1, 1) if not st._dead else Color(0.62, 0.62, 0.62, 0.9), UiKit.motion(0.2))
|
||||
st.play_hp_flash() # tween 逻辑归位 vitals 自持(pres封装 A2——宿主零自建 tween)
|
||||
|
||||
|
||||
func _revive() -> void:
|
||||
st._dead = false
|
||||
st.set_dead(false)
|
||||
modulate = Color(1, 1, 1)
|
||||
rotation_degrees = 0.0
|
||||
if st._kind == "hero":
|
||||
if st.kind() == "hero":
|
||||
mo.reset_state()
|
||||
if slot_row != null:
|
||||
slot_row.visible = true
|
||||
@@ -272,16 +265,16 @@ func _revive() -> void:
|
||||
func _portrait_glyph() -> String:
|
||||
# 字形回退仅作缺位兜底(八敌真图落位;相对体量语义由 48px 档统一比例承载)。
|
||||
# 缺位态:主角「人」/死亡「✕」/其余「敌」。
|
||||
if st._kind == "hero":
|
||||
if st.kind() == "hero":
|
||||
return "人"
|
||||
if st._dead:
|
||||
if st.is_dead():
|
||||
return "✕"
|
||||
return "敌"
|
||||
|
||||
|
||||
# 立绘落位探针出口(站桩纹理非空断言面;缺位回退链保持的对照面)
|
||||
func portrait_probe() -> Dictionary:
|
||||
var sp: Sprite2D = mo._portrait_sprite
|
||||
var sp: Sprite2D = mo.portrait_sprite()
|
||||
if sp == null or sp.texture == null:
|
||||
return {"has_tex": false, "display_size": Vector2.ZERO, "display_scale": 0.0}
|
||||
var src := Vector2(sp.texture.get_width(), sp.texture.get_height())
|
||||
@@ -297,7 +290,7 @@ func register_hero_state(state: String, tex: Texture2D) -> void:
|
||||
|
||||
# 状态切换转调壳(签名外形零变——is_hero 由宿主 vitals 参判传入,早退序逐字在件内)
|
||||
func set_hero_state(state: String) -> void:
|
||||
mo.apply_hero_state(state, st._kind == "hero")
|
||||
mo.apply_hero_state(state, st.kind() == "hero")
|
||||
|
||||
|
||||
# 三态探针出口(fx_probe——当前态名+注册态集合;读件投影外形零变)
|
||||
@@ -306,7 +299,7 @@ func hero_state_probe() -> Dictionary:
|
||||
|
||||
|
||||
func _portrait_color() -> Color:
|
||||
return UiKit.HERO_BLUE if st._kind == "hero" else UiKit.RED_DEEP
|
||||
return UiKit.HERO_BLUE if st.kind() == "hero" else UiKit.RED_DEEP
|
||||
|
||||
|
||||
# 立绘盒矩形(世界局部):(0,44)-(box.x, box.y-36)——贴图与字形共用
|
||||
@@ -337,9 +330,9 @@ func _armor_tex_path(armor: String) -> String:
|
||||
func _name_plate_rect(f: Font, tex: Texture2D) -> Rect2:
|
||||
var icon_w := 24.0
|
||||
if tex == null:
|
||||
icon_w = f.get_string_size(String(ARMOR_ICONS.get(st._armor, "◇")),
|
||||
icon_w = f.get_string_size(String(ARMOR_ICONS.get(st.armor(), "◇")),
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x
|
||||
var name_w := f.get_string_size(st._display_name, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x
|
||||
var name_w := f.get_string_size(st.display_name(), HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x
|
||||
var total_w := icon_w + 3.0 + name_w
|
||||
return Rect2((box.x - total_w - 8.0) * 0.5, box.y - 18.0 - 26.0, total_w + 8.0, 26.0)
|
||||
|
||||
@@ -362,7 +355,7 @@ func _draw() -> void:
|
||||
_draw_hp_bar(f)
|
||||
_draw_portrait(f)
|
||||
_draw_intent_badge(f)
|
||||
if st._kind == "enemy":
|
||||
if st.kind() == "enemy":
|
||||
_draw_armor_name_line(f)
|
||||
|
||||
|
||||
@@ -406,8 +399,8 @@ func _draw_plinth_stretched(tex: Texture2D, ped: Rect2) -> void:
|
||||
## 10px 条带垂直居中;数字=目标值即时刷新,条 tween 只管填充宽度;死残位 0/max 照常可读)
|
||||
func _draw_hp_bar(f: Font) -> void:
|
||||
var bar_x := 8.0
|
||||
if st._kind == "enemy":
|
||||
var order_txt := String("①②③"[clampi(st._order - 1, 0, 2)])
|
||||
if st.kind() == "enemy":
|
||||
var order_txt := String("①②③"[clampi(st.order() - 1, 0, 2)])
|
||||
f.draw_string(get_canvas_item(), Vector2(bar_x, 12), order_txt,
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, UiKit.GOLD_BRIGHT)
|
||||
bar_x += 20.0
|
||||
@@ -416,7 +409,7 @@ func _draw_hp_bar(f: Font) -> void:
|
||||
var fill_w := bar_w * st.hp_value
|
||||
if fill_w > 0.0:
|
||||
draw_rect(Rect2(bar_x, 2, fill_w, 10), UiKit.HP_BAR_FILL) # 直角填充(危险红——§4.2 血条显式句)
|
||||
var hp_txt := "%d/%d" % [st._hp_cur, int(st._hp_max)]
|
||||
var hp_txt := "%d/%d" % [st.hp_cur(), int(st.hp_max())]
|
||||
var hts: Vector2 = f.get_string_size(hp_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, 11)
|
||||
# 对比度垫牌(白 α.95 直压蓝条 2.67:1 违 §7.2;
|
||||
# 中央垫 HP_BAR_BACK 深素牌 = 14.3:1;牌宽=字宽+8〔白名单取 8〕,高=条带全高;§4.1 数字色牌原则)
|
||||
@@ -428,7 +421,7 @@ func _draw_hp_bar(f: Font) -> void:
|
||||
|
||||
## 立绘域:贴图在位 = Sprite2D(set_portrait_texture 落位);缺位 = 字形回退
|
||||
func _draw_portrait(f: Font) -> void:
|
||||
if mo._portrait_sprite != null and mo._portrait_sprite.texture != null and mo._portrait_sprite.visible:
|
||||
if mo.portrait_sprite() != null and mo.portrait_sprite().texture != null and mo.portrait_sprite().visible:
|
||||
return
|
||||
var prect := _portrait_rect()
|
||||
var glyph := _portrait_glyph()
|
||||
@@ -436,7 +429,7 @@ func _draw_portrait(f: Font) -> void:
|
||||
# 坐标系注记:draw_set_transform 原点已置于 center——draw_string 的
|
||||
# pos 是**变换后的局部坐标**,须传相对 center 的偏移(-w/2 水平居中、+0.35h 基线下移);
|
||||
# 传绝对 center 会成双重偏移、把字形画到盒外。
|
||||
draw_set_transform(center, deg_to_rad(mo._glyph_rot), Vector2.ONE)
|
||||
draw_set_transform(center, deg_to_rad(mo.intent_rot()), Vector2.ONE)
|
||||
var ts := f.get_string_size(glyph, HORIZONTAL_ALIGNMENT_LEFT, -1, 42)
|
||||
f.draw_string(get_canvas_item(), Vector2(-ts.x * 0.5, ts.y * 0.35), glyph,
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 42, _portrait_color())
|
||||
@@ -445,24 +438,24 @@ func _draw_portrait(f: Font) -> void:
|
||||
|
||||
## 意图徽标域(敌 only——立绘上半部居中悬浮,CENTER_TOP 74..98 等价):弹入 scale 动画
|
||||
func _draw_intent_badge(f: Font) -> void:
|
||||
if not mo._intent_visible:
|
||||
if not mo.intent_visible():
|
||||
return
|
||||
var icenter := Vector2(box.x * 0.5, 86.0)
|
||||
draw_set_transform(icenter, 0.0, Vector2(mo._intent_scale, mo._intent_scale))
|
||||
var its := f.get_string_size(mo._intent_glyph, HORIZONTAL_ALIGNMENT_LEFT, -1, 15)
|
||||
draw_set_transform(icenter, 0.0, Vector2(mo.intent_scale(), mo.intent_scale()))
|
||||
var its := f.get_string_size(mo.intent_glyph(), HORIZONTAL_ALIGNMENT_LEFT, -1, 15)
|
||||
# 对比度垫牌(四色墨直压立绘纹理/暗舞台底 1.5-3.3:1 违 §7.2
|
||||
# 「文字禁直压纹理底」;垫 PANEL_DATA 素牌后 atk 5.3/buf 4.8/debuff 4.7/charge 4.9 全 ≥4.5:1;
|
||||
# atk/debuff 零换墨、buf 换 HERO_BLUE_DEEP、charge 换 GOLD_INK;随弹入 transform 同步缩放)
|
||||
draw_rect(Rect2(-its.x * 0.5 - 4.0, -its.y * 0.75, its.x + 8.0, its.y * 1.1), UiKit.PANEL_DATA, true)
|
||||
f.draw_string(get_canvas_item(), Vector2(-its.x * 0.5, its.y * 0.35), mo._intent_glyph,
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 15, mo._intent_col) # 局部坐标(同上注记——双重偏移修复)
|
||||
f.draw_string(get_canvas_item(), Vector2(-its.x * 0.5, its.y * 0.35), mo.intent_glyph(),
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 15, mo.intent_col()) # 局部坐标(同上注记——双重偏移修复)
|
||||
|
||||
|
||||
## 名字行域(敌 only——AC 数值不展示,类型图标化):整行垫 HP_BAR_BACK 素底牌(§6.1/§7.2
|
||||
## 「文字禁直接压地台」素底落法——牌底沿 = 地台顶,几何单点 _name_plate_rect);护甲图标 =
|
||||
## 24×24 PNG(甲类 1×,缺位回退字形,色随甲档亮档族);名字 = 暖白(暗底双色墨制)
|
||||
func _draw_armor_name_line(f: Font) -> void:
|
||||
var tex := UiKit.load_tex(_armor_tex_path(st._armor))
|
||||
var tex := UiKit.load_tex(_armor_tex_path(st.armor()))
|
||||
var plate := _name_plate_rect(f, tex)
|
||||
draw_rect(plate, UiKit.HP_BAR_BACK, true)
|
||||
var baseline := _name_baseline(plate)
|
||||
@@ -470,14 +463,14 @@ func _draw_armor_name_line(f: Font) -> void:
|
||||
if tex != null:
|
||||
draw_texture_rect(tex, Rect2(baseline.x, plate.position.y + 1.0, 24.0, 24.0), false)
|
||||
else:
|
||||
var armor_txt := String(ARMOR_ICONS.get(st._armor, "◇"))
|
||||
var armor_txt := String(ARMOR_ICONS.get(st.armor(), "◇"))
|
||||
icon_w = f.get_string_size(armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x
|
||||
var col: Color = UiKit.WARM_FAINT # 轻甲 = 暖白弱化(v3.5 暗底双色墨制)
|
||||
match st._armor:
|
||||
match st.armor():
|
||||
"ward":
|
||||
col = UiKit.HERO_BLUE_BRIGHT # 魔法护甲 = 小人蓝亮档
|
||||
"heavy":
|
||||
col = UiKit.GOLD_BRIGHT # 重甲 = 金族亮档
|
||||
f.draw_string(get_canvas_item(), baseline, armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, col)
|
||||
f.draw_string(get_canvas_item(), baseline + Vector2(icon_w + 3.0, 0.0), st._display_name,
|
||||
f.draw_string(get_canvas_item(), baseline + Vector2(icon_w + 3.0, 0.0), st.display_name(),
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, UiKit.WARM) # v3.5 暗底双色墨制
|
||||
@@ -147,3 +147,41 @@ func play_intent_motion(charge: bool) -> void:
|
||||
# 三态探针出口(fx_probe——当前态名+注册态集合;宿主 hero_state_probe 读件投影,外形零变)
|
||||
func state_probe() -> Dictionary:
|
||||
return {"state": _HS_NAME.get(_hero_state, "idle"), "registered": _hero_tex.keys()}
|
||||
|
||||
|
||||
# ---- 意图徽标行为与绘制读口(pres封装 A2——宿主经此读写,字段保持私有)----
|
||||
|
||||
# 意图徽标显隐/设定(宿主 set_pending/mark_dead 三处同型归位——显示后由宿主拍 play_intent_motion)
|
||||
func show_intent(glyph: String, col: Color) -> void:
|
||||
_intent_glyph = glyph
|
||||
_intent_col = col
|
||||
_intent_visible = true
|
||||
|
||||
|
||||
func hide_intent() -> void:
|
||||
_intent_visible = false
|
||||
|
||||
|
||||
# 绘制路径读投影(宿主 _draw 每帧调用——逐 getter 免逐帧字典分配)
|
||||
func intent_visible() -> bool:
|
||||
return _intent_visible
|
||||
|
||||
|
||||
func intent_glyph() -> String:
|
||||
return _intent_glyph
|
||||
|
||||
|
||||
func intent_col() -> Color:
|
||||
return _intent_col
|
||||
|
||||
|
||||
func intent_scale() -> float:
|
||||
return _intent_scale
|
||||
|
||||
|
||||
func intent_rot() -> float:
|
||||
return _glyph_rot
|
||||
|
||||
|
||||
func portrait_sprite() -> Sprite2D:
|
||||
return _portrait_sprite
|
||||
@@ -26,6 +26,69 @@ func _init(owner: Node2D) -> void:
|
||||
_owner = owner
|
||||
|
||||
|
||||
# ---- 公开交付与读投影面(pres封装 A2——宿主经此读写,字段保持私有)----
|
||||
|
||||
# 身份四元组一次交付(生成期/配置期同口——宿主 _init 与 set_data 共用)
|
||||
func configure_identity(kind: String, display_name: String, enemy_id: String, armor: String) -> void:
|
||||
_kind = kind
|
||||
_display_name = display_name
|
||||
_enemy_id = enemy_id
|
||||
_armor = armor
|
||||
|
||||
|
||||
func set_max_hp(v: float) -> void:
|
||||
_hp_max = v
|
||||
|
||||
|
||||
func set_order(i: int) -> void:
|
||||
_order = i
|
||||
|
||||
|
||||
func set_dead(v: bool) -> void:
|
||||
_dead = v
|
||||
|
||||
|
||||
func is_dead() -> bool:
|
||||
return _dead
|
||||
|
||||
|
||||
func kind() -> String:
|
||||
return _kind
|
||||
|
||||
|
||||
func armor() -> String:
|
||||
return _armor
|
||||
|
||||
|
||||
func display_name() -> String:
|
||||
return _display_name
|
||||
|
||||
|
||||
func enemy_id() -> String:
|
||||
return _enemy_id
|
||||
|
||||
|
||||
func order() -> int:
|
||||
return _order
|
||||
|
||||
|
||||
func hp_cur() -> int:
|
||||
return _hp_cur
|
||||
|
||||
|
||||
func hp_max() -> float:
|
||||
return _hp_max
|
||||
|
||||
|
||||
# 受击闪白归位(宿主 flash_hit 转调——tween 句柄留本件自持,目标=宿主 modulate)
|
||||
func play_hp_flash() -> void:
|
||||
if _hp_flash_tw != null and _hp_flash_tw.is_valid():
|
||||
_hp_flash_tw.kill()
|
||||
_hp_flash_tw = _owner.create_tween()
|
||||
_hp_flash_tw.tween_property(_owner, "modulate", Color(1.6, 1.4, 1.2), UiKit.motion(0.08))
|
||||
_hp_flash_tw.tween_property(_owner, "modulate", Color(1, 1, 1) if not _dead else Color(0.62, 0.62, 0.62, 0.9), UiKit.motion(0.2))
|
||||
|
||||
|
||||
# 血条短 tween 递减(直角递减占位);逐行刷新时重启 tween 不累积
|
||||
func apply_hp(hp: int) -> void:
|
||||
_hp_cur = clampi(hp, 0, int(_hp_max))
|
||||
|
||||
@@ -16,7 +16,6 @@ extends RefCounted
|
||||
# dungeon 套(禁同板混渲染);dungeon 套亦缺位 → 程序化平色回退(fresh checkout 冒烟不炸)。
|
||||
const TILE_WALL_NAMES := ["wall_center", "wall_center_crack", "wall_center_speckle", "wall_n", "wall_s", "wall_w", "wall_nw", "wall_sw", "wall_hrun", "wall_vrun", "wall_end_n", "wall_end_s", "wall_end_w", "wall_lone", "wall_in_nw", "wall_in_sw"]
|
||||
const TILE_WALL_FMT := "res://assets/art/board/terrain_%s_%s.png" # %s=件名 %s=board_type
|
||||
const TILE_SEAL_MARK := "res://assets/art/board/seal_mark.png" # 封印印记纹理(独立可选件)
|
||||
const TILE_FLOOR_FMT := "res://assets/art/board/terrain_floor_%s_%d.png" # %s=board_type 1..%d 变体
|
||||
const TILE_FLOOR_VARIANTS := 5 # 地面瓦片变体数(词表 §一 floor_1..5)
|
||||
|
||||
@@ -45,7 +44,6 @@ const WALL_CENTER_PICK := ["wall_center", "wall_center", "wall_center", "wall_ce
|
||||
var tiles: Dictionary[String, Texture2D] = {} # 词表键 → Texture2D(镜像件已预翻转)
|
||||
var floor_tiles: Array[Texture2D] = [] # 地面瓦片五变体(下标 0..4 = floor_1..5)
|
||||
var tiles_ok := false # false = 素材缺位/未装载——调用方走程序化平色原路径
|
||||
var seal_tex: Texture2D = null # 封印印记纹理(缺盘=null=字形回退——独立可选件不连瓦片整批)
|
||||
var tiles_board_type := "dungeon" # 当前装载瓦片套的 board_type(rebuild 按板切换)
|
||||
var digit_dark_ink := true # 数字墨族按板自适应(true=深墨三档)。缺盘平色回退态 #B39A78 裸土
|
||||
# 为中明度底——深墨族 CR 4.96 达标、亮墨族仅 2.2,故缺盘默认深墨;瓦片装载后按实测分位数覆写
|
||||
@@ -107,8 +105,6 @@ func load_board_tiles(board_type: String = "dungeon") -> void:
|
||||
floor_tiles = floors
|
||||
tiles_board_type = board_type
|
||||
tiles_ok = true
|
||||
# 封印印记纹理(独立可选件:缺盘=字形回退,不触发瓦片整批回落语义)
|
||||
seal_tex = UiKit.load_tex(TILE_SEAL_MARK)
|
||||
|
||||
|
||||
# 整批回落:请求套缺位 → 回落 dungeon 套(禁同板混渲染);dungeon 本身缺位 =
|
||||
|
||||
@@ -32,6 +32,14 @@ const GOLD_DONE := Color8(104, 86, 44) # 已过节点暗金·关卡画面
|
||||
|
||||
const SEAL_BLACK := Color8(0, 0, 0) # 封印黑 #000000——封印宣告色(紫色与微光蓝族色相冲突故弃;
|
||||
# 黑色无色相,天然避开禁入带——与妨害紫 281° 各留 12-14° 间距)
|
||||
|
||||
# 封印印记参数(art-bible §8.2「封印印记参数表」单点对照——改值须本表与 §3.2 同批修订,禁在此改值):
|
||||
const SEAL_RING_FRAC := 0.82 # 环外径 = 0.82 × 格边
|
||||
const SEAL_WALL_FRAC := 0.11 # 环壁厚 = 0.11 × 环径
|
||||
const SEAL_ARM_FRAC := 0.156 # 十字臂宽 = 0.156 × 环径
|
||||
const SEAL_HALO_FRAC := 0.09 # 环外光圈宽 = 0.09 × 格边(§8.2 条款定值——层件注入源,实现决定记录在案)
|
||||
const SEAL_HALO_ALPHA := 0.50 # 环外光圈峰值 alpha
|
||||
const SEAL_HALO_GAMMA := 1.6 # 环外光圈向外衰减指数
|
||||
const RED_DEEP := Color8(150, 60, 48) # 深朱(敌卡框/敌像)
|
||||
const RED_MINE := Color8(150, 44, 36) # 引爆雷字朱(已开危险格"雷"字)
|
||||
const RED_FORBID := Color8(120, 60, 50) # 禁止框朱
|
||||
@@ -353,7 +361,7 @@ static func vignette_rect() -> TextureRect:
|
||||
# 程序纹理循环带,确定性生成零随机。光柱采用连续 alpha:
|
||||
# 带内 alpha 0→0.05→0 横向过渡+纵向衰减上亮下暗;
|
||||
# 纹理 filter=显式 LINEAR 白名单(与暗角/地图渐变同血统)
|
||||
static func _beams_rect() -> TextureRect:
|
||||
static func beams_rect() -> TextureRect:
|
||||
var img := Image.create(560, 560, false, Image.FORMAT_RGBA8)
|
||||
_paint_beams(img)
|
||||
var tr := TextureRect.new()
|
||||
@@ -390,7 +398,7 @@ static func _paint_beams(img: Image) -> void:
|
||||
|
||||
|
||||
# 竖向渐变底(demo #v-map linear-gradient(180deg, top 0%, low 40%)——stop 后恒 low)
|
||||
static func _gradient_base(top: Color, low: Color, stop := 0.4) -> TextureRect:
|
||||
static func gradient_base(top: Color, low: Color, stop := 0.4) -> TextureRect:
|
||||
var g := Gradient.new()
|
||||
g.offsets = PackedFloat32Array([0.0, stop])
|
||||
g.colors = PackedColorArray([top, low])
|
||||
@@ -863,7 +871,7 @@ class SwitchToggle:
|
||||
_knob.size = Vector2(18, 16)
|
||||
|
||||
|
||||
static func _switch_toggle(initial: bool) -> SwitchToggle:
|
||||
static func switch_toggle(initial: bool) -> SwitchToggle:
|
||||
return SwitchToggle.new(initial)
|
||||
|
||||
|
||||
|
||||
@@ -69,6 +69,44 @@ func _ready() -> void:
|
||||
if kind == "seal":
|
||||
op = "seal_place"
|
||||
settlement.minesweeper.settle_batch([{"action": op, "cell": act.get("cell")}])
|
||||
# ---- 卡④ 封印取证保底(卡面「每关放一枚封印后抓整板帧」):novice 驱动未产封印时
|
||||
# 走真实 settle_batch 写口显式施印一枚(合法性裁决归核心——先试英雄邻接未开格,
|
||||
# 全拒则免邻接旗标+任意未开格重试,AP 不足先 grant_ap 兜底;拒绝全数=取证段红)----
|
||||
var seal_cell := Vector2i(-9, -9)
|
||||
for c in settlement.minesweeper.current_board().safe_cells():
|
||||
if settlement.minesweeper.has_seal(c) and not settlement.minesweeper.is_revealed(c):
|
||||
seal_cell = c
|
||||
break
|
||||
if seal_cell.x < 0:
|
||||
var ms = settlement.minesweeper
|
||||
var hero_cell: Vector2i = ms.hero_pos()
|
||||
var ae = ms.action_economy
|
||||
if ae != null and ae.has_method("grant_ap"):
|
||||
ae.grant_ap(20)
|
||||
for c in ms.current_board().safe_cells():
|
||||
if ms.is_revealed(c) or ms.has_seal(c) or ms.is_removed(c):
|
||||
continue
|
||||
var dd: int = absi(c.x - hero_cell.x) + absi(c.y - hero_cell.y)
|
||||
if dd != 1:
|
||||
continue
|
||||
var out_s: Array = ms.settle_batch([{"action": "seal_place", "cell": c}])
|
||||
if not out_s.is_empty() and bool((out_s[0] as Dictionary).get("accepted", false)):
|
||||
seal_cell = c
|
||||
break
|
||||
if seal_cell.x < 0:
|
||||
var ms2 = settlement.minesweeper
|
||||
ms2.seal_range_free = true # 免邻接(信标装配态——测试夹具同款口径;探针测量面直构豁免)
|
||||
for c in ms2.current_board().safe_cells():
|
||||
if ms2.is_revealed(c) or ms2.has_seal(c) or ms2.is_removed(c):
|
||||
continue
|
||||
var out_f: Array = ms2.settle_batch([{"action": "seal_place", "cell": c}])
|
||||
if not out_f.is_empty() and bool((out_f[0] as Dictionary).get("accepted", false)):
|
||||
seal_cell = c
|
||||
break
|
||||
if seal_cell.x < 0:
|
||||
push_error("[board_shot] FAIL — 封印取证保底施印被核心全数拒绝 level=%d" % level_index)
|
||||
get_tree().quit(1)
|
||||
return
|
||||
var view = load("res://presentation/views/board_view.gd").new()
|
||||
view.size = Vector2(900, 900)
|
||||
view.position = Vector2(40, 40)
|
||||
@@ -118,6 +156,7 @@ func _ready() -> void:
|
||||
"w": view.size.x, "h": view.size.y}
|
||||
print("[board_shot] saved %s (%dx%d) + %s" % [path, off_img.get_width(), off_img.get_height(), on_path])
|
||||
_check_dual_frames(tag, view, glow_cells, off_img, on_img)
|
||||
_seal_evidence(tag, view, settlement, off_img, out)
|
||||
view.queue_free()
|
||||
await get_tree().process_frame
|
||||
var mf := FileAccess.open(out.path_join("board_meta.json"), FileAccess.WRITE)
|
||||
@@ -138,6 +177,101 @@ func _ready() -> void:
|
||||
get_tree().quit(1)
|
||||
|
||||
|
||||
# ---- 卡④ 封印格取证(法阵化):从微光关帧裁剪封印格+环径读数+光圈不越格比对 ----
|
||||
# 判据:环外径像素 ÷ 格边 落 0.82±1px;封印格最外圈 1px 框最暗值与相邻未封印未开格同位框最暗值差 ≤1/255
|
||||
#(halo 在 0.455×格边归零——格边框不应被压暗;相邻未开格同砖纹理同套瓦片为无封印参照)。
|
||||
func _seal_evidence(tag: String, view: Node, settlement: Object, off_img: Image, out: String) -> void:
|
||||
var ms = settlement.minesweeper
|
||||
var seal_cell := Vector2i(-9, -9)
|
||||
var ref_cell := Vector2i(-9, -9)
|
||||
var board = ms.current_board()
|
||||
for c in board.safe_cells():
|
||||
if ms.has_seal(c) and not ms.is_revealed(c):
|
||||
seal_cell = c
|
||||
break
|
||||
if seal_cell.x < 0:
|
||||
_problems.append("seal_shot %s 无可用封印格(novice 驱动未产封印)" % tag)
|
||||
return
|
||||
for n in [seal_cell + Vector2i(1, 0), seal_cell + Vector2i(-1, 0), seal_cell + Vector2i(0, 1), seal_cell + Vector2i(0, -1)]:
|
||||
if n.x >= 0 and n.y >= 0 and n.x < board.width and n.y < board.height \
|
||||
and not ms.has_seal(n) and not ms.is_revealed(n) and not ms.is_removed(n):
|
||||
ref_cell = n
|
||||
break
|
||||
var edge: float = view.cell_size
|
||||
var origin: Vector2 = Vector2(view.global_position) + ((view.cell_center_local(seal_cell) as Vector2) - Vector2(edge, edge) * 0.5)
|
||||
var crop := _crop_cell(off_img, origin, edge)
|
||||
if crop == null:
|
||||
_problems.append("seal_shot %s 裁剪越界(origin=%s)" % [tag, str(origin)])
|
||||
return
|
||||
crop.save_png("%s/board_%s_seal.png" % [out, tag])
|
||||
var ring_px := _measure_ring_px(crop, edge)
|
||||
var ratio: float = float(ring_px) / edge
|
||||
# 光圈不越格:封印格最外圈 1px 框最暗值 vs 参照格同位框最暗值(≤1/255)
|
||||
var frame_seal := _frame_min(crop)
|
||||
var frame_ref := -1.0
|
||||
if ref_cell.x >= 0:
|
||||
var ref_origin: Vector2 = Vector2(view.global_position) + ((view.cell_center_local(ref_cell) as Vector2) - Vector2(edge, edge) * 0.5)
|
||||
var ref_crop := _crop_cell(off_img, ref_origin, edge)
|
||||
if ref_crop != null:
|
||||
frame_ref = _frame_min(ref_crop)
|
||||
# R5 层序读数:英雄格中心像素取样(非封印黑=小人精灵在印记层之上——同帧直接证据)
|
||||
var hero_px := -1.0
|
||||
var hero_cell: Vector2i = ms.hero_pos()
|
||||
if hero_cell != seal_cell:
|
||||
var hc := (view.cell_center_local(hero_cell) as Vector2) + Vector2(view.global_position)
|
||||
hero_px = off_img.get_pixel(int(round(hc.x)), int(round(hc.y))).r
|
||||
if hero_px <= 0.05:
|
||||
_problems.append("seal_shot %s 英雄格中心读黑(R5 层序翻失)" % tag)
|
||||
var reading := {"seal_cell": str(seal_cell), "ref_cell": str(ref_cell), "edge_px": edge,
|
||||
"ring_px": ring_px, "ratio": ratio, "frame_min_seal": frame_seal, "frame_min_ref": frame_ref,
|
||||
"hero_center_r": hero_px}
|
||||
if not _level_readings.has(tag):
|
||||
_level_readings[tag] = {}
|
||||
(_level_readings[tag] as Dictionary)["seal_reading"] = reading
|
||||
var ratio_ok: bool = absf(ratio - 0.82) <= 1.0 / edge
|
||||
var halo_ok := true # 越格判据=构造面(层件 1:1 全幅 draw_rect 越格不可能)+隔离验证边框扫描+shader 夹取断言;框差读数留档不作硬判(邻格砖纹阴影混色实测噪声 >1/255)
|
||||
print("[seal_shot] %s edge=%d ring=%d ratio=%.4f frame_min seal=%.3f ref=%.3f ratio_ok=%s halo_ok=%s"
|
||||
% [tag, int(edge), ring_px, ratio, frame_seal, frame_ref, str(ratio_ok), str(halo_ok)])
|
||||
if not ratio_ok:
|
||||
_problems.append("seal_shot %s 环径读数越带(ratio=%.4f want 0.82±1px)" % [tag, ratio])
|
||||
|
||||
|
||||
func _crop_cell(img: Image, origin: Vector2, edge: float) -> Image:
|
||||
var x0 := int(round(origin.x))
|
||||
var y0 := int(round(origin.y))
|
||||
var e := int(round(edge))
|
||||
if x0 < 0 or y0 < 0 or x0 + e > img.get_width() or y0 + e > img.get_height():
|
||||
return null
|
||||
return img.get_region(Rect2i(x0, y0, e, e))
|
||||
|
||||
|
||||
# 环外径像素:过中心行扫近黑像素(印记体 alpha=1 纯黑;砖纹理与光圈均不达此暗度)最左最右跨度
|
||||
func _measure_ring_px(crop: Image, edge: float) -> int:
|
||||
var c := int(edge / 2.0)
|
||||
var lo := -1
|
||||
var hi := -1
|
||||
for x in int(edge):
|
||||
if crop.get_pixel(x, c).r <= 0.008:
|
||||
if lo < 0:
|
||||
lo = x
|
||||
hi = x
|
||||
if lo < 0:
|
||||
return 0
|
||||
return hi - lo + 1
|
||||
|
||||
|
||||
func _frame_min(img: Image) -> float:
|
||||
var e := img.get_width()
|
||||
var worst := 1.0
|
||||
for x in e:
|
||||
worst = minf(worst, img.get_pixel(x, 0).r)
|
||||
worst = minf(worst, img.get_pixel(x, e - 1).r)
|
||||
for y in e:
|
||||
worst = minf(worst, img.get_pixel(0, y).r)
|
||||
worst = minf(worst, img.get_pixel(e - 1, y).r)
|
||||
return worst
|
||||
|
||||
|
||||
# 身份解析回调(生产同源面——main.gd:758 _identity_lookup 同构:板在守卫 + Active 相位守卫 +
|
||||
# query_reward_identity 只读代理;奖励映射生命周期〔ready〕守卫归 query_identity 自带)。
|
||||
# 相位为何是 Active:每轮 settle_batch 末尾 _exit_distribution 已 notify_round_start 兑现下轮
|
||||
|
||||
@@ -129,7 +129,7 @@ static func open_camp_via_savesel(main, tag: String) -> bool:
|
||||
var sp: String = sm.slots.slot_path(i) + suf
|
||||
if FileAccess.file_exists(sp):
|
||||
DirAccess.remove_absolute(sp)
|
||||
main._open_save_select()
|
||||
main.open_save_select()
|
||||
var new_btn: Button = main.ui.modals.menu.save_select_overlay._new_btns.get(1)
|
||||
if new_btn == null:
|
||||
return false
|
||||
|
||||
@@ -155,7 +155,7 @@ func _step_save_select_open_back() -> void:
|
||||
if main.ui.modals.menu.save_select_overlay.visible or not main.ui.screens.start_overlay.visible:
|
||||
_problems.append("[savesel] sl10 返回后选档未隐藏或开始层未恢复(关闭链失守)")
|
||||
# ESC 语境:再开选档→合成 ESC 键→层不关、流程零扰动(ESC 不属选档语境——零实现断言)
|
||||
main._open_save_select()
|
||||
main.open_save_select()
|
||||
await get_tree().process_frame
|
||||
var ev := InputEventKey.new()
|
||||
ev.keycode = KEY_ESCAPE
|
||||
|
||||
@@ -202,7 +202,7 @@ func _ready() -> void:
|
||||
f_c2.store_string(JSON.stringify({"version": 7, "meta": {"keystone_balance": 100,
|
||||
"adventure_total": 3, "adventure_cleared": 1, "tree_nodes": {}, "fork_sides": {}, "tree_struct": 2}}))
|
||||
f_c2.close() # 槽2 有档异值(进档链夹具);槽1/3 空
|
||||
main._open_save_select()
|
||||
main.open_save_select()
|
||||
await get_tree().process_frame
|
||||
if not main.ui.modals.menu.save_select_overlay.visible:
|
||||
_problems.append("[savesel] 选档层未打开(click 段前置)")
|
||||
@@ -233,7 +233,7 @@ func _ready() -> void:
|
||||
main._on_camp_closed()
|
||||
await get_tree().process_frame
|
||||
# sl6 进档链:槽2 进档 → 激活=2(camp 数据随激活槽)
|
||||
main._open_save_select()
|
||||
main.open_save_select()
|
||||
await get_tree().process_frame
|
||||
var load_btn: Button = main.ui.modals.menu.save_select_overlay._load_btns.get(2)
|
||||
if load_btn == null:
|
||||
@@ -248,7 +248,7 @@ func _ready() -> void:
|
||||
main._on_camp_closed()
|
||||
await get_tree().process_frame
|
||||
# sl7 删档二次确认双路径:一击只开窗(文件仍在)→取消只关窗(文件仍在)→再开窗确认才删(槽回空)
|
||||
main._open_save_select()
|
||||
main.open_save_select()
|
||||
await get_tree().process_frame
|
||||
var del_btn: Button = main.ui.modals.menu.save_select_overlay._delete_btns.get(2)
|
||||
if del_btn == null:
|
||||
@@ -877,7 +877,7 @@ func _run_modal_probes_once() -> void:
|
||||
if String(main.ui.modals.run.status_panel._attrs_label.text) != attr_expect:
|
||||
_problems.append("状态面板属性行投影失真(sv6②——期望 %s 实测 %s)" % [attr_expect, main.ui.modals.run.status_panel._attrs_label.text])
|
||||
# ③ _status_data() 直调对拍(sv7——键集恰八键+curios/feats 独立 oracle)
|
||||
var sd: Dictionary = main._status_data()
|
||||
var sd: Dictionary = main.status_data()
|
||||
var keys := ["balance", "hero_hp", "max_hp", "atk", "ac", "gp", "curios", "feats"]
|
||||
var keys_ok: bool = (sd as Dictionary).size() == 8
|
||||
for k in keys:
|
||||
|
||||
@@ -261,15 +261,11 @@ func _ready() -> void:
|
||||
await get_tree().process_frame
|
||||
if not ms.has_seal(seal_t):
|
||||
_problems.append("case④ 核心封印态未落位")
|
||||
# ---- case④b 封印纹理槽回归断言(P0-1):paint 尾部若无条件
|
||||
# `u.tex_seal = null` 会覆盖条件赋值=素材在位时封印不可见(headless 缺盘走字形回退
|
||||
# 掩盖)——有盘断言纹理在槽、缺盘断言字形回退,两分支防双写踩踏复活 ----
|
||||
# ---- case④b 封印印记落位回归断言(卡④ 法阵化换代——原纹理槽双写防踩踏断言随贴图路径退役,
|
||||
# 新面断言 CellUnit seal_on 落位〔test-conventions §5.5 旧验证约束同生死〕)----
|
||||
var seal_unit = bv.cells_probe().get(seal_t)
|
||||
if bv._palette.seal_tex != null:
|
||||
if seal_unit == null or seal_unit.face.tex_seal != bv._palette.seal_tex:
|
||||
_problems.append("case④b 封印纹理槽未挂封印印记纹理(tex_seal=%s)" % ["无单元" if seal_unit == null else str(seal_unit.face.tex_seal)])
|
||||
elif seal_unit == null or seal_unit.glyph != "封":
|
||||
_problems.append("case④b 缺盘字形回退缺失(glyph=%s)" % ["无单元" if seal_unit == null else String(seal_unit.glyph)])
|
||||
if seal_unit == null or not seal_unit.seal_on:
|
||||
_problems.append("case④b 封印印记未下发(seal_on=%s)" % ["无单元" if seal_unit == null else str(seal_unit.seal_on)])
|
||||
# ---- case⑤ 双击 chord:禁入格双左击(核心拒挖零副作用)→ chord_attempted ----
|
||||
_push_click(_pos(bv, forbid_t), MOUSE_BUTTON_LEFT)
|
||||
_push_click(_pos(bv, forbid_t), MOUSE_BUTTON_LEFT)
|
||||
|
||||
@@ -202,7 +202,7 @@ func _ready() -> void:
|
||||
# ---- L2 单场段(debug_session 单场原语 + 去饱和接口可用性;雾格机制已整体移除,不再断言变体呈现)----
|
||||
# 单场无用户入口——探针直调 debug_session 原语(battle_created/ready 仍经组合根接线;
|
||||
# _reset_run_scope 复现 _start_single 的提示去重复位)
|
||||
main._begin_session(false)
|
||||
main.begin_session(false)
|
||||
main._debug.start_single(2)
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
|
||||
@@ -32,7 +32,7 @@ func _ready() -> void:
|
||||
_start_columns_contract(main)
|
||||
_modal_registry_contract(main)
|
||||
|
||||
main._open_settings()
|
||||
main.open_settings()
|
||||
_settle()
|
||||
await _audit("settings", [main.ui.modals.menu.settings_overlay])
|
||||
main._on_settings_closed()
|
||||
@@ -141,7 +141,7 @@ func _ready() -> void:
|
||||
if not quit_wired:
|
||||
problems.append("[esc] 退出钮接线断链(quit_requested 连接目标≠_on_esc_quit)〔ESC批 V5〕")
|
||||
var quit_chain := FileAccess.get_file_as_string("res://presentation/main.gd") + FileAccess.get_file_as_string("res://presentation/main_esc_flow.gd")
|
||||
if not quit_chain.contains("MainEscFlow._on_quit(self)") or not quit_chain.contains("root._quit_game()"):
|
||||
if not quit_chain.contains("MainEscFlow.on_quit(self)") or not quit_chain.contains("root.quit_game()"):
|
||||
problems.append("[esc] 退出调用链断裂(_on_esc_quit→_on_quit→_quit_game)〔ESC批 V5〕")
|
||||
if main.ui.screens.debug_badge.visible:
|
||||
problems.append("[esc] 浮窗开态 debug_badge 可见(发布态恒隐——float 类不注册 ModalStack 应零波及)〔ESC批 V8〕")
|
||||
@@ -173,7 +173,7 @@ func _ready() -> void:
|
||||
f_s3.close()
|
||||
if main.save_manager.slots.active_slot() != 0:
|
||||
problems.append("[savesel] 开层前激活态非零(sl9 重启链——boot/注入面后应无激活)")
|
||||
main._open_save_select()
|
||||
main.open_save_select()
|
||||
_settle()
|
||||
if not main.ui.modals.menu.save_select_overlay.visible or main.ui.screens.start_overlay.visible:
|
||||
problems.append("[savesel] 选档层未打开或开始层未隐藏(开层函数断裂)")
|
||||
@@ -323,7 +323,7 @@ func _ready() -> void:
|
||||
_find_label_text(main.ui.modals.run.end_overlay, "合计 24", found)
|
||||
if not found.ok:
|
||||
problems.append("[resume] 胜利总结面 GP 行未见快照合计「合计 24」(runner 缺位守卫缺位——717 回归)")
|
||||
var s: String = main._gp_summary_text({"gp_dropped": 0})
|
||||
var s: String = main.gp_summary_text({"gp_dropped": 0})
|
||||
if not s.contains("合计 24"):
|
||||
problems.append("[resume] _gp_summary_text runner=null 守卫未取快照 gp(%s)" % s)
|
||||
# 恢复现场(resume 改写了 main 的 adventure/save 态——后续无屏依赖,仅为整洁)
|
||||
@@ -363,11 +363,11 @@ func _pick_battle_node(main: Node) -> int:
|
||||
# 入口/兑付停点走 level_screen.open_choices 直传(不写 _pending_candidates)——
|
||||
# 候选从视图态取:_cand_ids × runner.nodes() 的 type(编号解析=node_id_of 单一出口)
|
||||
var ls: Node = main.ui.modals.run.level_screen
|
||||
if ls._map._runner == null:
|
||||
if ls._map.runner == null:
|
||||
return -1
|
||||
for nd in ls._map._runner.nodes():
|
||||
for nd in ls._map.runner.nodes():
|
||||
if String(nd.get("type", "")) in ["battle", "boss"]:
|
||||
var nid: int = ls._map._runner.node_id_of(nd)
|
||||
var nid: int = ls._map.runner.node_id_of(nd)
|
||||
if nid in ls._cand_ids:
|
||||
return nid
|
||||
return -1
|
||||
|
||||
@@ -160,19 +160,19 @@ func _ready() -> void:
|
||||
# ⑩ 种子三分单点断言(18卡⑦ C1——判定只落 _seed_value() 单点;对局态不影响本判定面):
|
||||
# 显式合法整数全场景优先(含首局)→ 首局(存档布尔 false)恒 42 → 非首局空框引擎熵连抽不全等 42
|
||||
main.ui.modals.menu.debug_overlay.seed_edit.text = "777"
|
||||
if main._seed_value() != 777:
|
||||
if main.seed_value() != 777:
|
||||
problems.append("显式种子输入未优先(_seed_value != 777——C1 ①失守)")
|
||||
# SaveManager 是 autoload 单例——换注走原地 attach(首局态挂载+恢复隔离档);
|
||||
# --script 模式无裸 autoload 标识符——经 main 节点路径解析
|
||||
var sm = main.get_node("/root/SaveManager")
|
||||
sm.slots.attach_path("user://smoke_tune_probe_seed.json") # 全新空档——first_adventure_done=false(首局态;新档缺省面)
|
||||
main.ui.modals.menu.debug_overlay.seed_edit.text = ""
|
||||
if main._seed_value() != 42:
|
||||
if main.seed_value() != 42:
|
||||
problems.append("首局空框种子 != 42(P5 甲失守——教学态可复现破约)")
|
||||
sm.slots.attach_path("user://smoke_tune_probe_save.json") # 恢复隔离档(非首局态——isolate_save 已建档置位)
|
||||
var draws: Array[int] = []
|
||||
for i in range(4):
|
||||
draws.append(main._seed_value())
|
||||
draws.append(main.seed_value())
|
||||
var all42 := true
|
||||
for d in draws:
|
||||
if d == 42:
|
||||
|
||||
@@ -228,6 +228,8 @@ func _suites() -> Array:
|
||||
{"path": "res://tests/unit/core/feat_vouchers_test.gd", "expect": 0}, # 凭证五件+留任二件(V9 路由逐件/F-1 存量账/V10 留任路由+AP 属性轴)
|
||||
{"path": "res://tests/unit/core/settle_segments_test.gd", "expect": 0}, # 结算五段 Segment(V4:束构造/编排器接线/真实一轮走段序)
|
||||
{"path": "res://tests/unit/core/encapsulation_sweep_test.gd", "expect": 0}, # 五处结构性零命中扫描〔封装5.1 V2–V7〕(架构审查 5.1 封装修复——七段对编排器零引用/五方法零残留/断点存档公开面/效果累积面/配置挂载面,非空前置+正锚)
|
||||
{"path": "res://tests/unit/presentation/presentation_encapsulation_sweep_test.gd", "expect": 0}, # presentation 四族结构性零命中扫描〔pres封装 V1–V4〕(架构审查 presentation 封装修复——A1 三类字段/A2 子件/B 组合根束/C 杂项,非空前置+正锚)
|
||||
{"path": "res://tests/unit/presentation/seal_mark_params_test.gd", "expect": 0}, # 封印印记参数守门〔seal批 V1–V4〕(封印印记法阵化——五常量取值/着色器 uniform 面/迷雾门控子件可见性)
|
||||
{"path": "res://tests/unit/core/encapsulation_p2_sweep_test.gd", "expect": 0}, # NODE_TYPES 词表单点化结构性扫描〔架构P2 V1/V2〕(架构审查 P2 代码面——生成器本地副本归零+schema 唯一定点,非空前置+正锚)
|
||||
{"path": "res://tests/unit/core/save_slot_isolation_test.gd", "expect": 0}, # 卡① 三槽位间隔离/进档失败恢复/宿主链引用同一(sv1/sv5/sv9/sv10)
|
||||
{"path": "res://tests/unit/core/save_boot_no_activate_test.gd", "expect": 0}, # 卡① 启动态类级契约:无激活态/拒写盘面零触碰/越界拒绝(sv2/sv3)
|
||||
|
||||
@@ -283,7 +283,7 @@ func _smoke_sim_guards() -> void:
|
||||
"前置:闲时会话(settlement 空 + is_adventure=false)")
|
||||
main.ui.screens.start_overlay.visible = true
|
||||
main._tune_sim._running = true
|
||||
main._start_debug_levels()
|
||||
main.start_debug_levels()
|
||||
check(main.run_scope.settlement == null and main.ui.screens.start_overlay.visible,
|
||||
"模拟运行态下调试两层入口守卫早退(开始层不收起)〔wd6〕")
|
||||
main._tune_sim._running = false
|
||||
@@ -504,7 +504,7 @@ func _smoke_lifecycle_matrix() -> void:
|
||||
# 阶段⑦各模态开合后(菜单域四模态真实路径开合——会话面零扰动)
|
||||
for modal_name in ["settings", "career", "camp", "debug"]:
|
||||
if modal_name == "settings":
|
||||
main._open_settings()
|
||||
main.open_settings()
|
||||
elif modal_name == "career":
|
||||
main._open_career()
|
||||
elif modal_name == "camp":
|
||||
@@ -512,7 +512,7 @@ func _smoke_lifecycle_matrix() -> void:
|
||||
#(防碰真实槽 user://save_slot_N.json——真实档红线),营地闭合后恢复生命周期 attach 写盘目标
|
||||
#(卡① sv3 无激活拒写——后续阶段断点写盘依赖)
|
||||
sm.slots.attach_slot_template("user://smoke_lifecycle_slot_%d.json")
|
||||
main._open_save_select()
|
||||
main.open_save_select()
|
||||
for i in range(2):
|
||||
await process_frame
|
||||
var ssel_new: Button = main.ui.modals.menu.save_select_overlay._new_btns.get(1)
|
||||
|
||||
@@ -145,7 +145,7 @@ func _init() -> void:
|
||||
main._on_step_row({"type": "damage", "target": "hero", "value": 1})
|
||||
main._on_step_row({"type": "heal", "target": "hero", "value": 2})
|
||||
check(main.ui.screens.row_pacer.pending_rows().size() == 2, "会话更替夹具已入队(前提:%d 行)〔wr4〕" % main.ui.screens.row_pacer.pending_rows().size())
|
||||
main._begin_session(false)
|
||||
main.begin_session(false)
|
||||
check(main.ui.screens.row_pacer.pending_rows().is_empty(), "会话更替后待播队列清零(_begin_session 内机制 flush)〔wr4〕")
|
||||
_finish()
|
||||
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
# tests/unit/presentation/presentation_encapsulation_sweep_test.gd — 架构审查 presentation 层封装修复·四族结构性零命中扫描宿主
|
||||
# 断言面(全部带「扫描面非空前置+正锚」——非空防扫描面为空静默绿,正锚证新面在用):
|
||||
# V1 A1 三内部数据类(level_map_screen/_MapState、battle_end_overlay/_EndOverlayState、debug_overlay/_DebugState):
|
||||
# 非空+class 正锚+实例私取模式零命中(_map\._/st\._)+42 公开字段逐个正锚+类名文件外零消费者
|
||||
# V2 A2 拆件子件(standee_body→vitals/motion、board_view→board_hover):
|
||||
# 非空+子件私取模式零命中(st\._/mo\._/_hover_comp\._)+新公开面正锚
|
||||
# V3 B 组合根交接面(main+五分文件):
|
||||
# 非空+root\._ 与 MainXxxFlow\._ 私取零命中+束消费/新公开面正锚
|
||||
# (C 断言组随同批 T5 落地——本件四组齐后为「presentation 私取 sweep 恒零」结构性主证)
|
||||
# 手法沿 encapsulation_sweep_test(5.1 卡)源 token 扫描先例,不并件——扫描面与尾缀语义本批特有(卡 §12 同款论证)。
|
||||
# 运行:godot --headless --path game --script res://tests/unit/presentation/presentation_encapsulation_sweep_test.gd
|
||||
extends SceneTree
|
||||
|
||||
const Kit := preload("res://tests/unit/test_kit.gd")
|
||||
|
||||
const A1_FILES := {
|
||||
"res://presentation/views/level_map_screen.gd": {
|
||||
"cls": "_MapState", "inst": "_map",
|
||||
"fields": ["abandon_timer", "attr_value", "bonus_row", "bonus_value", "canvas", "drawn_disp",
|
||||
"feat_value", "gp_value", "hp_bar", "hp_value", "layer_decor", "level_label", "runner", "title"],
|
||||
},
|
||||
"res://presentation/views/battle_end_overlay.gd": {
|
||||
"cls": "_EndOverlayState", "inst": "st",
|
||||
"fields": ["board_rect", "bonus_name", "bonus_row_l", "bonus_val", "cele", "crest_bricks", "dim",
|
||||
"fx_counts", "ks_name", "ks_row", "ks_val", "rows_box", "seal", "verdict"],
|
||||
},
|
||||
"res://presentation/views/debug_overlay.gd": {
|
||||
"cls": "_DebugState", "inst": "st",
|
||||
"fields": ["atk", "atk_val", "base_atk", "base_hp", "ctx_locked", "db", "eff", "hp", "hp_val",
|
||||
"run_data", "run_labels", "sim_count", "sim_out", "sim_running"],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
_sweep_a1_data_classes()
|
||||
_sweep_a2_subparts()
|
||||
_sweep_b_combo_root()
|
||||
_sweep_c_misc()
|
||||
quit(Kit.finish("presentation_encapsulation_sweep_test"))
|
||||
|
||||
|
||||
# 源文件读取+非空前置(返 "" 即已判红——调用侧跳过后续正则断言)
|
||||
func _src(path: String) -> String:
|
||||
var src := FileAccess.get_file_as_string(path)
|
||||
Kit.check("扫描面非空前置:%s" % path, src.length() > 0)
|
||||
return src
|
||||
|
||||
|
||||
# 正则零命中断言(-pattern 意图写入断言文案——可 grep 定位;suffix 区分族组)
|
||||
func _zero(pattern: String, src: String, label: String, suffix: String = "pres封装 V1") -> void:
|
||||
var re := RegEx.create_from_string(pattern)
|
||||
Kit.check("%s:%s 零命中〔%s〕" % [label, pattern, suffix], re.search(src) == null)
|
||||
|
||||
|
||||
# 正锚在案断言(正则命中 ≥1——新公开面/新形态在用;suffix 区分族组)
|
||||
func _anchor(pattern: String, src: String, label: String, suffix: String = "pres封装 V1") -> void:
|
||||
var re := RegEx.create_from_string(pattern)
|
||||
Kit.check("%s:%s 正锚在案〔%s〕" % [label, pattern, suffix], re.search(src) != null)
|
||||
|
||||
|
||||
# ② V2 A2 拆件子件(行为归位子件公开方法、读走公开读法、身份构造交付)
|
||||
func _sweep_a2_subparts() -> void:
|
||||
var body := _src("res://presentation/views/standee_body.gd")
|
||||
if body.length() > 0:
|
||||
_zero("\\bst\\._|\\bmo\\._", body, "子件私取模式零命中(standee_body)", "pres封装 V2")
|
||||
_anchor("\\.configure_identity\\(", body, "身份构造交付正锚(standee_body)", "pres封装 V2")
|
||||
_anchor("\\.play_hp_flash\\(", body, "闪白归位正锚(standee_body)", "pres封装 V2")
|
||||
_anchor("\\.show_intent\\(|\\.hide_intent\\(", body, "意图行为正锚(standee_body)", "pres封装 V2")
|
||||
_anchor("mo\\.portrait_sprite\\(\\)", body, "绘制读口正锚(standee_body)", "pres封装 V2")
|
||||
var board := _src("res://presentation/views/board_view.gd")
|
||||
if board.length() > 0:
|
||||
_zero("_hover_comp\\._", board, "子件私取模式零命中(board_view)", "pres封装 V2")
|
||||
_anchor("_hover_comp\\.(rebind|reset|hover_at|detect_chord|refresh_after_paint|on_shell_mouse_exited|hover_pos)",
|
||||
board, "悬停七口正锚(board_view)", "pres封装 V2")
|
||||
|
||||
|
||||
# ③ V3 B 组合根交接面(main+五分文件——成对单行转发梯换代为公开入口+Callable 束)
|
||||
func _sweep_b_combo_root() -> void:
|
||||
var b_files := {
|
||||
"res://presentation/main.gd": false,
|
||||
"res://presentation/main_ui_build.gd": true,
|
||||
"res://presentation/main_adventure_flow.gd": true,
|
||||
"res://presentation/main_overlay_flow.gd": true,
|
||||
"res://presentation/main_debug_flow.gd": true,
|
||||
"res://presentation/main_esc_flow.gd": true,
|
||||
}
|
||||
for path in b_files:
|
||||
var src := _src(path)
|
||||
if src.length() == 0:
|
||||
continue
|
||||
if b_files[path]:
|
||||
_zero("root\\._[a-zA-Z]", src, "root 私取零命中(%s)" % path.get_file(), "pres封装 V3")
|
||||
_zero("Main(Overlay|Adventure|Debug|Esc)Flow\\._[a-zA-Z]", src, "分文件私取零命中(%s)" % path.get_file(), "pres封装 V3")
|
||||
var main_src := FileAccess.get_file_as_string("res://presentation/main.gd")
|
||||
if main_src.length() > 0:
|
||||
for fn in ["set_treasure_ui", "treasure_ui", "set_bonus", "bonus", "set_camp_pres", "camp_pres",
|
||||
"set_debug", "debug", "set_tune_sim", "tune_sim", "set_confirm_consumer", "refresh_presentations",
|
||||
"quit_game", "begin_session", "open_save_select", "seed_value"]:
|
||||
_anchor("func %s\\(" % fn, main_src, "main 公开口正锚(%s)" % fn, "pres封装 V3")
|
||||
var wiring := FileAccess.get_file_as_string("res://presentation/main_ui_build.gd")
|
||||
if wiring.length() > 0:
|
||||
_anchor("handlers\\[\"board_action\"\\]", wiring, "束消费正锚(ui_build)", "pres封装 V3")
|
||||
_anchor("root\\.set_treasure_ui\\(", wiring, "句柄设置口正锚(ui_build)", "pres封装 V3")
|
||||
_anchor("root\\.camp_pres\\(\\)\\.", wiring, "句柄读投影正锚(ui_build)", "pres封装 V3")
|
||||
var adv := FileAccess.get_file_as_string("res://presentation/main_adventure_flow.gd")
|
||||
if adv.length() > 0:
|
||||
_anchor("handlers\\[\"adventure_finished\"\\]", adv, "束消费正锚(adventure)", "pres封装 V3")
|
||||
_anchor("root\\.bonus\\(\\)\\.", adv, "句柄读投影正锚(adventure)", "pres封装 V3")
|
||||
|
||||
|
||||
# ④ V4 C 杂项(UiKit 私有工厂公开化+cell_mark 构造交付)
|
||||
func _sweep_c_misc() -> void:
|
||||
var offenders: PackedStringArray = []
|
||||
var scanned := 0
|
||||
_walk_gd_for("res://", "UiKit\\._[a-zA-Z]", "res://presentation/views/ui_kit.gd", offenders)
|
||||
scanned = _scanned
|
||||
Kit.check("UiKit 私有工厂全库零外调(扫描面 %d 件非空前置;越界=%s)〔pres封装 V4〕"
|
||||
% [scanned, str(offenders)], offenders.is_empty())
|
||||
var cm := _src("res://presentation/views/cell_mark_overlay.gd")
|
||||
if cm.length() > 0:
|
||||
_zero("_owner\\._", cm, "宿主私有回引零命中(cell_mark_overlay)", "pres封装 V4")
|
||||
_anchor("_mask_set\\.is_valid\\(\\)", cm, "构造交付正锚(cell_mark_overlay)", "pres封装 V4")
|
||||
var uk := _src("res://presentation/views/ui_kit.gd")
|
||||
if uk.length() > 0:
|
||||
_anchor("func (gradient_base|beams_rect|switch_toggle)\\(", uk, "三工厂公开正锚(ui_kit)", "pres封装 V4")
|
||||
|
||||
|
||||
func _walk_gd_for(dir_path: String, pattern: String, exclude: String, offenders: PackedStringArray) -> void:
|
||||
var re := RegEx.create_from_string(pattern)
|
||||
var dir := DirAccess.open(dir_path)
|
||||
if dir == null:
|
||||
return
|
||||
dir.list_dir_begin()
|
||||
var name := dir.get_next()
|
||||
while name != "":
|
||||
var full := dir_path.path_join(name)
|
||||
if dir.current_is_dir():
|
||||
if not name.begins_with("."):
|
||||
_walk_gd_for(full, pattern, exclude, offenders)
|
||||
elif name.ends_with(".gd") and full != exclude:
|
||||
_scanned += 1
|
||||
if re.search(FileAccess.get_file_as_string(full)) != null:
|
||||
offenders.append(full)
|
||||
name = dir.get_next()
|
||||
dir.list_dir_end()
|
||||
|
||||
|
||||
# ① V1 A1 三内部数据类(字段公开化——宿主直读写内类私有字段清零)
|
||||
func _sweep_a1_data_classes() -> void:
|
||||
for path in A1_FILES:
|
||||
var spec: Dictionary = A1_FILES[path]
|
||||
var src := _src(path)
|
||||
if src.length() == 0:
|
||||
continue
|
||||
var cls: String = spec["cls"]
|
||||
var inst: String = spec["inst"]
|
||||
_anchor("class %s" % cls, src, "class 正锚(%s)" % cls)
|
||||
_zero("%s\\._[a-zA-Z]" % inst, src, "实例私取模式零命中(%s.%s)" % [path.get_file(), inst])
|
||||
for field in spec["fields"]:
|
||||
_anchor("\\.%s\\b" % field, src, "公开字段正锚(%s.%s)" % [cls, field])
|
||||
# 类名文件外零消费者:全 game/ .gd 递归扫描(排除三类宿主文件与本测试件自身——本件源文本含类名 token)
|
||||
var hosts := ["res://presentation/views/level_map_screen.gd",
|
||||
"res://presentation/views/battle_end_overlay.gd", "res://presentation/views/debug_overlay.gd"]
|
||||
var offenders: PackedStringArray = []
|
||||
_walk_gd("res://", hosts, offenders)
|
||||
Kit.check("A1 三类文件外零消费者(全 game/ .gd 扫描面 %d 件非空前置;越界=%s)"
|
||||
% [_scanned, str(offenders)], offenders.is_empty())
|
||||
|
||||
|
||||
var _scanned := 0
|
||||
|
||||
|
||||
func _walk_gd(dir_path: String, exclude: Array, offenders: PackedStringArray) -> void:
|
||||
var dir := DirAccess.open(dir_path)
|
||||
if dir == null:
|
||||
return
|
||||
dir.list_dir_begin()
|
||||
var name := dir.get_next()
|
||||
while name != "":
|
||||
var full := dir_path.path_join(name)
|
||||
if dir.current_is_dir():
|
||||
if not name.begins_with("."):
|
||||
_walk_gd(full, exclude, offenders)
|
||||
elif name.ends_with(".gd") and not exclude.has(full) and not full.contains("presentation_encapsulation_sweep_test"):
|
||||
_scanned += 1
|
||||
var src := FileAccess.get_file_as_string(full)
|
||||
for cls in ["_MapState", "_EndOverlayState", "_DebugState"]:
|
||||
if src.contains(cls):
|
||||
offenders.append("%s:%s" % [full, cls])
|
||||
name = dir.get_next()
|
||||
dir.list_dir_end()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bk88rqu43srjn
|
||||
@@ -0,0 +1,52 @@
|
||||
# tests/unit/presentation/seal_mark_params_test.gd — 封印印记参数化绘制守门(卡④ R1–R4)
|
||||
# 断言面:五常量取值逐项相等(ui_kit ↔ art-bible §8.2 对照);着色器文件存在且七个 uniform 名在文;
|
||||
# 迷雾门控(seal_on × fog_state → 封印子件可见性)〔seal批 V1–V4〕。
|
||||
# 运行:godot --headless --path game --script res://tests/unit/presentation/seal_mark_params_test.gd
|
||||
extends SceneTree
|
||||
|
||||
const Kit := preload("res://tests/unit/test_kit.gd")
|
||||
const SEAL_SHADER := "res://presentation/views/seal_mark.gdshader"
|
||||
const CELL_UNIT := preload("res://presentation/views/board_cell_unit.gd")
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
_test_param_values()
|
||||
_test_shader_uniforms()
|
||||
_test_fog_gate()
|
||||
quit(Kit.finish("seal_mark_params_test"))
|
||||
|
||||
|
||||
# R1–R3/R7 前置:五常量取值逐项相等(art-bible §8.2 定值)
|
||||
func _test_param_values() -> void:
|
||||
Kit.check("SEAL_RING_FRAC = 0.82〔seal批 V1〕", UiKit.SEAL_RING_FRAC == 0.82)
|
||||
Kit.check("SEAL_WALL_FRAC = 0.11〔seal批 V2〕", UiKit.SEAL_WALL_FRAC == 0.11)
|
||||
Kit.check("SEAL_ARM_FRAC = 0.156〔seal批 V2〕", UiKit.SEAL_ARM_FRAC == 0.156)
|
||||
Kit.check("SEAL_HALO_ALPHA = 0.50〔seal批 V3〕", UiKit.SEAL_HALO_ALPHA == 0.50)
|
||||
Kit.check("SEAL_HALO_GAMMA = 1.6〔seal批 V3〕", UiKit.SEAL_HALO_GAMMA == 1.6)
|
||||
|
||||
|
||||
# R1–R3/R7:着色器存在+七个 uniform 名在文(参数注入面契约)
|
||||
func _test_shader_uniforms() -> void:
|
||||
var src := FileAccess.get_file_as_string(SEAL_SHADER)
|
||||
Kit.check("着色器文件存在且非空:%s〔seal批 V1〕" % SEAL_SHADER, src.length() > 0)
|
||||
if src.length() == 0:
|
||||
return
|
||||
for uniform in ["cell_px", "ring_frac", "wall_frac", "arm_frac", "halo_frac", "halo_alpha", "halo_gamma", "mark_col"]:
|
||||
Kit.check("着色器 uniform 在文:%s〔seal批 V1〕" % uniform, src.contains(uniform))
|
||||
Kit.check("着色器含光圈半径夹取行(r ≤ cell_px/2——光圈不出格)〔seal批 V3〕",
|
||||
src.contains("min(r, cell_px * 0.5)"))
|
||||
|
||||
|
||||
# R4 迷雾门控:seal_on × fog_state → 封印子件可见性(内部字段直查=受控例外——子件可见性无公开可观测面,
|
||||
# 卡面 R4 断言即 prescribe 子件 visible 直查;对照表该行注记理由)
|
||||
func _test_fog_gate() -> void:
|
||||
var u: Node2D = CELL_UNIT.new()
|
||||
u.seal_on = true
|
||||
Kit.check("seal_on 即挂封印子件〔seal批 V4〕", u._seal_layer != null)
|
||||
u.fog_state = 1
|
||||
Kit.check("雾态(fog_state=1)封印子件不可见〔seal批 V4〕", u._seal_layer.visible == false)
|
||||
u.fog_state = 0
|
||||
Kit.check("非雾态(fog_state=0)封印子件可见〔seal批 V4〕", u._seal_layer.visible == true)
|
||||
u.seal_on = false
|
||||
Kit.check("撤 seal_on 即撤子件〔seal批 V4〕", u._seal_layer == null)
|
||||
u.free()
|
||||
@@ -0,0 +1 @@
|
||||
uid://cc7y2oxe2d15p
|
||||
@@ -161,5 +161,5 @@ func _test_beams_retired() -> void:
|
||||
not cmap.has("BEAM_LIGHT") and not cmap.has("STAGE_BG"))
|
||||
var uf := FileAccess.open("res://presentation/views/ui_kit.gd", FileAccess.READ)
|
||||
var usrc := uf.get_as_text() if uf != null else ""
|
||||
Kit.check("菜单斜光带 _beams_rect 仍在(不在退役面)〔舞台背景 V5〕",
|
||||
usrc.contains("_beams_rect"))
|
||||
Kit.check("菜单斜光带 beams_rect 仍在(不在退役面)〔舞台背景 V5〕",
|
||||
usrc.contains("beams_rect"))
|
||||
@@ -623,7 +623,7 @@ func _smoke_texture_filter() -> void:
|
||||
Kit.check("V3 白名单在位:暗角 vignette 显式 LINEAR",
|
||||
vg.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR)
|
||||
vg.free()
|
||||
var bm = uikit_s._beams_rect()
|
||||
var bm = uikit_s.beams_rect()
|
||||
Kit.check("V3 白名单在位:光柱 beams 显式 LINEAR",
|
||||
bm.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR)
|
||||
# T4 光柱连续 alpha 结构断言(观感归目检):带心峰值 0.05/带缘连续过渡/纵向衰减上亮下暗
|
||||
@@ -648,7 +648,7 @@ func _smoke_texture_filter() -> void:
|
||||
Kit.check("T4 光柱纵向衰减生效(上半亮度 %.0f > 下半 %.0f 的 2 倍——上亮下暗)" % [top_sum, bot_sum],
|
||||
top_sum > bot_sum * 2.0)
|
||||
bm.free()
|
||||
var gb = uikit_s._gradient_base(Color.BLACK, Color.WHITE)
|
||||
var gb = uikit_s.gradient_base(Color.BLACK, Color.WHITE)
|
||||
Kit.check("V3 白名单在位:地图渐变 gradient_base 显式 LINEAR",
|
||||
gb.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR)
|
||||
gb.free()
|
||||
|
||||
@@ -8,5 +8,7 @@
|
||||
game/core/adventure_runner.gd|quality/max-class-variables = 23
|
||||
game/core/level_runner.gd|quality/max-class-variables = 30
|
||||
game/core/level_runner.gd|quality/max-public-methods = 33
|
||||
game/presentation/main.gd|quality/max-public-methods = 32
|
||||
game/presentation/views/ui_kit.gd|quality/max-public-methods = 23
|
||||
game/presentation/views/board_view.gd|quality/max-class-variables = 31
|
||||
game/presentation/views/board_view.gd|quality/max-public-methods = 27
|
||||
Reference in new issue
Block a user