xiandie/manager/archive_manager/archive_manager.gd

225 lines
6.5 KiB
GDScript

@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()
archive = AssembledArchive.new()
var archive_path = _get_archive_path(id)
if id < 0:
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)
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)
archives[id] = archive
# this will auto trigger signal and load the new archive
GlobalConfigManager.config.current_selected_archive_id = id
# 超过 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