extends Node enum VNLoadSource { NEW_GAME = 300, FROM_MANUAL_SAVE = 301, FROM_AUTOSAVE = 302, } enum VNSaveTarget { MANUAL_SAVE = 401, AUTOSAVE = 402, } const GLOBAL_DATA_PATH = "user://data.json" const SAVES_DIR = "user://saves" const MAX_SAVES = 24 signal loaded signal updated_save_files(save_file_list) signal new_manual_save(save_file) signal new_autosave(save_file) var has_loaded = false var can_continue = false var current_load_source: VNLoadSource = VNLoadSource.NEW_GAME var current_load_path = "" var current_load_file = null var save_files = [] var global_data = null var new_game_save_file = {} func _ready(): DirAccess.make_dir_recursive_absolute(SAVES_DIR) # Populate previously saved files for file_name in DirAccess.get_files_at(SAVES_DIR): if file_name.length() == 7 and file_name.right(5) == ".json": can_continue = true var path = "%s/%s" % [SAVES_DIR, file_name] var file = FileAccess.open(path, FileAccess.READ) var data = JSON.parse_string(file.get_as_text()) file.close() save_files.append({ "idx": int(file_name.left(2)), "path": path, "timestamp": data["timestamp"], "name": data["name"], "is_autosave": data.get("is_autosave", false), }) save_files.sort_custom(func (a, b): return Time.get_unix_time_from_datetime_string(a["timestamp"]) > Time.get_unix_time_from_datetime_string(b["timestamp"])) has_loaded = true loaded.emit() func create_save_file(target: VNSaveTarget, data: Variant, save_name: String = "") -> String: var timestamp = Time.get_datetime_string_from_system(false, true) var idx = 1 var path = "" var is_autosave = target == VNSaveTarget.AUTOSAVE can_continue = true if save_files.size() > 0: idx = save_files[0]["idx"] % 99 + 1 # Avoid creating a 00.json file path = "%s/%02d.json" % [SAVES_DIR, idx] for i in range(save_files.size() - 1, -1, -1): # Remove file if MAX_SAVES limit would be exceeded or if "idx" wraps around and overwrites this if i >= MAX_SAVES - 1 or save_files[i]["idx"] == idx: # Should be safe to remove, since we're resizing from the right var save_to_delete = save_files.pop_at(i) DirAccess.remove_absolute(save_to_delete["path"]) var file = FileAccess.open(path, FileAccess.WRITE) file.store_string(JSON.stringify({ "timestamp": timestamp, "name": save_name, "is_autosave": is_autosave, "data": data, })) file.close() save_files.push_front({ "idx": idx, "path": path, "timestamp": timestamp, "name": save_name, "is_autosave": is_autosave, }) match target: VNSaveTarget.MANUAL_SAVE: new_manual_save.emit(save_files[0]) VNSaveTarget.AUTOSAVE: new_autosave.emit(save_files[0]) _: assert(false, "Unknown save target %s" % target) return path func load_save_file(load_target: VNLoadSource, load_file = null) -> Variant: current_load_source = load_target match load_target: VNLoadSource.NEW_GAME: return new_game_save_file #VNLoadSource.FROM_MANUAL_SAVE when not load_file: # current_load_source = VNLoadSource.FROM_AUTOSAVE VNLoadSource.FROM_AUTOSAVE: current_load_path = load_file["path"] VNLoadSource.FROM_MANUAL_SAVE: current_load_path = load_file["path"] _: assert(false, "Can't figure out where to load saved data from!") if not FileAccess.file_exists(current_load_path): push_warning("Load file not found, returning new game data...") current_load_path = "" current_load_source = VNLoadSource.NEW_GAME return {} return JSON.parse_string(FileAccess.open(current_load_path, FileAccess.READ).get_as_text())["data"] func delete_save_file(load_file = null): var i = save_files.find(load_file) if i == -1: push_warning("Couldn't locate file for deletion!") return DirAccess.remove_absolute(load_file["path"]) save_files.remove_at(i) updated_save_files.emit(save_files) func set_global_data(key: String, value: Variant): if FileAccess.file_exists(GLOBAL_DATA_PATH): var file = FileAccess.open(GLOBAL_DATA_PATH, FileAccess.READ) global_data = JSON.parse_string(file.get_as_text()) file.close() elif not global_data: global_data = {} var prev_value = global_data.get(key) global_data[key] = value if value != prev_value: var file = FileAccess.open(GLOBAL_DATA_PATH, FileAccess.WRITE) file.store_string(JSON.stringify(global_data)) file.close() return prev_value func get_global_data(key: String, default = null): if not global_data: if FileAccess.file_exists(GLOBAL_DATA_PATH): var file = FileAccess.open(GLOBAL_DATA_PATH, FileAccess.READ) global_data = JSON.parse_string(file.get_as_text()) file.close() else: global_data = {} return global_data.get(key, default)