@tool extends Node signal archive_loaded static var archive: AssembledArchive # current archive static var user_root_dir := "user://data/" # must end with "/" static var archive_dir := "user://data/archives/" static var archive_prefix := "save" var archives := {} var autosave_timer := Timer.new() func _ready() -> void: # 禁用默认退出行为,在 _notification 处理 NOTIFICATION_WM_CLOSE_REQUEST 时保存数据 get_tree().set_auto_accept_quit(false) if not _check_dirs_and_archives(): _handle_load_error("存档目录", "读写") return autosave_timer.timeout.connect(_try_auto_save) autosave_timer.stop() add_child(autosave_timer) # config should be loaded first load_config() # 在 debug or editor 模式下,直接保证有 archive if GlobalConfig.DEBUG or Engine.is_editor_hint(): if archives.size() == 0: create_and_use_new_archive() else: # debug 模式下默认使用 0 号存档 GlobalConfigManager.config.current_selected_archive_id = 0 func _notification(what): # handle window close request if what == NOTIFICATION_WM_CLOSE_REQUEST: save_all() if has_node("/root/Main"): print("Saved all success before Quit") # 已保存所有数据 [ID:ui_saved_all] SceneManager.pop_notification(tr("ui_saved_all")) var tree = get_tree() tree.create_timer(1.5).timeout.connect(tree.quit) else: get_tree().quit() func _on_archive_id_changed(): var selected_id = GlobalConfigManager.config.current_selected_archive_id if selected_id < 0: return if archive: if selected_id != archive.archive_id: ResourceSaver.save(archive) archive = null else: return if not archives.has(selected_id): create_and_use_new_archive(selected_id) # 已创建新存档 [ID:ui_new_archive] SceneManager.pop_notification(tr("ui_new_archive")) else: load_archive() func check_autosave_options(): if ( GlobalConfigManager.config.auto_save_enabled and archive and GlobalConfigManager.config.auto_save_seconds > 1 ): # reset left time autosave_timer.stop() autosave_timer.one_shot = false autosave_timer.wait_time = GlobalConfigManager.config.auto_save_seconds autosave_timer.start() else: autosave_timer.stop() if GlobalConfig.DEBUG: print( "check_autosave_option: ", GlobalConfigManager.config.auto_save_enabled, " wait_time=", autosave_timer.wait_time ) func _try_auto_save(): if GlobalConfig.DEBUG: print("Auto save") if archive and GlobalConfigManager.config.auto_save_seconds > 1: save_all() # 自动保存成功 [ID:ui_auto_saved] SceneManager.pop_notification(tr("ui_auto_saved")) func _check_dirs_and_archives() -> bool: if !DirAccess.dir_exists_absolute(user_root_dir): DirAccess.make_dir_recursive_absolute(user_root_dir) print("Create user_root_dir:", user_root_dir) # Check if the archive directory is accessible if !DirAccess.dir_exists_absolute(archive_dir): DirAccess.make_dir_recursive_absolute(archive_dir) print("Create archive_dir:", archive_dir) var archive_dir_access = DirAccess.open(archive_dir) if !archive_dir_access: _handle_load_error("存档目录", "读取") # TODO pop up a dialog to inform the user return false var files = archive_dir_access.get_files() files.sort() # get archive number for file in files: if file.begins_with(archive_prefix) and file.ends_with(GlobalConfig.RES_FILE_FORMAT): var id_str = file.get_basename().substr(archive_prefix.length()) # 低于三位数的 id 会被忽略 if id_str.length() < 3: continue var id = int(id_str) # 读取范围是 0-99 if id < 0 or id > 99: continue var path = archive_dir + file if not archives.has(id): archives[id] = ResourceLoader.load( path, "AssembledArchive", ResourceLoader.CACHE_MODE_REPLACE_DEEP ) return true # 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() var archive_path = _get_archive_path(id) if id < 0: # 如果 id 小于 0,找到一个新的 id,创建新存档 id = 0 # find a new id archive_path = _get_archive_path(id) while FileAccess.file_exists(archive_path): id += 1 archive_path = _get_archive_path(id) _create_and_save_new_archive_resoure(id) else: # 如果 id 大于等于 0,创建指定 id 的存档 if FileAccess.file_exists(archive_path): _create_and_save_new_archive_resoure(id, true) else: _create_and_save_new_archive_resoure(id) archives[id] = archive # this will auto trigger signal and load the new archive GlobalConfigManager.config.current_selected_archive_id = id func _create_and_save_new_archive_resoure(id, take_over_path = false) -> void: var archive_path = _get_archive_path(id) archive = AssembledArchive.new() as Resource 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) # 超过 999 个存档会出问题;不过这个游戏不会有这么多存档 func _get_archive_path(id: int) -> String: var id_str := "" if id < 10: id_str = "00" + str(id) elif id < 100: id_str = "0" + str(id) else: id_str = str(id) return archive_dir + archive_prefix + id_str + GlobalConfig.RES_FILE_FORMAT func save_all() -> void: # save config var config = GlobalConfigManager.config if config: ResourceSaver.save(config) # player_global_position var player = SceneManager.get_player() as MainPlayer # 在此处保存 player 的位置信息 if archive and player: archive.player_global_position_x = player.global_position.x archive.player_direction = player.facing_direction if archive: ResourceSaver.save(archive) # reset autosave timer check_autosave_options() func load_config() -> void: if GlobalConfigManager.config: return var path = user_root_dir + "config" + GlobalConfig.RES_FILE_FORMAT if FileAccess.file_exists(path): var config = ResourceLoader.load(path) GlobalConfigManager.config = config else: var config = GlobalConfig.new() GlobalConfigManager.config = config ResourceSaver.save(config, path) GlobalConfigManager.config.resource_path = path if Engine.is_editor_hint(): return # connect signals GlobalConfigManager.config.current_selected_archive_id_changed.connect(_on_archive_id_changed) GlobalConfigManager.config.auto_save_seconds_changed.connect(check_autosave_options) GlobalConfigManager.config.auto_save_enabled_changed.connect(check_autosave_options) func load_archive() -> void: _check_dirs_and_archives() var selected_id = GlobalConfigManager.config.current_selected_archive_id if archive and selected_id == archive.archive_id: return if not archives.has(selected_id): _handle_load_error(str(selected_id) + " 号存档", "查找") return archive = archives[selected_id] # emit signal archive_loaded.emit() check_autosave_options() func _handle_load_error(target, action) -> void: var msg = str(target) + " " + str(action) + " 失败,请检查文件访问权限" SceneManager.pop_notification(msg) printerr(msg) # TODO handle error