Files
spellforge/scripts/autoloads/profile_manager.gd
T
2026-07-20 10:56:52 +08:00

119 lines
3.7 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## ProfileManager — 局内 Run 存档 A/B 双槽(Autoload: ProfileManager
## 权威来源:architecture_design.md ADR-A2
## 下面实现了:
## - A/B 双槽交替写入防崩溃
## - schema_version 字段必须
## - 所有读取必须经过 _migrate_run()
## - NOTIFICATION_WM_CLOSE_REQUEST 尽力写最后一笔
extends Node
const SCHEMA_VERSION: int = 2 # v2:新增 wandCore/deck/bench)持久化
const PATH_A: String = "user://run_a.json"
const PATH_B: String = "user://run_b.json"
var _current_slot: String = PATH_A # 上次写入的槽
var _dirty: bool = false
## 法杖状态提供者(CombatManager 在 _ready 注册);须有 get_wand_save_data/apply_wand_save_data
var _wand_provider: Object = null
func set_wand_provider(p: Object) -> void:
_wand_provider = p
func _ready() -> void:
get_tree().root.connect("close_requested", _on_close_request)
func _on_close_request() -> void:
if _dirty:
save_run()
_dirty = false
## 写入当前 Run 状态(交替槽位)
func save_run() -> void:
var data: Dictionary = _collect_run_data()
data["schema_version"] = SCHEMA_VERSION
data["saved_at"] = Time.get_unix_time_from_system()
var json_str: String = JSON.stringify(data)
var target: String = _next_slot()
var file := FileAccess.open(target, FileAccess.WRITE)
if file:
file.store_string(json_str)
file.close()
_current_slot = target
_dirty = false
## 加载 Run,自动选择未损坏的最新槽
func load_run() -> Dictionary:
var data_a: Dictionary = _read_slot(PATH_A)
var data_b: Dictionary = _read_slot(PATH_B)
var best: Dictionary
if data_a.is_empty() and data_b.is_empty():
return {}
elif data_a.is_empty():
best = data_b
elif data_b.is_empty():
best = data_a
else:
var ta: int = int(data_a.get("saved_at", 0))
var tb: int = int(data_b.get("saved_at", 0))
best = data_a if ta > tb else data_b
return _migrate_run(best)
## 应用局内状态
func apply_run(data: Dictionary) -> void:
if data.is_empty():
return
PlayerStats.load_save_data(data.get("player_stats", {}))
WaveManager.current_wave = int(data.get("wave_num", 0))
var shop_seed: int = int(data.get("shop_seed", 0))
if shop_seed > 0:
ShopManager._shop_seed = shop_seed
# 恢复法杖(Core/deck/bench
if data.has("wand") and is_instance_valid(_wand_provider) and _wand_provider.has_method("apply_wand_save_data"):
_wand_provider.apply_wand_save_data(data.get("wand", {}))
## 清除 Run(死亡 / 新开局)
func clear_run() -> void:
for path in [PATH_A, PATH_B]:
if FileAccess.file_exists(path):
DirAccess.remove_absolute(ProjectSettings.globalize_path(path))
_dirty = false
func mark_dirty() -> void:
_dirty = true
func _next_slot() -> String:
return PATH_B if _current_slot == PATH_A else PATH_A
func _collect_run_data() -> Dictionary:
var d: Dictionary = {
"wave_num": WaveManager.current_wave,
"shop_seed": ShopManager.get_shop_seed(),
"player_stats": PlayerStats.get_save_data(),
}
if is_instance_valid(_wand_provider) and _wand_provider.has_method("get_wand_save_data"):
d["wand"] = _wand_provider.get_wand_save_data()
return d
func _read_slot(path: String) -> Dictionary:
if not FileAccess.file_exists(path):
return {}
var text: String = FileAccess.get_file_as_string(path)
if text.is_empty():
return {}
var parsed = JSON.parse_string(text)
if parsed is Dictionary:
return parsed
return {}
## 迁移旧存档
func _migrate_run(data: Dictionary) -> Dictionary:
var ver: int = int(data.get("schema_version", 0))
if ver < 1:
# v0→v1: 无破坏性字段变更
data["schema_version"] = 1
if ver < 2:
# v1→v2: 旧档无 wand 字段;apply_run 检测缺失即保留默认法杖,无需补字段
data["schema_version"] = 2
return data