# VERTICAL SLICE - NOT FOR PRODUCTION # 探针共用面:树寻址助手 + 存档/设置/元进度隔离样板单点。 # 覆盖:ui_probe / ui_click_probe / shot_probe / fx_shot_probe 四探针的树寻址样板 # 与隔离样板(各探针隔离文件名后缀 tag 保持不变)。 # 用法:探针侧 `const ProbeUiUtil := preload("res://probe/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 名 = _settings.cfg,与被收口探针原文件名逐一同名); # 「继续上局」按钮重刷;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, swap_save: bool = true): # refresh_resume 参数已随卡② _refresh_resume_button 删除退役(mn7) 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) return iso # 营地经进档开层(卡② mn9 新路径——campbtn 已撤;槽模板注入隔离命名空间,防碰真实槽 # user://save_slot_N.json——真实档红线)。创建=激活=暂行开营地(卡③ Q3 乙落点); # 驱动后写盘目标=隔离槽文件(持久化断言读 user://_slot_1.json)。返回 false=新档钮缺失(驱动中断)。 static func open_camp_via_savesel(main, tag: String) -> bool: var sm = main.get_node("/root/SaveManager") sm.attach_slot_template("user://%s_slot_%%d.json" % tag) for i in range(1, 4): # 夹具清场——空槽起(陈旧槽文件会翻有档态、新档钮缺席) for suf in ["", ".tmp", ".corrupt.bak"]: var sp: String = sm.slot_path(i) + suf if FileAccess.file_exists(sp): DirAccess.remove_absolute(sp) main._open_save_select() var new_btn: Button = main.save_select_overlay._new_btns.get(1) if new_btn == null: return false new_btn.pressed.emit() # 真实按钮信号路径 → _on_save_select_new → create_slot+open_camp return true