122 lines
5.3 KiB
GDScript
122 lines
5.3 KiB
GDScript
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||
# 探针共用面:树寻址助手 + 存档/设置/元进度隔离样板单点。
|
||
# 覆盖:ui_probe / ui_click_probe / shot_probe / fx_shot_probe 四探针的树寻址样板
|
||
# 与隔离样板(各探针隔离文件名后缀 tag 保持不变)。
|
||
# 用法:探针侧 `const ProbeUiUtil := preload("res://smoke/probe_ui_util.gd")` 后静态调用
|
||
#(探针载体含 --script 场景两种,均无全局标识符——禁 class_name 裸引用)。
|
||
extends RefCounted
|
||
|
||
const SaveManagerScript := preload("res://core/save_manager.gd")
|
||
const MetaProgressionScript := preload("res://core/meta_progression.gd")
|
||
const SettingsStoreScript := preload("res://presentation/settings_store.gd")
|
||
|
||
# ── 树寻址(递归只读——按钮/标签句柄未存组合根成员的探针专用面) ──────────────
|
||
# 匹配模式参数化(历史两种语义,禁混用禁静默改变):
|
||
# mode="prefix" = text.begins_with(needle)(ui_probe / ui_click_probe 历史语义)
|
||
# mode="contains" = text.contains(needle)(shot_probe / fx_shot_probe 历史语义)
|
||
# 调用点替换时逐一核对原语义后选参。
|
||
|
||
static func find_button(root: Node, needle: String, mode: String = "prefix") -> Button:
|
||
if root == null:
|
||
return null
|
||
if root is Button and _text_match(String(root.text), needle, mode):
|
||
return root
|
||
for child in root.get_children():
|
||
var found := find_button(child, needle, mode)
|
||
if found != null:
|
||
return found
|
||
return null
|
||
|
||
|
||
# CheckButton 同款寻址(ui_click_probe 语义 = contains——断言设置面旧入口不在)
|
||
static func find_check_button(root: Node, needle: String) -> CheckButton:
|
||
if root == null:
|
||
return null
|
||
if root is CheckButton and String(root.text).contains(needle):
|
||
return root
|
||
for child in root.get_children():
|
||
var found := find_check_button(child, needle)
|
||
if found != null:
|
||
return found
|
||
return null
|
||
|
||
|
||
# Label 按文案包含寻址(shot_probe GP 徽章读数锚)
|
||
static func find_label(root: Node, text_fragment: String) -> Label:
|
||
if root == null:
|
||
return null
|
||
if root is Label and String(root.text).contains(text_fragment):
|
||
return root
|
||
for child in root.get_children():
|
||
var found := find_label(child, text_fragment)
|
||
if found != null:
|
||
return found
|
||
return null
|
||
|
||
|
||
# 控件实测几何(shot_probe checker 文字框取样坐标源——禁硬编码像素,分辨率档位自适应)
|
||
static func label_rect(root: Node, text_fragment: String) -> Dictionary:
|
||
if root == null:
|
||
return {}
|
||
if root is Label and String(root.text).contains(text_fragment):
|
||
return {"x": root.global_position.x, "y": root.global_position.y,
|
||
"w": root.size.x, "h": root.size.y}
|
||
for child in root.get_children():
|
||
var r := label_rect(child, text_fragment)
|
||
if not r.is_empty():
|
||
return r
|
||
return {}
|
||
|
||
|
||
# 按脚本名寻址(resource_path contains——shot_probe hero 面板锚)
|
||
static func find_by_script(root: Node, script_name: String) -> Node:
|
||
if root == null:
|
||
return null
|
||
var s: Script = root.get_script()
|
||
if s != null and String(s.resource_path).contains(script_name):
|
||
return root
|
||
for child in root.get_children():
|
||
var r := find_by_script(child, script_name)
|
||
if r != null:
|
||
return r
|
||
return null
|
||
|
||
|
||
static func _text_match(text: String, needle: String, mode: String) -> bool:
|
||
return text.begins_with(needle) if mode == "prefix" else text.contains(needle)
|
||
|
||
|
||
# ── 存档/设置/元进度三面隔离(探针禁碰真实档 user://save.json——SaveManager 路径注入纪律) ──
|
||
# tag = 隔离档名(不带扩展名,如 "smoke_input_probe_save"——各探针后缀保持不变);先删后建防陈旧档泄入。
|
||
# mark_first_done = 对齐真实档游玩史口径(模板抽签路径不变);with_settings = 连设置面换注
|
||
#(隔离 cfg 名 = <tag>_settings.cfg,与被收口探针原文件名逐一同名);refresh_resume = 组合根
|
||
# 「继续上局」按钮重刷;swap_save = 是否换注全局 SaveManager(营地先行段只换 meta 侧不换 SaveManager)。
|
||
# 返回隔离 SaveManager 实例(调用方常需后续直读余额/重读档)。
|
||
# SaveManager/MetaProgression 是 autoload 单例——隔离面走单例原地重置
|
||
#(attach_path 沿 RngService.set_master_seed 先例;MetaProgression 持 save 引用透传,
|
||
# swap_save=false 分支换 meta 侧引用=独立 iso 实例,只换 meta 不换 save_manager)。
|
||
static func isolate_save(main, tag: String, mark_first_done: bool = false, with_settings: bool = false,
|
||
refresh_resume: bool = false, swap_save: bool = true):
|
||
var save_path := "user://%s.json" % tag
|
||
for p in [save_path, save_path + ".tmp", "user://%s_settings.cfg" % tag]:
|
||
if FileAccess.file_exists(p):
|
||
DirAccess.remove_absolute(p)
|
||
var iso = SaveManagerScript.new(save_path)
|
||
iso.load_from_disk()
|
||
if mark_first_done:
|
||
iso.mark_first_adventure_done()
|
||
# --script 模式无裸 autoload 标识符(run_all.gd 头注)——经 main 节点路径解析
|
||
var sm = main.get_node("/root/SaveManager")
|
||
if swap_save:
|
||
sm.attach_path(save_path)
|
||
if mark_first_done:
|
||
sm.mark_first_adventure_done()
|
||
iso = sm
|
||
else:
|
||
main.get_node("/root/MetaProgression").save = iso # 只换 meta 侧(营地段语义)——SaveManager 全局不动
|
||
if with_settings:
|
||
main.settings_store = SettingsStoreScript.new("user://%s_settings.cfg" % tag)
|
||
if refresh_resume:
|
||
main._refresh_resume_button()
|
||
return iso
|