class_name Quiz extends Control ## The quiz: the song that is playing, the playlist, the players and their points, and ## the popups opened from here (the quiz settings card, character select and winner). ## ## 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. ## Emitted when another MusicServer is picked in the settings, after the quiz has ## reloaded from it. signal server_changed ## Emitted when Search is pressed. The Search popup is in the main window. signal search_pressed ## Emitted with a line for the game log, e.g. when a player gets a point. signal event_logged(text: String) var current_player_row: PlayerRow ## True while statistics are off because a player won, until points are reset. 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 @onready var inspiration_list: InspirationList = $InspirationList @onready var now_playing: NowPlaying = $NowPlaying @onready var players_panel: PlayersPanel = $PlayersPanel @onready var toolbar: QuizToolbar = $QuizToolbar @onready var quiz_settings_card: QuizSettingsCard = $QuizSettingsCard @onready var quiz_settings_button: Button = $QuizSettingsButton @onready var winner_popup: WinnerPopup = %WinnerPopup @onready var song_controls: SongControls = $SongControls @onready var song_loader: SongLoader = $SongLoader @onready var music_list_panel: MusicListPanel = $MusicListPanel @onready var character_select_popup: CharacterSelectPopup = %CharacterSelectPopup @onready var music_player: MusicPlayer = $MusicPlayer func _ready() -> void: popup_backdrop = PopupBackdrop.add_to(self, _popups()) song_controls.next_pressed.connect(play_next_song) song_controls.show_answer_pressed.connect(show_answer_pressed) song_controls.show_winner_pressed.connect(show_final_winners) toolbar.sound_test_pressed.connect(music_player.get_sound_test_song) toolbar.search_pressed.connect(search_pressed.emit) 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_added.connect(_on_song_added) character_select_popup.character_selected.connect(_on_new_character_selected) music_list_panel.song_clicked.connect(song_object_clicked) players_panel.player_added.connect(_on_player_added) players_panel.player_removed.connect(_on_player_removed) 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) inspiration_list.load_soundtracks() song_loader.load_song_list() character_select_popup.load_characters() func _input(event: InputEvent) -> void: if event is InputEventKey and event.pressed: if event.alt_pressed && event.keycode == KEY_UP: GameLog.debug("Alt + UP pressed") music_player.change_volume(0.05) if event.alt_pressed && event.keycode == KEY_DOWN: GameLog.debug("Alt + DOWN pressed") music_player.change_volume(-0.05) if event.alt_pressed && event.ctrl_pressed && event.keycode == KEY_LEFT: GameLog.debug("Ctrl + Alt + LEFT pressed") music_player.restart() elif event.alt_pressed && event.keycode == KEY_LEFT: GameLog.debug("Alt + LEFT pressed") music_player.seek(-5) if event.alt_pressed && event.keycode == KEY_RIGHT: GameLog.debug("Alt + RIGHT pressed") music_player.seek(5) ## Disables the buttons that use the song list while the server syncs it. func lock_for_sync() -> void: toolbar.set_locked(true) song_controls.set_locked(true) ## Reloads from the server after a sync and starts a new game. func on_sync_finished() -> void: refresh_from_server() toolbar.set_locked(false) song_controls.set_locked(false) reset_points() ## Reloads the soundtracks, playlist and characters from the server. func refresh_from_server() -> void: GameLog.debug("refresh_from_server") inspiration_list.load_soundtracks() reset_playlist() character_select_popup.load_characters() ## Reloads everything from the server picked in the settings and starts a new game. func on_server_changed() -> void: GameLog.debug("on_server_changed") refresh_from_server() reset_points() server_changed.emit() func show_answer_pressed() -> void: GameLog.debug("show_answer_pressed") Playlist.song_is_answered() update_song_list() func play_next_song() -> void: GameLog.debug("play_next_song ", Playlist.latest_played_song) # Every song of a prepared quiz has played, e.g. when songs play on by themselves if Settings.is_prepared_quiz() && Playlist.first_song_played \ && Playlist.get_preloaded_songs() == 0: song_controls.show_winner_button() return if Playlist.first_song_played: Playlist.set_latest_played_song() Playlist.first_song_played = true # The song counts as played once it starts, which can take a moment if another song # is stopping, so the next song is picked after that await music_player.play_song_object(Playlist.latest_played_song) song_loader.fetch_song_if_idle() if !(Playlist.get_preloaded_songs() > 0): if Settings.is_prepared_quiz(): song_controls.show_winner_button() else: song_controls.set_next_enabled(false) # The quiz settings card is left out: it does not dim the quiz func _popups() -> Array[BasePopup]: return [character_select_popup, winner_popup] func _on_song_added() -> void: song_controls.set_next_enabled(true) if Settings.is_debug: update_song_list() func update_song_list() -> void: GameLog.debug("update_song_list") if Playlist.currently_playing_song >= 0: now_playing.show_song(Playlist.get_current_soundtrack_title(), Playlist.get_current_song_title()) music_list_panel.refresh() func song_object_clicked(song_no: int) -> void: GameLog.debug("Clicked Song Object: ", Playlist.song_object_array[song_no].to_string()) if !Settings.character_select_open: if Playlist.get_song(song_no) == null: if Settings.is_offline(): GameLog.warning("The song's file is gone: ", Playlist.song_object_array[song_no].song_path) return GameLog.debug("has no song, need to download") var song_downloaded: Callable = func song_downloaded(body: PackedByteArray) -> void: Playlist.add_song_to_song_object(song_no, body) music_player.play_song_object(song_no) MusicServerClient.fetch_song(song_no, song_downloaded) else: GameLog.debug("song already downloaded") music_player.play_song_object(song_no) func reset_playlist() -> void: GameLog.debug("reset_playlist") var playlist_reset_done: Callable = func playlist_reset_done() -> void: Playlist.reset_playlist() song_loader.load_song_list() if Settings.is_offline(): Settings.music_folder.restart() playlist_reset_done.call() return MusicServerClient.reset_playlist(playlist_reset_done) func reset_points() -> void: GameLog.debug("reset_points") for player_objects: PlayerObject in Settings.player_array: player_objects.reset_points() players_panel.reload() # The song playing now belongs to the game that just ended Playlist.clear_points() Playlist.game_start_song = Playlist.currently_playing_song + 1 music_list_panel.refresh() # A new game starts, so turn statistics back on if winning turned them off if stats_paused_by_win: Settings.add_to_stats = true stats_paused_by_win = false # Each new player's points reach the music player, the log and the winner popup. func _on_player_added(new_player_object: PlayerObject) -> void: new_player_object.first_point_triggered.connect(music_player._on_point_triggered.bind("first")) new_player_object.match_point_triggered.connect(music_player._on_point_triggered.bind("match")) new_player_object.winner_triggered.connect(_on_player_won.bind(new_player_object)) 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)) func _on_point_given(player_object: PlayerObject) -> void: GameLog.debug("_on_point_given") event_logged.emit(player_object.player_name + " got a point") if Playlist.currently_playing_song >= 0: Playlist.add_point(player_object.id) update_song_list() func _on_make_point_given_sound() -> void: music_player.play_sound_effect(preload("res://sounds/itemopen.wav")) func _on_point_taken(player_object: PlayerObject) -> void: GameLog.debug("_on_point_taken") event_logged.emit(player_object.player_name + " lost a point") music_player.play_sound_effect(preload("res://sounds/itemequip.wav")) if Playlist.currently_playing_song >= 0: Playlist.remove_point(player_object.id) update_song_list() ## Shows the players with the most points as the winners, at the end of a prepared quiz. func show_final_winners() -> void: GameLog.debug("show_final_winners") var top_score: int = 0 for player: PlayerObject in Settings.player_array: top_score = maxi(top_score, player.player_score) var winners: Array[PlayerObject] = [] for player: PlayerObject in Settings.player_array: if player.player_score == top_score: winners.append(player) winner_popup.show_winners(winners) music_player.play_song(preload("res://sounds/winning.mp3")) func _on_player_won(player_object: PlayerObject) -> void: GameLog.debug("_on_player_won") winner_popup.show_winner(player_object) music_player.play_song(preload("res://sounds/winning.mp3")) # Songs played after the game is won don't count towards the statistics if Settings.add_to_stats: Settings.add_to_stats = false stats_paused_by_win = true func _on_player_removed(player_object: PlayerObject) -> void: Playlist.remove_player_from_list(player_object.id) update_song_list() func _on_change_character_requested(new_player: PlayerRow, player_name: String) -> void: GameLog.debug("_on_player_change_character_clicked") current_player_row = new_player GameLog.debug("Change character for: " + player_name) character_select_popup.show_grid(player_name) func _on_new_character_selected(character_texture: ImageTexture) -> void: GameLog.debug("_on_new_character_selected") current_player_row.new_character_selected(character_texture)