xiandie/manager/archive_manager/archive_manager.gd

369 lines
10 KiB
GDScript3
Raw Normal View History

@tool
2024-12-23 01:29:31 +00:00
extends Node
signal archive_loaded
# Constants
const CURRENT_VERSION = 6
const ARCHIVE_ID_MIN = 0
const ARCHIVE_ID_MAX = 99
const ARCHIVE_ID_DIGITS = 3
# Static paths
static var user_data_root_dir := "user://data/" # must end with "/"
static var user_archives_dir := "user://data/archives/"
static var archive_prefix := "save"
# Archive management
2025-06-28 01:14:15 +00:00
var archive: AssembledArchive:
2025-05-14 20:43:55 +00:00
set(val):
archive = val
if archive:
# emit signal
archive_loaded.emit()
GlobalConfigManager.print_global_info()
2025-05-14 20:43:55 +00:00
print("use archive ", archive.resource_path)
2025-07-15 08:14:30 +00:00
var archives_dict: Dictionary[int, AssembledArchive] = {}
var archives_notes_dict: Dictionary[int, String] = {}
2024-12-23 01:29:31 +00:00
var autosave_timer := Timer.new()
2024-12-24 01:16:06 +00:00
func _ready() -> void:
# 禁用默认退出行为,在 _notification 处理 NOTIFICATION_WM_CLOSE_REQUEST 时保存数据
get_tree().set_auto_accept_quit(false)
2025-04-01 08:16:29 +00:00
process_mode = Node.PROCESS_MODE_ALWAYS
2024-12-24 11:24:55 +00:00
if not _check_dirs_and_archives():
2024-12-24 11:54:30 +00:00
_handle_load_error("存档目录", "读写")
2024-12-23 01:29:31 +00:00
return
_setup_autosave_timer()
2024-12-24 11:24:55 +00:00
# config should be loaded first
load_config()
# 在 debug or editor 模式下,直接保证有 archive
if GlobalConfig.DEBUG or Engine.is_editor_hint():
_ensure_debug_archive()
2024-12-24 11:24:55 +00:00
func _notification(what: int) -> void:
# handle window close request
if what == NOTIFICATION_WM_CLOSE_REQUEST:
2025-01-07 10:54:50 +00:00
save_all()
print("Saved all success before Quit")
# 已保存所有数据 [ID:ui_saved_all]
2025-07-02 16:32:37 +00:00
SceneManager.pop_notification("ui_saved_all")
SceneManager.quit_game()
func _setup_autosave_timer() -> void:
autosave_timer.timeout.connect(_try_auto_save)
autosave_timer.stop()
add_child(autosave_timer)
func _ensure_debug_archive() -> void:
if archives_dict.is_empty():
create_and_use_new_archive(0)
else:
# debug 模式下默认使用 0 号存档
GlobalConfigManager.config.current_selected_archive_id = 0
func _on_archive_id_changed() -> void:
2024-12-24 11:24:55 +00:00
var selected_id = GlobalConfigManager.config.current_selected_archive_id
if selected_id < 0:
return
2025-05-14 20:43:55 +00:00
print("_on_archive_id_changed id=", selected_id)
2025-07-15 08:14:30 +00:00
if not archives_dict.has(selected_id):
2025-05-14 20:43:55 +00:00
print("新建存档 ", selected_id)
2024-12-24 11:24:55 +00:00
create_and_use_new_archive(selected_id)
# 已创建新存档 [ID:ui_new_archive]
2025-07-02 16:32:37 +00:00
SceneManager.pop_notification("ui_new_archive")
2024-12-24 11:24:55 +00:00
else:
load_archive()
2024-12-23 01:29:31 +00:00
func check_autosave_options() -> void:
var config = GlobalConfigManager.config
var should_enable_autosave = (
config.auto_save_enabled
and archive
and config.auto_save_seconds > 1
)
if should_enable_autosave:
2024-12-24 11:24:55 +00:00
# reset left time
autosave_timer.stop()
autosave_timer.one_shot = false
autosave_timer.wait_time = config.auto_save_seconds
2024-12-23 01:29:31 +00:00
autosave_timer.start()
else:
autosave_timer.stop()
if GlobalConfig.DEBUG:
print(
"check_autosave_option: ",
config.auto_save_enabled,
" wait_time=",
autosave_timer.wait_time
)
2024-12-23 01:29:31 +00:00
func _try_auto_save() -> void:
2024-12-25 06:27:47 +00:00
if GlobalConfig.DEBUG:
print("Auto save")
2024-12-24 11:24:55 +00:00
if archive and GlobalConfigManager.config.auto_save_seconds > 1:
2024-12-23 01:29:31 +00:00
save_all()
# 自动保存成功 [ID:ui_auto_saved]
2025-07-02 16:32:37 +00:00
SceneManager.pop_notification("ui_auto_saved")
2024-12-23 01:29:31 +00:00
2024-12-24 11:24:55 +00:00
func _check_dirs_and_archives() -> bool:
# Ensure directories exist
_ensure_directory_exists(user_data_root_dir)
_ensure_directory_exists(user_archives_dir)
2024-12-23 01:29:31 +00:00
# Check if the archive directory is accessible
var archive_dir_access = DirAccess.open(user_archives_dir)
if not archive_dir_access:
2024-12-24 11:24:55 +00:00
_handle_load_error("存档目录", "读取")
2024-12-23 01:29:31 +00:00
return false
# Load existing archives
_load_existing_archives(archive_dir_access)
return true
func _ensure_directory_exists(dir_path: String) -> void:
if not DirAccess.dir_exists_absolute(dir_path):
DirAccess.make_dir_recursive_absolute(dir_path)
print("Create directory:", dir_path)
func _load_existing_archives(dir_access: DirAccess) -> void:
var files = dir_access.get_files()
files.sort()
2024-12-23 01:29:31 +00:00
for file in files:
if not _is_valid_archive_filename(file):
continue
var archive_info = _parse_archive_filename(file)
if not archive_info:
continue
var id = archive_info.id
var note = archive_info.note
archives_notes_dict[id] = note
if not archives_dict.has(id):
var archive_resource = _load_archive_resource(user_archives_dir + file)
if archive_resource:
archives_dict[id] = archive_resource
func _is_valid_archive_filename(filename: String) -> bool:
return filename.begins_with(archive_prefix) and filename.ends_with(GlobalConfig.RES_FILE_FORMAT)
func _parse_archive_filename(filename: String) -> Dictionary:
# format: save012_xxxxx; save000
var basename = filename.get_basename()
var id_and_note = basename.substr(archive_prefix.length()).strip_escapes().split("_", true, 1)
var id_str = id_and_note[0]
# 非三位数的 id 会被忽略
if id_str.length() != ARCHIVE_ID_DIGITS:
return {}
var id = int(id_str)
# 读取范围是 0-99
if id < ARCHIVE_ID_MIN or id > ARCHIVE_ID_MAX:
return {}
var note_str = id_and_note[1] if id_and_note.size() >= 2 else (archive_prefix + id_str)
return {
"id": id,
"note": note_str
}
func _load_archive_resource(path: String) -> AssembledArchive:
var res = ResourceLoader.load(
path, "AssembledArchive", ResourceLoader.CACHE_MODE_REPLACE_DEEP
)
if is_instance_valid(res) and res.version >= CURRENT_VERSION:
return res
else:
printerr("SKIP INVALID ARCHIVE! path=", path)
return null
2024-12-23 01:29:31 +00:00
2024-12-24 11:24:55 +00:00
# id = -1 means create a new archive, otherwise create an archive with the given id
func create_and_use_new_archive(id := -1) -> void:
_check_dirs_and_archives()
2024-12-24 11:24:55 +00:00
if id < 0:
# 如果 id 小于 0找到一个新的 id创建新存档
id = _find_next_available_id()
_create_and_save_new_archive_resoure(id)
else:
# 如果 id 大于等于 0创建指定 id 的存档
var archive_path = get_archive_path(id)
var take_over_path = FileAccess.file_exists(archive_path)
_create_and_save_new_archive_resoure(id, take_over_path)
2024-12-24 11:24:55 +00:00
# this will auto trigger signal and load the new archive
GlobalConfigManager.config.current_selected_archive_id = id
func _find_next_available_id() -> int:
var id = 0
var archive_path = get_archive_path(id)
while FileAccess.file_exists(archive_path) and id <= ARCHIVE_ID_MAX:
id += 1
archive_path = get_archive_path(id)
return id
func _create_and_save_new_archive_resoure(id: int, take_over_path := false) -> void:
var archive_path = get_archive_path(id)
archive = AssembledArchive.new() as Resource
archive.version = CURRENT_VERSION
if take_over_path:
archive.take_over_path(archive_path)
else:
archive.resource_path = archive_path
archive.archive_id = id
archive.created_time = Time.get_datetime_string_from_system(false, true)
ResourceSaver.save(archive, archive_path)
2025-07-15 08:14:30 +00:00
archives_dict[id] = archive
# 超过 999 个存档会出问题;不过这个游戏不会有这么多存档
func get_archive_path(id: int) -> String:
var id_str := str(id).pad_zeros(ARCHIVE_ID_DIGITS)
return user_archives_dir + archive_prefix + id_str + GlobalConfig.RES_FILE_FORMAT
2025-07-15 08:14:30 +00:00
func allow_resume(id := 1) -> bool:
return archives_dict.has(id)
2024-12-23 01:29:31 +00:00
func save_all() -> void:
# save config
var config = GlobalConfigManager.config
2024-12-24 11:24:55 +00:00
if config:
ResourceSaver.save(config)
# save player state
_save_player_state()
# save archive
2024-12-24 11:24:55 +00:00
if archive:
ResourceSaver.save(archive)
# reset autosave timer
check_autosave_options()
2024-12-24 11:24:55 +00:00
func _save_player_state() -> void:
if not archive:
return
var player = SceneManager.get_player() as MainPlayer
if player:
archive.player_global_position_x = player.global_position.x
archive.player_direction = player.facing_direction
2024-12-24 11:24:55 +00:00
func load_config() -> void:
if GlobalConfigManager.config:
2024-12-23 01:29:31 +00:00
return
var path = user_data_root_dir + "config" + GlobalConfig.RES_FILE_FORMAT
2024-12-23 01:29:31 +00:00
if FileAccess.file_exists(path):
var loaded_config = ResourceLoader.load(path)
if is_instance_valid(loaded_config) and loaded_config.version >= CURRENT_VERSION:
GlobalConfigManager.config = loaded_config
else:
printerr("SKIP INVALID CONFIG!")
if not GlobalConfigManager.config:
_create_default_config(path)
if not Engine.is_editor_hint():
_connect_config_signals()
func _create_default_config(path: String) -> void:
var config = GlobalConfig.new()
config.version = CURRENT_VERSION
config.resource_path = path
GlobalConfigManager.config = config
ResourceSaver.save(config, path)
func _connect_config_signals() -> void:
var config = GlobalConfigManager.config
config.current_selected_archive_id_changed.connect(_on_archive_id_changed)
config.auto_save_seconds_changed.connect(check_autosave_options)
config.auto_save_enabled_changed.connect(check_autosave_options)
2024-12-24 11:24:55 +00:00
func load_archive() -> void:
_check_dirs_and_archives()
var selected_id = 0
if GlobalConfigManager.config:
selected_id = GlobalConfigManager.config.current_selected_archive_id
2025-05-14 20:43:55 +00:00
print("load_archive ", selected_id)
2025-07-15 08:14:30 +00:00
if not archives_dict.has(selected_id):
2024-12-24 11:24:55 +00:00
_handle_load_error(str(selected_id) + " 号存档", "查找")
return
2025-07-15 08:14:30 +00:00
archive = archives_dict[selected_id]
check_autosave_options()
2024-12-24 11:24:55 +00:00
func _handle_load_error(target: String, action: String) -> void:
2025-07-02 16:32:37 +00:00
var msg = str(target) + " " + str(action) + " failed. Permission Error."
2024-12-24 11:24:55 +00:00
SceneManager.pop_notification(msg)
printerr(msg)
# TODO handle error
func set_global_entry(property: StringName, value) -> void:
if not archive:
printerr("Archive is null, cannot set global entry")
return
archive.global_data_dict[property] = value
func get_global_value(property: StringName, default = null) -> Variant:
if not archive:
printerr("Archive is null, cannot get global value")
return default
var val = archive.global_data_dict.get(property)
return default if val == null else val
2025-07-02 16:32:37 +00:00
func set_chapter_if_greater(c: int) -> void:
if not archive:
printerr("Archive is null, cannot set chapter")
return
# 1:序章2-5:一四章6:结尾
const MIN_CHAPTER = 1
const MAX_CHAPTER = 6
if c < MIN_CHAPTER or c > MAX_CHAPTER:
2025-07-02 16:32:37 +00:00
printerr("[ArchiveManager] set_chapter_if_greater: invalid chapter value: " + str(c))
return
if EventManager.get_chapter_stage() >= c:
2025-07-02 16:32:37 +00:00
return
# 进入下一章
2025-07-02 16:32:37 +00:00
print("[ArchiveManager] set_chapter_if_greater: " + str(c))
EventManager.set_stage_if_greater("current_chapter_stage", c)
func unlock_memory(id: int) -> void:
if not archive:
printerr("Archive is null, cannot unlock memory. id=", id)
return
if archive.mem_display_dict.get(id):
print("memory already unlocked. id=", id)
return
archive.mem_display_dict[id] = true
SceneManager.pop_notification("ui_notify_mem_update")