14 Commits
Author SHA1 Message Date
Sansan a7bf38c5f9 Version 2.0.0
Build / test (pull_request) Successful in 31s
Build / build (pull_request) Skipped
2026-10-06 12:04:42 +02:00
Sansan 5519afb5f6 Add what's new in 2.0.0 to the About popup
The layout test lets the changelog grow in height with its text, and still
checks its position and width.
2026-10-06 12:02:55 +02:00
Sansan d77b72159d Sort the scripts and scenes into folders
main/ holds the main window and top bar, quiz/ the quiz and its parts,
player/ the music player, popups/ every popup and what they share, data/ the
songs, players, playlist and settings, server/ the MusicServer client,
common/ the list row and GameLog, and themes/ the themes and style boxes.
2026-10-06 11:57:29 +02:00
Sansan a0428e3475 Follow the GDScript style guide in every script
- Types on every parameter, return value and variable, and snake_case names
  (the player name limit is the constant MAX_NAME_LENGTH).
- Spelling fixes: *_triggered, character_selected, separator, caught_errors.
  The server's catched_errors key stays as the server sends it.
- Signals are connected with signal.connect() instead of by name.
- Code is ordered signals, constants, exported vars, vars, @onready vars,
  functions, with @onready on one line and two blank lines between functions.
- Every script starts with a ## description. Template comments, old LOC notes
  and commented-out code are gone, apart from the local play code.
- Lines are at most 100 characters, apart from the local play path.
- The screenshot tour turns off the blinking text cursor so pictures match.
2026-10-06 11:53:26 +02:00
Sansan 674604425f Reach nodes by unique name instead of long paths
Nodes a script uses through a path of several nodes are marked unique in their
scene and found with %Name. The search list is renamed SearchResults, and the
player row's nodes get their real types.
2026-10-06 11:44:47 +02:00
Sansan 390da577d7 Name popups *Popup and give scripts, scenes and classes matching names
Every popup is now called *Popup instead of *Window in its file, root node,
node name, class and variable, and PopupWindow becomes BasePopup. Player is
PlayerRow, SongListObject is SongRow and Character is CharacterCard. Script
files are snake_case and scene files PascalCase, each matching its class or
root node, and LightGrayTheme.tres is light_gray_theme.tres.
2026-10-06 11:22:39 +02:00
Sansan b7d67b573e Give every script a class name and use it for typed references
The popups, the volume slider, the player name field and the main window get
a class_name, so the main window and the quiz hold them by their real type
instead of Control. The classes already use the names the files will get.
2026-10-06 11:16:09 +02:00
Sansan 5831e1e73d Log through a GameLog autoload instead of print()
GameLog.debug() prints only in debug builds, and GameLog.warning() reports
problems such as unexpected server data or failed downloads.
2026-10-06 11:15:03 +02:00
Sansan 5cdec5cc8f Move the log popup to the main window
The quiz reports point changes with an event_logged signal, and the main
window adds them to the log. Alt+L is handled by the main window.
2026-10-06 11:13:11 +02:00
Sansan e94c75e84d Split now playing, the quiz buttons and song loading out of the quiz
NowPlaying, QuizToolbar and SongControls become their own scenes, the auto
repeat switch moves into the music player scene, and downloading songs moves
into a SongLoader node. The quiz only wires the parts together. Every node
keeps its place on screen.

Screenshots of every UI state are pixel-identical to before, the layout test
still matches the original baseline, and each new part has its own tests.
2026-10-06 11:11:18 +02:00
Sansan bad4b3ad8b Indent every script with tabs, like the Godot editor does 2026-10-06 10:28:12 +02:00
Sansan 8c1987807c Make the music list, player list and inspiration list their own scenes
MusicListPanel, PlayersPanel (was Players) and InspirationList (was
ScrollContainer) each get their own scene and script. They report what
happens through signals, and the quiz wires them to the playlist, the
music player, the log and the popups. Every node keeps its place on screen.

Screenshots of every UI state are pixel-identical to before, the layout
test still matches the original baseline, and each part is tested on its
own as well as through the quiz.
2026-10-06 10:27:35 +02:00
Sansan 3c7b4e5c9d Split the main window into TopBar and Quiz scenes
The quiz (song, playlist, players, music player and the popups opened from
it) moves into Quiz.tscn and the top panel into TopBar.tscn. The main window
keeps only the top bar, the quiz and the popups opened from the top bar, so
other views can be added next to the quiz later. Every node keeps its place
on screen.

Screenshots of every UI state before and after are pixel-identical, and a
layout test checks each node against a baseline taken before the split.
2026-10-06 10:02:22 +02:00
Sansan 75c59eb223 Move Sync to the top row and Settings next to the reset buttons 2026-10-06 08:43:05 +02:00
154 changed files with 5617 additions and 2034 deletions

No files matched your search

-38
View File
@@ -1,38 +0,0 @@
extends ScrollContainer
# Scroll direction: 1 = down, -1 = up
var scroll_direction: int = 1
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
scroll_direction = 1
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
var speed_pixels_per_second: float = float(Settings.inspiration_list_speed) * 60.0
var scroll_amount: float = speed_pixels_per_second * delta * float(scroll_direction)
var scroll_bar = get_v_scroll_bar()
# Don't scroll if list is empty or too short
if scroll_bar.max_value <= 0:
return
var current_pos = scroll_bar.value
if scroll_direction == 1:
# Scrolling down
if current_pos + scroll_bar.page < scroll_bar.max_value:
scroll_bar.value = min(current_pos + scroll_amount, scroll_bar.max_value - scroll_bar.page)
else:
# Reached bottom, reverse direction
scroll_direction = -1
scroll_bar.value = scroll_bar.max_value - scroll_bar.page
else:
# Scrolling up
if current_pos > 0:
scroll_bar.value = max(current_pos + scroll_amount, 0)
else:
# Reached top, reverse direction
scroll_direction = 1
scroll_bar.value = 0
-572
View File
@@ -1,572 +0,0 @@
extends Control
# 601 LOC 11/9 - 2023
# 628 LOC 15/9 - 2023
# 631 LOC 16/9 - 2023
# 554 LOC 30/5 - 2024
# 551 LOC 3/6 - 2024
# 386 LOC 12/6 - 2024
##TODO
# 15. Fix for local play
@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 sync_button: Button = $SyncButton
@onready
var sync_window: Control = %SyncWindow
@onready
var settings_button: Button = $SettingsButton
@onready
var settings_window: Control = $SettingsWindow
@onready
var shortcuts_window: Control = %ShortcutsWindow
@onready
var qr_window: Control = %QRWindow
@onready
var winner_window: Control = %WinnerWindow
@onready
var qr_button: Button = $QRButton
@onready
var statistics_button: Button = $StatisticsButton
@onready
var statistic_popup: PopupPanel = $StatisticsPopupPanel
@onready
var statistic_label: Label = $StatisticsPopupPanel/StatisticsLabel
@onready
var shortcuts_button: Button = $ShortcutsButton
@onready
var about_button: Button = $AboutButton
@onready
var about_window: Control = %AboutWindow
@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 search_button: Button = $SearchButton
@onready
var search_view: Control= $Search
@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: ColorRect
## Seconds to wait before retrying a song download step that failed.
var song_fetch_retry_seconds: float = 5.0
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
_setup_popup_backdrop()
var debug: bool = OS.has_feature("debug")
var is_mac: bool = OS.has_feature("macos")
if debug:
print("is_debug")
debug_label.visible = true
Settings.is_debug = true
settings_window.set_winning_score(5)
settings_window.set_selected_server(4)
if is_mac:
print("is_mac")
next_button.pressed.connect(play_next_song)
show_answer_button.pressed.connect(show_answer_pressed)
search_button.pressed.connect(show_search)
sync_button.pressed.connect(sync_soundtracks)
sound_test_button.pressed.connect(music_player_container.get_sound_test_song)
statistics_button.pressed.connect(get_statistics)
shortcuts_button.pressed.connect(show_shortcuts)
about_button.pressed.connect(show_about)
settings_button.pressed.connect(show_settings)
qr_button.pressed.connect(show_qr)
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", server_updated)
settings_window.visibility_changed.connect(_on_settings_window_visibility_changed)
sync_window.connect("sync_started", _on_sync_started)
sync_window.connect("sync_finished", _on_sync_finished)
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()
search_view.get_list_of_soundtracks()
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_ENTER:
print("Alt + Enter pressed")
Settings.fullscreen = !Settings.fullscreen
settings_window.fullscreen_button.button_pressed = Settings.fullscreen
if Settings.fullscreen == true:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
if Settings.fullscreen == false:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
if event.alt_pressed && event.keycode == KEY_K:
shortcuts_window.toggle()
if event.alt_pressed && event.keycode == KEY_L:
log_window.toggle()
func server_updated() -> void:
print("server_updated")
update_from_server()
reset_points()
func update_from_server() -> void:
print("update_from_server")
search_view.get_list_of_soundtracks()
get_suggestion_list()
reset_playlist()
new_character_select.load_characters()
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()
#not first "/music/addPlayed"
#"/music/rand"
#"/music/addQue"
#"/music/info"
func _popups() -> Array[PopupWindow]:
return [search_view, sync_window, settings_window, qr_window, new_character_select,
shortcuts_window, about_window, winner_window, log_window]
# The backdrop sits above the main window and below every popup. Popups close
# themselves when clicked outside, and the backdrop keeps that click from reaching
# the buttons behind them.
func _setup_popup_backdrop() -> void:
popup_backdrop = ColorRect.new()
popup_backdrop.name = "PopupBackdrop"
popup_backdrop.color = Color(0, 0, 0, 0.55)
popup_backdrop.mouse_filter = Control.MOUSE_FILTER_STOP
add_child(popup_backdrop)
popup_backdrop.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
for popup: PopupWindow in _popups():
move_child(popup, -1)
popup.visibility_changed.connect(_update_popup_backdrop)
_update_popup_backdrop()
func _update_popup_backdrop() -> void:
popup_backdrop.visible = _popups().any(func(popup: PopupWindow) -> bool: return popup.visible)
# 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_about() -> void:
print("show_about")
about_window.open()
func show_shortcuts() -> void:
print("show_shortcuts")
shortcuts_window.open()
func show_settings() -> void:
print("show_settings")
settings_window.open()
func show_qr() -> void:
print("show_qr")
qr_window.open()
func get_statistics() -> void:
print("get_statistics")
statistic_popup.visible = true
statistic_label.text = "Total amount of soundtracks in the playlist: " + str(soundtracks.size())
func sync_soundtracks() -> void:
print("sync_soundtracks")
sync_window.start_sync()
func _on_sync_started() -> void:
print("_on_sync_started")
sync_button.disabled = false
sound_test_button.disabled = true
reset_playlist_button.disabled = true
show_answer_button.disabled = true
next_button.disabled = true
func _on_sync_finished() -> void:
print("_on_sync_finished")
update_from_server()
sound_test_button.disabled = false
reset_playlist_button.disabled = false
show_answer_button.disabled = false
reset_points()
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)
func show_search() -> void:
print("show_search")
search_view.toggle()
###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))
-502
View File
@@ -1,502 +0,0 @@
[gd_scene format=3 uid="uid://xwq863o6uvsu"]
[ext_resource type="Script" uid="uid://b8f4g15cas2j2" path="res://MainWindow.gd" id="1_eu0t5"]
[ext_resource type="Script" uid="uid://elgn7gkxgagq" path="res://InsperationScrollContainer.gd" id="2_gxtxm"]
[ext_resource type="Texture2D" uid="uid://bcfmpd7h512ef" path="res://icons/person_add_light.svg" id="5_31tjv"]
[ext_resource type="PackedScene" uid="uid://ds15cgsf8vpvc" path="res://MusicPlayer.tscn" id="5_emn36"]
[ext_resource type="PackedScene" uid="uid://cdy4kvemwaiom" path="res://new_character_select.tscn" id="6_dhrvg"]
[ext_resource type="Script" uid="uid://gf1bk6xbe3j" path="res://MusicListScrollContainer.gd" id="7_dj026"]
[ext_resource type="Script" uid="uid://cgakje8gb37tf" path="res://PlayerNameField.gd" id="7_qsdfy"]
[ext_resource type="Theme" uid="uid://rxexo3ur85as" path="res://LightGrayTheme.tres" id="7_wxbv6"]
[ext_resource type="PackedScene" uid="uid://bxydgil1yifps" path="res://SearchWindow.tscn" id="9_5ijvr"]
[ext_resource type="PackedScene" uid="uid://dpdem7pdxweb5" path="res://SyncWindow.tscn" id="10_yxw0b"]
[ext_resource type="PackedScene" uid="uid://dldpeo5y3l5hq" path="res://SettingsWindow.tscn" id="11_k62u5"]
[ext_resource type="PackedScene" uid="uid://bd1by80q1v27e" path="res://shortcutsWindow.tscn" id="13_8eebo"]
[ext_resource type="PackedScene" uid="uid://bijh5h5yrivm3" path="res://Log.tscn" id="14_26rwn"]
[ext_resource type="PackedScene" uid="uid://btupbowehiyyu" path="res://QRWindow.tscn" id="15_qr_window"]
[ext_resource type="PackedScene" uid="uid://cp54idaudb8dp" path="res://AboutWindow.tscn" id="16_about_window"]
[ext_resource type="PackedScene" uid="uid://cxlsmxnrp0agh" path="res://WinnerWindow.tscn" id="17_winner_window"]
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"]
[sub_resource type="LabelSettings" id="LabelSettings_ychxr"]
font_size = 25
[sub_resource type="LabelSettings" id="LabelSettings_qspbu"]
font_size = 50
[sub_resource type="LabelSettings" id="LabelSettings_3m52w"]
font_size = 35
[sub_resource type="InputEventKey" id="InputEventKey_03bm3"]
device = -1
alt_pressed = true
keycode = 65
unicode = 63743
[sub_resource type="Shortcut" id="Shortcut_jafqj"]
events = [SubResource("InputEventKey_03bm3")]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qk7sj"]
bg_color = Color(0.180392, 0.180392, 0.180392, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_k1ygi"]
bg_color = Color(0.180392, 0.180392, 0.180392, 1)
[sub_resource type="InputEventKey" id="InputEventKey_06rl4"]
device = -1
alt_pressed = true
keycode = 86
unicode = 8249
[sub_resource type="Shortcut" id="Shortcut_a7fvb"]
events = [SubResource("InputEventKey_06rl4")]
[sub_resource type="InputEventKey" id="InputEventKey_7dnqw"]
device = -1
alt_pressed = true
keycode = 67
unicode = 231
[sub_resource type="Shortcut" id="Shortcut_d6fml"]
events = [SubResource("InputEventKey_7dnqw")]
[sub_resource type="InputEventKey" id="InputEventKey_ujjlu"]
device = -1
alt_pressed = true
keycode = 83
[sub_resource type="Shortcut" id="Shortcut_fbju4"]
events = [SubResource("InputEventKey_ujjlu")]
[node name="Control" type="Control" unique_id=407149696]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_eu0t5")
[node name="TopPanelContainer" type="PanelContainer" parent="." unique_id=663404711]
layout_mode = 0
offset_right = 1920.0
offset_bottom = 56.0
[node name="Title" type="Label" parent="." unique_id=1187907861]
layout_mode = 0
offset_left = 64.0
offset_top = 12.0
offset_right = 369.0
offset_bottom = 50.0
size_flags_horizontal = 0
text = "Music Player Randomizer"
label_settings = SubResource("LabelSettings_ychxr")
[node name="Open" type="Button" parent="." unique_id=165052329]
visible = false
layout_mode = 0
offset_left = 1296.0
offset_top = 66.0
offset_right = 1346.0
offset_bottom = 97.0
text = "Open"
[node name="FileDialog" type="FileDialog" parent="." unique_id=485771559]
title = "Open a Directory"
initial_position = 2
size = Vector2i(616, 400)
ok_button_text = "Select Current Folder"
file_mode = 2
access = 2
show_hidden_files = true
[node name="MusicListPanel" type="PanelContainer" parent="." unique_id=2011292375]
layout_mode = 0
offset_left = 64.0
offset_top = 384.0
offset_right = 1152.0
offset_bottom = 896.0
theme = ExtResource("7_wxbv6")
[node name="ScrollContainer" type="ScrollContainer" parent="MusicListPanel" unique_id=707750459]
layout_mode = 2
follow_focus = true
script = ExtResource("7_dj026")
[node name="MusicList" type="VBoxContainer" parent="MusicListPanel/ScrollContainer" unique_id=373398568]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/separation = 10
[node name="ScrollContainer" type="ScrollContainer" parent="." unique_id=498283761]
layout_mode = 0
offset_left = 1216.0
offset_top = 128.0
offset_right = 1857.0
offset_bottom = 512.0
horizontal_scroll_mode = 0
vertical_scroll_mode = 3
script = ExtResource("2_gxtxm")
[node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer" unique_id=1215162949]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=42468498]
layout_mode = 0
offset_left = 64.0
offset_top = 64.0
offset_right = 1152.0
offset_bottom = 384.0
[node name="SoundtrackLabel" type="Label" parent="VBoxContainer" unique_id=173147793]
custom_minimum_size = Vector2(1, 1)
layout_mode = 2
label_settings = SubResource("LabelSettings_qspbu")
horizontal_alignment = 1
autowrap_mode = 2
[node name="SongLabel" type="Label" parent="VBoxContainer" unique_id=1687490027]
custom_minimum_size = Vector2(1, 1)
layout_mode = 2
label_settings = SubResource("LabelSettings_3m52w")
horizontal_alignment = 1
autowrap_mode = 2
[node name="MusicPlayer" parent="." unique_id=1990459832 instance=ExtResource("5_emn36")]
layout_mode = 0
anchors_preset = 0
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
offset_left = 65.0
offset_top = 992.0
offset_right = 1856.0
offset_bottom = 1024.0
grow_horizontal = 1
grow_vertical = 1
[node name="Players" type="PanelContainer" parent="." unique_id=1708375945]
layout_mode = 0
offset_left = 1216.0
offset_top = 576.0
offset_right = 1857.0
offset_bottom = 977.0
[node name="VBoxContainer" type="VBoxContainer" parent="Players" unique_id=270084211]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="Players/VBoxContainer" unique_id=513047023]
custom_minimum_size = Vector2(2.08165e-12, 40)
layout_mode = 2
[node name="Label" type="Label" parent="Players/VBoxContainer/HBoxContainer" unique_id=1331357386]
custom_minimum_size = Vector2(2.08165e-12, 40)
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 1
text = "Players"
horizontal_alignment = 1
vertical_alignment = 1
[node name="AddPlayersButton" type="TextureButton" parent="Players/VBoxContainer/HBoxContainer" unique_id=634410148]
custom_minimum_size = Vector2(2.08165e-12, 40)
layout_mode = 2
size_flags_horizontal = 8
focus_mode = 0
action_mode = 0
shortcut = SubResource("Shortcut_jafqj")
texture_normal = ExtResource("5_31tjv")
[node name="AddPlayerContainer" type="HBoxContainer" parent="Players/VBoxContainer" unique_id=1371948739]
visible = false
custom_minimum_size = Vector2(2.08165e-12, 40)
layout_mode = 2
[node name="Panel" type="Panel" parent="Players/VBoxContainer/AddPlayerContainer" unique_id=1569854056]
layout_mode = 2
size_flags_horizontal = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_qk7sj")
[node name="PlayerNameField" type="TextEdit" parent="Players/VBoxContainer/AddPlayerContainer" unique_id=332535327]
custom_minimum_size = Vector2(2.08165e-12, 40)
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "Player name"
script = ExtResource("7_qsdfy")
[node name="AddPlayerButton" type="Button" parent="Players/VBoxContainer/AddPlayerContainer" unique_id=245001313]
custom_minimum_size = Vector2(2.08165e-12, 40)
layout_mode = 2
text = "Add"
[node name="Panel2" type="Panel" parent="Players/VBoxContainer/AddPlayerContainer" unique_id=60215448]
custom_minimum_size = Vector2(2.08165e-12, 40)
layout_mode = 2
size_flags_horizontal = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_k1ygi")
[node name="ScrollContainer" type="ScrollContainer" parent="Players/VBoxContainer" unique_id=1000000001]
layout_mode = 2
size_flags_vertical = 15
horizontal_scroll_mode = 0
[node name="PlayersListVBoxContainer" type="VBoxContainer" parent="Players/VBoxContainer/ScrollContainer" unique_id=1000000002]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 15
[node name="ResetPlaylistButton" type="Button" parent="." unique_id=1754155620]
layout_mode = 0
offset_left = 1360.0
offset_top = 64.0
offset_right = 1471.0
offset_bottom = 95.0
focus_mode = 0
action_mode = 0
text = "Reset playlist"
[node name="ResetPointsButton" type="Button" parent="." unique_id=942880776]
layout_mode = 0
offset_left = 1488.0
offset_top = 64.0
offset_right = 1599.0
offset_bottom = 95.0
focus_mode = 0
action_mode = 0
text = "Reset points"
[node name="SyncButton" type="Button" parent="." unique_id=1228313556]
layout_mode = 0
offset_left = 1616.0
offset_top = 64.0
offset_right = 1759.0
offset_bottom = 95.0
focus_mode = 0
action_mode = 0
text = "Sync soundtracks"
[node name="SoundTestButton" type="Button" parent="." unique_id=1332971879]
layout_mode = 0
offset_left = 1776.0
offset_top = 64.0
offset_right = 1887.0
offset_bottom = 95.0
focus_mode = 0
action_mode = 0
text = "Sound test"
[node name="ShowAnswerButton" type="Button" parent="." unique_id=415388039]
layout_mode = 0
offset_left = 65.0
offset_top = 944.0
offset_right = 176.0
offset_bottom = 975.0
focus_mode = 0
action_mode = 0
shortcut = SubResource("Shortcut_a7fvb")
text = "Show answer"
[node name="NextButton" type="Button" parent="." unique_id=1744703488]
layout_mode = 0
offset_left = 193.0
offset_top = 944.0
offset_right = 368.0
offset_bottom = 975.0
focus_mode = 0
disabled = true
action_mode = 0
shortcut = SubResource("Shortcut_d6fml")
text = "Randomize new track"
[node name="SearchButton" type="Button" parent="." unique_id=1782453581]
layout_mode = 0
offset_left = 1354.0
offset_top = 8.0
offset_right = 1424.0
offset_bottom = 48.0
focus_mode = 0
action_mode = 0
shortcut = SubResource("Shortcut_fbju4")
text = "Search"
[node name="SettingsButton" type="Button" parent="." unique_id=1193400323]
layout_mode = 0
offset_left = 1442.0
offset_top = 8.0
offset_right = 1512.0
offset_bottom = 48.0
focus_mode = 0
action_mode = 0
text = "Settings"
[node name="StatisticsButton" type="Button" parent="." unique_id=2045021269]
layout_mode = 0
offset_left = 1530.0
offset_top = 8.0
offset_right = 1600.0
offset_bottom = 48.0
focus_mode = 0
action_mode = 0
text = "Statistics"
[node name="ShortcutsButton" type="Button" parent="." unique_id=2066498226]
layout_mode = 0
offset_left = 1626.0
offset_top = 8.0
offset_right = 1696.0
offset_bottom = 48.0
focus_mode = 0
action_mode = 0
text = "Shortcuts"
[node name="AboutButton" type="Button" parent="." unique_id=1057035253]
layout_mode = 0
offset_left = 1722.0
offset_top = 8.0
offset_right = 1792.0
offset_bottom = 48.0
focus_mode = 0
action_mode = 0
text = "About"
[node name="QRButton" type="Button" parent="." unique_id=2021908559]
layout_mode = 0
offset_left = 1802.0
offset_top = 8.0
offset_right = 1872.0
offset_bottom = 48.0
focus_mode = 0
action_mode = 0
text = "QR"
[node name="Search" parent="." unique_id=1661250375 instance=ExtResource("9_5ijvr")]
visible = false
layout_mode = 1
offset_left = 56.0
offset_top = 96.0
offset_right = 56.0
offset_bottom = 96.0
[node name="SyncWindow" parent="." unique_id=182499699 instance=ExtResource("10_yxw0b")]
unique_name_in_owner = true
visible = false
layout_mode = 1
offset_left = 210.0
offset_top = 153.0
offset_right = 210.0
offset_bottom = 153.0
[node name="SettingsWindow" parent="." unique_id=1323246978 instance=ExtResource("11_k62u5")]
visible = false
layout_mode = 1
offset_left = 720.0
offset_top = 153.0
offset_right = 720.0
offset_bottom = 153.0
[node name="QRWindow" parent="." unique_id=589733821 instance=ExtResource("15_qr_window")]
unique_name_in_owner = true
visible = false
layout_mode = 1
offset_left = 720.0
offset_top = 153.0
offset_right = 720.0
offset_bottom = 153.0
[node name="NewCharacterSelect" parent="." unique_id=1679856322 instance=ExtResource("6_dhrvg")]
unique_name_in_owner = true
visible = false
layout_mode = 1
offset_left = 405.0
offset_top = 165.0
offset_right = 1557.0
offset_bottom = 805.0
[node name="ShortcutsWindow" parent="." unique_id=1513416242 instance=ExtResource("13_8eebo")]
unique_name_in_owner = true
visible = false
layout_mode = 1
offset_left = 562.0
offset_top = 160.0
offset_right = 1262.0
offset_bottom = 860.0
[node name="StatisticsPopupPanel" type="PopupPanel" parent="." unique_id=298311407]
theme = ExtResource("popup_theme")
oversampling_override = 1.0
initial_position = 2
size = Vector2i(450, 100)
[node name="Label" type="Label" parent="StatisticsPopupPanel" unique_id=548316493]
offset_left = 4.0
offset_top = 4.0
offset_right = 446.0
offset_bottom = 96.0
text = "Statistics"
horizontal_alignment = 1
[node name="StatisticsLabel" type="Label" parent="StatisticsPopupPanel" unique_id=58226477]
offset_left = 4.0
offset_top = 4.0
offset_right = 446.0
offset_bottom = 96.0
text = "Total amount of soundtracks in the playlist: 9000"
horizontal_alignment = 1
vertical_alignment = 1
[node name="AboutWindow" parent="." unique_id=141261008 instance=ExtResource("16_about_window")]
unique_name_in_owner = true
visible = false
layout_mode = 1
offset_left = 720.0
offset_top = 153.0
offset_right = 720.0
offset_bottom = 153.0
[node name="WinnerWindow" parent="." unique_id=999999999 instance=ExtResource("17_winner_window")]
unique_name_in_owner = true
visible = false
layout_mode = 1
offset_left = 720.0
offset_top = 153.0
offset_right = 720.0
offset_bottom = 153.0
[node name="RepeatSongCheckButton" type="CheckButton" parent="." unique_id=776835053]
layout_mode = 0
offset_left = 64.0
offset_top = 1032.0
offset_right = 208.0
offset_bottom = 1063.0
focus_mode = 0
button_pressed = true
action_mode = 0
text = "Auto repeat"
[node name="DebugLabel" type="Label" parent="." unique_id=1959221257]
visible = false
layout_mode = 0
offset_left = 974.0
offset_top = 904.0
offset_right = 1014.0
offset_bottom = 927.0
scale = Vector2(3, 3)
text = "DEBUG"
horizontal_alignment = 1
vertical_alignment = 1
autowrap_mode = 2
[node name="Log" parent="." unique_id=1289881388 instance=ExtResource("14_26rwn")]
unique_name_in_owner = true
visible = false
layout_mode = 1
offset_left = 562.0
offset_top = 132.0
offset_right = 1262.0
offset_bottom = 832.0
[connection signal="dir_selected" from="FileDialog" to="." method="_on_file_dialog_dir_selected"]
-8
View File
@@ -1,8 +0,0 @@
extends ScrollContainer
var max_value = 0;
func _draw():
if max_value != self.get_v_scroll_bar().max_value:
max_value = self.get_v_scroll_bar().max_value
self.scroll_vertical = max_value
+1 -1
View File
@@ -5,7 +5,7 @@ Releases are made from the version in `project.godot`
same version in the About window. same version in the About window.
1. Bump the version, e.g. from `1.9.0` to `1.10.0`, and add a `WHATS_NEW` entry in 1. Bump the version, e.g. from `1.9.0` to `1.10.0`, and add a `WHATS_NEW` entry in
`AboutWindow.gd`. `popups/about_popup.gd`.
2. Merge to `main` and push. 2. Merge to `main` and push.
The `Build` workflow (`.gitea/workflows/gitea-build.yaml`) then: The `Build` workflow (`.gitea/workflows/gitea-build.yaml`) then:
+1 -1
View File
@@ -27,7 +27,7 @@ pass.
| Folder | Contents | | Folder | Contents |
|---|---| |---|---|
| `tests/unit/` | Logic without scenes: playlist, songs, player scoring, time formatting, search, the server client | | `tests/unit/` | Logic without scenes: playlist, songs, player scoring, time formatting, search, the server client |
| `tests/integration/` | Real scenes (`MainWindow.tscn`, `SyncWindow.tscn`) run against a fake server, including the point keybinds | | `tests/integration/` | Real scenes (`MainWindow.tscn`, `SyncPopup.tscn`) run against a fake server, including the point keybinds |
| `tests/fakes/` | `FakeHttpTransport`, `AutoloadSnapshot` and the `MainWindowTestSuite` base class | | `tests/fakes/` | `FakeHttpTransport`, `AutoloadSnapshot` and the `MainWindowTestSuite` base class |
| `tests/data/` | Files the fake server sends, e.g. a short MP3 | | `tests/data/` | Files the fake server sends, e.g. a short MP3 |
+21
View File
@@ -0,0 +1,21 @@
extends Node
## Writes messages about what the game is doing, for finding problems.
##
## Registered as the [code]GameLog[/code] autoload. Use [method debug] instead of
## [code]print()[/code]: the messages show in debug builds and the editor, and are left
## out of released games. Problems the player may notice go through [method warning].
## Prints [param parts] joined together, like [code]print()[/code], in debug builds only.
func debug(...parts: Array) -> void:
if OS.is_debug_build():
print(_join(parts))
## Reports a problem in the editor's debugger and the console, in every build.
func warning(...parts: Array) -> void:
push_warning(_join(parts))
func _join(parts: Array) -> String:
return "".join(parts.map(func(part: Variant) -> String: return str(part)))
+1
View File
@@ -0,0 +1 @@
uid://c37mlo6svk03q
+59
View File
@@ -0,0 +1,59 @@
class_name ListRow
extends PanelContainer
## One item in a list of names, such as the inspiration list or a sync result column.
##
## Every row has its own background, alternating between two shades, so it is clear
## where an item starts even when its text wraps over several lines.
const EVEN_BACKGROUND: Color = Color(1, 1, 1, 0.05)
const ODD_BACKGROUND: Color = Color(1, 1, 1, 0.11)
static var _even_style: StyleBoxFlat
static var _odd_style: StyleBoxFlat
## The label showing the item's text.
var label: Label
## The item's text.
var text: String:
get:
return label.text
## Creates the row for item number [param row] (counting from 0) in a list.
static func create(
item_text: String,
row: int,
font_size: int,
alignment: HorizontalAlignment = HORIZONTAL_ALIGNMENT_LEFT,
) -> ListRow:
var list_row: ListRow = ListRow.new()
list_row.add_theme_stylebox_override("panel", _style(row % 2 != 0))
list_row.label = Label.new()
list_row.label.add_theme_font_size_override("font_size", font_size)
list_row.label.text = item_text
list_row.label.horizontal_alignment = alignment
list_row.label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
list_row.add_child(list_row.label)
return list_row
# Rows share two styles instead of each creating its own
static func _style(odd: bool) -> StyleBoxFlat:
if _even_style == null:
_even_style = _make_style(EVEN_BACKGROUND)
_odd_style = _make_style(ODD_BACKGROUND)
return _odd_style if odd else _even_style
static func _make_style(background: Color) -> StyleBoxFlat:
var style: StyleBoxFlat = StyleBoxFlat.new()
style.bg_color = background
style.set_corner_radius_all(4)
style.content_margin_left = 8
style.content_margin_right = 8
style.content_margin_top = 2
style.content_margin_bottom = 2
return style
File renamed without changes.
+23 -19
View File
@@ -1,40 +1,42 @@
extends RefCounted
class_name PlayerObject class_name PlayerObject
extends RefCounted
## A player in the quiz: name, score and character. The players are kept in
## [code]Settings.player_array[/code].
signal first_point_triggerd signal first_point_triggered
signal match_point_triggerd signal match_point_triggered
signal winner_triggerd signal winner_triggered
signal point_given_sound signal point_given_sound
signal player_point_given signal player_point_given
signal player_point_taken signal player_point_taken
## Unique id that never changes, unlike the player's position in Settings.player_array.
var id: String = _generate_uuid()
@export
var player_name: String
@export
var player_score: int
var is_first_point: bool = true
## Picture shown until the player picks a character. ## Picture shown until the player picks a character.
const NO_CHARACTER_PATH: String = "res://noCharacter.png" const NO_CHARACTER_PATH: String = "res://noCharacter.png"
@export var player_name: String
@export var player_score: int
## Unique id that never changes, unlike the player's position in Settings.player_array.
var id: String = _generate_uuid()
var is_first_point: bool = true
var character: Texture = load(NO_CHARACTER_PATH) var character: Texture = load(NO_CHARACTER_PATH)
func _init(new_name: String) -> void: func _init(new_name: String) -> void:
player_name = new_name player_name = new_name
player_score = 0 player_score = 0
## Returns a random version 4 UUID, e.g. "3f2b8c1e-9a4d-4e7f-b2c6-5d8e1f0a7b93". ## Returns a random version 4 UUID, e.g. "3f2b8c1e-9a4d-4e7f-b2c6-5d8e1f0a7b93".
static func _generate_uuid() -> String: static func _generate_uuid() -> String:
var bytes: PackedByteArray = Crypto.new().generate_random_bytes(16) var bytes: PackedByteArray = Crypto.new().generate_random_bytes(16)
bytes[6] = (bytes[6] & 0x0f) | 0x40 bytes[6] = (bytes[6] & 0x0f) | 0x40
bytes[8] = (bytes[8] & 0x3f) | 0x80 bytes[8] = (bytes[8] & 0x3f) | 0x80
var hex: String = bytes.hex_encode() var hex: String = bytes.hex_encode()
return "%s-%s-%s-%s-%s" % [hex.substr(0, 8), hex.substr(8, 4), hex.substr(12, 4), hex.substr(16, 4), hex.substr(20, 12)] return "%s-%s-%s-%s-%s" % [hex.substr(0, 8), hex.substr(8, 4), hex.substr(12, 4),
hex.substr(16, 4), hex.substr(20, 12)]
func add_point() -> void: func add_point() -> void:
if player_score < Settings.winning_score: if player_score < Settings.winning_score:
@@ -46,14 +48,15 @@ func add_point() -> void:
# With a low winning score the first point can also be match point or the win, # With a low winning score the first point can also be match point or the win,
# so the more important event is checked first # so the more important event is checked first
if player_score == Settings.winning_score: if player_score == Settings.winning_score:
winner_triggerd.emit() winner_triggered.emit()
elif player_score == Settings.winning_score - 1: elif player_score == Settings.winning_score - 1:
match_point_triggerd.emit() match_point_triggered.emit()
elif first_point: elif first_point:
first_point_triggerd.emit() first_point_triggered.emit()
else: else:
point_given_sound.emit() point_given_sound.emit()
func minus_point() -> void: func minus_point() -> void:
if player_score > 0: if player_score > 0:
player_score -= 1 player_score -= 1
@@ -61,6 +64,7 @@ func minus_point() -> void:
if player_score == 0: if player_score == 0:
is_first_point = true is_first_point = true
func reset_points() -> void: func reset_points() -> void:
player_score = 0 player_score = 0
is_first_point = true is_first_point = true
File renamed without changes.
+27 -4
View File
@@ -1,26 +1,32 @@
extends Node extends Node
## The songs of the current quiz, in the order they were added, and which of them are
## played, answered and playing.
##
## Registered as the [code]Playlist[/code] autoload.
var song_object_array: Array[SongObject] = [] var song_object_array: Array[SongObject] = []
var latest_played_song: int = 0 var latest_played_song: int = 0
var currently_playing_song: int = -1 var currently_playing_song: int = -1
var first_song_downloaded: bool = false var first_song_downloaded: bool = false
var first_song_played: bool = false var first_song_played: bool = false
var number_of_tracks_to_preload: int = 1 var number_of_tracks_to_preload: int = 1
var is_song_downloading: bool = false var is_song_downloading: bool = false
func get_next_song() -> SongObject: func get_next_song() -> SongObject:
return song_object_array[latest_played_song] return song_object_array[latest_played_song]
func set_latest_played_song() -> void: func set_latest_played_song() -> void:
latest_played_song += 1 latest_played_song += 1
if latest_played_song > song_object_array.size() - 1: if latest_played_song > song_object_array.size() - 1:
latest_played_song = song_object_array.size() - 1 latest_played_song = song_object_array.size() - 1
func update_last_played_song() -> void: func update_last_played_song() -> void:
latest_played_song = song_object_array.size() latest_played_song = song_object_array.size()
func get_preloaded_songs() -> int: func get_preloaded_songs() -> int:
var number_of_tracks_preloaded: int = 0 var number_of_tracks_preloaded: int = 0
for song: SongObject in song_object_array: for song: SongObject in song_object_array:
@@ -28,22 +34,26 @@ func get_preloaded_songs() -> int:
number_of_tracks_preloaded += 1 number_of_tracks_preloaded += 1
return number_of_tracks_preloaded return number_of_tracks_preloaded
func should_preload_more_songs() -> bool: func should_preload_more_songs() -> bool:
return number_of_tracks_to_preload > get_preloaded_songs() return number_of_tracks_to_preload > get_preloaded_songs()
func song_is_answered(song_no: int = currently_playing_song) -> void: func song_is_answered(song_no: int = currently_playing_song) -> void:
# Nothing is playing yet (-1); a negative index would answer the last, unplayed song # Nothing is playing yet (-1); a negative index would answer the last, unplayed song
if song_no < 0 || song_no >= song_object_array.size(): if song_no < 0 || song_no >= song_object_array.size():
return return
print("show_answer_pressed song_object: ", song_object_array[song_no]._to_string()) GameLog.debug("show_answer_pressed song_object: ", song_object_array[song_no]._to_string())
song_object_array[song_no].is_answered = true song_object_array[song_no].is_answered = true
if Settings.add_to_stats: if Settings.add_to_stats:
print("add to stats: ", song_no) GameLog.debug("add to stats: ", song_no)
MusicServerClient.mark_song_played(song_no) MusicServerClient.mark_song_played(song_no)
func add_to_playlist(new_song_object: SongObject) -> void: func add_to_playlist(new_song_object: SongObject) -> void:
song_object_array.append(new_song_object) song_object_array.append(new_song_object)
func get_playlist() -> Array[SongObject]: func get_playlist() -> Array[SongObject]:
if Settings.is_debug: if Settings.is_debug:
return song_object_array return song_object_array
@@ -54,46 +64,59 @@ func get_playlist() -> Array[SongObject]:
temp_list.append(s) temp_list.append(s)
return temp_list return temp_list
func reset_playlist() -> void: func reset_playlist() -> void:
currently_playing_song = -1 currently_playing_song = -1
latest_played_song = 0 latest_played_song = 0
clear_song_list() clear_song_list()
func clear_song_list() -> void: func clear_song_list() -> void:
song_object_array.clear() song_object_array.clear()
func unset_is_playing() -> void: func unset_is_playing() -> void:
for song_object: SongObject in song_object_array: for song_object: SongObject in song_object_array:
song_object.is_playing = false song_object.is_playing = false
func set_currently_playing_song(value: int) -> void: func set_currently_playing_song(value: int) -> void:
currently_playing_song = value currently_playing_song = value
func get_current_soundtrack_title() -> String: func get_current_soundtrack_title() -> String:
return song_object_array[currently_playing_song].get_soundtrack_title() return song_object_array[currently_playing_song].get_soundtrack_title()
func get_current_song_title() -> String: func get_current_song_title() -> String:
return song_object_array[currently_playing_song].get_song_title() return song_object_array[currently_playing_song].get_song_title()
func add_point(player_id: String) -> void: func add_point(player_id: String) -> void:
song_object_array[currently_playing_song].add_point(player_id) song_object_array[currently_playing_song].add_point(player_id)
func remove_point(player_id: String) -> void: func remove_point(player_id: String) -> void:
song_object_array[currently_playing_song].remove_point(player_id) song_object_array[currently_playing_song].remove_point(player_id)
## Removes a player's points from every song. ## Removes a player's points from every song.
func remove_player_from_list(player_id: String) -> void: func remove_player_from_list(player_id: String) -> void:
for song_object: SongObject in song_object_array: for song_object: SongObject in song_object_array:
song_object.remove_point(player_id) song_object.remove_point(player_id)
func get_song(song_no: int) -> AudioStream: func get_song(song_no: int) -> AudioStream:
return song_object_array[song_no].song return song_object_array[song_no].song
func add_song_to_song_object(song_no: int, sound: AudioStream) -> void: func add_song_to_song_object(song_no: int, sound: AudioStream) -> void:
song_object_array[song_no].song = sound song_object_array[song_no].song = sound
func song_has_played(song_no: int = latest_played_song) -> void: func song_has_played(song_no: int = latest_played_song) -> void:
song_object_array[song_no].has_played = true song_object_array[song_no].has_played = true
func song_is_playing(song_no: int = latest_played_song) -> void: func song_is_playing(song_no: int = latest_played_song) -> void:
song_object_array[song_no].is_playing = true song_object_array[song_no].is_playing = true
File renamed without changes.
+4 -4
View File
@@ -1,14 +1,14 @@
extends Node extends Node
## The game settings and the players, shared by the whole game.
##
## Registered as the [code]Settings[/code] autoload.
const MAX_NUMBER_OF_PLAYERS: int = 7 const MAX_NUMBER_OF_PLAYERS: int = 7
var default_path: String = "https://music.sanplex.xyz" var default_path: String = "https://music.sanplex.xyz"
var selected_server: int = 0 var selected_server: int = 0
var is_local: bool = false var is_local: bool = false
var is_debug: bool = false var is_debug: bool = false
var stop_after_current: bool = true var stop_after_current: bool = true
var auto_repeat_song: bool = true var auto_repeat_song: bool = true
var hide_next_track: bool = true var hide_next_track: bool = true
@@ -22,7 +22,6 @@ var hide_for_seconds: float = 5.0
var hide_beginning: bool = false var hide_beginning: bool = false
var hide_length: bool = false var hide_length: bool = false
var hide_ticks: bool = false var hide_ticks: bool = false
var player_array: Array[PlayerObject] var player_array: Array[PlayerObject]
var edit_players: bool = false var edit_players: bool = false
var currently_syncing: bool = false var currently_syncing: bool = false
@@ -32,6 +31,7 @@ var character_select_open: bool = false
## Releases are made from it, see RELEASING.md. ## Releases are made from it, see RELEASING.md.
var version: String = ProjectSettings.get_setting("application/config/version", "") var version: String = ProjectSettings.get_setting("application/config/version", "")
func delete_children(node: Node) -> void: func delete_children(node: Node) -> void:
for n: Node in node.get_children(): for n: Node in node.get_children():
node.remove_child(n) node.remove_child(n)
File renamed without changes.
+14 -10
View File
@@ -3,53 +3,57 @@ extends RefCounted
class_name SongObject class_name SongObject
var song_number: int var song_number: int
var song_title: String var song_title: String
var soundtrack_title: String var soundtrack_title: String
## Ids of the players who got a point for this song. ## Ids of the players who got a point for this song.
var players_given_point: Array[String] = [] var players_given_point: Array[String] = []
var is_answered: bool = false var is_answered: bool = false
var has_played: bool = false var has_played: bool = false
var is_playing: bool = false var is_playing: bool = false
var song: AudioStream var song: AudioStream
func get_soundtrack_title() -> String: func get_soundtrack_title() -> String:
if is_answered: if is_answered:
return soundtrack_title return soundtrack_title
else: else:
return "????????" return "????????"
func get_song_title() -> String: func get_song_title() -> String:
if is_answered: if is_answered:
return song_title return song_title
else: else:
return "??????" return "??????"
func get_song_info() -> String: func get_song_info() -> String:
if Settings.is_debug: if Settings.is_debug:
var format_string: String = "%d. %s - %s | played: %s | answered: %s" var format_string: String = "%d. %s - %s | played: %s | answered: %s"
return format_string % [(song_number + 1), soundtrack_title, song_title, has_played, is_answered] return format_string % [(song_number + 1), soundtrack_title, song_title, has_played,
is_answered]
elif is_answered: elif is_answered:
var format_string: String = "%d. %s - %s" var format_string: String = "%d. %s - %s"
return format_string % [(song_number + 1), soundtrack_title, song_title] return format_string % [(song_number + 1), soundtrack_title, song_title]
else: else:
return "??? - ???" return "??? - ???"
func add_point(player_id: String) -> void: func add_point(player_id: String) -> void:
print("add_point") GameLog.debug("add_point")
if !players_given_point.has(player_id): if !players_given_point.has(player_id):
players_given_point.append(player_id) players_given_point.append(player_id)
func remove_point(player_id: String) -> void: func remove_point(player_id: String) -> void:
print("remove_point") GameLog.debug("remove_point")
players_given_point.erase(player_id) players_given_point.erase(player_id)
func _to_string() -> String: func _to_string() -> String:
var format_string: String = "song_number: %d | soundtrack_title: %s | song_title: %s | is_answered: %s | has_played: %s | is_playing: %s" var format_string: String = "song_number: %d | soundtrack_title: %s | song_title: %s" \
return format_string % [(song_number), soundtrack_title, song_title, is_answered, has_played, is_playing] + " | is_answered: %s | has_played: %s | is_playing: %s"
return format_string % [song_number, soundtrack_title, song_title, is_answered, has_played,
is_playing]
File renamed without changes.
-42
View File
@@ -1,42 +0,0 @@
class_name HttpTransport
extends RefCounted
## Sends HTTP requests for [code]MusicServerClient[/code] using [HTTPRequest] nodes.
##
## Tests subclass this to answer requests without a network connection.
## Seconds before a request is given up and reported as failed. This covers the whole
## download, so it is generous enough for long songs on a slow connection.
var timeout_seconds: float = 120.0
var _owner: Node
func _init(owner: Node) -> void:
_owner = owner
## Requests [param url] and calls [param on_complete] with
## [code](response_code: int, body: PackedByteArray)[/code]. Connection errors and
## timeouts are reported with response code 0.
func request(url: String, method: int, on_complete: Callable) -> void:
var http_request: HTTPRequest = HTTPRequest.new()
http_request.timeout = timeout_seconds
_owner.add_child(http_request)
http_request.request_completed.connect(
func(
result: int,
response_code: int,
_headers: PackedStringArray,
body: PackedByteArray,
) -> void:
http_request.queue_free()
on_complete.call(response_code if result == HTTPRequest.RESULT_SUCCESS else 0, body)
)
var headers: PackedStringArray = []
if method == HTTPClient.METHOD_PUT:
headers.append("Content-Type: application/json")
var error: int = http_request.request(url, headers, method)
if error != OK:
http_request.queue_free()
push_error("Could not send request to %s (error %d)" % [url, error])
on_complete.call(0, PackedByteArray())
-57
View File
@@ -1,57 +0,0 @@
class_name ListRow
extends PanelContainer
## One item in a list of names, such as the inspiration list or a sync result column.
##
## Every row has its own background, alternating between two shades, so it is clear
## where an item starts even when its text wraps over several lines.
const EVEN_BACKGROUND: Color = Color(1, 1, 1, 0.05)
const ODD_BACKGROUND: Color = Color(1, 1, 1, 0.11)
static var _even_style: StyleBoxFlat
static var _odd_style: StyleBoxFlat
## The label showing the item's text.
var label: Label
## The item's text.
var text: String:
get:
return label.text
## Creates the row for item number [param row] (counting from 0) in a list.
static func create(
item_text: String,
row: int,
font_size: int,
alignment: HorizontalAlignment = HORIZONTAL_ALIGNMENT_LEFT,
) -> ListRow:
var list_row: ListRow = ListRow.new()
list_row.add_theme_stylebox_override("panel", _style(row % 2 != 0))
list_row.label = Label.new()
list_row.label.add_theme_font_size_override("font_size", font_size)
list_row.label.text = item_text
list_row.label.horizontal_alignment = alignment
list_row.label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
list_row.add_child(list_row.label)
return list_row
# Rows share two styles instead of each creating its own
static func _style(odd: bool) -> StyleBoxFlat:
if _even_style == null:
_even_style = _make_style(EVEN_BACKGROUND)
_odd_style = _make_style(ODD_BACKGROUND)
return _odd_style if odd else _even_style
static func _make_style(background: Color) -> StyleBoxFlat:
var style: StyleBoxFlat = StyleBoxFlat.new()
style.bg_color = background
style.set_corner_radius_all(4)
style.content_margin_left = 8
style.content_margin_right = 8
style.content_margin_top = 2
style.content_margin_bottom = 2
return style
+102
View File
@@ -0,0 +1,102 @@
[gd_scene format=3 uid="uid://xwq863o6uvsu"]
[ext_resource type="PackedScene" path="res://main/TopBar.tscn" id="top_bar_scene"]
[ext_resource type="PackedScene" path="res://quiz/Quiz.tscn" id="quiz_scene"]
[ext_resource type="Script" uid="uid://b8f4g15cas2j2" path="res://main/main_window.gd" id="1_eu0t5"]
[ext_resource type="PackedScene" uid="uid://bxydgil1yifps" path="res://popups/SearchPopup.tscn" id="9_5ijvr"]
[ext_resource type="PackedScene" uid="uid://dpdem7pdxweb5" path="res://popups/SyncPopup.tscn" id="10_yxw0b"]
[ext_resource type="PackedScene" uid="uid://bd1by80q1v27e" path="res://popups/ShortcutsPopup.tscn" id="13_8eebo"]
[ext_resource type="PackedScene" uid="uid://btupbowehiyyu" path="res://popups/QRPopup.tscn" id="15_qr_window"]
[ext_resource type="PackedScene" uid="uid://cp54idaudb8dp" path="res://popups/AboutPopup.tscn" id="16_about_window"]
[ext_resource type="Theme" path="res://themes/popup_theme.tres" id="popup_theme"]
[ext_resource type="PackedScene" uid="uid://bijh5h5yrivm3" path="res://popups/LogPopup.tscn" id="14_26rwn"]
[node name="MainWindow" type="Control" unique_id=407149696]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_eu0t5")
[node name="TopBar" parent="." unique_id=1734261695 instance=ExtResource("top_bar_scene")]
[node name="Quiz" parent="." unique_id=1357908642 instance=ExtResource("quiz_scene")]
[node name="SearchPopup" parent="." unique_id=1661250375 instance=ExtResource("9_5ijvr")]
visible = false
layout_mode = 1
offset_left = 56.0
offset_top = 96.0
offset_right = 56.0
offset_bottom = 96.0
[node name="SyncPopup" parent="." unique_id=182499699 instance=ExtResource("10_yxw0b")]
unique_name_in_owner = true
visible = false
layout_mode = 1
offset_left = 210.0
offset_top = 153.0
offset_right = 210.0
offset_bottom = 153.0
[node name="QRPopup" parent="." unique_id=589733821 instance=ExtResource("15_qr_window")]
unique_name_in_owner = true
visible = false
layout_mode = 1
offset_left = 720.0
offset_top = 153.0
offset_right = 720.0
offset_bottom = 153.0
[node name="ShortcutsPopup" parent="." unique_id=1513416242 instance=ExtResource("13_8eebo")]
unique_name_in_owner = true
visible = false
layout_mode = 1
offset_left = 562.0
offset_top = 160.0
offset_right = 1262.0
offset_bottom = 860.0
[node name="StatisticsPopupPanel" type="PopupPanel" parent="." unique_id=298311407]
theme = ExtResource("popup_theme")
oversampling_override = 1.0
initial_position = 2
size = Vector2i(450, 100)
[node name="Label" type="Label" parent="StatisticsPopupPanel" unique_id=548316493]
offset_left = 4.0
offset_top = 4.0
offset_right = 446.0
offset_bottom = 96.0
text = "Statistics"
horizontal_alignment = 1
[node name="StatisticsLabel" type="Label" parent="StatisticsPopupPanel" unique_id=58226477]
unique_name_in_owner = true
offset_left = 4.0
offset_top = 4.0
offset_right = 446.0
offset_bottom = 96.0
text = "Total amount of soundtracks in the playlist: 9000"
horizontal_alignment = 1
vertical_alignment = 1
[node name="AboutPopup" parent="." unique_id=141261008 instance=ExtResource("16_about_window")]
unique_name_in_owner = true
visible = false
layout_mode = 1
offset_left = 720.0
offset_top = 153.0
offset_right = 720.0
offset_bottom = 153.0
[node name="LogPopup" parent="." unique_id=1289881388 instance=ExtResource("14_26rwn")]
unique_name_in_owner = true
visible = false
layout_mode = 1
offset_left = 562.0
offset_top = 132.0
offset_right = 1262.0
offset_bottom = 832.0
+100
View File
@@ -0,0 +1,100 @@
[gd_scene format=3]
[ext_resource type="Script" path="res://main/top_bar.gd" id="top_bar_script"]
[sub_resource type="LabelSettings" id="LabelSettings_ychxr"]
font_size = 25
[sub_resource type="InputEventKey" id="InputEventKey_ujjlu"]
device = -1
alt_pressed = true
keycode = 83
[sub_resource type="Shortcut" id="Shortcut_fbju4"]
events = [SubResource("InputEventKey_ujjlu")]
[node name="TopBar" type="Control" unique_id=1489162753]
layout_mode = 3
anchors_preset = 10
anchor_right = 1.0
offset_bottom = 56.0
grow_horizontal = 2
mouse_filter = 2
script = ExtResource("top_bar_script")
[node name="TopPanelContainer" type="PanelContainer" parent="." unique_id=663404711]
layout_mode = 0
offset_right = 1920.0
offset_bottom = 56.0
[node name="Title" type="Label" parent="." unique_id=1187907861]
layout_mode = 0
offset_left = 64.0
offset_top = 12.0
offset_right = 369.0
offset_bottom = 50.0
size_flags_horizontal = 0
text = "Music Player Randomizer"
label_settings = SubResource("LabelSettings_ychxr")
[node name="SyncButton" type="Button" parent="." unique_id=1228313556]
layout_mode = 0
offset_left = 1442.0
offset_top = 8.0
offset_right = 1512.0
offset_bottom = 48.0
focus_mode = 0
action_mode = 0
text = "Sync"
[node name="SearchButton" type="Button" parent="." unique_id=1782453581]
layout_mode = 0
offset_left = 1354.0
offset_top = 8.0
offset_right = 1424.0
offset_bottom = 48.0
focus_mode = 0
action_mode = 0
shortcut = SubResource("Shortcut_fbju4")
text = "Search"
[node name="StatisticsButton" type="Button" parent="." unique_id=2045021269]
layout_mode = 0
offset_left = 1530.0
offset_top = 8.0
offset_right = 1600.0
offset_bottom = 48.0
focus_mode = 0
action_mode = 0
text = "Statistics"
[node name="ShortcutsButton" type="Button" parent="." unique_id=2066498226]
layout_mode = 0
offset_left = 1626.0
offset_top = 8.0
offset_right = 1696.0
offset_bottom = 48.0
focus_mode = 0
action_mode = 0
text = "Shortcuts"
[node name="AboutButton" type="Button" parent="." unique_id=1057035253]
layout_mode = 0
offset_left = 1722.0
offset_top = 8.0
offset_right = 1792.0
offset_bottom = 48.0
focus_mode = 0
action_mode = 0
text = "About"
[node name="QRButton" type="Button" parent="." unique_id=2021908559]
layout_mode = 0
offset_left = 1802.0
offset_top = 8.0
offset_right = 1872.0
offset_bottom = 48.0
focus_mode = 0
action_mode = 0
text = "QR"
+102
View File
@@ -0,0 +1,102 @@
class_name MainWindow
extends Control
## The main window: the top bar, the popups opened from it and the quiz below it.
##
## Everything that is not part of a quiz lives here, so other views can be added next
## to the quiz later.
## Dims the window behind an open popup and keeps clicks from reaching the buttons under it.
var popup_backdrop: PopupBackdrop
@onready var top_bar: TopBar = $TopBar
@onready var quiz: Quiz = $Quiz
@onready var search_popup: SearchPopup = $SearchPopup
@onready var sync_popup: SyncPopup = %SyncPopup
@onready var shortcuts_popup: ShortcutsPopup = %ShortcutsPopup
@onready var qr_popup: QRPopup = %QRPopup
@onready var about_popup: AboutPopup = %AboutPopup
@onready var log_popup: LogPopup = %LogPopup
@onready var statistic_popup: PopupPanel = $StatisticsPopupPanel
@onready var statistic_label: Label = %StatisticsLabel
func _ready() -> void:
popup_backdrop = PopupBackdrop.add_to(self, _popups())
if OS.has_feature("macos"):
GameLog.debug("is_mac")
top_bar.search_pressed.connect(show_search)
top_bar.sync_pressed.connect(sync_soundtracks)
top_bar.statistics_pressed.connect(get_statistics)
top_bar.shortcuts_pressed.connect(show_shortcuts)
top_bar.about_pressed.connect(show_about)
top_bar.qr_pressed.connect(show_qr)
sync_popup.sync_started.connect(_on_sync_started)
sync_popup.sync_finished.connect(_on_sync_finished)
quiz.server_changed.connect(search_popup.get_list_of_soundtracks)
quiz.event_logged.connect(log_popup.add_log_row)
search_popup.get_list_of_soundtracks()
func _input(event: InputEvent) -> void:
if event is InputEventKey and event.pressed:
if event.alt_pressed && event.keycode == KEY_ENTER:
GameLog.debug("Alt + Enter pressed")
Settings.fullscreen = !Settings.fullscreen
if Settings.fullscreen:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
if !Settings.fullscreen:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
if event.alt_pressed && event.keycode == KEY_K:
shortcuts_popup.toggle()
if event.alt_pressed && event.keycode == KEY_L:
log_popup.toggle()
func _popups() -> Array[BasePopup]:
return [search_popup, sync_popup, qr_popup, shortcuts_popup, about_popup, log_popup]
func show_search() -> void:
GameLog.debug("show_search")
search_popup.toggle()
func show_about() -> void:
GameLog.debug("show_about")
about_popup.open()
func show_shortcuts() -> void:
GameLog.debug("show_shortcuts")
shortcuts_popup.open()
func show_qr() -> void:
GameLog.debug("show_qr")
qr_popup.open()
func get_statistics() -> void:
GameLog.debug("get_statistics")
statistic_popup.visible = true
statistic_label.text = "Total amount of soundtracks in the playlist: " \
+ str(search_popup.soundtracks.size())
func sync_soundtracks() -> void:
GameLog.debug("sync_soundtracks")
sync_popup.start_sync()
func _on_sync_started() -> void:
GameLog.debug("_on_sync_started")
top_bar.sync_button.disabled = false
quiz.lock_for_sync()
func _on_sync_finished() -> void:
GameLog.debug("_on_sync_finished")
search_popup.get_list_of_soundtracks()
quiz.on_sync_finished()
File renamed without changes.
+22
View File
@@ -0,0 +1,22 @@
class_name TopBar
extends Control
## The bar along the top of the main window, with the title and the buttons for things
## that are not part of a quiz.
signal search_pressed
signal sync_pressed
signal statistics_pressed
signal shortcuts_pressed
signal about_pressed
signal qr_pressed
@onready var sync_button: Button = $SyncButton
func _ready() -> void:
$SearchButton.pressed.connect(search_pressed.emit)
sync_button.pressed.connect(sync_pressed.emit)
$StatisticsButton.pressed.connect(statistics_pressed.emit)
$ShortcutsButton.pressed.connect(shortcuts_pressed.emit)
$AboutButton.pressed.connect(about_pressed.emit)
$QRButton.pressed.connect(qr_pressed.emit)
+1
View File
@@ -0,0 +1 @@
uid://bb2ukabiec1vv
+34 -21
View File
@@ -1,10 +1,10 @@
[gd_scene load_steps=10 format=3 uid="uid://ds15cgsf8vpvc"] [gd_scene load_steps=10 format=3 uid="uid://ds15cgsf8vpvc"]
[ext_resource type="Script" uid="uid://d1v0rquma6nt8" path="res://MusicPlayer.gd" id="1_t24ra"] [ext_resource type="Script" uid="uid://d1v0rquma6nt8" path="res://player/music_player.gd" id="1_t24ra"]
[ext_resource type="AudioStream" uid="uid://n2g8jddr85h2" path="res://sounds/01. Opening.mp3" id="2_xti80"] [ext_resource type="AudioStream" uid="uid://n2g8jddr85h2" path="res://sounds/01. Opening.mp3" id="2_xti80"]
[ext_resource type="Texture2D" uid="uid://comxqfiykp54f" path="res://icons/play_icon_light.svg" id="3_6g308"] [ext_resource type="Texture2D" uid="uid://comxqfiykp54f" path="res://icons/play_icon_light.svg" id="3_6g308"]
[ext_resource type="Texture2D" uid="uid://ccb6rvbldlgdg" path="res://icons/reload_light_icon.svg" id="4_jleuo"] [ext_resource type="Texture2D" uid="uid://ccb6rvbldlgdg" path="res://icons/reload_light_icon.svg" id="4_jleuo"]
[ext_resource type="PackedScene" uid="uid://w400rnew7453" path="res://volume_slider.tscn" id="5_iifuj"] [ext_resource type="PackedScene" uid="uid://w400rnew7453" path="res://player/VolumeSlider.tscn" id="5_iifuj"]
[sub_resource type="InputEventKey" id="InputEventKey_r4qo3"] [sub_resource type="InputEventKey" id="InputEventKey_r4qo3"]
device = -1 device = -1
@@ -26,19 +26,11 @@ unicode = 90
[sub_resource type="Shortcut" id="Shortcut_i7swq"] [sub_resource type="Shortcut" id="Shortcut_i7swq"]
events = [SubResource("InputEventKey_2nvce")] events = [SubResource("InputEventKey_2nvce")]
[node name="MusicPlayer" type="PanelContainer"] [node name="MusicPlayer" type="Control"]
anchors_preset = 8 layout_mode = 3
anchor_left = 0.5 offset_right = 1792.0
anchor_top = 0.5 offset_bottom = 71.0
anchor_right = 0.5 mouse_filter = 2
anchor_bottom = 0.5
offset_left = -121.0
offset_top = -15.5
offset_right = 121.0
offset_bottom = 16.5
grow_horizontal = 2
grow_vertical = 2
size_flags_vertical = 4
script = ExtResource("1_t24ra") script = ExtResource("1_t24ra")
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."] [node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
@@ -53,11 +45,18 @@ bus = &"music"
stream = ExtResource("2_xti80") stream = ExtResource("2_xti80")
bus = &"music" bus = &"music"
[node name="MusicPlayerContainer" type="HBoxContainer" parent="."] [node name="PlayerBar" type="PanelContainer" parent="."]
layout_mode = 0
offset_left = 1.0
offset_right = 1792.0
offset_bottom = 32.0
[node name="MusicPlayerContainer" type="HBoxContainer" parent="PlayerBar"]
layout_mode = 2 layout_mode = 2
size_flags_vertical = 4 size_flags_vertical = 4
[node name="PlayTextureButton" type="TextureButton" parent="MusicPlayerContainer"] [node name="PlayTextureButton" type="TextureButton" parent="PlayerBar/MusicPlayerContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(32, 32) custom_minimum_size = Vector2(32, 32)
layout_mode = 2 layout_mode = 2
focus_mode = 0 focus_mode = 0
@@ -67,7 +66,8 @@ texture_normal = ExtResource("3_6g308")
ignore_texture_size = true ignore_texture_size = true
stretch_mode = 0 stretch_mode = 0
[node name="RestartTextureButton" type="TextureButton" parent="MusicPlayerContainer"] [node name="RestartTextureButton" type="TextureButton" parent="PlayerBar/MusicPlayerContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(32, 32) custom_minimum_size = Vector2(32, 32)
layout_mode = 2 layout_mode = 2
focus_mode = 0 focus_mode = 0
@@ -78,17 +78,30 @@ ignore_texture_size = true
stretch_mode = 0 stretch_mode = 0
flip_h = true flip_h = true
[node name="MusicPlayerSlider" type="HSlider" parent="MusicPlayerContainer"] [node name="MusicPlayerSlider" type="HSlider" parent="PlayerBar/MusicPlayerContainer"]
unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
size_flags_vertical = 4 size_flags_vertical = 4
scrollable = false scrollable = false
[node name="MusicTimeLabel" type="Label" parent="MusicPlayerContainer"] [node name="MusicTimeLabel" type="Label" parent="PlayerBar/MusicPlayerContainer"]
unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "0:00 / 0:00" text = "0:00 / 0:00"
[node name="VolumeSlider" parent="MusicPlayerContainer" instance=ExtResource("5_iifuj")] [node name="VolumeSlider" parent="PlayerBar/MusicPlayerContainer" instance=ExtResource("5_iifuj")]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 0) custom_minimum_size = Vector2(100, 0)
layout_mode = 2 layout_mode = 2
size_flags_vertical = 4 size_flags_vertical = 4
[node name="RepeatSongCheckButton" type="CheckButton" parent="."]
layout_mode = 0
offset_top = 40.0
offset_right = 144.0
offset_bottom = 71.0
focus_mode = 0
button_pressed = true
action_mode = 0
text = "Auto repeat"
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://w400rnew7453"] [gd_scene load_steps=2 format=3 uid="uid://w400rnew7453"]
[ext_resource type="Script" uid="uid://bgviu7smansj1" path="res://volume_slider.gd" id="1_1aufb"] [ext_resource type="Script" uid="uid://bgviu7smansj1" path="res://player/volume_slider.gd" id="1_1aufb"]
[node name="VolumeSlider" type="HSlider"] [node name="VolumeSlider" type="HSlider"]
offset_right = 8.0 offset_right = 8.0
+47 -40
View File
@@ -1,47 +1,30 @@
class_name MusicPlayer class_name MusicPlayer
extends PanelContainer extends Control
## The bar that plays the songs, with play, restart, progress and volume, and the
## auto repeat switch under it.
# LOC 20/1-24: 136 signal update_song_list
# LOC 12/6-24: 170 signal play_next_song
@onready
var play_button: TextureButton = $MusicPlayerContainer/PlayTextureButton
@onready
var restart_button: TextureButton = $MusicPlayerContainer/RestartTextureButton
@onready
var audio_player: AudioStreamPlayer = $AudioStreamPlayer
@onready
var sound_player: AudioStreamPlayer = $AudioSoundPlayer
@onready
var sound_effect_player: AudioStreamPlayer = $AudioSoundEffectPlayer
@onready
var progress_slider: HSlider = $MusicPlayerContainer/MusicPlayerSlider
@onready
var volume_slider: HSlider = $MusicPlayerContainer/VolumeSlider
@onready
var music_time_label: Label = $MusicPlayerContainer/MusicTimeLabel
var play_icon: Texture = preload("res://icons/play_icon_light.svg") var play_icon: Texture = preload("res://icons/play_icon_light.svg")
var pause_icon: Texture = preload("res://icons/pause_icon_light.svg") var pause_icon: Texture = preload("res://icons/pause_icon_light.svg")
var songs: Array = [] var songs: Array = []
var is_changing: bool = false var is_changing: bool = false
var playback_position: float var playback_position: float
var stream: AudioStream var stream: AudioStream
var song_finished: bool = false var song_finished: bool = false
signal update_song_list @onready var play_button: TextureButton = %PlayTextureButton
signal play_next_song @onready var restart_button: TextureButton = %RestartTextureButton
@onready var audio_player: AudioStreamPlayer = $AudioStreamPlayer
@onready var sound_player: AudioStreamPlayer = $AudioSoundPlayer
@onready var sound_effect_player: AudioStreamPlayer = $AudioSoundEffectPlayer
@onready var progress_slider: HSlider = %MusicPlayerSlider
@onready var volume_slider: VolumeSlider = %VolumeSlider
@onready var music_time_label: Label = %MusicTimeLabel
@onready var auto_repeat_button: CheckButton = $RepeatSongCheckButton
# Called when the node enters the scene tree for the first time.
func _ready() -> void: func _ready() -> void:
play_button.pressed.connect(play_or_pause) play_button.pressed.connect(play_or_pause)
restart_button.pressed.connect(restart) restart_button.pressed.connect(restart)
@@ -49,21 +32,30 @@ func _ready() -> void:
progress_slider.drag_ended.connect(_on_drag_ended) progress_slider.drag_ended.connect(_on_drag_ended)
audio_player.finished.connect(_on_finished) audio_player.finished.connect(_on_finished)
auto_repeat_button.pressed.connect(_on_auto_repeat_pressed)
func _on_auto_repeat_pressed() -> void:
Settings.auto_repeat_song = !Settings.auto_repeat_song
func _process(_delta: float) -> void: func _process(_delta: float) -> void:
if audio_player.has_stream_playback() && !is_changing && !audio_player.stream_paused: if audio_player.has_stream_playback() && !is_changing && !audio_player.stream_paused:
if (!Settings.hide_beginning || audio_player.get_playback_position() > Settings.hide_for_seconds): if !Settings.hide_beginning \
|| audio_player.get_playback_position() > Settings.hide_for_seconds:
progress_slider.value = audio_player.get_playback_position() progress_slider.value = audio_player.get_playback_position()
if stream != null: if stream != null:
music_time_label.text = format_text(progress_slider.value, stream.get_length()) music_time_label.text = format_text(progress_slider.value, stream.get_length())
else: else:
music_time_label.text = format_text(progress_slider.value, 0.0) music_time_label.text = format_text(progress_slider.value, 0.0)
func format_time(time: float) -> String: func format_time(time: float) -> String:
var total_seconds: int = roundi(time) var total_seconds: int = roundi(time)
@warning_ignore("integer_division") @warning_ignore("integer_division")
return "%02d:%02d" % [total_seconds / 60, total_seconds % 60] return "%02d:%02d" % [total_seconds / 60, total_seconds % 60]
func format_text(part: float, total: float) -> String: func format_text(part: float, total: float) -> String:
if (Settings.hide_beginning && part < Settings.hide_for_seconds) && Settings.hide_length: if (Settings.hide_beginning && part < Settings.hide_for_seconds) && Settings.hide_length:
return "??:?? / ??:??" return "??:?? / ??:??"
@@ -74,6 +66,7 @@ func format_text(part: float, total: float) -> String:
else: else:
return format_time(part) + " / " + format_time(total) return format_time(part) + " / " + format_time(total)
func play_or_pause() -> void: func play_or_pause() -> void:
if song_finished: if song_finished:
restart() restart()
@@ -82,7 +75,7 @@ func play_or_pause() -> void:
audio_player.stream_paused = false audio_player.stream_paused = false
song_finished = false song_finished = false
audio_player.seek(playback_position) audio_player.seek(playback_position)
print("continue") GameLog.debug("continue")
progress_slider.max_value = round(stream.get_length()) progress_slider.max_value = round(stream.get_length())
if Settings.hide_ticks: if Settings.hide_ticks:
progress_slider.tick_count = 0 progress_slider.tick_count = 0
@@ -91,11 +84,13 @@ func play_or_pause() -> void:
else: else:
pause() pause()
func pause() -> void: func pause() -> void:
audio_player.stream_paused = true audio_player.stream_paused = true
playback_position = audio_player.get_playback_position() playback_position = audio_player.get_playback_position()
play_button.texture_normal = play_icon play_button.texture_normal = play_icon
func restart() -> void: func restart() -> void:
audio_player.stop() audio_player.stop()
audio_player.stream_paused = false audio_player.stream_paused = false
@@ -106,6 +101,7 @@ func restart() -> void:
play_button.texture_normal = pause_icon play_button.texture_normal = pause_icon
audio_player.play() audio_player.play()
func _on_finished() -> void: func _on_finished() -> void:
play_button.texture_normal = play_icon play_button.texture_normal = play_icon
song_finished = true song_finished = true
@@ -114,14 +110,17 @@ func _on_finished() -> void:
if Settings.auto_repeat_song: if Settings.auto_repeat_song:
restart() restart()
func _on_drag_started() -> void: func _on_drag_started() -> void:
is_changing = true is_changing = true
func _on_drag_ended(_changed: bool) -> void: func _on_drag_ended(_changed: bool) -> void:
audio_player.seek(progress_slider.value) audio_player.seek(progress_slider.value)
playback_position = progress_slider.value playback_position = progress_slider.value
is_changing = false is_changing = false
func seek(new_position: float) -> void: func seek(new_position: float) -> void:
progress_slider.value += new_position progress_slider.value += new_position
is_changing = true is_changing = true
@@ -129,10 +128,12 @@ func seek(new_position: float) -> void:
playback_position = progress_slider.value playback_position = progress_slider.value
is_changing = false is_changing = false
func change_volume(value: float) -> void: func change_volume(value: float) -> void:
volume_slider.value += value volume_slider.value += value
volume_slider.change_volume(volume_slider.value) volume_slider.change_volume(volume_slider.value)
func _on_point_triggered(point: String) -> void: func _on_point_triggered(point: String) -> void:
if point == "first": if point == "first":
var value: int = randi_range(0, 10) var value: int = randi_range(0, 10)
@@ -145,10 +146,12 @@ func _on_point_triggered(point: String) -> void:
elif point == "match": elif point == "match":
play_sound(preload("res://sounds/sound0.mp3")) play_sound(preload("res://sounds/sound0.mp3"))
func play_sound_effect(sound_effect_name: AudioStream) -> void: func play_sound_effect(sound_effect_name: AudioStream) -> void:
sound_effect_player.stream = sound_effect_name sound_effect_player.stream = sound_effect_name
sound_effect_player.play() sound_effect_player.play()
func play_sound(sound_name: AudioStream) -> void: func play_sound(sound_name: AudioStream) -> void:
sound_player.stream = sound_name sound_player.stream = sound_name
audio_player.stop() audio_player.stop()
@@ -156,8 +159,7 @@ func play_sound(sound_name: AudioStream) -> void:
song_finished = true song_finished = true
play_button.texture_normal = play_icon play_button.texture_normal = play_icon
progress_slider.value = 0 progress_slider.value = 0
#if stream != null:
# music_time_label.text = format_text(progress_slider.value, stream.get_length())
func play_song(song: AudioStream) -> void: func play_song(song: AudioStream) -> void:
audio_player.stream = song audio_player.stream = song
@@ -174,8 +176,9 @@ func play_song(song: AudioStream) -> void:
progress_slider.tick_count = round(stream.get_length() / 60) progress_slider.tick_count = round(stream.get_length() / 60)
music_time_label.text = format_text(progress_slider.value, stream.get_length()) music_time_label.text = format_text(progress_slider.value, stream.get_length())
func play_song_object(song_object_no: int) -> void: func play_song_object(song_object_no: int) -> void:
print("play_song_object") GameLog.debug("play_song_object")
if audio_player.is_playing(): if audio_player.is_playing():
audio_player.stop() audio_player.stop()
await get_tree().create_timer(0.5).timeout await get_tree().create_timer(0.5).timeout
@@ -195,12 +198,13 @@ func play_song_object(song_object_no: int) -> void:
Playlist.set_currently_playing_song(song_object_no) Playlist.set_currently_playing_song(song_object_no)
Playlist.unset_is_playing() Playlist.unset_is_playing()
if !Settings.hide_next_track: if !Settings.hide_next_track:
print("Show answer now!!") GameLog.debug("Show answer now!!")
Playlist.song_is_answered(song_object_no) Playlist.song_is_answered(song_object_no)
Playlist.song_has_played(song_object_no) Playlist.song_has_played(song_object_no)
Playlist.song_is_playing(song_object_no) Playlist.song_is_playing(song_object_no)
update_song_list.emit() update_song_list.emit()
func get_sound_test_song() -> void: func get_sound_test_song() -> void:
var test_sound: Callable = func test_sound(body: PackedByteArray) -> void: var test_sound: Callable = func test_sound(body: PackedByteArray) -> void:
var sound: AudioStream = AudioStreamMP3.new() var sound: AudioStream = AudioStreamMP3.new()
@@ -211,18 +215,21 @@ func get_sound_test_song() -> void:
##### LOCAL ##### LOCAL
var local_path: String = '/Users/sebastian/ResilioSync/Sorterat_test/Metal Gear Solid 4 - Guns of the Patriots/2-16 Metal Gear Saga.mp3' var local_path: String = '/Users/sebastian/ResilioSync/Sorterat_test/Metal Gear Solid 4 - Guns of the Patriots/2-16 Metal Gear Saga.mp3'
func load_mp3(_path: String) -> AudioStream: func load_mp3(_path: String) -> AudioStream:
var file: FileAccess = FileAccess.open(_path, FileAccess.READ) var file: FileAccess = FileAccess.open(_path, FileAccess.READ)
var sound: AudioStream = AudioStreamMP3.new() var sound: AudioStream = AudioStreamMP3.new()
sound.data = file.get_buffer(file.get_length()) sound.data = file.get_buffer(file.get_length())
return sound return sound
func play_local_song() -> void: func play_local_song() -> void:
if songs: if songs:
local_path = songs[0] local_path = songs[0]
print(local_path) GameLog.debug(local_path)
print(FileAccess.file_exists(local_path)) GameLog.debug(FileAccess.file_exists(local_path))
play_sound(load_mp3(local_path)) play_sound(load_mp3(local_path))
func sound_test_local() -> void: func sound_test_local() -> void:
play_sound(preload("res://sounds/01. Opening.mp3")) play_sound(preload("res://sounds/01. Opening.mp3"))
File renamed without changes.
+12 -6
View File
@@ -1,18 +1,24 @@
class_name VolumeSlider
extends HSlider extends HSlider
## A slider that sets the volume of an audio bus.
@export ## The audio bus whose volume the slider sets.
var audio_bus_name := "music"
@export var audio_bus_name: String = "music"
@onready
var _bus: int var _bus: int
func _ready():
func _ready() -> void:
_bus = AudioServer.get_bus_index(audio_bus_name) _bus = AudioServer.get_bus_index(audio_bus_name)
value_changed.connect(_on_value_changed) value_changed.connect(_on_value_changed)
value = db_to_linear(AudioServer.get_bus_volume_db(_bus)) value = db_to_linear(AudioServer.get_bus_volume_db(_bus))
func _on_value_changed(changed_value: float) -> void:
AudioServer.set_bus_volume_db(_bus, linear_to_db(changed_value))
## Sets the bus volume to [param changed_value], from 0 (silent) to 1 (full).
func change_volume(changed_value: float) -> void: func change_volume(changed_value: float) -> void:
AudioServer.set_bus_volume_db(_bus, linear_to_db(changed_value)) AudioServer.set_bus_volume_db(_bus, linear_to_db(changed_value))
func _on_value_changed(changed_value: float) -> void:
AudioServer.set_bus_volume_db(_bus, linear_to_db(changed_value))
File renamed without changes.
+3 -3
View File
@@ -1,9 +1,9 @@
[gd_scene format=3 uid="uid://cp54idaudb8dp"] [gd_scene format=3 uid="uid://cp54idaudb8dp"]
[ext_resource type="Script" uid="uid://b7bh0of7olbcb" path="res://AboutWindow.gd" id="1_about_script"] [ext_resource type="Script" uid="uid://b7bh0of7olbcb" path="res://popups/about_popup.gd" id="1_about_script"]
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"] [ext_resource type="Theme" path="res://themes/popup_theme.tres" id="popup_theme"]
[node name="AboutControl" type="Control" unique_id=1626010355 node_paths=PackedStringArray("panel")] [node name="AboutPopup" type="Control" unique_id=1626010355 node_paths=PackedStringArray("panel")]
theme = ExtResource("popup_theme") theme = ExtResource("popup_theme")
layout_mode = 3 layout_mode = 3
anchors_preset = 15 anchors_preset = 15
+2 -2
View File
@@ -1,12 +1,12 @@
[gd_scene load_steps=4 format=3 uid="uid://c7ecjj2af6usf"] [gd_scene load_steps=4 format=3 uid="uid://c7ecjj2af6usf"]
[ext_resource type="Script" uid="uid://i35b3scbpwpu" path="res://character.gd" id="1_pjt0s"] [ext_resource type="Script" uid="uid://i35b3scbpwpu" path="res://popups/character_card.gd" id="1_pjt0s"]
[ext_resource type="Texture2D" uid="uid://cv3hdd6bsruau" path="res://icon.svg" id="2_pjt0s"] [ext_resource type="Texture2D" uid="uid://cv3hdd6bsruau" path="res://icon.svg" id="2_pjt0s"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_pjt0s"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_pjt0s"]
bg_color = Color(0.302, 0.302, 0.302, 1) bg_color = Color(0.302, 0.302, 0.302, 1)
[node name="Control" type="Control"] [node name="CharacterCard" type="Control"]
layout_mode = 3 layout_mode = 3
anchors_preset = 0 anchors_preset = 0
script = ExtResource("1_pjt0s") script = ExtResource("1_pjt0s")
@@ -1,12 +1,12 @@
[gd_scene load_steps=3 format=3 uid="uid://cdy4kvemwaiom"] [gd_scene load_steps=3 format=3 uid="uid://cdy4kvemwaiom"]
[ext_resource type="Script" uid="uid://cvffg87wr6r3y" path="res://new_character_select.gd" id="1_h4tdp"] [ext_resource type="Script" uid="uid://cvffg87wr6r3y" path="res://popups/character_select_popup.gd" id="1_h4tdp"]
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"] [ext_resource type="Theme" path="res://themes/popup_theme.tres" id="popup_theme"]
[sub_resource type="LabelSettings" id="LabelSettings_p4iny"] [sub_resource type="LabelSettings" id="LabelSettings_p4iny"]
font_size = 25 font_size = 25
[node name="Control" type="Control" node_paths=PackedStringArray("panel")] [node name="CharacterSelectPopup" type="Control" node_paths=PackedStringArray("panel")]
theme = ExtResource("popup_theme") theme = ExtResource("popup_theme")
layout_mode = 3 layout_mode = 3
anchors_preset = 0 anchors_preset = 0
+3 -3
View File
@@ -1,9 +1,9 @@
[gd_scene load_steps=2 format=3 uid="uid://bijh5h5yrivm3"] [gd_scene load_steps=2 format=3 uid="uid://bijh5h5yrivm3"]
[ext_resource type="Script" uid="uid://cw41y87l64qo7" path="res://log.gd" id="1_q72ua"] [ext_resource type="Script" uid="uid://cw41y87l64qo7" path="res://popups/log_popup.gd" id="1_q72ua"]
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"] [ext_resource type="Theme" path="res://themes/popup_theme.tres" id="popup_theme"]
[node name="Control" type="Control" node_paths=PackedStringArray("panel")] [node name="LogPopup" type="Control" node_paths=PackedStringArray("panel")]
theme = ExtResource("popup_theme") theme = ExtResource("popup_theme")
custom_minimum_size = Vector2(700, 700) custom_minimum_size = Vector2(700, 700)
layout_mode = 3 layout_mode = 3
+4 -3
View File
@@ -1,10 +1,10 @@
[gd_scene format=3 uid="uid://btupbowehiyyu"] [gd_scene format=3 uid="uid://btupbowehiyyu"]
[ext_resource type="Script" uid="uid://bsa6xg5xos6h7" path="res://QRWindow.gd" id="1_qr_script"] [ext_resource type="Script" uid="uid://bsa6xg5xos6h7" path="res://popups/qr_popup.gd" id="1_qr_script"]
[ext_resource type="Script" uid="uid://dynulhnyq5hpq" path="res://addons/kenyoni/qr_code/qr_code_rect.gd" id="2_jpsxt"] [ext_resource type="Script" uid="uid://dynulhnyq5hpq" path="res://addons/kenyoni/qr_code/qr_code_rect.gd" id="2_jpsxt"]
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"] [ext_resource type="Theme" path="res://themes/popup_theme.tres" id="popup_theme"]
[node name="QRControl" type="Control" unique_id=1996957585 node_paths=PackedStringArray("panel")] [node name="QRPopup" type="Control" unique_id=1996957585 node_paths=PackedStringArray("panel")]
theme = ExtResource("popup_theme") theme = ExtResource("popup_theme")
layout_mode = 3 layout_mode = 3
anchors_preset = 15 anchors_preset = 15
@@ -22,6 +22,7 @@ offset_right = 450.0
offset_bottom = 520.0 offset_bottom = 520.0
[node name="QRCodeRect" type="TextureRect" parent="QRPanel" unique_id=1054378438] [node name="QRCodeRect" type="TextureRect" parent="QRPanel" unique_id=1054378438]
unique_name_in_owner = true
layout_mode = 0 layout_mode = 0
offset_left = 20.0 offset_left = 20.0
offset_top = 20.0 offset_top = 20.0
@@ -1,9 +1,9 @@
[gd_scene format=3 uid="uid://bxydgil1yifps"] [gd_scene format=3 uid="uid://bxydgil1yifps"]
[ext_resource type="Script" uid="uid://bg5yo08638600" path="res://SearchWindow.gd" id="1_m7bij"] [ext_resource type="Script" uid="uid://bg5yo08638600" path="res://popups/search_popup.gd" id="1_m7bij"]
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"] [ext_resource type="Theme" path="res://themes/popup_theme.tres" id="popup_theme"]
[node name="Control" type="Control" unique_id=616238617 node_paths=PackedStringArray("panel")] [node name="SearchPopup" type="Control" unique_id=616238617 node_paths=PackedStringArray("panel")]
theme = ExtResource("popup_theme") theme = ExtResource("popup_theme")
layout_mode = 3 layout_mode = 3
anchors_preset = 15 anchors_preset = 15
@@ -47,6 +47,7 @@ offset_right = 576.0
offset_bottom = 384.0 offset_bottom = 384.0
scale = Vector2(2, 2) scale = Vector2(2, 2)
[node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer" unique_id=1567403144] [node name="SearchResults" type="VBoxContainer" parent="ScrollContainer" unique_id=1567403144]
unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
@@ -1,12 +1,12 @@
[gd_scene format=3 uid="uid://dldpeo5y3l5hq"] [gd_scene format=3 uid="uid://dldpeo5y3l5hq"]
[ext_resource type="Script" uid="uid://dukqyjoduf1af" path="res://SettingsWindow.gd" id="1_bt55j"] [ext_resource type="Script" uid="uid://dukqyjoduf1af" path="res://popups/settings_popup.gd" id="1_bt55j"]
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"] [ext_resource type="Theme" path="res://themes/popup_theme.tres" id="popup_theme"]
[sub_resource type="LabelSettings" id="LabelSettings_3xrlm"] [sub_resource type="LabelSettings" id="LabelSettings_3xrlm"]
font_size = 25 font_size = 25
[node name="SettingsControl" type="Control" unique_id=1298840328 node_paths=PackedStringArray("panel")] [node name="SettingsPopup" type="Control" unique_id=1298840328 node_paths=PackedStringArray("panel")]
theme = ExtResource("popup_theme") theme = ExtResource("popup_theme")
layout_mode = 3 layout_mode = 3
anchors_preset = 15 anchors_preset = 15
@@ -1,9 +1,9 @@
[gd_scene load_steps=2 format=3 uid="uid://bd1by80q1v27e"] [gd_scene load_steps=2 format=3 uid="uid://bd1by80q1v27e"]
[ext_resource type="Script" uid="uid://rolypd0cqd6h" path="res://shortcuts_window.gd" id="1_12pxi"] [ext_resource type="Script" uid="uid://rolypd0cqd6h" path="res://popups/shortcuts_popup.gd" id="1_12pxi"]
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"] [ext_resource type="Theme" path="res://themes/popup_theme.tres" id="popup_theme"]
[node name="ShorcutsControl" type="Control" node_paths=PackedStringArray("panel")] [node name="ShortcutsPopup" type="Control" node_paths=PackedStringArray("panel")]
theme = ExtResource("popup_theme") theme = ExtResource("popup_theme")
custom_minimum_size = Vector2(700, 700) custom_minimum_size = Vector2(700, 700)
layout_mode = 3 layout_mode = 3
+5 -5
View File
@@ -1,12 +1,12 @@
[gd_scene format=3 uid="uid://dpdem7pdxweb5"] [gd_scene format=3 uid="uid://dpdem7pdxweb5"]
[ext_resource type="Script" uid="uid://c3ejukwth512a" path="res://sync_window.gd" id="1_8ba47"] [ext_resource type="Script" uid="uid://c3ejukwth512a" path="res://popups/sync_popup.gd" id="1_8ba47"]
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"] [ext_resource type="Theme" path="res://themes/popup_theme.tres" id="popup_theme"]
[sub_resource type="LabelSettings" id="LabelSettings_anbfi"] [sub_resource type="LabelSettings" id="LabelSettings_anbfi"]
font_size = 25 font_size = 25
[node name="Control2" type="Control" unique_id=1302914356 node_paths=PackedStringArray("panel")] [node name="SyncPopup" type="Control" unique_id=1302914356 node_paths=PackedStringArray("panel")]
theme = ExtResource("popup_theme") theme = ExtResource("popup_theme")
layout_mode = 3 layout_mode = 3
anchors_preset = 15 anchors_preset = 15
@@ -145,13 +145,13 @@ text = "SoundtracksRemoved"
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
[node name="CatchedErrorsVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer" unique_id=63733711] [node name="CaughtErrorsVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer" unique_id=63733711]
unique_name_in_owner = true unique_name_in_owner = true
visible = false visible = false
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
[node name="CatchedErrorsLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/CatchedErrorsVBox" unique_id=1784389644] [node name="CaughtErrorsLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/CaughtErrorsVBox" unique_id=1784389644]
layout_mode = 2 layout_mode = 2
text = "CatchedErrors" text = "CatchedErrors"
horizontal_alignment = 1 horizontal_alignment = 1
@@ -1,13 +1,13 @@
[gd_scene format=3 uid="uid://cxlsmxnrp0agh"] [gd_scene format=3 uid="uid://cxlsmxnrp0agh"]
[ext_resource type="Script" uid="uid://dxpb22xbwf3t1" path="res://winner_window.gd" id="1_winner_script"] [ext_resource type="Script" uid="uid://dxpb22xbwf3t1" path="res://popups/winner_popup.gd" id="1_winner_script"]
[ext_resource type="Texture2D" uid="uid://r4as0nmtoa7p" path="res://noCharacter.png" id="2_no_character"] [ext_resource type="Texture2D" uid="uid://r4as0nmtoa7p" path="res://noCharacter.png" id="2_no_character"]
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"] [ext_resource type="Theme" path="res://themes/popup_theme.tres" id="popup_theme"]
[sub_resource type="LabelSettings" id="LabelSettings_hr75l"] [sub_resource type="LabelSettings" id="LabelSettings_hr75l"]
font_size = 35 font_size = 35
[node name="WinnerControl" type="Control" unique_id=1111111111 node_paths=PackedStringArray("panel")] [node name="WinnerPopup" type="Control" unique_id=1111111111 node_paths=PackedStringArray("panel")]
theme = ExtResource("popup_theme") theme = ExtResource("popup_theme")
layout_mode = 3 layout_mode = 3
anchors_preset = 15 anchors_preset = 15
+21 -13
View File
@@ -1,18 +1,22 @@
extends PopupWindow class_name AboutPopup
extends BasePopup
## Shows the game version and what is new in each version.
@onready ## What is new in each version, newest first.
var about_panel: Panel = %AboutPanel
@onready
var version_label: Label = %VersionLabel
@onready
var whats_new_label: Label = %WhatsNewLabel
# Changelog as Dictionary with version as key and description as value
const WHATS_NEW: Dictionary = { const WHATS_NEW: Dictionary = {
"2.0.0": "Reworked all popups, they dim the background and close when clicking outside
Fixed blurry text and the layout of the player list
Moved Sync to the top row and Settings next to the reset buttons
The inspiration and sync lists now show where each item starts
The sync result now looks right with any number of columns
Failed song downloads are now retried instead of getting stuck
The game no longer waits forever when the server does not answer
The song cache now fills up to the cache size in the settings
Statistics are turned back on when a new game starts
Fixed bugs with low winning scores and Show answer before a song is playing
The log now shows the time on every row
New app icon and a menu entry on Linux
Reorganized the code and added automated tests",
"1.9.0": "Made improvements to the Search window "1.9.0": "Made improvements to the Search window
Made small changes to the inspiration list scrolling Made small changes to the inspiration list scrolling
Made small changes to the player list Made small changes to the player list
@@ -50,6 +54,10 @@ const WHATS_NEW: Dictionary = {
"0.7-Beta": "Can now hop between songs" "0.7-Beta": "Can now hop between songs"
} }
@onready var about_panel: Panel = %AboutPanel
@onready var version_label: Label = %VersionLabel
@onready var whats_new_label: Label = %WhatsNewLabel
func open() -> void: func open() -> void:
super() super()
File renamed without changes.
+2 -1
View File
@@ -1,4 +1,4 @@
class_name PopupWindow class_name BasePopup
extends Control extends Control
## A window shown on top of the main window, such as Settings or About. ## A window shown on top of the main window, such as Settings or About.
## ##
@@ -6,6 +6,7 @@ extends Control
## override [method open] or [method close] and call [code]super()[/code]. ## override [method open] or [method close] and call [code]super()[/code].
## The visible part of the popup. Clicks outside it close the popup. ## The visible part of the popup. Clicks outside it close the popup.
@export var panel: Control @export var panel: Control
File renamed without changes.
+23 -21
View File
@@ -1,31 +1,41 @@
class_name Character class_name CharacterCard
extends Control extends Control
## One character in the character select popup: its picture and name. Clicking it picks
## the character.
@onready ## Emitted with the character's picture when the card is clicked.
var panel: Panel = $Panel
@onready
var character_image: TextureRect = %CharacterTextureRect
@onready
var character_name_label: Label = %CharacterName
signal new_character_selected(img_texture: ImageTexture) signal new_character_selected(img_texture: ImageTexture)
@onready var panel: Panel = $Panel
@onready var character_image: TextureRect = %CharacterTextureRect
@onready var character_name_label: Label = %CharacterName
func _input(event: InputEvent) -> void:
if visible && Settings.character_select_open:
if event is InputEventMouseButton && event.is_pressed() \
&& event.get_button_index() == MOUSE_BUTTON_LEFT:
var local_event: InputEvent = make_input_local(event)
if Rect2(Vector2(0, 0), panel.size).has_point(local_event.position):
GameLog.debug(character_name_label.text)
new_character_selected.emit(character_image.texture)
## Shows [param character_name] and fetches its picture from the server.
func set_character_name(character_name: String) -> void: func set_character_name(character_name: String) -> void:
panel.size = Vector2(240, 145) panel.size = Vector2(240, 145)
#margin.size = Vector2(240, 145)
#margin set margin 15, 10, 15, 0
character_name_label.text = character_name.split(".")[0] character_name_label.text = character_name.split(".")[0]
character_name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER character_name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
var image_fetched: Callable = func image_fetched(image: PackedByteArray) -> void: var image_fetched: Callable = func image_fetched(image: PackedByteArray) -> void:
var img: Image = Image.new() var img: Image = Image.new()
var err: Error = img.load_png_from_buffer(image) var err: Error = img.load_png_from_buffer(image)
if err != OK: if err != OK:
print(err) GameLog.debug("Character image is not a PNG, trying JPG: ", error_string(err))
var err2: Error = img.load_jpg_from_buffer(image) var err2: Error = img.load_jpg_from_buffer(image)
if err2 != OK: if err2 != OK:
print(err2) GameLog.warning("Could not load the image for ", character_name, ": ",
error_string(err2))
var new_image: ImageTexture = ImageTexture.new() var new_image: ImageTexture = ImageTexture.new()
new_image.set_image(img) new_image.set_image(img)
character_image.custom_minimum_size = Vector2(200, 100) character_image.custom_minimum_size = Vector2(200, 100)
@@ -36,11 +46,3 @@ func set_character_name(character_name: String) -> void:
character_image.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED character_image.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
character_image.texture = new_image character_image.texture = new_image
MusicServerClient.fetch_character_image(character_name, image_fetched) MusicServerClient.fetch_character_image(character_name, image_fetched)
func _input(event: InputEvent) -> void:
if self.visible == true && Settings.character_select_open == true:
if event is InputEventMouseButton && event.is_pressed() && event.get_button_index() == MOUSE_BUTTON_LEFT:
var evLocal: InputEvent = make_input_local(event)
if Rect2(Vector2(0, 0), panel.size).has_point(evLocal.position):
print(character_name_label.text)
new_character_selected.emit(character_image.texture)
File renamed without changes.
@@ -1,65 +1,65 @@
extends PopupWindow class_name CharacterSelectPopup
extends BasePopup
@onready ## Emitted with the picture of the character that was picked.
var character_panel: Panel = %CharacterPanel signal character_selected(texture: ImageTexture)
@onready
var title_label: Label = %TitleLabel
@onready
var character_grid: GridContainer = %CharacterGridContainer
@onready
var close_timer: Timer = %CloseTimer
var can_select_character: bool = false var can_select_character: bool = false
var character: PackedScene = preload("res://popups/CharacterCard.tscn")
signal new_new_character_selected(file_name: String) @onready var character_panel: Panel = %CharacterPanel
@onready var title_label: Label = %TitleLabel
@onready var character_grid: GridContainer = %CharacterGridContainer
@onready var close_timer: Timer = %CloseTimer
var character: PackedScene = preload("res://character.tscn")
func _ready() -> void: func _ready() -> void:
close_timer.timeout.connect(_on_close_timeout) close_timer.timeout.connect(_on_close_timeout)
func _on_close_timeout() -> void: func _on_close_timeout() -> void:
print("_on_close_timeout!") GameLog.debug("_on_close_timeout!")
Settings.character_select_open = false Settings.character_select_open = false
close_timer.stop() close_timer.stop()
func load_characters() -> void: func load_characters() -> void:
print("load_characters") GameLog.debug("load_characters")
var fetch_character_list: Callable = func fetch_character_list(list) -> void: var fetch_character_list: Callable = func fetch_character_list(list: Variant) -> void:
print("fetch_character_list") GameLog.debug("fetch_character_list")
Settings.delete_children(character_grid) Settings.delete_children(character_grid)
if typeof(list) == TYPE_ARRAY: if typeof(list) == TYPE_ARRAY:
for character_name: String in list: for character_name: String in list:
print("character_name ", character_name) GameLog.debug("character_name ", character_name)
var character_box: Character = character.instantiate() var character_box: CharacterCard = character.instantiate()
character_grid.add_child(character_box) character_grid.add_child(character_box)
character_box.custom_minimum_size = Vector2(235, 145) character_box.custom_minimum_size = Vector2(235, 145)
character_box.size = Vector2(235, 145) character_box.size = Vector2(235, 145)
character_box.set_character_name(character_name) character_box.set_character_name(character_name)
character_box.connect("new_character_selected", _on_character_selected) character_box.new_character_selected.connect(_on_character_selected)
MusicServerClient.fetch_character_list(fetch_character_list) MusicServerClient.fetch_character_list(fetch_character_list)
func show_grid(player_name: String) -> void: func show_grid(player_name: String) -> void:
print("show_grid") GameLog.debug("show_grid")
title_label.text = "Select character for " + player_name title_label.text = "Select character for " + player_name
close_timer.stop() close_timer.stop()
open() open()
can_select_character = true can_select_character = true
Settings.character_select_open = true Settings.character_select_open = true
func close() -> void: func close() -> void:
super() super()
Settings.character_select_open = false Settings.character_select_open = false
func _on_character_selected(texture: ImageTexture) -> void: func _on_character_selected(texture: ImageTexture) -> void:
print("_on_character_selected") GameLog.debug("_on_character_selected")
# Not close(): character_select_open stays true until close_timer fires, so the click # Not close(): character_select_open stays true until close_timer fires, so the click
# that picked the character does not also select a song behind it # that picked the character does not also select a song behind it
self.visible = false visible = false
if can_select_character: if can_select_character:
new_new_character_selected.emit(texture) character_selected.emit(texture)
close_timer.start() close_timer.start()
can_select_character = false can_select_character = false
File renamed without changes.
+10 -7
View File
@@ -1,18 +1,20 @@
extends PopupWindow class_name LogPopup
extends BasePopup
@onready ## The game log: a row with the time for each thing that happens in the quiz, such as a
var log_panel: Panel = %LogPanel ## player getting a point. Opened with Alt+L.
@onready
var log_box: VBoxContainer = %LogVBoxContainer
var log_rows: Array var log_rows: Array
@onready var log_panel: Panel = %LogPanel
@onready var log_box: VBoxContainer = %LogVBoxContainer
func add_log_row(text: String) -> void: func add_log_row(text: String) -> void:
var log_row: String = get_time() + text var log_row: String = get_time() + text
log_rows.append(log_row) log_rows.append(log_row)
log_box.add_child(make_log_label(log_row)) log_box.add_child(make_log_label(log_row))
func make_log_label(row: String) -> Label: func make_log_label(row: String) -> Label:
var log_label: Label = Label.new() var log_label: Label = Label.new()
log_label.add_theme_font_size_override("font_size", 20) log_label.add_theme_font_size_override("font_size", 20)
@@ -20,6 +22,7 @@ func make_log_label(row: String) -> Label:
log_label.autowrap_mode = TextServer.AUTOWRAP_WORD log_label.autowrap_mode = TextServer.AUTOWRAP_WORD
return log_label return log_label
func get_time() -> String: func get_time() -> String:
var time: Dictionary = Time.get_time_dict_from_system() var time: Dictionary = Time.get_time_dict_from_system()
return "%02d:%02d:%02d " % [time.hour, time.minute, time.second] return "%02d:%02d:%02d " % [time.hour, time.minute, time.second]
File renamed without changes.
+31
View File
@@ -0,0 +1,31 @@
class_name PopupBackdrop
extends ColorRect
## Dims everything behind an open popup and keeps clicks from reaching the buttons
## under it.
##
## The backdrop sits above the rest of its parent and below the popups it belongs to.
## Popups close themselves when clicked outside, and the backdrop keeps that click from
## reaching the buttons behind them.
var _popups: Array[BasePopup] = []
## Adds a backdrop to [param parent] and moves [param popups] above it. The backdrop
## shows while any of them is open.
static func add_to(parent: Control, popups: Array[BasePopup]) -> PopupBackdrop:
var backdrop: PopupBackdrop = PopupBackdrop.new()
backdrop.name = "PopupBackdrop"
backdrop.color = Color(0, 0, 0, 0.55)
backdrop.mouse_filter = Control.MOUSE_FILTER_STOP
backdrop._popups = popups
parent.add_child(backdrop)
backdrop.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
for popup: BasePopup in popups:
parent.move_child(popup, -1)
popup.visibility_changed.connect(backdrop._update)
backdrop._update()
return backdrop
func _update() -> void:
visible = _popups.any(func(popup: BasePopup) -> bool: return popup.visible)
+1
View File
@@ -0,0 +1 @@
uid://swjhekc8beob
+9 -10
View File
@@ -1,13 +1,11 @@
extends PopupWindow class_name QRPopup
extends BasePopup
## Shows a QR code with the address of the search page on the MusicServer, so players
## can search for soundtracks on their phones.
@onready @onready var qr_panel: Panel = $QRPanel
var qr_panel: Panel = $QRPanel @onready var qr_code_rect: QRCodeRect = %QRCodeRect
@onready var url_label: Label = %URLLabel
@onready
var qr_code_rect: QRCodeRect = $QRPanel/QRCodeRect
@onready
var url_label: Label = $QRPanel/URLLabel
func _ready() -> void: func _ready() -> void:
@@ -19,7 +17,8 @@ func _ready() -> void:
var url: String = Settings.default_path + "/search" var url: String = Settings.default_path + "/search"
qr_code_rect.data = url qr_code_rect.data = url
url_label.text = qr_code_rect.data url_label.text = qr_code_rect.data
print("QR code generated for: ", url) GameLog.debug("QR code generated for: ", url)
func open() -> void: func open() -> void:
super() super()
File renamed without changes.
+50 -39
View File
@@ -1,45 +1,44 @@
extends PopupWindow class_name SearchPopup
extends BasePopup
@onready ## Searches every soundtrack on the server as you type. Matches ignore case, spaces and
var search_list: VBoxContainer = $ScrollContainer/VBoxContainer ## special characters, and also find the letters in order with others in between.
@onready
var search_panel: Panel = %SearchPanel
@onready
var clear_button: Button = $ClearButton
@onready
var search_bar: TextEdit = $Searchbar
var soundtracks: Array = [] var soundtracks: Array = []
# Precomputed soundtrack data: array of dicts with original, lowercase, and cleaned fields ## Each soundtrack as a Dictionary with its original, lowercase and cleaned name.
var precomputed_soundtracks: Array = [] var precomputed_soundtracks: Array = []
# Regex cache: maps cleaned search terms to compiled RegEx objects ## Compiled patterns for the letters-in-order search, by cleaned search term.
var regex_cache: Dictionary = {} var regex_cache: Dictionary = {}
var label_pool: Array[Label] = [] var label_pool: Array[Label] = []
# Called when the node enters the scene tree for the first time. @onready var search_list: VBoxContainer = %SearchResults
@onready var search_panel: Panel = %SearchPanel
@onready var clear_button: Button = $ClearButton
@onready var search_bar: TextEdit = $Searchbar
func _ready() -> void: func _ready() -> void:
clear_button.pressed.connect(clear) clear_button.pressed.connect(clear)
search_bar.grab_focus() search_bar.grab_focus()
search_bar.text_changed.connect(search) search_bar.text_changed.connect(search)
visibility_changed.connect(focus) visibility_changed.connect(focus)
func focus() -> void: func focus() -> void:
if self.visible == true: if visible:
search_bar.grab_focus() search_bar.grab_focus()
clear() clear()
func close() -> void: func close() -> void:
clear() clear()
super() super()
func search() -> void: func search() -> void:
print(search_bar.text) GameLog.debug(search_bar.text)
var search_text: String = search_bar.text var search_text: String = search_bar.text
if search_text == "": if search_text == "":
@@ -48,6 +47,7 @@ func search() -> void:
perform_search(search_text) perform_search(search_text)
func show_all_soundtracks() -> void: func show_all_soundtracks() -> void:
# Hide all visible labels and add to pool # Hide all visible labels and add to pool
for child: Label in search_list.get_children(): for child: Label in search_list.get_children():
@@ -59,6 +59,7 @@ func show_all_soundtracks() -> void:
for soundtrack: String in soundtracks: for soundtrack: String in soundtracks:
add_soundtrack_reuse(soundtrack) add_soundtrack_reuse(soundtrack)
func perform_search(search_text: String) -> void: func perform_search(search_text: String) -> void:
# Hide all visible labels and add to pool # Hide all visible labels and add to pool
for child: Label in search_list.get_children(): for child: Label in search_list.get_children():
@@ -69,6 +70,7 @@ func perform_search(search_text: String) -> void:
for soundtrack: String in find_matches(search_text): for soundtrack: String in find_matches(search_text):
add_soundtrack_reuse(soundtrack) add_soundtrack_reuse(soundtrack)
## Returns the soundtracks matching [param search_text], best matches first: plain ## Returns the soundtracks matching [param search_text], best matches first: plain
## case-insensitive matches, then matches ignoring spaces and special characters, ## case-insensitive matches, then matches ignoring spaces and special characters,
## then matches with the letters in order but not next to each other. ## then matches with the letters in order but not next to each other.
@@ -80,25 +82,25 @@ func find_matches(search_text: String) -> Array[String]:
var matches: Array[String] = [] var matches: Array[String] = []
# Pass 1: Exact case-insensitive contains match on lowercase versions # Pass 1: Exact case-insensitive contains match on lowercase versions
for i in range(precomputed_soundtracks.size()): for i: int in range(precomputed_soundtracks.size()):
var st_data = precomputed_soundtracks[i] var st_data: Dictionary = precomputed_soundtracks[i]
if st_data["lowercase"].contains(search_text_lower): if st_data["lowercase"].contains(search_text_lower):
if not st_data["original"] in seen: if not st_data["original"] in seen:
seen[st_data["original"]] = true seen[st_data["original"]] = true
matches.append(st_data["original"]) matches.append(st_data["original"])
# Pass 2: Contains match on cleaned terms # Pass 2: Contains match on cleaned terms
for i in range(precomputed_soundtracks.size()): for i: int in range(precomputed_soundtracks.size()):
var st_data = precomputed_soundtracks[i] var st_data: Dictionary = precomputed_soundtracks[i]
if st_data["cleaned"].contains(clean_search_text): if st_data["cleaned"].contains(clean_search_text):
if not st_data["original"] in seen: if not st_data["original"] in seen:
seen[st_data["original"]] = true seen[st_data["original"]] = true
matches.append(st_data["original"]) matches.append(st_data["original"])
# Pass 3: Regex match on cleaned terms # Pass 3: Regex match on cleaned terms
var regex = compile_regex_cached(clean_search_text) var regex: RegEx = compile_regex_cached(clean_search_text)
for i in range(precomputed_soundtracks.size()): for i: int in range(precomputed_soundtracks.size()):
var st_data = precomputed_soundtracks[i] var st_data: Dictionary = precomputed_soundtracks[i]
if regex.search(st_data["cleaned"]): if regex.search(st_data["cleaned"]):
if not st_data["original"] in seen: if not st_data["original"] in seen:
seen[st_data["original"]] = true seen[st_data["original"]] = true
@@ -106,30 +108,35 @@ func find_matches(search_text: String) -> Array[String]:
return matches return matches
# Cleans a term by removing spaces and replacing special characters.
# Matches MusicServer's clean_term() function.
func clean_term(term: String) -> String:
return term.replace(" ", "").replace("é", "e").replace("+", "plus").replace("&", "and").replace("'n", "and").to_lower()
# Compiles a regex pattern with caching to avoid recompiling the same pattern multiple times. ## Cleans a term by removing spaces and replacing special characters.
## Matches MusicServer's clean_term() function.
func clean_term(term: String) -> String:
return term.replace(" ", "").replace("é", "e").replace("+", "plus").replace("&", "and") \
.replace("'n", "and").to_lower()
## Compiles the letters-in-order pattern for [param search_term], once per term.
func compile_regex_cached(search_term: String) -> RegEx: func compile_regex_cached(search_term: String) -> RegEx:
if not search_term in regex_cache: if not search_term in regex_cache:
var regex = RegEx.new() var regex: RegEx = RegEx.new()
var regText: String = ".*" var regex_text: String = ".*"
for letter: String in search_term: for letter: String in search_term:
# Escape regex metacharacters so searches like "Q*bert" or "(Remix)" work # Escape regex metacharacters so searches like "Q*bert" or "(Remix)" work
if "\\^$.|?*+()[]{}".contains(letter): if "\\^$.|?*+()[]{}".contains(letter):
regText += "\\" regex_text += "\\"
regText += letter + ".*" regex_text += letter + ".*"
regex.compile(regText) regex.compile(regex_text)
regex_cache[search_term] = regex regex_cache[search_term] = regex
return regex_cache[search_term] return regex_cache[search_term]
func add_soundtrack(soundtrack: String) -> void: func add_soundtrack(soundtrack: String) -> void:
# Backward compatibility - delegates to reuse version # Backward compatibility - delegates to reuse version
add_soundtrack_reuse(soundtrack) add_soundtrack_reuse(soundtrack)
# Optimized version that reuses label nodes from pool instead of creating new ones
## Shows [param soundtrack], reusing a hidden label from the pool when there is one.
func add_soundtrack_reuse(soundtrack: String) -> void: func add_soundtrack_reuse(soundtrack: String) -> void:
var label: Label var label: Label
if label_pool.size() > 0: if label_pool.size() > 0:
@@ -145,6 +152,7 @@ func add_soundtrack_reuse(soundtrack: String) -> void:
search_list.add_child(label) search_list.add_child(label)
label.visible = true label.visible = true
func check_if_soundtrack_exists(soundtrack: String) -> bool: func check_if_soundtrack_exists(soundtrack: String) -> bool:
var soundtrack_exists: bool = false var soundtrack_exists: bool = false
for child: Label in search_list.get_children(): for child: Label in search_list.get_children():
@@ -152,9 +160,10 @@ func check_if_soundtrack_exists(soundtrack: String) -> bool:
soundtrack_exists = true soundtrack_exists = true
return soundtrack_exists return soundtrack_exists
func get_list_of_soundtracks() -> void: func get_list_of_soundtracks() -> void:
print("get_list_of_soundtracks") GameLog.debug("get_list_of_soundtracks")
var handle_soundtracks: Callable = func handle_soundtracks(array) -> void: var handle_soundtracks: Callable = func handle_soundtracks(array: Variant) -> void:
if typeof(array) == TYPE_ARRAY: if typeof(array) == TYPE_ARRAY:
# Reuse existing labels: hide all and add to pool # Reuse existing labels: hide all and add to pool
for child: Label in search_list.get_children(): for child: Label in search_list.get_children():
@@ -164,9 +173,10 @@ func get_list_of_soundtracks() -> void:
for soundtrack: String in soundtracks: for soundtrack: String in soundtracks:
add_soundtrack_reuse(soundtrack) add_soundtrack_reuse(soundtrack)
else: else:
print("Unexpected data") GameLog.warning("Unexpected data")
MusicServerClient.fetch_all_soundtracks_ordered(handle_soundtracks) MusicServerClient.fetch_all_soundtracks_ordered(handle_soundtracks)
## Replaces the searchable soundtracks and precomputes their search forms. ## Replaces the searchable soundtracks and precomputes their search forms.
func set_soundtracks(list: Array) -> void: func set_soundtracks(list: Array) -> void:
soundtracks = [] soundtracks = []
@@ -179,6 +189,7 @@ func set_soundtracks(list: Array) -> void:
"cleaned": clean_term(soundtrack) "cleaned": clean_term(soundtrack)
}) })
func clear() -> void: func clear() -> void:
search_bar.text = "" search_bar.text = ""
show_all_soundtracks() show_all_soundtracks()
File renamed without changes.
+74 -84
View File
@@ -1,77 +1,36 @@
extends PopupWindow class_name SettingsPopup
extends BasePopup
@onready ## The settings: how songs are shown and played, the winning score, the song cache,
var settings_panel: Panel = %SettingsPanel ## which MusicServer to use and fullscreen.
@onready
var stop_after_current_button: CheckButton = %StopAfterCurrentCheckButton
@onready
var hide_next_track_button: CheckButton = %HideNextTrackCheckButton
@onready
var add_to_database_button: CheckButton = %AddToDatabaseCheckButton
@onready
var low_played_button: CheckButton = %LowPlayedCheckButton
@onready
var hide_beginning_button: CheckButton = %HideBeginningCheckButton
@onready
var hide_length_button: CheckButton = %HideLengthCheckButton
@onready
var hide_ticks_button: CheckButton = %HideTicksCheckButton
@onready
var score_label: Label = %ScoreLabel
@onready
var lower_winning_score_button: Button = %LowerButton
@onready
var increase_winning_score_button: Button = %IncreaseButton
@onready
var cache_label: Label = %CacheLabel
@onready
var lower_cache_button: Button = %LowerCacheButton
@onready
var increase_cache_button: Button = %IncreaseCacheButton
@onready
var inspiration_speed_label: Label = %InspirationListSpeedLabel
@onready
var lower_inspiration_speed_button: Button = %LowerInspirationSpeedButton
@onready
var increase_inspiration_speed_button: Button = %IncreaseInspirationSpeedButton
@onready
var hide_for_seconds_label: Label = %HideForSecondsLabel
@onready
var lower_hide_for_seconds_button: Button = %LowerHideForSecondsButton
@onready
var increase_hide_for_seconds_button: Button = %IncreaseHideForSecondsButton
@onready
var select_server_button: OptionButton = %SelectServerButton
@onready
var fullscreen_button: CheckButton = %FullscreenButton
@onready
var local_button: CheckButton = %LocalButton
## Emitted when another MusicServer is picked.
signal server_changed signal server_changed
# Called when the node enters the scene tree for the first time. @onready var settings_panel: Panel = %SettingsPanel
@onready var stop_after_current_button: CheckButton = %StopAfterCurrentCheckButton
@onready var hide_next_track_button: CheckButton = %HideNextTrackCheckButton
@onready var add_to_database_button: CheckButton = %AddToDatabaseCheckButton
@onready var low_played_button: CheckButton = %LowPlayedCheckButton
@onready var hide_beginning_button: CheckButton = %HideBeginningCheckButton
@onready var hide_length_button: CheckButton = %HideLengthCheckButton
@onready var hide_ticks_button: CheckButton = %HideTicksCheckButton
@onready var score_label: Label = %ScoreLabel
@onready var lower_winning_score_button: Button = %LowerButton
@onready var increase_winning_score_button: Button = %IncreaseButton
@onready var cache_label: Label = %CacheLabel
@onready var lower_cache_button: Button = %LowerCacheButton
@onready var increase_cache_button: Button = %IncreaseCacheButton
@onready var inspiration_speed_label: Label = %InspirationListSpeedLabel
@onready var lower_inspiration_speed_button: Button = %LowerInspirationSpeedButton
@onready var increase_inspiration_speed_button: Button = %IncreaseInspirationSpeedButton
@onready var hide_for_seconds_label: Label = %HideForSecondsLabel
@onready var lower_hide_for_seconds_button: Button = %LowerHideForSecondsButton
@onready var increase_hide_for_seconds_button: Button = %IncreaseHideForSecondsButton
@onready var select_server_button: OptionButton = %SelectServerButton
@onready var fullscreen_button: CheckButton = %FullscreenButton
@onready var local_button: CheckButton = %LocalButton
func _ready() -> void: func _ready() -> void:
fullscreen_button.button_pressed = Settings.fullscreen fullscreen_button.button_pressed = Settings.fullscreen
select_server_button.selected = Settings.selected_server select_server_button.selected = Settings.selected_server
@@ -110,109 +69,140 @@ func _ready() -> void:
select_server_button.select(Settings.selected_server) select_server_button.select(Settings.selected_server)
select_server_button.item_selected.connect(select_server) select_server_button.item_selected.connect(select_server)
visibility_changed.connect(_on_visibility_changed) visibility_changed.connect(_on_visibility_changed)
# Alt+Enter switches fullscreen from the main window, which resizes the window
get_window().size_changed.connect(_update_fullscreen_button)
# Settings can change outside this window, e.g. winning turns statistics off
# Settings can change outside this popup, e.g. winning turns statistics off
func _on_visibility_changed() -> void: func _on_visibility_changed() -> void:
if visible: if visible:
add_to_database_button.button_pressed = Settings.add_to_stats add_to_database_button.button_pressed = Settings.add_to_stats
_update_fullscreen_button()
func _update_fullscreen_button() -> void:
fullscreen_button.set_pressed_no_signal(Settings.fullscreen)
func fullscreen() -> void: func fullscreen() -> void:
Settings.fullscreen = !Settings.fullscreen Settings.fullscreen = !Settings.fullscreen
if Settings.fullscreen == true: if Settings.fullscreen:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN) DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
if Settings.fullscreen == false: if !Settings.fullscreen:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
func local_play() -> void: func local_play() -> void:
Settings.play_local = !Settings.play_local Settings.play_local = !Settings.play_local
func stop_after_current() -> void: func stop_after_current() -> void:
Settings.stop_after_current = !Settings.stop_after_current Settings.stop_after_current = !Settings.stop_after_current
func hide_next_track() -> void: func hide_next_track() -> void:
Settings.hide_next_track = !Settings.hide_next_track Settings.hide_next_track = !Settings.hide_next_track
func add_to_database() -> void: func add_to_database() -> void:
Settings.add_to_stats = !Settings.add_to_stats Settings.add_to_stats = !Settings.add_to_stats
func low_played() -> void: func low_played() -> void:
Settings.use_low_played_mode = !Settings.use_low_played_mode Settings.use_low_played_mode = !Settings.use_low_played_mode
func hide_beginning() -> void: func hide_beginning() -> void:
Settings.hide_beginning = !Settings.hide_beginning Settings.hide_beginning = !Settings.hide_beginning
update_hide_for_seconds_enabled() update_hide_for_seconds_enabled()
func update_hide_for_seconds_enabled() -> void: func update_hide_for_seconds_enabled() -> void:
var enabled: bool = Settings.hide_beginning var enabled: bool = Settings.hide_beginning
%HideForSecondsHBoxContainer.visible = enabled %HideForSecondsHBoxContainer.visible = enabled
func hide_length() -> void: func hide_length() -> void:
Settings.hide_length = !Settings.hide_length Settings.hide_length = !Settings.hide_length
func hide_ticks() -> void: func hide_ticks() -> void:
Settings.hide_ticks = !Settings.hide_ticks Settings.hide_ticks = !Settings.hide_ticks
func lower_winning_score() -> void: func lower_winning_score() -> void:
Settings.winning_score = max(Settings.winning_score - 1, 1) Settings.winning_score = max(Settings.winning_score - 1, 1)
score_label.text = str(Settings.winning_score) score_label.text = str(Settings.winning_score)
func increase_winning_score() -> void: func increase_winning_score() -> void:
Settings.winning_score += 1 Settings.winning_score += 1
score_label.text = str(Settings.winning_score) score_label.text = str(Settings.winning_score)
func set_winning_score(new_score: int) -> void: func set_winning_score(new_score: int) -> void:
Settings.winning_score = new_score Settings.winning_score = new_score
score_label.text = str(Settings.winning_score) score_label.text = str(Settings.winning_score)
func lower_cache() -> void: func lower_cache() -> void:
Playlist.number_of_tracks_to_preload -= 1 Playlist.number_of_tracks_to_preload -= 1
if Playlist.number_of_tracks_to_preload < 1: if Playlist.number_of_tracks_to_preload < 1:
Playlist.number_of_tracks_to_preload = 1 Playlist.number_of_tracks_to_preload = 1
cache_label.text = str(Playlist.number_of_tracks_to_preload) cache_label.text = str(Playlist.number_of_tracks_to_preload)
func increase_cache() -> void: func increase_cache() -> void:
Playlist.number_of_tracks_to_preload += 1 Playlist.number_of_tracks_to_preload += 1
cache_label.text = str(Playlist.number_of_tracks_to_preload) cache_label.text = str(Playlist.number_of_tracks_to_preload)
func lower_inspiration_speed() -> void: func lower_inspiration_speed() -> void:
Settings.inspiration_list_speed -= 1 Settings.inspiration_list_speed -= 1
if Settings.inspiration_list_speed < 1: if Settings.inspiration_list_speed < 1:
Settings.inspiration_list_speed = 1 Settings.inspiration_list_speed = 1
inspiration_speed_label.text = str(Settings.inspiration_list_speed) inspiration_speed_label.text = str(Settings.inspiration_list_speed)
func increase_inspiration_speed() -> void: func increase_inspiration_speed() -> void:
Settings.inspiration_list_speed += 1 Settings.inspiration_list_speed += 1
inspiration_speed_label.text = str(Settings.inspiration_list_speed ) inspiration_speed_label.text = str(Settings.inspiration_list_speed )
func lower_hide_for_seconds() -> void: func lower_hide_for_seconds() -> void:
Settings.hide_for_seconds -= 1.0 Settings.hide_for_seconds -= 1.0
if Settings.hide_for_seconds < 0: if Settings.hide_for_seconds < 0:
Settings.hide_for_seconds = 0 Settings.hide_for_seconds = 0
hide_for_seconds_label.text = str(Settings.hide_for_seconds) hide_for_seconds_label.text = str(Settings.hide_for_seconds)
func increase_hide_for_seconds() -> void: func increase_hide_for_seconds() -> void:
Settings.hide_for_seconds += 1.0 Settings.hide_for_seconds += 1.0
if Settings.hide_for_seconds > 10.0: if Settings.hide_for_seconds > 10.0:
Settings.hide_for_seconds = 10.0 Settings.hide_for_seconds = 10.0
hide_for_seconds_label.text = str(Settings.hide_for_seconds) hide_for_seconds_label.text = str(Settings.hide_for_seconds)
func select_server(new_server: int) -> void: func select_server(new_server: int) -> void:
print("select_server") GameLog.debug("select_server")
Settings.default_path = select_server_button.get_item_text(new_server) Settings.default_path = select_server_button.get_item_text(new_server)
Settings.selected_server = new_server Settings.selected_server = new_server
print("Settings.default_path: " + Settings.default_path) GameLog.debug("Settings.default_path: " + Settings.default_path)
print("Settings.selected_server: " + str(Settings.selected_server)) GameLog.debug("Settings.selected_server: " + str(Settings.selected_server))
print("new_server: " + str(new_server)) GameLog.debug("new_server: " + str(new_server))
print("select_server_button.get_item_text(new_server): " + select_server_button.get_item_text(new_server)) GameLog.debug("select_server_button.get_item_text(new_server): ",
select_server_button.get_item_text(new_server))
server_changed.emit() server_changed.emit()
func set_selected_server(new_server: int) -> void: func set_selected_server(new_server: int) -> void:
print("set_selected_server") GameLog.debug("set_selected_server")
select_server_button.select(new_server) select_server_button.select(new_server)
Settings.default_path = select_server_button.get_item_text(new_server) Settings.default_path = select_server_button.get_item_text(new_server)
Settings.selected_server = new_server Settings.selected_server = new_server
print("Settings.default_path: " + Settings.default_path) GameLog.debug("Settings.default_path: " + Settings.default_path)
print("Settings.selected_server: " + str(Settings.selected_server)) GameLog.debug("Settings.selected_server: " + str(Settings.selected_server))
print("new_server: " + str(new_server)) GameLog.debug("new_server: " + str(new_server))
print("select_server_button.get_item_text(new_server): " + select_server_button.get_item_text(new_server)) GameLog.debug("select_server_button.get_item_text(new_server): ",
select_server_button.get_item_text(new_server))
server_changed.emit() server_changed.emit()
File renamed without changes.
@@ -1,10 +1,6 @@
extends PopupWindow class_name ShortcutsPopup
extends BasePopup
@onready ## Lists the keyboard shortcuts. Opened with Alt+K or the Shortcuts button.
var shortcuts_vbox: VBoxContainer = %shortcutsVBox
@onready
var shortcuts_panel: Panel = %ShortcutsPanel
var shortcuts_dic: Dictionary = { var shortcuts_dic: Dictionary = {
"Alt + S": "Search", "Alt + S": "Search",
@@ -25,10 +21,13 @@ var shortcuts_dic: Dictionary = {
"Alt + Ctrl + 1 - 9": "Take point from player", "Alt + Ctrl + 1 - 9": "Take point from player",
} }
@onready var shortcuts_vbox: VBoxContainer = %shortcutsVBox
@onready var shortcuts_panel: Panel = %ShortcutsPanel
#Color(0.18, 0.18, 0.18) #Color(0.18, 0.18, 0.18)
#Color(0.302, 0.302, 0.302) #Color(0.302, 0.302, 0.302)
#Color(0.259, 0.259, 0.259) #Color(0.259, 0.259, 0.259)
func _ready() -> void: func _ready() -> void:
shortcuts_dic.sort() shortcuts_dic.sort()
Settings.delete_children(shortcuts_vbox) Settings.delete_children(shortcuts_vbox)
@@ -63,8 +62,6 @@ func _ready() -> void:
var new_stylebox_normal: Resource = panel.get_theme_stylebox("panel").duplicate() var new_stylebox_normal: Resource = panel.get_theme_stylebox("panel").duplicate()
new_stylebox_normal.bg_color = Color(0.18, 0.18, 0.18) new_stylebox_normal.bg_color = Color(0.18, 0.18, 0.18)
panel.add_theme_stylebox_override("panel", new_stylebox_normal) panel.add_theme_stylebox_override("panel", new_stylebox_normal)
#label.add_theme_stylebox_override("normal", new_stylebox_normal)
#label2.add_theme_stylebox_override("normal", new_stylebox_normal)
hbox.add_child(label) hbox.add_child(label)
hbox.add_child(label2) hbox.add_child(label2)
File renamed without changes.
+77 -88
View File
@@ -1,78 +1,49 @@
extends PopupWindow class_name SyncPopup
extends BasePopup
## Asks the server to sync its soundtracks with the music folder, shows the progress,
## and then lists what was added, changed or removed.
@onready ## Emitted when the server has started syncing.
var sync_panel: Panel = %SyncPanel
@onready
var sync_title_label: Label = %SyncTitleLabel
@onready
var no_changes_label: Label = %NoChangesLabel
@onready
var progress_hbox: HBoxContainer = %ProgressHBoxContainer
@onready
var sync_hbox: HBoxContainer = %SyncHBoxContainer
@onready
var sync_progress_bar: ProgressBar = %SyncProgressBar
@onready
var soundtracks_added_vbox: VBoxContainer = %SoundtracksAddedVBox
@onready
var soundtracks_added_separator: VSeparator = %SoundtracksAddedSeparator
@onready
var soundtracks_readded_vbox: VBoxContainer = %SoundtracksReAddedVBox
@onready
var soundtracks_readded_separator: VSeparator = %SoundtracksReAddedSeparator
@onready
var soundtracks_changed_title_vbox: VBoxContainer = %SoundtracksChangedTitleVBox
@onready
var soundtracks_changed_title_separator: VSeparator = %SoundtracksChangedTitleSeparator
@onready
var soundtracks_changed_content_vbox: VBoxContainer = %SoundtracksChangedContentVBox
@onready
var soundtracks_changed_content_separator: VSeparator = %SoundtracksChangedContentSeparator
@onready
var soundtracks_removed_vbox: VBoxContainer = %SoundtracksRemovedVBox
@onready
var soundtracks_removed_separator: VSeparator = %SoundtracksRemovedSeparator
@onready
var catched_errors_vbox: VBoxContainer = %CatchedErrorsVBox
@onready
var sync_time: Label = %SyncTime
@onready
var sync_timer: Timer = %SyncTimer
@onready
var restart_sync_button: Button = %RestartSyncButton
var changes: bool = false
var has_synced: bool = false
signal sync_started signal sync_started
## Emitted when the sync is done and its result is shown.
signal sync_finished signal sync_finished
var changes: bool = false
var has_synced: bool = false
@onready var sync_panel: Panel = %SyncPanel
@onready var sync_title_label: Label = %SyncTitleLabel
@onready var no_changes_label: Label = %NoChangesLabel
@onready var progress_hbox: HBoxContainer = %ProgressHBoxContainer
@onready var sync_hbox: HBoxContainer = %SyncHBoxContainer
@onready var sync_progress_bar: ProgressBar = %SyncProgressBar
@onready var soundtracks_added_vbox: VBoxContainer = %SoundtracksAddedVBox
@onready var soundtracks_added_separator: VSeparator = %SoundtracksAddedSeparator
@onready var soundtracks_readded_vbox: VBoxContainer = %SoundtracksReAddedVBox
@onready var soundtracks_readded_separator: VSeparator = %SoundtracksReAddedSeparator
@onready var soundtracks_changed_title_vbox: VBoxContainer = %SoundtracksChangedTitleVBox
@onready var soundtracks_changed_title_separator: VSeparator = %SoundtracksChangedTitleSeparator
@onready var soundtracks_changed_content_vbox: VBoxContainer = %SoundtracksChangedContentVBox
@onready var soundtracks_changed_content_separator: VSeparator = %SoundtracksChangedContentSeparator
@onready var soundtracks_removed_vbox: VBoxContainer = %SoundtracksRemovedVBox
@onready var soundtracks_removed_separator: VSeparator = %SoundtracksRemovedSeparator
@onready var caught_errors_vbox: VBoxContainer = %CaughtErrorsVBox
@onready var sync_time: Label = %SyncTime
@onready var sync_timer: Timer = %SyncTimer
@onready var restart_sync_button: Button = %RestartSyncButton
func _ready() -> void: func _ready() -> void:
sync_timer.timeout.connect(_on_sync_timeout) sync_timer.timeout.connect(_on_sync_timeout)
restart_sync_button.pressed.connect(start_sync) restart_sync_button.pressed.connect(start_sync)
restart_sync_button.visible = false restart_sync_button.visible = false
func show_section_if_has_data(data: Variant, vbox: VBoxContainer, separator: VSeparator, header_text: String) -> bool:
## Lists [param data] under [param header_text] in [param vbox] and returns true, or hides
## the column and its separator when there is nothing to list.
func show_section_if_has_data(data: Variant, vbox: VBoxContainer, separator: VSeparator,
header_text: String) -> bool:
if data != null && data.size() > 0: if data != null && data.size() > 0:
vbox.visible = true vbox.visible = true
make_list(data, vbox, header_text) make_list(data, vbox, header_text)
@@ -82,9 +53,10 @@ func show_section_if_has_data(data: Variant, vbox: VBoxContainer, separator: VSe
separator.visible = false separator.visible = false
return false return false
func start_sync() -> void: func start_sync() -> void:
print("start_sync") GameLog.debug("start_sync")
self.visible = true visible = true
if has_synced: if has_synced:
# Already synced, just show the results # Already synced, just show the results
show_sync_result() show_sync_result()
@@ -97,11 +69,12 @@ func start_sync() -> void:
if !Settings.currently_syncing: if !Settings.currently_syncing:
MusicServerClient.start_sync(sync_request_sent) MusicServerClient.start_sync(sync_request_sent)
func _on_sync_timeout() -> void: func _on_sync_timeout() -> void:
print("_on_sync_timeout!") GameLog.debug("_on_sync_timeout!")
var get_progress: Callable = func get_progress(data_received: Dictionary) -> void: var get_progress: Callable = func get_progress(data_received: Dictionary) -> void:
print("get_progress") GameLog.debug("get_progress")
print("data_received: ", data_received) GameLog.debug("data_received: ", data_received)
if data_received != null: if data_received != null:
if data_received.has("progress"): if data_received.has("progress"):
sync_progress_bar.value = int(data_received.progress) sync_progress_bar.value = int(data_received.progress)
@@ -110,37 +83,45 @@ func _on_sync_timeout() -> void:
on_sync_finished(data_received) on_sync_finished(data_received)
MusicServerClient.fetch_sync_progress(get_progress) MusicServerClient.fetch_sync_progress(get_progress)
func on_sync_finished(data_received: Dictionary) -> void: func on_sync_finished(data_received: Dictionary) -> void:
has_synced = true has_synced = true
reset_sync_result() reset_sync_result()
if show_section_if_has_data(data_received.soundtracks_added, soundtracks_added_vbox, soundtracks_added_separator, "Added"): if show_section_if_has_data(data_received.soundtracks_added, soundtracks_added_vbox,
soundtracks_added_separator, "Added"):
changes = true changes = true
if show_section_if_has_data(data_received.soundtracks_re_added, soundtracks_readded_vbox, soundtracks_readded_separator, "Re-added"): if show_section_if_has_data(data_received.soundtracks_re_added, soundtracks_readded_vbox,
soundtracks_readded_separator, "Re-added"):
changes = true changes = true
if data_received.soundtracks_changed_title != null && data_received.soundtracks_changed_title.size() > 0: var changed_titles: Variant = data_received.soundtracks_changed_title
if changed_titles != null && changed_titles.size() > 0:
changes = true changes = true
Settings.delete_children(soundtracks_changed_title_vbox) Settings.delete_children(soundtracks_changed_title_vbox)
make_header_label(soundtracks_changed_title_vbox, "Changed Title") make_header_label(soundtracks_changed_title_vbox, "Changed Title")
var row: int = 0 var row: int = 0
for old_soundtrack_title: String in data_received.soundtracks_changed_title: for old_soundtrack_title: String in changed_titles:
var new_soundtrack_title: String = data_received.soundtracks_changed_title[old_soundtrack_title] var new_soundtrack_title: String = changed_titles[old_soundtrack_title]
soundtracks_changed_title_vbox.add_child(make_soundtrack_row( soundtracks_changed_title_vbox.add_child(make_soundtrack_row(
old_soundtrack_title + "\nchanged title to\n" + new_soundtrack_title, row)) old_soundtrack_title + "\nchanged title to\n" + new_soundtrack_title, row))
row += 1 row += 1
soundtracks_changed_title_vbox.visible = true soundtracks_changed_title_vbox.visible = true
if show_section_if_has_data(data_received.soundtracks_changed_content, soundtracks_changed_content_vbox, soundtracks_changed_content_separator, "Changed Content"): if show_section_if_has_data(data_received.soundtracks_changed_content,
soundtracks_changed_content_vbox, soundtracks_changed_content_separator,
"Changed Content"):
changes = true changes = true
if show_section_if_has_data(data_received.soundtracks_removed, soundtracks_removed_vbox, soundtracks_removed_separator, "Removed"): if show_section_if_has_data(data_received.soundtracks_removed, soundtracks_removed_vbox,
soundtracks_removed_separator, "Removed"):
changes = true changes = true
# The server's name for the errors it caught
if data_received.catched_errors != null && data_received.catched_errors.size() > 0: if data_received.catched_errors != null && data_received.catched_errors.size() > 0:
catched_errors_vbox.visible = true caught_errors_vbox.visible = true
make_list(data_received.catched_errors, catched_errors_vbox, "Errors") make_list(data_received.catched_errors, caught_errors_vbox, "Errors")
# Separators go only between visible columns, not after the last one # Separators go only between visible columns, not after the last one
var visible_sections: Array = [] var visible_sections: Array = []
@@ -156,9 +137,9 @@ func on_sync_finished(data_received: Dictionary) -> void:
visible_sections.append("removed") visible_sections.append("removed")
# Show section separators only if there's a visible section after them # Show section separators only if there's a visible section after them
for i in range(visible_sections.size()): for i: int in range(visible_sections.size()):
var section = visible_sections[i] var section: String = visible_sections[i]
var separator_map = { var separator_map: Dictionary = {
"added": soundtracks_added_separator, "added": soundtracks_added_separator,
"readded": soundtracks_readded_separator, "readded": soundtracks_readded_separator,
"changed_title": soundtracks_changed_title_separator, "changed_title": soundtracks_changed_title_separator,
@@ -173,17 +154,20 @@ func on_sync_finished(data_received: Dictionary) -> void:
if !changes: if !changes:
no_changes_label.visible = true no_changes_label.visible = true
sync_time.text = "Total time: " + data_received.total_time if data_received.has("total_time") else "" sync_time.text = "Total time: " + data_received.total_time \
if data_received.has("total_time") else ""
Settings.currently_syncing = false Settings.currently_syncing = false
sync_timer.stop() sync_timer.stop()
print("sync finished") GameLog.debug("sync finished")
show_sync_result() show_sync_result()
restart_sync_button.visible = true restart_sync_button.visible = true
sync_finished.emit() sync_finished.emit()
func list_visible(vbox: VBoxContainer, seperator: VSeparator) -> void:
func list_visible(vbox: VBoxContainer, separator: VSeparator) -> void:
vbox.visible = true vbox.visible = true
seperator.visible = true separator.visible = true
func make_header_label(vbox: VBoxContainer, text: String) -> void: func make_header_label(vbox: VBoxContainer, text: String) -> void:
var label: Label = Label.new() var label: Label = Label.new()
@@ -193,6 +177,7 @@ func make_header_label(vbox: VBoxContainer, text: String) -> void:
label.text = text label.text = text
vbox.add_child(label) vbox.add_child(label)
# Items alternate between white and orange text as well as background shade # Items alternate between white and orange text as well as background shade
func make_soundtrack_row(text: String, row: int) -> ListRow: func make_soundtrack_row(text: String, row: int) -> ListRow:
var list_row: ListRow = ListRow.create(text, row, 18, HORIZONTAL_ALIGNMENT_CENTER) var list_row: ListRow = ListRow.create(text, row, 18, HORIZONTAL_ALIGNMENT_CENTER)
@@ -200,6 +185,7 @@ func make_soundtrack_row(text: String, row: int) -> ListRow:
list_row.label.add_theme_color_override("font_color", Color(1, 0.5, 0)) list_row.label.add_theme_color_override("font_color", Color(1, 0.5, 0))
return list_row return list_row
func reset_sync_result() -> void: func reset_sync_result() -> void:
sync_progress_bar.value = 0 sync_progress_bar.value = 0
sync_time.text = "" sync_time.text = ""
@@ -215,7 +201,8 @@ func reset_sync_result() -> void:
soundtracks_changed_content_separator.visible = false soundtracks_changed_content_separator.visible = false
soundtracks_removed_vbox.visible = false soundtracks_removed_vbox.visible = false
soundtracks_removed_separator.visible = false soundtracks_removed_separator.visible = false
catched_errors_vbox.visible = false caught_errors_vbox.visible = false
func make_list(list: Array, vbox: VBoxContainer, text: String) -> void: func make_list(list: Array, vbox: VBoxContainer, text: String) -> void:
Settings.delete_children(vbox) Settings.delete_children(vbox)
@@ -223,16 +210,18 @@ func make_list(list: Array, vbox: VBoxContainer, text: String) -> void:
for row: int in list.size(): for row: int in list.size():
vbox.add_child(make_soundtrack_row(list[row], row)) vbox.add_child(make_soundtrack_row(list[row], row))
func show_sync_result() -> void: func show_sync_result() -> void:
sync_title_label.text = "Sync Result" sync_title_label.text = "Sync Result"
progress_hbox.visible = false progress_hbox.visible = false
sync_hbox.visible = true sync_hbox.visible = true
restart_sync_button.visible = true restart_sync_button.visible = true
func show_sync_progress() -> void: func show_sync_progress() -> void:
sync_title_label.text = "Sync in Progress" sync_title_label.text = "Sync in Progress"
progress_hbox.visible = true progress_hbox.visible = true
no_changes_label.visible = false no_changes_label.visible = false
sync_hbox.visible = false sync_hbox.visible = false
catched_errors_vbox.visible = false caught_errors_vbox.visible = false
restart_sync_button.visible = false restart_sync_button.visible = false
File renamed without changes.
+7 -15
View File
@@ -1,16 +1,11 @@
extends PopupWindow class_name WinnerPopup
extends BasePopup
## Shows who won, with their character, and where the other players placed.
@onready @onready var winner_panel: Panel = %WinnerPanel
var winner_panel: Panel = %WinnerPanel @onready var winner_picture: TextureRect = %WinnerPicture
@onready var winner_label: Label = %WinnerLabel
@onready @onready var players_list: VBoxContainer = %PlayersList
var winner_picture: TextureRect = %WinnerPicture
@onready
var winner_label: Label = %WinnerLabel
@onready
var players_list: VBoxContainer = %PlayersList
func _ready() -> void: func _ready() -> void:
@@ -53,6 +48,3 @@ func show_winner(winner: PlayerObject) -> void:
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
players_list.add_child(label) players_list.add_child(label)
placing += 1 placing += 1
File renamed without changes.
+6 -5
View File
@@ -15,16 +15,17 @@ compatibility/default_parent_skeleton_in_mesh_instance_3d=true
[application] [application]
config/name="MusicPlayer" config/name="MusicPlayer"
config/version="1.9.0" config/version="2.0.0"
run/main_scene="res://MainWindow.tscn" run/main_scene="res://main/MainWindow.tscn"
config/features=PackedStringArray("4.7", "GL Compatibility") config/features=PackedStringArray("4.7", "GL Compatibility")
config/icon="res://icon.svg" config/icon="res://icon.svg"
[autoload] [autoload]
Settings="*res://Settings.gd" GameLog="*res://common/game_log.gd"
Playlist="*res://playlist.gd" Settings="*res://data/settings.gd"
MusicServerClient="*res://music_server_client.gd" Playlist="*res://data/playlist.gd"
MusicServerClient="*res://server/music_server_client.gd"
[debug] [debug]
+16
View File
@@ -0,0 +1,16 @@
[gd_scene format=3]
[ext_resource type="Script" uid="uid://elgn7gkxgagq" path="res://quiz/inspiration_list.gd" id="2_gxtxm"]
[node name="InspirationList" type="ScrollContainer" unique_id=498283761]
layout_mode = 3
horizontal_scroll_mode = 0
vertical_scroll_mode = 3
offset_right = 641.0
offset_bottom = 384.0
script = ExtResource("2_gxtxm")
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=1215162949]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
+24
View File
@@ -0,0 +1,24 @@
[gd_scene format=3]
[ext_resource type="Script" path="res://quiz/music_list_panel.gd" id="part_script"]
[ext_resource type="Script" uid="uid://gf1bk6xbe3j" path="res://quiz/scroll_to_end_container.gd" id="7_dj026"]
[ext_resource type="Theme" uid="uid://rxexo3ur85as" path="res://themes/light_gray_theme.tres" id="7_wxbv6"]
[node name="MusicListPanel" type="PanelContainer" unique_id=2011292375]
layout_mode = 3
theme = ExtResource("7_wxbv6")
offset_right = 1088.0
offset_bottom = 512.0
script = ExtResource("part_script")
[node name="ScrollContainer" type="ScrollContainer" parent="." unique_id=707750459]
layout_mode = 2
follow_focus = true
script = ExtResource("7_dj026")
[node name="MusicList" type="VBoxContainer" parent="ScrollContainer" unique_id=373398568]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/separation = 10
+29
View File
@@ -0,0 +1,29 @@
[gd_scene format=3]
[ext_resource type="Script" path="res://quiz/now_playing.gd" id="part_script"]
[sub_resource type="LabelSettings" id="LabelSettings_qspbu"]
font_size = 50
[sub_resource type="LabelSettings" id="LabelSettings_3m52w"]
font_size = 35
[node name="NowPlaying" type="VBoxContainer" unique_id=42468498]
layout_mode = 3
offset_right = 1088.0
offset_bottom = 320.0
script = ExtResource("part_script")
[node name="SoundtrackLabel" type="Label" parent="." unique_id=173147793]
custom_minimum_size = Vector2(1, 1)
layout_mode = 2
label_settings = SubResource("LabelSettings_qspbu")
horizontal_alignment = 1
autowrap_mode = 2
[node name="SongLabel" type="Label" parent="." unique_id=1687490027]
custom_minimum_size = Vector2(1, 1)
layout_mode = 2
label_settings = SubResource("LabelSettings_3m52w")
horizontal_alignment = 1
autowrap_mode = 2
+7 -2
View File
@@ -1,6 +1,6 @@
[gd_scene load_steps=9 format=3 uid="uid://cslogy2csnd7a"] [gd_scene load_steps=9 format=3 uid="uid://cslogy2csnd7a"]
[ext_resource type="Script" uid="uid://dl6no2wr2c86y" path="res://Player.gd" id="1_if4kc"] [ext_resource type="Script" uid="uid://dl6no2wr2c86y" path="res://quiz/player_row.gd" id="1_if4kc"]
[ext_resource type="Texture2D" uid="uid://r4as0nmtoa7p" path="res://noCharacter.png" id="2_hpj3s"] [ext_resource type="Texture2D" uid="uid://r4as0nmtoa7p" path="res://noCharacter.png" id="2_hpj3s"]
[ext_resource type="Texture2D" uid="uid://b2kj6m8qpsgb1" path="res://icons/person_remove_light.svg" id="3_j3uxe"] [ext_resource type="Texture2D" uid="uid://b2kj6m8qpsgb1" path="res://icons/person_remove_light.svg" id="3_j3uxe"]
@@ -19,7 +19,7 @@ bg_color = Color(0.180392, 0.180392, 0.180392, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_b2crt"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_b2crt"]
bg_color = Color(0.180392, 0.180392, 0.180392, 1) bg_color = Color(0.180392, 0.180392, 0.180392, 1)
[node name="Player" type="Control"] [node name="PlayerRow" type="Control"]
custom_minimum_size = Vector2(2.08165e-12, 40) custom_minimum_size = Vector2(2.08165e-12, 40)
layout_mode = 3 layout_mode = 3
anchors_preset = 0 anchors_preset = 0
@@ -42,6 +42,7 @@ layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_821k2") theme_override_styles/panel = SubResource("StyleBoxFlat_821k2")
[node name="Name" type="Label" parent="HBoxContainer"] [node name="Name" type="Label" parent="HBoxContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(130, 40) custom_minimum_size = Vector2(130, 40)
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
@@ -57,6 +58,7 @@ layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_w10e8") theme_override_styles/panel = SubResource("StyleBoxFlat_w10e8")
[node name="Character" type="TextureButton" parent="HBoxContainer"] [node name="Character" type="TextureButton" parent="HBoxContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(80, 40) custom_minimum_size = Vector2(80, 40)
layout_mode = 2 layout_mode = 2
focus_mode = 0 focus_mode = 0
@@ -71,6 +73,7 @@ layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_78a8g") theme_override_styles/panel = SubResource("StyleBoxFlat_78a8g")
[node name="AddPoint" type="Button" parent="HBoxContainer"] [node name="AddPoint" type="Button" parent="HBoxContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(40, 40) custom_minimum_size = Vector2(40, 40)
layout_mode = 2 layout_mode = 2
focus_mode = 0 focus_mode = 0
@@ -83,6 +86,7 @@ layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_i56hb") theme_override_styles/panel = SubResource("StyleBoxFlat_i56hb")
[node name="RemovePoint" type="Button" parent="HBoxContainer"] [node name="RemovePoint" type="Button" parent="HBoxContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(40, 40) custom_minimum_size = Vector2(40, 40)
layout_mode = 2 layout_mode = 2
focus_mode = 0 focus_mode = 0
@@ -95,6 +99,7 @@ layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_b2crt") theme_override_styles/panel = SubResource("StyleBoxFlat_b2crt")
[node name="RemovePlayer" type="TextureButton" parent="HBoxContainer"] [node name="RemovePlayer" type="TextureButton" parent="HBoxContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(40, 40) custom_minimum_size = Vector2(40, 40)
layout_mode = 2 layout_mode = 2
focus_mode = 0 focus_mode = 0
+94
View File
@@ -0,0 +1,94 @@
[gd_scene format=3]
[ext_resource type="Script" path="res://quiz/players_panel.gd" id="part_script"]
[ext_resource type="Texture2D" uid="uid://bcfmpd7h512ef" path="res://icons/person_add_light.svg" id="5_31tjv"]
[ext_resource type="Script" uid="uid://cgakje8gb37tf" path="res://quiz/player_name_field.gd" id="7_qsdfy"]
[sub_resource type="InputEventKey" id="InputEventKey_03bm3"]
device = -1
alt_pressed = true
keycode = 65
unicode = 63743
[sub_resource type="Shortcut" id="Shortcut_jafqj"]
events = [SubResource("InputEventKey_03bm3")]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qk7sj"]
bg_color = Color(0.180392, 0.180392, 0.180392, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_k1ygi"]
bg_color = Color(0.180392, 0.180392, 0.180392, 1)
[node name="PlayersPanel" type="PanelContainer" unique_id=1708375945]
layout_mode = 3
offset_right = 641.0
offset_bottom = 401.0
script = ExtResource("part_script")
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=270084211]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer" unique_id=513047023]
custom_minimum_size = Vector2(2.08165e-12, 40)
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer" unique_id=1331357386]
custom_minimum_size = Vector2(2.08165e-12, 40)
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 1
text = "Players"
horizontal_alignment = 1
vertical_alignment = 1
[node name="AddPlayersButton" type="TextureButton" parent="VBoxContainer/HBoxContainer" unique_id=634410148]
unique_name_in_owner = true
custom_minimum_size = Vector2(2.08165e-12, 40)
layout_mode = 2
size_flags_horizontal = 8
focus_mode = 0
action_mode = 0
shortcut = SubResource("Shortcut_jafqj")
texture_normal = ExtResource("5_31tjv")
[node name="AddPlayerContainer" type="HBoxContainer" parent="VBoxContainer" unique_id=1371948739]
unique_name_in_owner = true
visible = false
custom_minimum_size = Vector2(2.08165e-12, 40)
layout_mode = 2
[node name="Panel" type="Panel" parent="VBoxContainer/AddPlayerContainer" unique_id=1569854056]
layout_mode = 2
size_flags_horizontal = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_qk7sj")
[node name="PlayerNameField" type="TextEdit" parent="VBoxContainer/AddPlayerContainer" unique_id=332535327]
unique_name_in_owner = true
custom_minimum_size = Vector2(2.08165e-12, 40)
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "Player name"
script = ExtResource("7_qsdfy")
[node name="AddPlayerButton" type="Button" parent="VBoxContainer/AddPlayerContainer" unique_id=245001313]
unique_name_in_owner = true
custom_minimum_size = Vector2(2.08165e-12, 40)
layout_mode = 2
text = "Add"
[node name="Panel2" type="Panel" parent="VBoxContainer/AddPlayerContainer" unique_id=60215448]
custom_minimum_size = Vector2(2.08165e-12, 40)
layout_mode = 2
size_flags_horizontal = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_k1ygi")
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer" unique_id=1000000001]
layout_mode = 2
size_flags_vertical = 15
horizontal_scroll_mode = 0
[node name="PlayersListVBoxContainer" type="VBoxContainer" parent="VBoxContainer/ScrollContainer" unique_id=1000000002]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 15
+135
View File
@@ -0,0 +1,135 @@
[gd_scene format=3]
[ext_resource type="Script" path="res://quiz/quiz.gd" id="quiz_script"]
[ext_resource type="PackedScene" uid="uid://ds15cgsf8vpvc" path="res://player/MusicPlayer.tscn" id="5_emn36"]
[ext_resource type="PackedScene" uid="uid://cdy4kvemwaiom" path="res://popups/CharacterSelectPopup.tscn" id="6_dhrvg"]
[ext_resource type="PackedScene" uid="uid://dldpeo5y3l5hq" path="res://popups/SettingsPopup.tscn" id="11_k62u5"]
[ext_resource type="PackedScene" uid="uid://cxlsmxnrp0agh" path="res://popups/WinnerPopup.tscn" id="17_winner_window"]
[ext_resource type="PackedScene" path="res://quiz/MusicListPanel.tscn" id="scene_MusicListPanel"]
[ext_resource type="PackedScene" path="res://quiz/PlayersPanel.tscn" id="scene_PlayersPanel"]
[ext_resource type="PackedScene" path="res://quiz/InspirationList.tscn" id="scene_InspirationList"]
[ext_resource type="PackedScene" path="res://quiz/QuizToolbar.tscn" id="scene_QuizToolbar"]
[ext_resource type="PackedScene" path="res://quiz/SongControls.tscn" id="scene_SongControls"]
[ext_resource type="PackedScene" path="res://quiz/NowPlaying.tscn" id="scene_NowPlaying"]
[ext_resource type="Script" path="res://quiz/song_loader.gd" id="song_loader_script"]
[node name="Quiz" type="Control" unique_id=1146705218]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
script = ExtResource("quiz_script")
[node name="Open" type="Button" parent="." unique_id=165052329]
visible = false
layout_mode = 0
offset_left = 1296.0
offset_top = 66.0
offset_right = 1346.0
offset_bottom = 97.0
text = "Open"
[node name="FileDialog" type="FileDialog" parent="." unique_id=485771559]
title = "Open a Directory"
initial_position = 2
size = Vector2i(616, 400)
ok_button_text = "Select Current Folder"
file_mode = 2
access = 2
show_hidden_files = true
[node name="MusicListPanel" parent="." unique_id=2011292375 instance=ExtResource("scene_MusicListPanel")]
layout_mode = 0
offset_left = 64.0
offset_top = 384.0
offset_right = 1152.0
offset_bottom = 896.0
[node name="InspirationList" parent="." unique_id=498283761 instance=ExtResource("scene_InspirationList")]
layout_mode = 0
offset_left = 1216.0
offset_top = 128.0
offset_right = 1857.0
offset_bottom = 512.0
[node name="NowPlaying" parent="." unique_id=42468498 instance=ExtResource("scene_NowPlaying")]
layout_mode = 0
offset_left = 64.0
offset_top = 64.0
offset_right = 1152.0
offset_bottom = 384.0
[node name="MusicPlayer" parent="." unique_id=1990459832 instance=ExtResource("5_emn36")]
layout_mode = 0
offset_left = 64.0
offset_top = 992.0
offset_right = 1856.0
offset_bottom = 1063.0
[node name="PlayersPanel" parent="." unique_id=1708375945 instance=ExtResource("scene_PlayersPanel")]
layout_mode = 0
offset_left = 1216.0
offset_top = 576.0
offset_right = 1857.0
offset_bottom = 977.0
[node name="QuizToolbar" parent="." unique_id=1873301941 instance=ExtResource("scene_QuizToolbar")]
layout_mode = 0
offset_left = 1360.0
offset_top = 64.0
offset_right = 1855.0
offset_bottom = 95.0
[node name="SongControls" parent="." unique_id=1640212773 instance=ExtResource("scene_SongControls")]
layout_mode = 0
offset_left = 65.0
offset_top = 944.0
offset_right = 368.0
offset_bottom = 975.0
[node name="SettingsPopup" parent="." unique_id=1323246978 instance=ExtResource("11_k62u5")]
visible = false
layout_mode = 1
offset_left = 720.0
offset_top = 153.0
offset_right = 720.0
offset_bottom = 153.0
[node name="CharacterSelectPopup" parent="." unique_id=1679856322 instance=ExtResource("6_dhrvg")]
unique_name_in_owner = true
visible = false
layout_mode = 1
offset_left = 405.0
offset_top = 165.0
offset_right = 1557.0
offset_bottom = 805.0
[node name="WinnerPopup" parent="." unique_id=999999999 instance=ExtResource("17_winner_window")]
unique_name_in_owner = true
visible = false
layout_mode = 1
offset_left = 720.0
offset_top = 153.0
offset_right = 720.0
offset_bottom = 153.0
[node name="DebugLabel" type="Label" parent="." unique_id=1959221257]
visible = false
layout_mode = 0
offset_left = 974.0
offset_top = 904.0
offset_right = 1014.0
offset_bottom = 927.0
scale = Vector2(3, 3)
text = "DEBUG"
horizontal_alignment = 1
vertical_alignment = 1
autowrap_mode = 2
[node name="SongLoader" type="Node" parent="." unique_id=1520867349]
script = ExtResource("song_loader_script")
[connection signal="dir_selected" from="FileDialog" to="." method="_on_file_dialog_dir_selected"]
+45
View File
@@ -0,0 +1,45 @@
[gd_scene format=3]
[ext_resource type="Script" path="res://quiz/quiz_toolbar.gd" id="part_script"]
[node name="QuizToolbar" type="Control" unique_id=1873301941]
layout_mode = 3
offset_right = 495.0
offset_bottom = 31.0
mouse_filter = 2
script = ExtResource("part_script")
[node name="ResetPlaylistButton" type="Button" parent="." unique_id=1754155620]
layout_mode = 0
offset_right = 111.0
offset_bottom = 31.0
focus_mode = 0
action_mode = 0
text = "Reset playlist"
[node name="ResetPointsButton" type="Button" parent="." unique_id=942880776]
layout_mode = 0
offset_left = 128.0
offset_right = 239.0
offset_bottom = 31.0
focus_mode = 0
action_mode = 0
text = "Reset points"
[node name="SettingsButton" type="Button" parent="." unique_id=1193400323]
layout_mode = 0
offset_left = 256.0
offset_right = 367.0
offset_bottom = 31.0
focus_mode = 0
action_mode = 0
text = "Settings"
[node name="SoundTestButton" type="Button" parent="." unique_id=1332971879]
layout_mode = 0
offset_left = 384.0
offset_right = 495.0
offset_bottom = 31.0
focus_mode = 0
action_mode = 0
text = "Sound test"
+48
View File
@@ -0,0 +1,48 @@
[gd_scene format=3]
[ext_resource type="Script" path="res://quiz/song_controls.gd" id="part_script"]
[sub_resource type="InputEventKey" id="InputEventKey_06rl4"]
device = -1
alt_pressed = true
keycode = 86
unicode = 8249
[sub_resource type="Shortcut" id="Shortcut_a7fvb"]
events = [SubResource("InputEventKey_06rl4")]
[sub_resource type="InputEventKey" id="InputEventKey_7dnqw"]
device = -1
alt_pressed = true
keycode = 67
unicode = 231
[sub_resource type="Shortcut" id="Shortcut_d6fml"]
events = [SubResource("InputEventKey_7dnqw")]
[node name="SongControls" type="Control" unique_id=1640212773]
layout_mode = 3
offset_right = 303.0
offset_bottom = 31.0
mouse_filter = 2
script = ExtResource("part_script")
[node name="ShowAnswerButton" type="Button" parent="." unique_id=415388039]
layout_mode = 0
offset_right = 111.0
offset_bottom = 31.0
focus_mode = 0
action_mode = 0
shortcut = SubResource("Shortcut_a7fvb")
text = "Show answer"
[node name="NextButton" type="Button" parent="." unique_id=1744703488]
layout_mode = 0
offset_left = 128.0
offset_right = 303.0
offset_bottom = 31.0
focus_mode = 0
disabled = true
action_mode = 0
shortcut = SubResource("Shortcut_d6fml")
text = "Randomize new track"
+6 -4
View File
@@ -1,11 +1,11 @@
[gd_scene load_steps=5 format=3 uid="uid://1jbtmhbqolqe"] [gd_scene load_steps=5 format=3 uid="uid://1jbtmhbqolqe"]
[ext_resource type="Texture2D" uid="uid://c8r7ykphss4c2" path="res://characters/Link.png" id="1_ll28s"] [ext_resource type="Texture2D" uid="uid://c8r7ykphss4c2" path="res://characters/Link.png" id="1_ll28s"]
[ext_resource type="Script" uid="uid://cjo13wixip8mq" path="res://song_list_object.gd" id="1_lspki"] [ext_resource type="Script" uid="uid://cjo13wixip8mq" path="res://quiz/song_row.gd" id="1_lspki"]
[ext_resource type="Theme" uid="uid://8bllart6xn1f" path="res://default_theme.tres" id="1_x7fq8"] [ext_resource type="Theme" uid="uid://8bllart6xn1f" path="res://themes/default_theme.tres" id="1_x7fq8"]
[ext_resource type="StyleBox" uid="uid://bsv6yb0yir04l" path="res://style_box_flat.tres" id="3_5h84f"] [ext_resource type="StyleBox" uid="uid://bsv6yb0yir04l" path="res://themes/style_box_flat.tres" id="3_5h84f"]
[node name="SongListObject" type="PanelContainer"] [node name="SongRow" type="PanelContainer"]
custom_minimum_size = Vector2(1000, 0) custom_minimum_size = Vector2(1000, 0)
offset_right = 1088.0 offset_right = 1088.0
offset_bottom = 59.0 offset_bottom = 59.0
@@ -17,6 +17,7 @@ layout_mode = 2
size_flags_vertical = 0 size_flags_vertical = 0
[node name="SongListLabel" type="Label" parent="HBoxContainer"] [node name="SongListLabel" type="Label" parent="HBoxContainer"]
unique_name_in_owner = true
texture_filter = 2 texture_filter = 2
custom_minimum_size = Vector2(865, 28) custom_minimum_size = Vector2(865, 28)
layout_mode = 2 layout_mode = 2
@@ -34,6 +35,7 @@ size_flags_horizontal = 3
size_flags_vertical = 0 size_flags_vertical = 0
[node name="SongListPlayers" type="HBoxContainer" parent="HBoxContainer"] [node name="SongListPlayers" type="HBoxContainer" parent="HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
size_flags_vertical = 0 size_flags_vertical = 0
+68
View File
@@ -0,0 +1,68 @@
class_name InspirationList
extends ScrollContainer
## The list of every soundtrack, shown for inspiration. It scrolls slowly up and down
## by itself, at the speed set in the settings.
## The soundtracks shown, in order.
var soundtracks: Array = []
# Scroll direction: 1 = down, -1 = up
var scroll_direction: int = 1
@onready var _rows: VBoxContainer = $VBoxContainer
func _ready() -> void:
scroll_direction = 1
func _process(delta: float) -> void:
var speed_pixels_per_second: float = float(Settings.inspiration_list_speed) * 60.0
var scroll_amount: float = speed_pixels_per_second * delta * float(scroll_direction)
var scroll_bar: VScrollBar = get_v_scroll_bar()
# Don't scroll if list is empty or too short
if scroll_bar.max_value <= 0:
return
var current_pos: float = scroll_bar.value
if scroll_direction == 1:
# Scrolling down
if current_pos + scroll_bar.page < scroll_bar.max_value:
scroll_bar.value = minf(current_pos + scroll_amount,
scroll_bar.max_value - scroll_bar.page)
else:
# Reached bottom, reverse direction
scroll_direction = -1
scroll_bar.value = scroll_bar.max_value - scroll_bar.page
else:
# Scrolling up
if current_pos > 0:
scroll_bar.value = maxf(current_pos + scroll_amount, 0)
else:
# Reached top, reverse direction
scroll_direction = 1
scroll_bar.value = 0
## Fetches every soundtrack from the server and shows them.
func load_from_server() -> void:
GameLog.debug("load_from_server")
var populate_list: Callable = func populate_list(array: Variant) -> void:
if typeof(array) == TYPE_ARRAY:
show_soundtracks(array)
else:
GameLog.warning("Unexpected data")
MusicServerClient.fetch_all_soundtracks(populate_list)
## Shows [param list] instead of the soundtracks shown now.
func show_soundtracks(list: Array) -> void:
soundtracks = []
soundtracks.append_array(list)
Settings.delete_children(_rows)
for i: int in soundtracks.size():
_rows.add_child(ListRow.create(soundtracks[i], i, 20))
File renamed without changes.
+26
View File
@@ -0,0 +1,26 @@
class_name MusicListPanel
extends PanelContainer
## The list of songs in the playlist, one row per song.
## Emitted when the row of song number [param song_no] is clicked.
signal song_clicked(song_no: int)
var _song_list_object: PackedScene = preload("res://quiz/SongRow.tscn")
@onready var _rows: VBoxContainer = %MusicList
## Shows the songs in the playlist again, so it shows their latest state.
func refresh() -> void:
Settings.delete_children(_rows)
for s: SongObject in Playlist.get_playlist():
var new_song_list_object: SongRow = _song_list_object.instantiate()
new_song_list_object.song_object = s
new_song_list_object.gui_input.connect(_on_row_input.bind(s.song_number))
_rows.add_child(new_song_list_object)
func _on_row_input(event: InputEvent, song_no: int) -> void:
if event is InputEventMouseButton && event.pressed && event.button_index == MOUSE_BUTTON_LEFT:
song_clicked.emit(song_no)
+1
View File
@@ -0,0 +1 @@
uid://b8e27uui01tt
+10
View File
@@ -0,0 +1,10 @@
class_name NowPlaying
extends VBoxContainer
## The soundtrack and song that are playing, in large text at the top of the quiz.
## Shows [param soundtrack_title] and [param song_title]. The playlist hides them as
## question marks until the answer is shown.
func show_song(soundtrack_title: String, song_title: String) -> void:
$SoundtrackLabel.text = soundtrack_title
$SongLabel.text = song_title
+1
View File
@@ -0,0 +1 @@
uid://beoi75d4u568h
@@ -1,18 +1,28 @@
class_name PlayerNameField
extends TextEdit extends TextEdit
## The field for a new player's name. It keeps to one line of at most
## [constant MAX_NAME_LENGTH] characters, and reports Enter so the player can be added.
## Emitted when Enter is pressed while the field has focus.
signal enter_key_pressed signal enter_key_pressed
## Emitted when Alt+A is pressed.
signal close_pressed signal close_pressed
@export ## The longest name a player can have.
var LIMIT: int = 10 const MAX_NAME_LENGTH: int = 10
var current_text = ''
var cursor_line = 0
var cursor_column = 0
func _ready(): var current_text: String = ""
var cursor_line: int = 0
var cursor_column: int = 0
func _ready() -> void:
text_changed.connect(_on_text_changed) text_changed.connect(_on_text_changed)
func _input(event):
func _input(event: InputEvent) -> void:
if event is InputEventKey and event.pressed: if event is InputEventKey and event.pressed:
if event.alt_pressed && event.keycode == KEY_A: if event.alt_pressed && event.keycode == KEY_A:
close_pressed.emit() close_pressed.emit()
@@ -20,12 +30,13 @@ func _input(event):
if event.keycode == KEY_ENTER || event.keycode == KEY_KP_ENTER: if event.keycode == KEY_ENTER || event.keycode == KEY_KP_ENTER:
enter_key_pressed.emit() enter_key_pressed.emit()
func _on_text_changed():
func _on_text_changed() -> void:
if text.contains("\n"): if text.contains("\n"):
text = text.replace("\n", "") text = text.replace("\n", "")
var new_text : String = text var new_text: String = text
if new_text.length() > LIMIT: if new_text.length() > MAX_NAME_LENGTH:
text = current_text text = current_text
# when replacing the text, the cursor will get moved to the beginning of the # when replacing the text, the cursor will get moved to the beginning of the
# text, so move it back to where it was # text, so move it back to where it was
File renamed without changes.
+20 -20
View File
@@ -1,21 +1,6 @@
class_name Player class_name PlayerRow
extends Control extends Control
@onready
var player_name_field := $HBoxContainer/Name
@onready
var add_button := $HBoxContainer/AddPoint
@onready
var minus_button := $HBoxContainer/RemovePoint
@onready
var character := $HBoxContainer/Character
@onready
var remove_player := $HBoxContainer/RemovePlayer
signal change_character_clicked signal change_character_clicked
signal player_removed signal player_removed
@@ -26,7 +11,13 @@ var player_object: PlayerObject
## gives this player a point and Ctrl+Alt+<list_index + 1> takes one away. ## gives this player a point and Ctrl+Alt+<list_index + 1> takes one away.
var list_index: int var list_index: int
# Called when the node enters the scene tree for the first time. @onready var player_name_field: Label = %Name
@onready var add_button: Button = %AddPoint
@onready var minus_button: Button = %RemovePoint
@onready var character: TextureButton = %Character
@onready var remove_player: TextureButton = %RemovePlayer
func _ready() -> void: func _ready() -> void:
add_button.pressed.connect(add_point) add_button.pressed.connect(add_point)
add_button.set_shortcut(create_button_shortcut(list_index, false)) add_button.set_shortcut(create_button_shortcut(list_index, false))
@@ -37,42 +28,51 @@ func _ready() -> void:
update_score() update_score()
set_player_character() set_player_character()
func _process(delta: float) -> void: func _process(delta: float) -> void:
remove_player.visible = Settings.edit_players remove_player.visible = Settings.edit_players
func create_button_shortcut(index: int, ctrl_pressed: bool) -> Shortcut: func create_button_shortcut(index: int, ctrl_pressed: bool) -> Shortcut:
var button_shortcut: Shortcut = Shortcut.new() var button_shortcut: Shortcut = Shortcut.new()
var button_event : InputEventKey = InputEventKey.new() var button_event: InputEventKey = InputEventKey.new()
button_event.keycode = KEY_1 + index button_event.keycode = KEY_1 + index
button_event.alt_pressed = true button_event.alt_pressed = true
button_event.ctrl_pressed = ctrl_pressed button_event.ctrl_pressed = ctrl_pressed
button_shortcut.events.append(button_event) button_shortcut.events.append(button_event)
return button_shortcut return button_shortcut
func add_point() -> void: func add_point() -> void:
player_object.add_point() player_object.add_point()
update_score() update_score()
func minus_point() -> void: func minus_point() -> void:
player_object.minus_point() player_object.minus_point()
update_score() update_score()
func change_character() -> void: func change_character() -> void:
change_character_clicked.emit() change_character_clicked.emit()
func new_character_selected(character_texture: ImageTexture) -> void: func new_character_selected(character_texture: ImageTexture) -> void:
print("Back in player list with new") GameLog.debug("Back in player list with new")
player_object.character = character_texture player_object.character = character_texture
set_player_character() set_player_character()
func set_player_character() -> void: func set_player_character() -> void:
character.custom_minimum_size = Vector2(80, 40) character.custom_minimum_size = Vector2(80, 40)
character.ignore_texture_size = true character.ignore_texture_size = true
character.stretch_mode = TextureButton.STRETCH_KEEP_ASPECT_CENTERED character.stretch_mode = TextureButton.STRETCH_KEEP_ASPECT_CENTERED
character.texture_normal = player_object.character character.texture_normal = player_object.character
func update_score() -> void: func update_score() -> void:
if OS.has_feature("debug"): if OS.has_feature("debug"):
player_name_field.text = str(list_index + 1) + " " + player_object.player_name + ": " + str(player_object.player_score) player_name_field.text = "%d %s: %d" % [list_index + 1, player_object.player_name,
player_object.player_score]
else: else:
player_name_field.text = player_object.player_name + ": " + str(player_object.player_score) player_name_field.text = player_object.player_name + ": " + str(player_object.player_score)
File renamed without changes.
+84
View File
@@ -0,0 +1,84 @@
class_name PlayersPanel
extends PanelContainer
## The players and their points, with a row for adding new players.
##
## The players themselves live in [code]Settings.player_array[/code]; this panel shows
## them and adds and removes them there.
## Emitted when a player has been added to [code]Settings.player_array[/code], before
## the list shows it.
signal player_added(player_object: PlayerObject)
## Emitted when a player has been removed from [code]Settings.player_array[/code].
signal player_removed(player_object: PlayerObject)
## Emitted when the character button in [param row] is pressed.
signal change_character_requested(row: PlayerRow, player_name: String)
var _player_row: PackedScene = preload("res://quiz/PlayerRow.tscn")
@onready var add_players_button: TextureButton = %AddPlayersButton
@onready var add_player_container: HBoxContainer = %AddPlayerContainer
@onready var add_player_button: Button = %AddPlayerButton
@onready var new_player_name_field: PlayerNameField = %PlayerNameField
@onready var players_list_container: VBoxContainer = %PlayersListVBoxContainer
func _ready() -> void:
add_players_button.pressed.connect(add_players)
add_player_button.pressed.connect(add_player)
new_player_name_field.enter_key_pressed.connect(add_player)
## Shows or hides the row for adding players, and the buttons for removing them.
func add_players() -> void:
GameLog.debug("add_players")
Settings.edit_players = !Settings.edit_players
add_player_container.visible = !add_player_container.visible
new_player_name_field.grab_focus()
## Adds a player with the name in the name field, unless it is empty or there are
## already as many players as there can be.
func add_player() -> void:
GameLog.debug("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)
player_added.emit(new_player_object)
reload()
## Shows the players in [code]Settings.player_array[/code] again, e.g. after their
## points were reset.
func reload() -> void:
GameLog.debug("load_players")
Settings.delete_children(players_list_container)
var counter: int = 0
for player_object: PlayerObject in Settings.player_array:
GameLog.debug(player_object.player_name)
var new_player: PlayerRow = _player_row.instantiate()
new_player.player_object = player_object
new_player.list_index = counter
new_player.change_character_clicked.connect(
_on_change_character_clicked.bind(new_player, player_object.player_name))
new_player.player_removed.connect(_on_player_removed.bind(player_object))
players_list_container.add_child(new_player)
counter += 1
func _on_change_character_clicked(row: PlayerRow, player_name: String) -> void:
change_character_requested.emit(row, player_name)
func _on_player_removed(player_object: PlayerObject) -> void:
GameLog.debug("_on_player_removed ", player_object.player_name)
Settings.player_array.erase(player_object)
reload()
player_removed.emit(player_object)
+1
View File
@@ -0,0 +1 @@
uid://ctayn0soyatfk
+298
View File
@@ -0,0 +1,298 @@
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
## Emitted with a line for the game log, e.g. when a player gets a point.
signal event_logged(text: String)
var songs: Array = []
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 open_button: Button = $Open
@onready var file_dialog: FileDialog = $FileDialog
@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 settings_popup: SettingsPopup = $SettingsPopup
@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
@onready var debug_label: Label = $DebugLabel
func _ready() -> void:
popup_backdrop = PopupBackdrop.add_to(self, _popups())
if OS.has_feature("debug"):
GameLog.debug("is_debug")
debug_label.visible = true
Settings.is_debug = true
settings_popup.set_winning_score(5)
settings_popup.set_selected_server(4)
song_controls.next_pressed.connect(play_next_song)
song_controls.show_answer_pressed.connect(show_answer_pressed)
toolbar.sound_test_pressed.connect(music_player.get_sound_test_song)
toolbar.settings_pressed.connect(show_settings)
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)
settings_popup.server_changed.connect(_on_server_changed)
settings_popup.visibility_changed.connect(_on_settings_popup_visibility_changed)
open_button.pressed.connect(open)
inspiration_list.load_from_server()
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_from_server()
reset_playlist()
character_select_popup.load_characters()
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)
if Playlist.first_song_played:
Playlist.set_latest_played_song()
Playlist.first_song_played = true
music_player.play_song_object(Playlist.latest_played_song)
if !(Playlist.get_preloaded_songs() > 0):
song_controls.set_next_enabled(false)
song_loader.fetch_song_if_idle()
func _popups() -> Array[BasePopup]:
return [settings_popup, character_select_popup, winner_popup]
# 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_popup_visibility_changed() -> void:
if !settings_popup.visible:
song_loader.fill_cache()
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:
GameLog.debug("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.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()
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()
# 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:
GameLog.debug("show_settings")
settings_popup.open()
# 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()
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)
###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:
file_dialog.popup()
func _on_file_dialog_dir_selected(path: String) -> void:
GameLog.debug(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()
var soundtracks: Array = []
while file_name != "":
if dir.current_is_dir():
#GameLog.debug("Found directory: " + file_name)
soundtracks.append(file_name)
else:
#GameLog.debug("Found file: " + file_name)
if file_name.ends_with(".mp3"):
songs.append(path + "/" + file_name)
file_name = dir.get_next()
inspiration_list.show_soundtracks(soundtracks)
else:
GameLog.warning("An error occurred when trying to access the path.")
+1
View File
@@ -0,0 +1 @@
uid://b0od47vseenx4
+25
View File
@@ -0,0 +1,25 @@
class_name QuizToolbar
extends Control
## The row of quiz buttons under the top bar: reset playlist, reset points, settings
## and sound test.
signal reset_playlist_pressed
signal reset_points_pressed
signal settings_pressed
signal sound_test_pressed
@onready var reset_playlist_button: Button = $ResetPlaylistButton
@onready var sound_test_button: Button = $SoundTestButton
func _ready() -> void:
reset_playlist_button.pressed.connect(reset_playlist_pressed.emit)
$ResetPointsButton.pressed.connect(reset_points_pressed.emit)
$SettingsButton.pressed.connect(settings_pressed.emit)
sound_test_button.pressed.connect(sound_test_pressed.emit)
## Disables the buttons that use the song list, e.g. while the server syncs it.
func set_locked(locked: bool) -> void:
reset_playlist_button.disabled = locked
sound_test_button.disabled = locked
+1
View File
@@ -0,0 +1 @@
uid://dmsy36s1ht7x5
+12
View File
@@ -0,0 +1,12 @@
class_name ScrollToEndContainer
extends ScrollContainer
## A scroll container that scrolls to the end whenever its content grows, so the newest
## item is always in view.
var max_value: float = 0.0
func _draw() -> void:
if max_value != get_v_scroll_bar().max_value:
max_value = get_v_scroll_bar().max_value
scroll_vertical = int(max_value)
File renamed without changes.
+27
View File
@@ -0,0 +1,27 @@
class_name SongControls
extends Control
## The buttons for the song that is playing: show the answer, and go to the next song.
signal show_answer_pressed
signal next_pressed
@onready var show_answer_button: Button = $ShowAnswerButton
@onready var next_button: Button = $NextButton
func _ready() -> void:
show_answer_button.pressed.connect(show_answer_pressed.emit)
next_button.pressed.connect(next_pressed.emit)
## Enables Next while there is a downloaded song to play next.
func set_next_enabled(enabled: bool) -> void:
next_button.disabled = !enabled
## Disables both buttons, e.g. while the server syncs the song list. Unlocking enables
## Show answer again; Next comes back when the next song has been downloaded.
func set_locked(locked: bool) -> void:
show_answer_button.disabled = locked
if locked:
next_button.disabled = true
+1
View File
@@ -0,0 +1 @@
uid://d6nevh66yaw1
+101
View File
@@ -0,0 +1,101 @@
class_name SongLoader
extends Node
## Downloads songs from the server into the Playlist ahead of time, so the next song
## can start at once.
##
## How many songs it keeps ready is the cache size in the settings
## ([code]Playlist.number_of_tracks_to_preload[/code]).
## Emitted when the songs already in the server playlist have been loaded into the
## Playlist.
signal song_list_loaded
## Emitted when a new song has been downloaded and added to the Playlist.
signal song_added
## Seconds to wait before retrying a download step that failed.
var retry_seconds: float = 5.0
## 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 load_song_list() -> void:
GameLog.debug("load_song_list")
var song_list_fetched: Callable = func song_list_fetched(data: Variant) -> 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:
GameLog.warning("Unexpected data")
Playlist.update_last_played_song()
song_list_loaded.emit()
fetch_song_if_idle()
MusicServerClient.fetch_song_list(song_list_fetched, _retry_later.bind(load_song_list))
## Starts downloading one more song, which keeps downloading until the cache is full.
func fetch_song() -> void:
GameLog.debug("fetch_song")
Playlist.is_song_downloading = true
_download_random_song(SongObject.new())
## Starts downloading a song unless one is already downloading.
func fetch_song_if_idle() -> void:
if !Playlist.is_song_downloading:
fetch_song()
## Starts downloading if nothing is downloading and the cache is not full, e.g. after
## the cache size was raised.
func fill_cache() -> void:
if !Playlist.is_song_downloading && Playlist.should_preload_more_songs():
fetch_song()
# 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:
GameLog.debug("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:
GameLog.debug("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
GameLog.debug("new_song_object: ", new_song_object.to_string())
Playlist.add_to_playlist(new_song_object)
if Playlist.should_preload_more_songs():
fetch_song()
else:
Playlist.is_song_downloading = false
song_added.emit()
MusicServerClient.fetch_song_info(song_info_fetched,
_retry_later.bind(_fetch_song_info.bind(new_song_object)))
func _retry_later(step: Callable) -> void:
GameLog.warning("Song download failed, retrying in %s seconds" % retry_seconds)
get_tree().create_timer(retry_seconds).timeout.connect(step)
+1
View File
@@ -0,0 +1 @@
uid://dwqirg7x7mmj6
+8 -21
View File
@@ -1,33 +1,19 @@
class_name SongListObject class_name SongRow
extends PanelContainer extends PanelContainer
## One song in the music list: its number, soundtrack and title, and a marker for
@onready ## each player who got a point for it.
var song_list_label: Label = $HBoxContainer/SongListLabel
@onready
var song_list_players: HBoxContainer = $HBoxContainer/SongListPlayers
var song_object: SongObject var song_object: SongObject
#Color(0.18, 0.18, 0.18) @onready var song_list_label: Label = %SongListLabel
#Color(0.302, 0.302, 0.302) @onready var song_list_players: HBoxContainer = %SongListPlayers
#Color(0.259, 0.259, 0.259)
# Called when the node enters the scene tree for the first time.
func _ready() -> void: func _ready() -> void:
#if song_object != null && song_object.song_number % 2 == 0:
# var new_stylebox_normal = self.get_theme_stylebox("panel").duplicate()
# new_stylebox_normal.bg_color = Color(0.18, 0.18, 0.18)
# self.add_theme_stylebox_override("panel", new_stylebox_normal)
# song_list_label.add_theme_stylebox_override("normal", new_stylebox_normal)
#else:
# var new_stylebox_normal = self.get_theme_stylebox("panel").duplicate()
# new_stylebox_normal.bg_color = Color(0.18, 0.18, 0.18)
# self.add_theme_stylebox_override("panel", new_stylebox_normal)
# song_list_label.add_theme_stylebox_override("normal", new_stylebox_normal)
set_label() set_label()
update_players() update_players()
func set_label() -> void: func set_label() -> void:
if song_object != null: if song_object != null:
var size_to_change: int = song_object.players_given_point.size() * 50 var size_to_change: int = song_object.players_given_point.size() * 50
@@ -36,6 +22,7 @@ func set_label() -> void:
song_list_label.add_theme_color_override("font_color", Color(1, 0.5, 0)) song_list_label.add_theme_color_override("font_color", Color(1, 0.5, 0))
song_list_label.text = song_object.get_song_info() song_list_label.text = song_object.get_song_info()
func update_players() -> void: func update_players() -> void:
Settings.delete_children(song_list_players) Settings.delete_children(song_list_players)
if song_object != null: if song_object != null:
File renamed without changes.
Loaded 100 of 154 files, more files were not shown because too many files have changed in this diff. Show more