Save the quiz and continue it after a restart; load characters from a folder
The quiz is saved to current_quiz.json in the config folder after every change, and the game goes on where it was when it starts again: the players with their points and characters, every song with who got a point for it and whether it was played, answered or is playing, and the songs downloaded ahead with their files. The song that was playing is ready to play again. Songs whose files are gone are downloaded again. On a server, the saved songs are checked against the server's playlist. If it changed, e.g. after a sync, or another server is used, the quiz starts over with the same players. A quiz from a music folder continues offline, including how far a prepared quiz has come. A new game is saved like any other change, and a file that can't be read starts a new quiz. Characters can be read from a folder picked in Settings instead of the server, for this session. A music folder with a characters folder brings its own characters, e.g. for a themed quiz; that folder is not counted as a soundtrack. When the characters come from somewhere else, players pick their characters again. Closes #11, closes #44
This commit is contained in:
1 parent
4a934840f6
commit
e4352a9f34
33 files changed
+2886
-499
No files matched your search
+130
-4
@@ -6,6 +6,9 @@ extends Control
|
||||
## The main window shows the quiz below its top bar. Pictures and buttons keep the
|
||||
## positions they had in the main window, so the quiz fills the whole window and lets
|
||||
## clicks through where it has nothing.
|
||||
##
|
||||
## The quiz is saved after every change and continued when the game starts again, see
|
||||
## [QuizSave].
|
||||
|
||||
## Emitted when another MusicServer is picked in the settings, after the quiz has
|
||||
## reloaded from it.
|
||||
@@ -26,6 +29,11 @@ var stats_paused_by_win: bool = false
|
||||
## Dims the window behind an open popup and keeps clicks from reaching the buttons under it.
|
||||
var popup_backdrop: PopupBackdrop
|
||||
|
||||
# True from start until the saved quiz's songs are back, so a half restored quiz is
|
||||
# never saved over the whole one
|
||||
var _restoring: bool = false
|
||||
var _save_queued: bool = false
|
||||
|
||||
@onready var inspiration_list: InspirationList = $InspirationList
|
||||
@onready var now_playing: NowPlaying = $NowPlaying
|
||||
@onready var players_panel: PlayersPanel = $PlayersPanel
|
||||
@@ -50,7 +58,7 @@ func _ready() -> void:
|
||||
quiz_settings_button.pressed.connect(quiz_settings_card.toggle)
|
||||
toolbar.reset_playlist_pressed.connect(reset_playlist)
|
||||
toolbar.reset_points_pressed.connect(reset_points)
|
||||
song_loader.song_list_loaded.connect(update_song_list)
|
||||
song_loader.song_list_loaded.connect(_on_song_list_loaded)
|
||||
song_loader.song_added.connect(_on_song_added)
|
||||
|
||||
character_select_popup.character_selected.connect(_on_new_character_selected)
|
||||
@@ -60,10 +68,15 @@ func _ready() -> void:
|
||||
players_panel.change_character_requested.connect(_on_change_character_requested)
|
||||
music_player.play_next_song.connect(play_next_song)
|
||||
music_player.update_song_list.connect(update_song_list)
|
||||
character_select_popup.source_changed.connect(_on_character_source_changed)
|
||||
song_loader.saved_songs_dropped.connect(_on_saved_songs_dropped)
|
||||
|
||||
var saved: QuizSave = QuizSave.read()
|
||||
_continue_saved_quiz(saved)
|
||||
inspiration_list.load_soundtracks()
|
||||
song_loader.load_song_list()
|
||||
song_loader.load_song_list(saved)
|
||||
character_select_popup.load_characters()
|
||||
_load_saved_characters(saved)
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
@@ -151,6 +164,7 @@ func _on_song_added() -> void:
|
||||
song_controls.set_next_enabled(true)
|
||||
if Settings.is_debug:
|
||||
update_song_list()
|
||||
save_soon()
|
||||
|
||||
|
||||
func update_song_list() -> void:
|
||||
@@ -159,6 +173,114 @@ func update_song_list() -> void:
|
||||
now_playing.show_song(Playlist.get_current_soundtrack_title(),
|
||||
Playlist.get_current_song_title())
|
||||
music_list_panel.refresh()
|
||||
save_soon()
|
||||
|
||||
|
||||
## Saves the quiz at the end of this frame, once however many changes it had.
|
||||
func save_soon() -> void:
|
||||
if _restoring || _save_queued:
|
||||
return
|
||||
_save_queued = true
|
||||
_save_now.call_deferred()
|
||||
|
||||
|
||||
func _save_now() -> void:
|
||||
_save_queued = false
|
||||
if is_inside_tree():
|
||||
QuizSave.capture(stats_paused_by_win).write()
|
||||
|
||||
|
||||
# Puts back the saved quiz's music folder, players and statistics. Its songs come back
|
||||
# with the song list, see SongLoader.load_song_list(), and its characters' pictures once
|
||||
# the characters are loaded.
|
||||
func _continue_saved_quiz(saved: QuizSave) -> void:
|
||||
if saved == null:
|
||||
Playlist.clear_song_files()
|
||||
return
|
||||
_restoring = true
|
||||
if !saved.music_folder_path.is_empty():
|
||||
var folder: MusicFolder = MusicFolder.scan(saved.music_folder_path)
|
||||
if folder.kind == MusicFolder.Kind.EMPTY:
|
||||
GameLog.warning("The saved quiz's music folder is gone: ", saved.music_folder_path)
|
||||
saved.drop_songs()
|
||||
else:
|
||||
folder.restore_progress(saved.music_folder_progress)
|
||||
Settings.music_folder = folder
|
||||
elif saved.server != Settings.default_path:
|
||||
# Another server has another playlist
|
||||
saved.drop_songs()
|
||||
Playlist.clear_song_files(saved.song_files())
|
||||
for player: PlayerObject in saved.players:
|
||||
Settings.player_array.append(player)
|
||||
_on_player_added(player)
|
||||
if saved.characters_dir != Settings.characters_dir():
|
||||
for player: PlayerObject in saved.players:
|
||||
player.clear_character()
|
||||
players_panel.reload()
|
||||
if saved.stats_paused_by_win:
|
||||
stats_paused_by_win = true
|
||||
Settings.add_to_stats = false
|
||||
|
||||
|
||||
# The players' characters are saved by name; their pictures come from the characters
|
||||
func _load_saved_characters(saved: QuizSave) -> void:
|
||||
if saved == null:
|
||||
return
|
||||
for player: PlayerObject in saved.players:
|
||||
var character_name: String = player.character_name
|
||||
if character_name.is_empty():
|
||||
continue
|
||||
character_select_popup.source.fetch_image(character_name, func(picture: Image) -> void:
|
||||
# Another character may have been picked meanwhile
|
||||
if player.character_name == character_name:
|
||||
player.character = ImageTexture.create_from_image(picture)
|
||||
players_panel.reload())
|
||||
|
||||
|
||||
func _on_song_list_loaded() -> void:
|
||||
if _restoring:
|
||||
_finish_restoring()
|
||||
update_song_list()
|
||||
|
||||
|
||||
# The saved songs are back: the song that was playing is ready to play again, and songs
|
||||
# whose files are gone are downloaded again
|
||||
func _finish_restoring() -> void:
|
||||
_restoring = false
|
||||
if !Settings.is_offline():
|
||||
for i: int in Playlist.song_object_array.size():
|
||||
var song_object: SongObject = Playlist.song_object_array[i]
|
||||
var needed: bool = !song_object.has_played || i == Playlist.currently_playing_song
|
||||
if needed && Playlist.get_song(i) == null:
|
||||
MusicServerClient.fetch_song(song_object.song_number,
|
||||
_on_saved_song_downloaded.bind(i))
|
||||
if Playlist.currently_playing_song >= 0:
|
||||
music_player.load_song_object(Playlist.currently_playing_song)
|
||||
if Playlist.get_preloaded_songs() > 0:
|
||||
song_controls.set_next_enabled(true)
|
||||
|
||||
|
||||
func _on_saved_song_downloaded(body: PackedByteArray, song_no: int) -> void:
|
||||
if song_no >= Playlist.song_object_array.size():
|
||||
return
|
||||
Playlist.add_song_to_song_object(song_no, body)
|
||||
if song_no == Playlist.currently_playing_song && !music_player.audio_player.playing:
|
||||
music_player.load_song_object(song_no)
|
||||
save_soon()
|
||||
|
||||
|
||||
func _on_saved_songs_dropped() -> void:
|
||||
for player: PlayerObject in Settings.player_array:
|
||||
player.reset_points()
|
||||
players_panel.reload()
|
||||
|
||||
|
||||
# Characters picked before are not among the new ones, so everyone picks again
|
||||
func _on_character_source_changed() -> void:
|
||||
for player: PlayerObject in Settings.player_array:
|
||||
player.clear_character()
|
||||
players_panel.reload()
|
||||
save_soon()
|
||||
|
||||
|
||||
func song_object_clicked(song_no: int) -> void:
|
||||
@@ -204,6 +326,7 @@ func reset_points() -> void:
|
||||
if stats_paused_by_win:
|
||||
Settings.add_to_stats = true
|
||||
stats_paused_by_win = false
|
||||
save_soon()
|
||||
|
||||
|
||||
# Each new player's points reach the music player, the log and the winner popup.
|
||||
@@ -214,6 +337,7 @@ func _on_player_added(new_player_object: PlayerObject) -> void:
|
||||
new_player_object.point_given_sound.connect(_on_make_point_given_sound)
|
||||
new_player_object.player_point_given.connect(_on_point_given.bind(new_player_object))
|
||||
new_player_object.player_point_taken.connect(_on_point_taken.bind(new_player_object))
|
||||
save_soon()
|
||||
|
||||
|
||||
func _on_point_given(player_object: PlayerObject) -> void:
|
||||
@@ -259,6 +383,7 @@ func _on_player_won(player_object: PlayerObject) -> void:
|
||||
if Settings.add_to_stats:
|
||||
Settings.add_to_stats = false
|
||||
stats_paused_by_win = true
|
||||
save_soon()
|
||||
|
||||
|
||||
func _on_player_removed(player_object: PlayerObject) -> void:
|
||||
@@ -273,6 +398,7 @@ func _on_change_character_requested(new_player: PlayerRow, player_name: String)
|
||||
character_select_popup.show_grid(player_name)
|
||||
|
||||
|
||||
func _on_new_character_selected(character_texture: ImageTexture) -> void:
|
||||
func _on_new_character_selected(character_texture: ImageTexture, character_name: String) -> void:
|
||||
GameLog.debug("_on_new_character_selected")
|
||||
current_player_row.new_character_selected(character_texture)
|
||||
current_player_row.new_character_selected(character_texture, character_name)
|
||||
save_soon()
|
||||
Reference in new issue
Block a user