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 (settings, character select, winner and log). ## ## 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 @onready var open_button: Button = $Open @onready var fileDialog: FileDialog = $FileDialog @onready var inspiration_scroll: ScrollContainer = $ScrollContainer @onready var inspiration_list: VBoxContainer = $ScrollContainer/VBoxContainer @onready var soundtrack_label: Label = $VBoxContainer/SoundtrackLabel @onready var song_label: Label = $VBoxContainer/SongLabel @onready var add_player_container: HBoxContainer = $Players/VBoxContainer/AddPlayerContainer @onready var add_players_button: TextureButton = $Players/VBoxContainer/HBoxContainer/AddPlayersButton @onready var add_player_button: Button = $Players/VBoxContainer/AddPlayerContainer/AddPlayerButton @onready var new_player_name_field: TextEdit = $Players/VBoxContainer/AddPlayerContainer/PlayerNameField @onready var reset_playlist_button: Button = $ResetPlaylistButton @onready var reset_points_button: Button = $ResetPointsButton @onready var sound_test_button: Button = $SoundTestButton @onready var settings_button: Button = $SettingsButton @onready var settings_window: Control = $SettingsWindow @onready var winner_window: Control = %WinnerWindow @onready var show_answer_button: Button = $ShowAnswerButton @onready var next_button: Button = $NextButton @onready var music_list_scroll: ScrollContainer = $MusicListPanel/ScrollContainer @onready var music_list: VBoxContainer = $MusicListPanel/ScrollContainer/MusicList @onready var player_container: VBoxContainer = $Players/VBoxContainer @onready var players_list_container: VBoxContainer = $Players/VBoxContainer/ScrollContainer/PlayersListVBoxContainer @onready var new_character_select: Control = %NewCharacterSelect @onready var auto_repeat_song_button: CheckButton = $RepeatSongCheckButton @onready var music_player_container: PanelContainer = $MusicPlayer @onready var log_window: Control = %Log @onready var debug_label: Label = $DebugLabel var player: PackedScene = preload("res://Player.tscn") var song_list_object: PackedScene = preload("res://song_list_object.tscn") var songs: Array= [] var soundtracks: Array = [] var current_player: Node ## 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 ## Seconds to wait before retrying a song download step that failed. var song_fetch_retry_seconds: float = 5.0 func _ready() -> void: popup_backdrop = PopupBackdrop.add_to(self, _popups()) if OS.has_feature("debug"): print("is_debug") debug_label.visible = true Settings.is_debug = true settings_window.set_winning_score(5) settings_window.set_selected_server(4) next_button.pressed.connect(play_next_song) show_answer_button.pressed.connect(show_answer_pressed) sound_test_button.pressed.connect(music_player_container.get_sound_test_song) settings_button.pressed.connect(show_settings) reset_playlist_button.pressed.connect(reset_playlist) reset_points_button.pressed.connect(reset_points) new_character_select.connect("new_new_character_selected", _on_new_character_selected) new_player_name_field.connect("enter_key_pressed", add_player) music_player_container.connect("play_next_song", play_next_song) music_player_container.connect("update_song_list", update_song_list) settings_window.connect("server_changed", _on_server_changed) settings_window.visibility_changed.connect(_on_settings_window_visibility_changed) add_players_button.pressed.connect(add_players) add_player_button.pressed.connect(add_player) open_button.pressed.connect(open) auto_repeat_song_button.pressed.connect(repeat_song) get_suggestion_list() fetch_song_list_at_start() new_character_select.load_characters() func _input(event: InputEvent) -> void: if event is InputEventKey and event.pressed: if event.alt_pressed && event.keycode == KEY_UP: print("Alt + UP pressed") music_player_container.change_volume(0.05) if event.alt_pressed && event.keycode == KEY_DOWN: print("Alt + DOWN pressed") music_player_container.change_volume(-0.05) if event.alt_pressed && event.ctrl_pressed && event.keycode == KEY_LEFT: print("Ctrl + Alt + LEFT pressed") music_player_container.restart() elif event.alt_pressed && event.keycode == KEY_LEFT: print("Alt + LEFT pressed") music_player_container.seek(-5) if event.alt_pressed && event.keycode == KEY_RIGHT: print("Alt + RIGHT pressed") music_player_container.seek(5) if event.alt_pressed && event.keycode == KEY_L: log_window.toggle() ## Disables the buttons that use the song list while the server syncs it. func lock_for_sync() -> void: sound_test_button.disabled = true reset_playlist_button.disabled = true show_answer_button.disabled = true next_button.disabled = true ## Reloads from the server after a sync and starts a new game. func on_sync_finished() -> void: refresh_from_server() sound_test_button.disabled = false reset_playlist_button.disabled = false show_answer_button.disabled = false reset_points() ## Reloads the soundtracks, playlist and characters from the server. func refresh_from_server() -> void: print("refresh_from_server") get_suggestion_list() reset_playlist() new_character_select.load_characters() func _on_server_changed() -> void: print("_on_server_changed") refresh_from_server() reset_points() server_changed.emit() func repeat_song() -> void: Settings.auto_repeat_song = !Settings.auto_repeat_song func show_answer_pressed() -> void: print("show_answer_pressed") Playlist.song_is_answered() update_song_list() # Loads the songs already in the server playlist, then starts preloading new ones. # Preloading waits for the list, because loading the list clears the playlist. func fetch_song_list_at_start() -> void: print("fetch_song_list_at_start") var fetch_song_list_at_start_done: Callable = func fetch_songs_at_start_done(data) -> void: Playlist.clear_song_list() if typeof(data) == TYPE_ARRAY: for d: Dictionary in data: var new_song_object: SongObject = SongObject.new() new_song_object.song_number = d.SongNo new_song_object.soundtrack_title = d.Soundtrack new_song_object.song_title = d.Song new_song_object.is_answered = true new_song_object.has_played = true Playlist.add_to_playlist(new_song_object) elif data != null: print("Unexpected data") Playlist.update_last_played_song() update_song_list() if !Playlist.is_song_downloading: fetch_song() MusicServerClient.fetch_song_list(fetch_song_list_at_start_done, _retry_later.bind(fetch_song_list_at_start)) func play_next_song() -> void: print("play_next_song ", Playlist.latest_played_song) if Playlist.first_song_played: Playlist.set_latest_played_song() Playlist.first_song_played = true music_player_container.play_song_object(Playlist.latest_played_song) if !(Playlist.get_preloaded_songs() > 0): next_button.disabled = true if !Playlist.is_song_downloading: fetch_song() func _popups() -> Array[PopupWindow]: return [settings_window, new_character_select, winner_window, log_window] # The cache size can be changed in the settings. Waiting until the settings are closed # avoids starting downloads while the user is still clicking the number up and down. func _on_settings_window_visibility_changed() -> void: if !settings_window.visible && !Playlist.is_song_downloading && Playlist.should_preload_more_songs(): fetch_song() func fetch_song() -> void: print("fetch_song") Playlist.is_song_downloading = true _download_random_song(SongObject.new()) # Fetching a song takes three requests. If one fails, only that step is retried, so # the server and the game keep the same songs in their playlists. func _download_random_song(new_song_object: SongObject) -> void: var song_downloaded: Callable = func song_downloaded(body: PackedByteArray) -> void: print("song_downloaded") var sound: AudioStream = AudioStreamMP3.new() sound.data = body new_song_object.song = sound _add_song_to_queue(new_song_object) MusicServerClient.fetch_random_song(Settings.use_low_played_mode, song_downloaded, _retry_later.bind(_download_random_song.bind(new_song_object))) func _add_song_to_queue(new_song_object: SongObject) -> void: MusicServerClient.add_to_queue(_fetch_song_info.bind(new_song_object), _retry_later.bind(_add_song_to_queue.bind(new_song_object))) func _fetch_song_info(new_song_object: SongObject) -> void: var song_info_fetched: Callable = func song_info_fetched(data_received: Dictionary) -> void: print("song_info_fetched") new_song_object.song_number = data_received.SongNo new_song_object.soundtrack_title = data_received.Soundtrack new_song_object.song_title = data_received.Song print("new_song_object: ", new_song_object.to_string()) Playlist.add_to_playlist(new_song_object) next_button.disabled = false if Playlist.should_preload_more_songs(): fetch_song() else: Playlist.is_song_downloading = false if Settings.is_debug: update_song_list() MusicServerClient.fetch_song_info(song_info_fetched, _retry_later.bind(_fetch_song_info.bind(new_song_object))) func _retry_later(step: Callable) -> void: print("Song download failed, retrying in %s seconds" % song_fetch_retry_seconds) get_tree().create_timer(song_fetch_retry_seconds).timeout.connect(step) func update_song_list() -> void: print("update_song_list") if Playlist.currently_playing_song >= 0: soundtrack_label.text = Playlist.get_current_soundtrack_title() song_label.text = Playlist.get_current_song_title() Settings.delete_children(music_list) for s: SongObject in Playlist.get_playlist(): var new_song_list_object: SongListObject = song_list_object.instantiate() new_song_list_object.song_object = s new_song_list_object.gui_input.connect(song_object_clicked.bind(s.song_number)) music_list.add_child(new_song_list_object) func song_object_clicked(event: InputEvent, song_no: int) -> void: if (event is InputEventMouseButton && event.pressed && event.button_index == MOUSE_BUTTON_LEFT): print("Clicked Song Object: ", Playlist.song_object_array[song_no].to_string()) if Settings.character_select_open == false: if Playlist.get_song(song_no) == null: print("has no song, need to download") var song_downloaded: Callable = func song_downloaded(body: PackedByteArray) -> void: var sound: AudioStream = AudioStreamMP3.new() sound.data = body Playlist.add_song_to_song_object(song_no, sound) music_player_container.play_song_object(song_no) MusicServerClient.fetch_song(song_no, song_downloaded) else: print("song already downloaded") music_player_container.play_song_object(song_no) func reset_playlist() -> void: print("reset_playlist") var playlist_reset_done: Callable = func playlist_reset_done() -> void: Playlist.reset_playlist() fetch_song_list_at_start() MusicServerClient.reset_playlist(playlist_reset_done) func reset_points() -> void: print("reset_points") for player_objects: PlayerObject in Settings.player_array: player_objects.reset_points() load_players() # 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 func show_settings() -> void: print("show_settings") settings_window.open() func get_suggestion_list() -> void: print("get_suggestion_list") var populate_list: Callable = func populate_list(array) -> void: if typeof(array) == TYPE_ARRAY: soundtracks = [] Settings.delete_children(inspiration_list) soundtracks.append_array(array) for i: int in soundtracks.size(): inspiration_list.add_child(ListRow.create(soundtracks[i], i, 20)) else: print("Unexpected data") MusicServerClient.fetch_all_soundtracks(populate_list) func add_players() -> void: print("add_players") Settings.edit_players = !Settings.edit_players add_player_container.visible = !add_player_container.visible new_player_name_field.grab_focus() func add_player() -> void: print("add_player") if new_player_name_field.text != "" && Settings.player_array.size() < Settings.MAX_NUMBER_OF_PLAYERS: var new_player_object: PlayerObject = PlayerObject.new(new_player_name_field.text) new_player_name_field.text = "" Settings.player_array.append(new_player_object) new_player_object.connect("first_point_triggerd", music_player_container._on_point_triggered.bind("first")) new_player_object.connect("match_point_triggerd", music_player_container._on_point_triggered.bind("match")) new_player_object.connect("winner_triggerd", _on_player_won.bind(new_player_object)) new_player_object.connect("point_given_sound", _on_make_point_given_sound) new_player_object.connect("player_point_given", _on_point_given.bind(new_player_object)) new_player_object.connect("player_point_taken", _on_point_taken.bind(new_player_object)) load_players() func load_players() -> void: print("load_players") Settings.delete_children(players_list_container) var counter: int = 0 for player_object: PlayerObject in Settings.player_array: print(player_object.player_name) var new_player: Player = player.instantiate() new_player.player_object = player_object new_player.list_index = counter new_player.connect("change_character_clicked", _on_player_change_character_clicked.bind(new_player, player_object.player_name)) new_player.connect("player_removed", _on_player_removed.bind(player_object)) players_list_container.add_child(new_player) counter += 1 func _on_point_given(player_object: PlayerObject) -> void: print("_on_point_given") log_window.add_log_row(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_container.play_sound_effect(preload("res://sounds/itemopen.wav")) func _on_point_taken(player_object: PlayerObject) -> void: print("_on_point_taken") log_window.add_log_row(player_object.player_name + " lost a point") music_player_container.play_sound_effect(preload("res://sounds/itemequip.wav")) if Playlist.currently_playing_song >= 0: Playlist.remove_point(player_object.id) update_song_list() func _on_player_won(player_object: PlayerObject) -> void: print("_on_player_won") winner_window.show_winner(player_object) music_player_container.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: print("_on_player_removed ", player_object.player_name) Settings.player_array.erase(player_object) load_players() Playlist.remove_player_from_list(player_object.id) update_song_list() func _on_player_change_character_clicked(new_player: Player, player_name: String) -> void: print("_on_player_change_character_clicked") current_player = new_player print("Change character for: " + player_name) new_character_select.show_grid(player_name) func _on_new_character_selected(character_texture: ImageTexture) -> void: print("_on_new_character_selected") current_player.new_character_selected(character_texture) ###Local var local_path: String = '/Users/sebastian/ResilioSync/Sorterat_test/Metal Gear Solid 4 - Guns of the Patriots/2-16 Metal Gear Saga.mp3' func open() -> void: fileDialog.popup() func _on_file_dialog_dir_selected(path: String) -> void: print(path) dir_contents(path) func dir_contents(path: String) -> void: var dir: DirAccess = DirAccess.open(path) if dir: dir.list_dir_begin() var file_name: String = dir.get_next() songs.clear() soundtracks = [] while file_name != "": if dir.current_is_dir(): #print("Found directory: " + file_name) soundtracks.append(file_name) else: #print("Found file: " + file_name) if file_name.ends_with(".mp3"): songs.append(path + "/" + file_name) file_name = dir.get_next() else: print("An error occurred when trying to access the path.") for i: int in soundtracks.size(): inspiration_list.add_child(ListRow.create(soundtracks[i], i, 20))