class_name QuizSave extends RefCounted ## The quiz going on, saved to a file after every change, so the game goes on where it ## was when it is started again: the players with their points and characters, and every ## song with who got a point for it and whether it was played, answered or is playing. ## ## Songs downloaded ahead keep their files, so they are not downloaded again. A new game ## (Reset points, a sync or another server) is saved like any other change, so the file ## always holds the quiz as it is now. ## ## Kept in [code]current_quiz.json[/code] next to the settings, or ## [code]current_quiz_debug.json[/code] in debug mode, like the saved settings. const FILE_NAME: String = "current_quiz.json" const DEBUG_FILE_NAME: String = "current_quiz_debug.json" ## Files with another version are from a game that saved differently, and are ignored. const VERSION: int = 1 var players: Array[PlayerObject] = [] var songs: Array[SongObject] = [] var currently_playing_song: int = -1 var latest_played_song: int = 0 var game_start_song: int = 0 var first_song_played: bool = false ## Whether statistics were turned off because a player won, see [code]Quiz[/code]. var stats_paused_by_win: bool = false ## The server the songs came from. Empty when they came from a music folder. var server: String = "" ## The music folder the songs came from, or empty when they came from the server. var music_folder_path: String = "" ## How far a prepared quiz in [member music_folder_path] has come, see ## [method MusicFolder.progress]. var music_folder_progress: Dictionary = {} ## Where the characters came from, see [method Settings.characters_dir]. var characters_dir: String = "" ## Where the quiz is saved. static func path() -> String: return Settings.config_dir().path_join(DEBUG_FILE_NAME if Settings.is_debug else FILE_NAME) ## The quiz as it is now, in [code]Settings[/code] and [code]Playlist[/code]. static func capture(paused_by_win: bool) -> QuizSave: var save: QuizSave = QuizSave.new() save.players = Settings.player_array.duplicate() save.songs = Playlist.song_object_array.duplicate() save.currently_playing_song = Playlist.currently_playing_song save.latest_played_song = Playlist.latest_played_song save.game_start_song = Playlist.game_start_song save.first_song_played = Playlist.first_song_played save.stats_paused_by_win = paused_by_win if Settings.is_offline(): save.music_folder_path = Settings.music_folder.path save.music_folder_progress = Settings.music_folder.progress() else: save.server = Settings.default_path save.characters_dir = Settings.characters_dir() return save ## Reads the saved quiz, or returns null when there is none or it can't be read. static func read() -> QuizSave: if !FileAccess.file_exists(path()): return null var save: QuizSave = from_dict(JSON.parse_string(FileAccess.get_file_as_string(path()))) if save == null: GameLog.warning("Could not read the saved quiz in ", path(), ", starting a new one") return save ## Deletes the saved quiz. static func delete() -> void: if FileAccess.file_exists(path()): DirAccess.remove_absolute(path()) ## Saves the quiz. It is written to another file first and then moved, so a crash while ## saving never leaves half a file. Returns false when it could not be saved. func write() -> bool: var target: String = path() var temporary: String = target + ".tmp" DirAccess.make_dir_recursive_absolute(target.get_base_dir()) var file: FileAccess = FileAccess.open(temporary, FileAccess.WRITE) if file == null: GameLog.warning("Could not save the quiz to ", temporary, ": ", error_string(FileAccess.get_open_error())) return false file.store_string(JSON.stringify(to_dict(), "\t")) file.close() var error: Error = DirAccess.rename_absolute(temporary, target) if error != OK: GameLog.warning("Could not save the quiz to ", target, ": ", error_string(error)) return false return true ## The song files the saved songs use, which must not be deleted. func song_files() -> PackedStringArray: var files: PackedStringArray = [] for song_object: SongObject in songs: if !song_object.song_path.is_empty(): files.append(song_object.song_path) return files func to_dict() -> Dictionary: var player_list: Array[Dictionary] = [] for player: PlayerObject in players: player_list.append(player.to_dict()) var song_list: Array[Dictionary] = [] for song_object: SongObject in songs: song_list.append(song_object.to_dict()) return { "version": VERSION, "players": player_list, "songs": song_list, "currently_playing_song": currently_playing_song, "latest_played_song": latest_played_song, "game_start_song": game_start_song, "first_song_played": first_song_played, "stats_paused_by_win": stats_paused_by_win, "server": server, "music_folder": {"path": music_folder_path, "progress": music_folder_progress}, "characters_dir": characters_dir, } ## A save made from [param data], as given by [method to_dict], or null when ## [param data] is not a saved quiz of this [constant VERSION]. Players and songs that ## can't be read are left out. static func from_dict(data: Variant) -> QuizSave: if !(data is Dictionary) || int(data.get("version", 0)) != VERSION \ || !(data.get("players") is Array) || !(data.get("songs") is Array): return null var save: QuizSave = QuizSave.new() for player_data: Variant in data.players: var player: PlayerObject = PlayerObject.from_dict(player_data) if player != null && save.players.size() < Settings.MAX_NUMBER_OF_PLAYERS: save.players.append(player) for song_data: Variant in data.songs: var song_object: SongObject = SongObject.from_dict(song_data) if song_object == null: # The songs' places in the list are their numbers, so none can be left out save.songs.clear() break save.songs.append(song_object) save.currently_playing_song = clampi(int(data.get("currently_playing_song", -1)), -1, save.songs.size() - 1) save.latest_played_song = clampi(int(data.get("latest_played_song", 0)), 0, maxi(save.songs.size() - 1, 0)) save.game_start_song = clampi(int(data.get("game_start_song", 0)), 0, save.songs.size()) save.first_song_played = bool(data.get("first_song_played", false)) save.stats_paused_by_win = bool(data.get("stats_paused_by_win", false)) save.server = str(data.get("server", "")) var folder: Variant = data.get("music_folder", {}) if folder is Dictionary: save.music_folder_path = str(folder.get("path", "")) if folder.get("progress") is Dictionary: save.music_folder_progress = folder.progress save.characters_dir = str(data.get("characters_dir", "")) return save ## Forgets the songs, e.g. when the server's playlist no longer has them. The players are ## kept, without points. func drop_songs() -> void: songs.clear() currently_playing_song = -1 latest_played_song = 0 game_start_song = 0 first_song_played = false for player: PlayerObject in players: player.reset_points()