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.
This commit is contained in:
Sansan committed 2026-10-06 11:53:26 +02:00
1 parent 674604425f
commit a0428e3475
45 files changed
+580 -589

No files matched your search

+2 -2
View File
@@ -145,13 +145,13 @@ text = "SoundtracksRemoved"
unique_name_in_owner = true
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
visible = false
layout_mode = 2
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
text = "CatchedErrors"
horizontal_alignment = 1
+8 -14
View File
@@ -1,18 +1,8 @@
class_name AboutPopup
extends BasePopup
## Shows the game version and what is new in each version.
@onready
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
## What is new in each version, newest first.
const WHATS_NEW: Dictionary = {
"1.9.0": "Made improvements to the Search window
Made small changes to the inspiration list scrolling
@@ -51,15 +41,19 @@ const WHATS_NEW: Dictionary = {
"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:
super()
version_label.text = Settings.version
# Build changelog text with version headers
var changelog_text: String = ""
for version: String in WHATS_NEW.keys():
changelog_text += "\n" + version + "\n"
changelog_text += WHATS_NEW[version] + "\n"
whats_new_label.text = changelog_text
+1
View File
@@ -6,6 +6,7 @@ extends Control
## override [method open] or [method close] and call [code]super()[/code].
## The visible part of the popup. Clicks outside it close the popup.
@export var panel: Control
+19 -18
View File
@@ -1,21 +1,30 @@
class_name CharacterCard
extends Control
## One character in the character select popup: its picture and name. Clicking it picks
## the character.
@onready
var panel: Panel = $Panel
@onready
var character_image: TextureRect = %CharacterTextureRect
@onready
var character_name_label: Label = %CharacterName
## Emitted with the character's picture when the card is clicked.
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:
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.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
var image_fetched: Callable = func image_fetched(image: PackedByteArray) -> void:
@@ -37,11 +46,3 @@ func set_character_name(character_name: String) -> void:
character_image.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
character_image.texture = new_image
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):
GameLog.debug(character_name_label.text)
new_character_selected.emit(character_image.texture)
+17 -18
View File
@@ -1,35 +1,31 @@
class_name CharacterSelectPopup
extends BasePopup
@onready
var character_panel: Panel = %CharacterPanel
@onready
var title_label: Label = %TitleLabel
@onready
var character_grid: GridContainer = %CharacterGridContainer
@onready
var close_timer: Timer = %CloseTimer
## Emitted with the picture of the character that was picked.
signal character_selected(texture: ImageTexture)
var can_select_character: bool = false
signal new_new_character_selected(file_name: String)
var character: PackedScene = preload("res://CharacterCard.tscn")
@onready var character_panel: Panel = %CharacterPanel
@onready var title_label: Label = %TitleLabel
@onready var character_grid: GridContainer = %CharacterGridContainer
@onready var close_timer: Timer = %CloseTimer
func _ready() -> void:
close_timer.timeout.connect(_on_close_timeout)
func _on_close_timeout() -> void:
GameLog.debug("_on_close_timeout!")
Settings.character_select_open = false
close_timer.stop()
func load_characters() -> void:
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:
GameLog.debug("fetch_character_list")
Settings.delete_children(character_grid)
if typeof(list) == TYPE_ARRAY:
@@ -40,9 +36,10 @@ func load_characters() -> void:
character_box.custom_minimum_size = Vector2(235, 145)
character_box.size = Vector2(235, 145)
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)
func show_grid(player_name: String) -> void:
GameLog.debug("show_grid")
title_label.text = "Select character for " + player_name
@@ -51,16 +48,18 @@ func show_grid(player_name: String) -> void:
can_select_character = true
Settings.character_select_open = true
func close() -> void:
super()
Settings.character_select_open = false
func _on_character_selected(texture: ImageTexture) -> void:
GameLog.debug("_on_character_selected")
# 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
self.visible = false
visible = false
if can_select_character:
new_new_character_selected.emit(texture)
character_selected.emit(texture)
close_timer.start()
can_select_character = false
+1 -1
View File
@@ -6,8 +6,8 @@ extends RefCounted
## 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 timeout_seconds: float = 120.0
var _owner: Node
+10 -9
View File
@@ -4,35 +4,36 @@ extends ScrollContainer
## 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
@onready var _rows: VBoxContainer = $VBoxContainer
# 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()
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 = scroll_bar.value
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 = min(current_pos + scroll_amount, scroll_bar.max_value - scroll_bar.page)
scroll_bar.value = minf(current_pos + scroll_amount,
scroll_bar.max_value - scroll_bar.page)
else:
# Reached bottom, reverse direction
scroll_direction = -1
@@ -40,7 +41,7 @@ func _process(delta: float) -> void:
else:
# Scrolling up
if current_pos > 0:
scroll_bar.value = max(current_pos + scroll_amount, 0)
scroll_bar.value = maxf(current_pos + scroll_amount, 0)
else:
# Reached top, reverse direction
scroll_direction = 1
@@ -50,7 +51,7 @@ func _process(delta: float) -> void:
## 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) -> void:
var populate_list: Callable = func populate_list(array: Variant) -> void:
if typeof(array) == TYPE_ARRAY:
show_soundtracks(array)
else:
+2
View File
@@ -16,6 +16,8 @@ var label: Label
## The item's text.
var text: String:
get:
return label.text
+8 -6
View File
@@ -1,19 +1,20 @@
class_name LogPopup
extends BasePopup
@onready
var log_panel: Panel = %LogPanel
@onready
var log_box: VBoxContainer = %LogVBoxContainer
## The game log: a row with the time for each thing that happens in the quiz, such as a
## player getting a point. Opened with Alt+L.
var log_rows: Array
@onready var log_panel: Panel = %LogPanel
@onready var log_box: VBoxContainer = %LogVBoxContainer
func add_log_row(text: String) -> void:
var log_row: String = get_time() + text
log_rows.append(log_row)
log_box.add_child(make_log_label(log_row))
func make_log_label(row: String) -> Label:
var log_label: Label = Label.new()
log_label.add_theme_font_size_override("font_size", 20)
@@ -21,6 +22,7 @@ func make_log_label(row: String) -> Label:
log_label.autowrap_mode = TextServer.AUTOWRAP_WORD
return log_label
func get_time() -> String:
var time: Dictionary = Time.get_time_dict_from_system()
return "%02d:%02d:%02d " % [time.hour, time.minute, time.second]
+15 -34
View File
@@ -5,39 +5,20 @@ extends Control
## Everything that is not part of a quiz lives here, so other views can be added next
## to the quiz later.
@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
## 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())
@@ -50,8 +31,8 @@ func _ready() -> void:
top_bar.about_pressed.connect(show_about)
top_bar.qr_pressed.connect(show_qr)
sync_popup.connect("sync_started", _on_sync_started)
sync_popup.connect("sync_finished", _on_sync_finished)
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)
@@ -63,9 +44,9 @@ func _input(event: InputEvent) -> void:
if event.alt_pressed && event.keycode == KEY_ENTER:
GameLog.debug("Alt + Enter pressed")
Settings.fullscreen = !Settings.fullscreen
if Settings.fullscreen == true:
if Settings.fullscreen:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
if Settings.fullscreen == false:
if !Settings.fullscreen:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
if event.alt_pressed && event.keycode == KEY_K:
shortcuts_popup.toggle()
+2 -2
View File
@@ -3,12 +3,12 @@ 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://SongRow.tscn")
@onready
var _rows: VBoxContainer = %MusicList
@onready var _rows: VBoxContainer = %MusicList
## Shows the songs in the playlist again, so it shows their latest state.
+41 -43
View File
@@ -3,76 +3,59 @@ 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
# LOC 12/6-24: 170
@onready
var play_button: TextureButton = %PlayTextureButton
@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
signal update_song_list
signal play_next_song
var play_icon: Texture = preload("res://icons/play_icon_light.svg")
var pause_icon: Texture = preload("res://icons/pause_icon_light.svg")
var songs: Array = []
var is_changing: bool = false
var playback_position: float
var stream: AudioStream
var song_finished: bool = false
signal update_song_list
signal play_next_song
@onready var play_button: TextureButton = %PlayTextureButton
@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:
play_button.pressed.connect(play_or_pause)
restart_button.pressed.connect(restart)
progress_slider.drag_started.connect(_on_drag_started)
progress_slider.drag_ended.connect(_on_drag_ended)
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:
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()
if stream != null:
music_time_label.text = format_text(progress_slider.value, stream.get_length())
else:
music_time_label.text = format_text(progress_slider.value, 0.0)
func format_time(time: float) -> String:
var total_seconds: int = roundi(time)
@warning_ignore("integer_division")
return "%02d:%02d" % [total_seconds / 60, total_seconds % 60]
func format_text(part: float, total: float) -> String:
if (Settings.hide_beginning && part < Settings.hide_for_seconds) && Settings.hide_length:
return "??:?? / ??:??"
@@ -83,6 +66,7 @@ func format_text(part: float, total: float) -> String:
else:
return format_time(part) + " / " + format_time(total)
func play_or_pause() -> void:
if song_finished:
restart()
@@ -100,11 +84,13 @@ func play_or_pause() -> void:
else:
pause()
func pause() -> void:
audio_player.stream_paused = true
playback_position = audio_player.get_playback_position()
play_button.texture_normal = play_icon
func restart() -> void:
audio_player.stop()
audio_player.stream_paused = false
@@ -114,7 +100,8 @@ func restart() -> void:
audio_player.seek(playback_position)
play_button.texture_normal = pause_icon
audio_player.play()
func _on_finished() -> void:
play_button.texture_normal = play_icon
song_finished = true
@@ -122,26 +109,31 @@ func _on_finished() -> void:
play_next_song.emit()
if Settings.auto_repeat_song:
restart()
func _on_drag_started() -> void:
is_changing = true
func _on_drag_ended(_changed: bool) -> void:
audio_player.seek(progress_slider.value)
playback_position = progress_slider.value
is_changing = false
func seek(new_position: float) -> void:
progress_slider.value += new_position
is_changing = true
audio_player.seek(progress_slider.value)
playback_position = progress_slider.value
is_changing = false
func change_volume(value: float) -> void:
volume_slider.value += value
volume_slider.change_volume(volume_slider.value)
func _on_point_triggered(point: String) -> void:
if point == "first":
var value: int = randi_range(0, 10)
@@ -153,11 +145,13 @@ func _on_point_triggered(point: String) -> void:
play_sound(preload("res://sounds/intro_short.mp3"))
elif point == "match":
play_sound(preload("res://sounds/sound0.mp3"))
func play_sound_effect(sound_effect_name: AudioStream) -> void:
sound_effect_player.stream = sound_effect_name
sound_effect_player.play()
func play_sound(sound_name: AudioStream) -> void:
sound_player.stream = sound_name
audio_player.stop()
@@ -165,9 +159,8 @@ func play_sound(sound_name: AudioStream) -> void:
song_finished = true
play_button.texture_normal = play_icon
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:
audio_player.stream = song
audio_player.play()
@@ -183,6 +176,7 @@ func play_song(song: AudioStream) -> void:
progress_slider.tick_count = round(stream.get_length() / 60)
music_time_label.text = format_text(progress_slider.value, stream.get_length())
func play_song_object(song_object_no: int) -> void:
GameLog.debug("play_song_object")
if audio_player.is_playing():
@@ -210,6 +204,7 @@ func play_song_object(song_object_no: int) -> void:
Playlist.song_is_playing(song_object_no)
update_song_list.emit()
func get_sound_test_song() -> void:
var test_sound: Callable = func test_sound(body: PackedByteArray) -> void:
var sound: AudioStream = AudioStreamMP3.new()
@@ -220,12 +215,14 @@ func get_sound_test_song() -> void:
##### 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 load_mp3(_path: String) -> AudioStream:
var file: FileAccess = FileAccess.open(_path, FileAccess.READ)
var sound: AudioStream = AudioStreamMP3.new()
sound.data = file.get_buffer(file.get_length())
return sound
func play_local_song() -> void:
if songs:
local_path = songs[0]
@@ -233,5 +230,6 @@ func play_local_song() -> void:
GameLog.debug(FileAccess.file_exists(local_path))
play_sound(load_mp3(local_path))
func sound_test_local() -> void:
play_sound(preload("res://sounds/01. Opening.mp3"))
+1
View File
@@ -7,6 +7,7 @@ extends Node
## without a running server.
## Sends the actual HTTP requests. Swap it out in tests.
var transport: HttpTransport
+22 -12
View File
@@ -1,19 +1,28 @@
class_name PlayerNameField
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
## Emitted when Alt+A is pressed.
signal close_pressed
@export
var LIMIT: int = 10
var current_text = ''
var cursor_line = 0
var cursor_column = 0
## The longest name a player can have.
const MAX_NAME_LENGTH: int = 10
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)
func _input(event):
func _input(event: InputEvent) -> void:
if event is InputEventKey and event.pressed:
if event.alt_pressed && event.keycode == KEY_A:
close_pressed.emit()
@@ -21,15 +30,16 @@ func _input(event):
if event.keycode == KEY_ENTER || event.keycode == KEY_KP_ENTER:
enter_key_pressed.emit()
func _on_text_changed():
func _on_text_changed() -> void:
if text.contains("\n"):
text = text.replace("\n", "")
var new_text : String = text
if new_text.length() > LIMIT:
var new_text: String = text
if new_text.length() > MAX_NAME_LENGTH:
text = current_text
# 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
set_caret_line(cursor_line)
set_caret_column(cursor_column)
+23 -19
View File
@@ -1,40 +1,42 @@
extends RefCounted
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 match_point_triggerd
signal winner_triggerd
signal first_point_triggered
signal match_point_triggered
signal winner_triggered
signal point_given_sound
signal player_point_given
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.
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)
func _init(new_name: String) -> void:
player_name = new_name
player_score = 0
## Returns a random version 4 UUID, e.g. "3f2b8c1e-9a4d-4e7f-b2c6-5d8e1f0a7b93".
static func _generate_uuid() -> String:
var bytes: PackedByteArray = Crypto.new().generate_random_bytes(16)
bytes[6] = (bytes[6] & 0x0f) | 0x40
bytes[8] = (bytes[8] & 0x3f) | 0x80
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:
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,
# so the more important event is checked first
if player_score == Settings.winning_score:
winner_triggerd.emit()
winner_triggered.emit()
elif player_score == Settings.winning_score - 1:
match_point_triggerd.emit()
match_point_triggered.emit()
elif first_point:
first_point_triggerd.emit()
first_point_triggered.emit()
else:
point_given_sound.emit()
func minus_point() -> void:
if player_score > 0:
player_score -= 1
@@ -61,6 +64,7 @@ func minus_point() -> void:
if player_score == 0:
is_first_point = true
func reset_points() -> void:
player_score = 0
is_first_point = true
+19 -19
View File
@@ -1,21 +1,6 @@
class_name PlayerRow
extends Control
@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
signal change_character_clicked
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.
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:
add_button.pressed.connect(add_point)
add_button.set_shortcut(create_button_shortcut(list_index, false))
@@ -37,42 +28,51 @@ func _ready() -> void:
update_score()
set_player_character()
func _process(delta: float) -> void:
remove_player.visible = Settings.edit_players
func create_button_shortcut(index: int, ctrl_pressed: bool) -> Shortcut:
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.alt_pressed = true
button_event.ctrl_pressed = ctrl_pressed
button_shortcut.events.append(button_event)
return button_shortcut
func add_point() -> void:
player_object.add_point()
update_score()
func minus_point() -> void:
player_object.minus_point()
update_score()
func change_character() -> void:
change_character_clicked.emit()
func new_character_selected(character_texture: ImageTexture) -> void:
GameLog.debug("Back in player list with new")
player_object.character = character_texture
set_player_character()
func set_player_character() -> void:
character.custom_minimum_size = Vector2(80, 40)
character.ignore_texture_size = true
character.stretch_mode = TextureButton.STRETCH_KEEP_ASPECT_CENTERED
character.texture_normal = player_object.character
func update_score() -> void:
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:
player_name_field.text = player_object.player_name + ": " + str(player_object.player_score)
+12 -18
View File
@@ -7,6 +7,7 @@ extends PanelContainer
## 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].
@@ -17,26 +18,17 @@ signal change_character_requested(row: PlayerRow, player_name: String)
var _player_row: PackedScene = preload("res://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: TextEdit = %PlayerNameField
@onready
var players_list_container: VBoxContainer = %PlayersListVBoxContainer
@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.connect("enter_key_pressed", 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.
@@ -51,7 +43,8 @@ func add_players() -> void:
## 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:
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)
@@ -72,8 +65,9 @@ func reload() -> void:
new_player.player_object = player_object
new_player.list_index = counter
new_player.connect("change_character_clicked", _on_change_character_clicked.bind(new_player, player_object.player_name))
new_player.connect("player_removed", _on_player_removed.bind(player_object))
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
+26 -3
View File
@@ -1,26 +1,32 @@
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 latest_played_song: int = 0
var currently_playing_song: int = -1
var first_song_downloaded: bool = false
var first_song_played: bool = false
var number_of_tracks_to_preload: int = 1
var is_song_downloading: bool = false
func get_next_song() -> SongObject:
return song_object_array[latest_played_song]
func set_latest_played_song() -> void:
latest_played_song += 1
if latest_played_song > song_object_array.size() - 1:
latest_played_song = song_object_array.size() - 1
func update_last_played_song() -> void:
latest_played_song = song_object_array.size()
func get_preloaded_songs() -> int:
var number_of_tracks_preloaded: int = 0
for song: SongObject in song_object_array:
@@ -28,9 +34,11 @@ func get_preloaded_songs() -> int:
number_of_tracks_preloaded += 1
return number_of_tracks_preloaded
func should_preload_more_songs() -> bool:
return number_of_tracks_to_preload > get_preloaded_songs()
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
if song_no < 0 || song_no >= song_object_array.size():
@@ -41,9 +49,11 @@ func song_is_answered(song_no: int = currently_playing_song) -> void:
GameLog.debug("add to stats: ", song_no)
MusicServerClient.mark_song_played(song_no)
func add_to_playlist(new_song_object: SongObject) -> void:
song_object_array.append(new_song_object)
func get_playlist() -> Array[SongObject]:
if Settings.is_debug:
return song_object_array
@@ -54,46 +64,59 @@ func get_playlist() -> Array[SongObject]:
temp_list.append(s)
return temp_list
func reset_playlist() -> void:
currently_playing_song = -1
latest_played_song = 0
clear_song_list()
func clear_song_list() -> void:
song_object_array.clear()
func unset_is_playing() -> void:
for song_object: SongObject in song_object_array:
song_object.is_playing = false
func set_currently_playing_song(value: int) -> void:
currently_playing_song = value
func get_current_soundtrack_title() -> String:
return song_object_array[currently_playing_song].get_soundtrack_title()
func get_current_song_title() -> String:
return song_object_array[currently_playing_song].get_song_title()
func add_point(player_id: String) -> void:
song_object_array[currently_playing_song].add_point(player_id)
func remove_point(player_id: String) -> void:
song_object_array[currently_playing_song].remove_point(player_id)
## Removes a player's points from every song.
func remove_player_from_list(player_id: String) -> void:
for song_object: SongObject in song_object_array:
song_object.remove_point(player_id)
func get_song(song_no: int) -> AudioStream:
return song_object_array[song_no].song
func add_song_to_song_object(song_no: int, sound: AudioStream) -> void:
song_object_array[song_no].song = sound
func song_has_played(song_no: int = latest_played_song) -> void:
song_object_array[song_no].has_played = true
func song_is_playing(song_no: int = latest_played_song) -> void:
song_object_array[song_no].is_playing = true
+7 -9
View File
@@ -1,27 +1,25 @@
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
var qr_panel: Panel = $QRPanel
@onready
var qr_code_rect: QRCodeRect = %QRCodeRect
@onready
var url_label: Label = %URLLabel
@onready var qr_panel: Panel = $QRPanel
@onready var qr_code_rect: QRCodeRect = %QRCodeRect
@onready var url_label: Label = %URLLabel
func _ready() -> void:
qr_code_rect.light_module_color = Color.WHITE
qr_code_rect.dark_module_color = Color.BLACK
qr_code_rect.quiet_zone_size = 4
# Generate QR code for the search URL
var url: String = Settings.default_path + "/search"
qr_code_rect.data = url
url_label.text = qr_code_rect.data
GameLog.debug("QR code generated for: ", url)
func open() -> void:
super()
# Regenerate QR code in case settings changed
+29 -54
View File
@@ -9,54 +9,13 @@ extends Control
## 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)
@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
var songs: Array= []
var songs: Array = []
var current_player_row: PlayerRow
## True while statistics are off because a player won, until points are reset.
@@ -65,6 +24,21 @@ 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())
@@ -83,14 +57,14 @@ func _ready() -> void:
song_loader.song_list_loaded.connect(update_song_list)
song_loader.song_added.connect(_on_song_added)
character_select_popup.connect("new_new_character_selected", _on_new_character_selected)
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.connect("play_next_song", play_next_song)
music_player.connect("update_song_list", update_song_list)
settings_popup.connect("server_changed", _on_server_changed)
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)
@@ -192,7 +166,7 @@ func update_song_list() -> void:
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 == false:
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:
@@ -233,12 +207,12 @@ func show_settings() -> void:
# 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.connect("first_point_triggerd", music_player._on_point_triggered.bind("first"))
new_player_object.connect("match_point_triggerd", music_player._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))
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:
@@ -293,6 +267,7 @@ func _on_new_character_selected(character_texture: ImageTexture) -> void:
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()
+8 -5
View File
@@ -1,9 +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 = 0;
var max_value: float = 0.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
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)
+48 -38
View File
@@ -1,65 +1,65 @@
class_name SearchPopup
extends BasePopup
@onready
var search_list: VBoxContainer = %SearchResults
@onready
var search_panel: Panel = %SearchPanel
@onready
var clear_button: Button = $ClearButton
@onready
var search_bar: TextEdit = $Searchbar
## Searches every soundtrack on the server as you type. Matches ignore case, spaces and
## special characters, and also find the letters in order with others in between.
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 = []
# 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 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:
clear_button.pressed.connect(clear)
search_bar.grab_focus()
search_bar.text_changed.connect(search)
visibility_changed.connect(focus)
func focus() -> void:
if self.visible == true:
if visible:
search_bar.grab_focus()
clear()
func close() -> void:
clear()
super()
func search() -> void:
GameLog.debug(search_bar.text)
var search_text: String = search_bar.text
if search_text == "":
show_all_soundtracks()
return
perform_search(search_text)
func show_all_soundtracks() -> void:
# Hide all visible labels and add to pool
for child: Label in search_list.get_children():
if child.visible:
child.visible = false
label_pool.append(child)
# Show all soundtracks in order
for soundtrack: String in soundtracks:
add_soundtrack_reuse(soundtrack)
func perform_search(search_text: String) -> void:
# Hide all visible labels and add to pool
for child: Label in search_list.get_children():
@@ -70,6 +70,7 @@ func perform_search(search_text: String) -> void:
for soundtrack: String in find_matches(search_text):
add_soundtrack_reuse(soundtrack)
## Returns the soundtracks matching [param search_text], best matches first: plain
## case-insensitive matches, then matches ignoring spaces and special characters,
## then matches with the letters in order but not next to each other.
@@ -81,25 +82,25 @@ func find_matches(search_text: String) -> Array[String]:
var matches: Array[String] = []
# Pass 1: Exact case-insensitive contains match on lowercase versions
for i in range(precomputed_soundtracks.size()):
var st_data = precomputed_soundtracks[i]
for i: int in range(precomputed_soundtracks.size()):
var st_data: Dictionary = precomputed_soundtracks[i]
if st_data["lowercase"].contains(search_text_lower):
if not st_data["original"] in seen:
seen[st_data["original"]] = true
matches.append(st_data["original"])
# Pass 2: Contains match on cleaned terms
for i in range(precomputed_soundtracks.size()):
var st_data = precomputed_soundtracks[i]
for i: int in range(precomputed_soundtracks.size()):
var st_data: Dictionary = precomputed_soundtracks[i]
if st_data["cleaned"].contains(clean_search_text):
if not st_data["original"] in seen:
seen[st_data["original"]] = true
matches.append(st_data["original"])
# Pass 3: Regex match on cleaned terms
var regex = compile_regex_cached(clean_search_text)
for i in range(precomputed_soundtracks.size()):
var st_data = precomputed_soundtracks[i]
var regex: RegEx = compile_regex_cached(clean_search_text)
for i: int in range(precomputed_soundtracks.size()):
var st_data: Dictionary = precomputed_soundtracks[i]
if regex.search(st_data["cleaned"]):
if not st_data["original"] in seen:
seen[st_data["original"]] = true
@@ -107,30 +108,35 @@ func find_matches(search_text: String) -> Array[String]:
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:
if not search_term in regex_cache:
var regex = RegEx.new()
var regText: String = ".*"
var regex: RegEx = RegEx.new()
var regex_text: String = ".*"
for letter: String in search_term:
# Escape regex metacharacters so searches like "Q*bert" or "(Remix)" work
if "\\^$.|?*+()[]{}".contains(letter):
regText += "\\"
regText += letter + ".*"
regex.compile(regText)
regex_text += "\\"
regex_text += letter + ".*"
regex.compile(regex_text)
regex_cache[search_term] = regex
return regex_cache[search_term]
func add_soundtrack(soundtrack: String) -> void:
# Backward compatibility - delegates to reuse version
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:
var label: Label
if label_pool.size() > 0:
@@ -146,6 +152,7 @@ func add_soundtrack_reuse(soundtrack: String) -> void:
search_list.add_child(label)
label.visible = true
func check_if_soundtrack_exists(soundtrack: String) -> bool:
var soundtrack_exists: bool = false
for child: Label in search_list.get_children():
@@ -153,9 +160,10 @@ func check_if_soundtrack_exists(soundtrack: String) -> bool:
soundtrack_exists = true
return soundtrack_exists
func get_list_of_soundtracks() -> void:
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:
# Reuse existing labels: hide all and add to pool
for child: Label in search_list.get_children():
@@ -168,6 +176,7 @@ func get_list_of_soundtracks() -> void:
GameLog.warning("Unexpected data")
MusicServerClient.fetch_all_soundtracks_ordered(handle_soundtracks)
## Replaces the searchable soundtracks and precomputes their search forms.
func set_soundtracks(list: Array) -> void:
soundtracks = []
@@ -180,6 +189,7 @@ func set_soundtracks(list: Array) -> void:
"cleaned": clean_term(soundtrack)
})
func clear() -> void:
search_bar.text = ""
show_all_soundtracks()
+4 -4
View File
@@ -1,14 +1,14 @@
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
var default_path: String = "https://music.sanplex.xyz"
var selected_server: int = 0
var is_local: bool = false
var is_debug: bool = false
var stop_after_current: bool = true
var auto_repeat_song: 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_length: bool = false
var hide_ticks: bool = false
var player_array: Array[PlayerObject]
var edit_players: 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.
var version: String = ProjectSettings.get_setting("application/config/version", "")
func delete_children(node: Node) -> void:
for n: Node in node.get_children():
node.remove_child(n)
+62 -79
View File
@@ -1,82 +1,40 @@
class_name SettingsPopup
extends BasePopup
## The settings: how songs are shown and played, the winning score, the song cache,
## which MusicServer to use and fullscreen.
@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
## Emitted when another MusicServer is picked.
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:
fullscreen_button.button_pressed = Settings.fullscreen
select_server_button.selected = Settings.selected_server
stop_after_current_button.pressed.connect(stop_after_current)
hide_next_track_button.pressed.connect(hide_next_track)
add_to_database_button.pressed.connect(add_to_database)
@@ -94,7 +52,7 @@ func _ready() -> void:
increase_hide_for_seconds_button.pressed.connect(increase_hide_for_seconds)
fullscreen_button.pressed.connect(fullscreen)
local_button.pressed.connect(local_play)
stop_after_current_button.button_pressed = Settings.stop_after_current
hide_next_track_button.button_pressed = Settings.hide_next_track
add_to_database_button.button_pressed = Settings.add_to_stats
@@ -114,95 +72,117 @@ func _ready() -> void:
# 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 popup, e.g. winning turns statistics off
func _on_visibility_changed() -> void:
if visible:
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:
Settings.fullscreen = !Settings.fullscreen
if Settings.fullscreen == true:
if Settings.fullscreen:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
if Settings.fullscreen == false:
if !Settings.fullscreen:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
func local_play() -> void:
Settings.play_local = !Settings.play_local
func stop_after_current() -> void:
Settings.stop_after_current = !Settings.stop_after_current
func hide_next_track() -> void:
Settings.hide_next_track = !Settings.hide_next_track
func add_to_database() -> void:
Settings.add_to_stats = !Settings.add_to_stats
func low_played() -> void:
Settings.use_low_played_mode = !Settings.use_low_played_mode
func hide_beginning() -> void:
Settings.hide_beginning = !Settings.hide_beginning
update_hide_for_seconds_enabled()
func update_hide_for_seconds_enabled() -> void:
var enabled: bool = Settings.hide_beginning
%HideForSecondsHBoxContainer.visible = enabled
func hide_length() -> void:
Settings.hide_length = !Settings.hide_length
func hide_ticks() -> void:
Settings.hide_ticks = !Settings.hide_ticks
func lower_winning_score() -> void:
Settings.winning_score = max(Settings.winning_score - 1, 1)
score_label.text = str(Settings.winning_score)
func increase_winning_score() -> void:
Settings.winning_score += 1
score_label.text = str(Settings.winning_score)
func set_winning_score(new_score: int) -> void:
Settings.winning_score = new_score
score_label.text = str(Settings.winning_score)
func lower_cache() -> void:
Playlist.number_of_tracks_to_preload -= 1
if Playlist.number_of_tracks_to_preload < 1:
Playlist.number_of_tracks_to_preload = 1
cache_label.text = str(Playlist.number_of_tracks_to_preload)
func increase_cache() -> void:
Playlist.number_of_tracks_to_preload += 1
cache_label.text = str(Playlist.number_of_tracks_to_preload)
func lower_inspiration_speed() -> void:
Settings.inspiration_list_speed -= 1
if Settings.inspiration_list_speed < 1:
Settings.inspiration_list_speed = 1
inspiration_speed_label.text = str(Settings.inspiration_list_speed)
func increase_inspiration_speed() -> void:
Settings.inspiration_list_speed += 1
inspiration_speed_label.text = str(Settings.inspiration_list_speed )
func lower_hide_for_seconds() -> void:
Settings.hide_for_seconds -= 1.0
if Settings.hide_for_seconds < 0:
Settings.hide_for_seconds = 0
hide_for_seconds_label.text = str(Settings.hide_for_seconds)
func increase_hide_for_seconds() -> void:
Settings.hide_for_seconds += 1.0
if Settings.hide_for_seconds > 10.0:
Settings.hide_for_seconds = 10.0
hide_for_seconds_label.text = str(Settings.hide_for_seconds)
func select_server(new_server: int) -> void:
GameLog.debug("select_server")
Settings.default_path = select_server_button.get_item_text(new_server)
@@ -210,9 +190,11 @@ func select_server(new_server: int) -> void:
GameLog.debug("Settings.default_path: " + Settings.default_path)
GameLog.debug("Settings.selected_server: " + str(Settings.selected_server))
GameLog.debug("new_server: " + str(new_server))
GameLog.debug("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()
func set_selected_server(new_server: int) -> void:
GameLog.debug("set_selected_server")
select_server_button.select(new_server)
@@ -221,5 +203,6 @@ func set_selected_server(new_server: int) -> void:
GameLog.debug("Settings.default_path: " + Settings.default_path)
GameLog.debug("Settings.selected_server: " + str(Settings.selected_server))
GameLog.debug("new_server: " + str(new_server))
GameLog.debug("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()
+10 -14
View File
@@ -1,11 +1,6 @@
class_name ShortcutsPopup
extends BasePopup
@onready
var shortcuts_vbox: VBoxContainer = %shortcutsVBox
@onready
var shortcuts_panel: Panel = %ShortcutsPanel
## Lists the keyboard shortcuts. Opened with Alt+K or the Shortcuts button.
var shortcuts_dic: Dictionary = {
"Alt + S": "Search",
@@ -26,14 +21,17 @@ var shortcuts_dic: Dictionary = {
"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.302, 0.302, 0.302)
#Color(0.259, 0.259, 0.259)
func _ready() -> void:
shortcuts_dic.sort()
Settings.delete_children(shortcuts_vbox)
var title: Label = Label.new()
title.add_theme_font_size_override("font_size", 20)
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
@@ -45,28 +43,26 @@ func _ready() -> void:
var panel: PanelContainer = PanelContainer.new()
var hbox: HBoxContainer = HBoxContainer.new()
hbox.alignment = BoxContainer.ALIGNMENT_CENTER
var label: Label = Label.new()
label.add_theme_font_size_override("font_size", 20)
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
label.text = shortcut
label.autowrap_mode = TextServer.AUTOWRAP_WORD
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
var label2: Label = Label.new()
label2.add_theme_font_size_override("font_size", 20)
label2.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
label2.text = shortcuts_dic[shortcut]
label2.autowrap_mode = TextServer.AUTOWRAP_WORD
label2.size_flags_horizontal = Control.SIZE_EXPAND_FILL
if count % 2 == 0:
var new_stylebox_normal: Resource = panel.get_theme_stylebox("panel").duplicate()
new_stylebox_normal.bg_color = Color(0.18, 0.18, 0.18)
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(label2)
panel.add_child(hbox)
+2 -1
View File
@@ -8,6 +8,7 @@ extends Node
## 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.
@@ -21,7 +22,7 @@ var retry_seconds: float = 5.0
## 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) -> void:
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:
+12 -8
View File
@@ -3,53 +3,57 @@ extends RefCounted
class_name SongObject
var song_number: int
var song_title: String
var soundtrack_title: String
## Ids of the players who got a point for this song.
var players_given_point: Array[String] = []
var is_answered: bool = false
var has_played: bool = false
var is_playing: bool = false
var song: AudioStream
func get_soundtrack_title() -> String:
if is_answered:
return soundtrack_title
else:
return "????????"
func get_song_title() -> String:
if is_answered:
return song_title
else:
return "??????"
func get_song_info() -> String:
if Settings.is_debug:
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:
var format_string: String = "%d. %s - %s"
return format_string % [(song_number + 1), soundtrack_title, song_title]
else:
return "??? - ???"
func add_point(player_id: String) -> void:
GameLog.debug("add_point")
if !players_given_point.has(player_id):
players_given_point.append(player_id)
func remove_point(player_id: String) -> void:
GameLog.debug("remove_point")
players_given_point.erase(player_id)
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"
return format_string % [(song_number), soundtrack_title, song_title, is_answered, has_played, is_playing]
var format_string: String = "song_number: %d | soundtrack_title: %s | song_title: %s" \
+ " | is_answered: %s | has_played: %s | is_playing: %s"
return format_string % [song_number, soundtrack_title, song_title, is_answered, has_played,
is_playing]
+7 -20
View File
@@ -1,33 +1,19 @@
class_name SongRow
extends PanelContainer
@onready
var song_list_label: Label = %SongListLabel
@onready
var song_list_players: HBoxContainer = %SongListPlayers
## One song in the music list: its number, soundtrack and title, and a marker for
## each player who got a point for it.
var song_object: SongObject
#Color(0.18, 0.18, 0.18)
#Color(0.302, 0.302, 0.302)
#Color(0.259, 0.259, 0.259)
@onready var song_list_label: Label = %SongListLabel
@onready var song_list_players: HBoxContainer = %SongListPlayers
# Called when the node enters the scene tree for the first time.
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()
update_players()
func set_label() -> void:
if song_object != null:
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.text = song_object.get_song_info()
func update_players() -> void:
Settings.delete_children(song_list_players)
if song_object != null:
+80 -92
View File
@@ -1,79 +1,49 @@
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
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
## Emitted when the server has started syncing.
signal sync_started
## Emitted when the sync is done and its result is shown.
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:
sync_timer.timeout.connect(_on_sync_timeout)
restart_sync_button.pressed.connect(start_sync)
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:
vbox.visible = true
make_list(data, vbox, header_text)
@@ -83,9 +53,10 @@ func show_section_if_has_data(data: Variant, vbox: VBoxContainer, separator: VSe
separator.visible = false
return false
func start_sync() -> void:
GameLog.debug("start_sync")
self.visible = true
visible = true
if has_synced:
# Already synced, just show the results
show_sync_result()
@@ -98,6 +69,7 @@ func start_sync() -> void:
if !Settings.currently_syncing:
MusicServerClient.start_sync(sync_request_sent)
func _on_sync_timeout() -> void:
GameLog.debug("_on_sync_timeout!")
var get_progress: Callable = func get_progress(data_received: Dictionary) -> void:
@@ -111,38 +83,46 @@ func _on_sync_timeout() -> void:
on_sync_finished(data_received)
MusicServerClient.fetch_sync_progress(get_progress)
func on_sync_finished(data_received: Dictionary) -> void:
has_synced = true
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
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
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
Settings.delete_children(soundtracks_changed_title_vbox)
make_header_label(soundtracks_changed_title_vbox, "Changed Title")
var row: int = 0
for old_soundtrack_title: String in data_received.soundtracks_changed_title:
var new_soundtrack_title: String = data_received.soundtracks_changed_title[old_soundtrack_title]
for old_soundtrack_title: String in changed_titles:
var new_soundtrack_title: String = changed_titles[old_soundtrack_title]
soundtracks_changed_title_vbox.add_child(make_soundtrack_row(
old_soundtrack_title + "\nchanged title to\n" + new_soundtrack_title, row))
row += 1
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
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
# The server's name for the errors it caught
if data_received.catched_errors != null && data_received.catched_errors.size() > 0:
catched_errors_vbox.visible = true
make_list(data_received.catched_errors, catched_errors_vbox, "Errors")
caught_errors_vbox.visible = true
make_list(data_received.catched_errors, caught_errors_vbox, "Errors")
# Separators go only between visible columns, not after the last one
var visible_sections: Array = []
if soundtracks_added_vbox.visible:
@@ -155,11 +135,11 @@ func on_sync_finished(data_received: Dictionary) -> void:
visible_sections.append("changed_content")
if soundtracks_removed_vbox.visible:
visible_sections.append("removed")
# Show section separators only if there's a visible section after them
for i in range(visible_sections.size()):
var section = visible_sections[i]
var separator_map = {
for i: int in range(visible_sections.size()):
var section: String = visible_sections[i]
var separator_map: Dictionary = {
"added": soundtracks_added_separator,
"readded": soundtracks_readded_separator,
"changed_title": soundtracks_changed_title_separator,
@@ -171,10 +151,11 @@ func on_sync_finished(data_received: Dictionary) -> void:
separator_map[section].visible = true
else:
separator_map[section].visible = false
if !changes:
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
sync_timer.stop()
GameLog.debug("sync finished")
@@ -182,9 +163,11 @@ func on_sync_finished(data_received: Dictionary) -> void:
restart_sync_button.visible = true
sync_finished.emit()
func list_visible(vbox: VBoxContainer, seperator: VSeparator) -> void:
func list_visible(vbox: VBoxContainer, separator: VSeparator) -> void:
vbox.visible = true
seperator.visible = true
separator.visible = true
func make_header_label(vbox: VBoxContainer, text: String) -> void:
var label: Label = Label.new()
@@ -194,6 +177,7 @@ func make_header_label(vbox: VBoxContainer, text: String) -> void:
label.text = text
vbox.add_child(label)
# Items alternate between white and orange text as well as background shade
func make_soundtrack_row(text: String, row: int) -> ListRow:
var list_row: ListRow = ListRow.create(text, row, 18, HORIZONTAL_ALIGNMENT_CENTER)
@@ -201,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))
return list_row
func reset_sync_result() -> void:
sync_progress_bar.value = 0
sync_time.text = ""
@@ -216,7 +201,8 @@ func reset_sync_result() -> void:
soundtracks_changed_content_separator.visible = false
soundtracks_removed_vbox.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:
Settings.delete_children(vbox)
@@ -224,16 +210,18 @@ func make_list(list: Array, vbox: VBoxContainer, text: String) -> void:
for row: int in list.size():
vbox.add_child(make_soundtrack_row(list[row], row))
func show_sync_result() -> void:
sync_title_label.text = "Sync Result"
progress_hbox.visible = false
sync_hbox.visible = true
restart_sync_button.visible = true
func show_sync_progress() -> void:
sync_title_label.text = "Sync in Progress"
progress_hbox.visible = true
no_changes_label.visible = false
sync_hbox.visible = false
catched_errors_vbox.visible = false
caught_errors_vbox.visible = false
restart_sync_button.visible = false
+9
View File
@@ -87,6 +87,7 @@ static func prepare_autoloads() -> void:
func run(on_state: Callable) -> void:
# The debug build lowers the winning score on startup; keep it out of reach
Settings.winning_score = 20
_stop_caret_blink(_main)
await _settle()
await on_state.call("startup", null)
@@ -194,6 +195,14 @@ func _call(method: String, args: Array = []) -> Variant:
return _main.callv(method, args)
# A blinking text cursor would be shown in some pictures and not in others
func _stop_caret_blink(node: Node) -> void:
if node is TextEdit || node is LineEdit:
node.caret_blink = false
for child: Node in node.get_children():
_stop_caret_blink(child)
func _server() -> FakeHttpTransport:
return MusicServerClient.transport
+8 -2
View File
@@ -58,9 +58,14 @@ const RENAMED_SCENES: Dictionary = {
"Player": "PlayerRow",
}
## Nodes renamed inside a popup: old path from the main window, and new path.
const SYNC_COLUMNS: String = "SyncPopup/SyncPanel/ScrollContainer/SyncVBoxContainer"
## Nodes renamed inside a popup: the path after the moves above, and the new path.
const RENAMED_PATHS: Dictionary = {
"SearchPopup/ScrollContainer/VBoxContainer": "SearchPopup/ScrollContainer/SearchResults",
SYNC_COLUMNS + "/CatchedErrorsVBox": SYNC_COLUMNS + "/CaughtErrorsVBox",
SYNC_COLUMNS + "/CaughtErrorsVBox/CatchedErrorsLabel":
SYNC_COLUMNS + "/CaughtErrorsVBox/CaughtErrorsLabel",
}
## How far a node may be from its old place, in pixels.
@@ -102,9 +107,10 @@ static func new_path(old_path: String) -> String:
var first: String = parts[0]
parts[0] = MOVED.get(first, first)
var path: String = "/".join(parts)
# In order, so a node inside a renamed node is found after its parent's rename
for renamed: String in RENAMED_PATHS:
if path == renamed || path.begins_with(renamed + "/"):
return RENAMED_PATHS[renamed] + path.trim_prefix(renamed)
path = RENAMED_PATHS[renamed] + path.trim_prefix(renamed)
return path
+2 -1
View File
@@ -105,7 +105,8 @@ func test_rows_follow_player_order_and_numbering() -> void:
_add("Marle")
_add("Lucca")
assert_array(_rows().map(func(row: PlayerRow) -> String: return row.player_object.player_name)) \
assert_array(_rows().map(
func(row: PlayerRow) -> String: return row.player_object.player_name)) \
.contains_exactly(["Crono", "Marle", "Lucca"])
assert_array(_rows().map(func(row: PlayerRow) -> int: return row.list_index)) \
.contains_exactly([0, 1, 2])
+15 -8
View File
@@ -9,6 +9,12 @@ const MAIN_WINDOW_CHILDREN: Array[String] = [
"ShortcutsPopup", "StatisticsPopupPanel", "AboutPopup", "LogPopup",
]
## The quiz buttons that are disabled while the server syncs.
const LOCKED_BY_SYNC: Array[String] = [
"QuizToolbar/SoundTestButton", "QuizToolbar/ResetPlaylistButton",
"SongControls/ShowAnswerButton", "SongControls/NextButton",
]
const TOP_BAR_CHILDREN: Array[String] = [
"TopPanelContainer", "Title", "SyncButton", "SearchButton", "StatisticsButton",
"ShortcutsButton", "AboutButton", "QRButton",
@@ -130,7 +136,8 @@ func test_points_from_the_player_list_reach_the_song_and_log() -> void:
(_player_row(crono).get_node("HBoxContainer/AddPoint") as BaseButton).pressed.emit()
assert_array(Playlist.song_object_array[0].players_given_point).contains_exactly([crono.id])
assert_int((_window.get_node("%LogPopup").get_node("%LogVBoxContainer") as Node).get_child_count()) \
var log_rows: Node = _window.get_node("%LogPopup").get_node("%LogVBoxContainer")
assert_int(log_rows.get_child_count()) \
.is_equal(1)
@@ -143,12 +150,13 @@ func test_character_button_in_the_player_list_picks_a_new_character() -> void:
assert_bool(character_select.visible).is_true()
var texture: ImageTexture = ImageTexture.create_from_image(
Image.create(4, 4, false, Image.FORMAT_RGBA8))
character_select.new_new_character_selected.emit(texture)
character_select.character_selected.emit(texture)
assert_object(crono.character).is_same(texture)
func test_sound_test_button_fetches_the_sound_test_song() -> void:
_server.respond_bytes("/music/soundTest", FileAccess.get_file_as_bytes("res://tests/data/short.mp3"))
_server.respond_bytes("/music/soundTest",
FileAccess.get_file_as_bytes("res://tests/data/short.mp3"))
await _click_at(_center(_quiz.get_node("QuizToolbar/SoundTestButton")))
@@ -166,7 +174,8 @@ func test_loaded_song_list_shows_in_the_music_list() -> void:
_quiz.song_loader.load_song_list()
var rows: Array[Node] = _quiz.get_node("MusicListPanel/ScrollContainer/MusicList").get_children()
var rows: Array[Node] = _quiz.get_node("MusicListPanel/ScrollContainer/MusicList") \
.get_children()
assert_array(rows.map(func(row: SongRow) -> int: return row.song_object.song_number)) \
.contains_exactly([0, 1])
@@ -203,8 +212,7 @@ func test_sync_disables_quiz_buttons_until_finished() -> void:
_top_bar_button("SyncButton").pressed.emit()
sync_popup.sync_timer.stop()
for button_name: String in ["QuizToolbar/SoundTestButton", "QuizToolbar/ResetPlaylistButton", "SongControls/ShowAnswerButton",
"SongControls/NextButton"]:
for button_name: String in LOCKED_BY_SYNC:
assert_bool((_quiz.get_node(button_name) as Button).disabled) \
.override_failure_message(button_name + " not disabled").is_true()
assert_bool(_top_bar_button("SyncButton").disabled).is_false()
@@ -214,8 +222,7 @@ func test_sync_disables_quiz_buttons_until_finished() -> void:
sync_popup._on_sync_timeout()
await _runner.simulate_frames(2)
for button_name: String in ["QuizToolbar/SoundTestButton", "QuizToolbar/ResetPlaylistButton", "SongControls/ShowAnswerButton",
"SongControls/NextButton"]:
for button_name: String in LOCKED_BY_SYNC:
assert_bool((_quiz.get_node(button_name) as Button).disabled) \
.override_failure_message(button_name + " still disabled").is_false()
# The search list in the main window and the quiz playlist are both reloaded
+9 -4
View File
@@ -1,7 +1,9 @@
extends GdUnitTestSuite
## Downloading songs into the Playlist ahead of time, against a fake server.
const SONG_INFO: Dictionary = {"SongNo": 0, "Soundtrack": "Chrono Trigger", "Song": "Corridors of Time"}
const SONG_INFO: Dictionary = {
"SongNo": 0, "Soundtrack": "Chrono Trigger", "Song": "Corridors of Time",
}
var _snapshot: AutoloadSnapshot
var _server: FakeHttpTransport
@@ -32,7 +34,8 @@ func test_load_song_list_adds_songs_already_played_on_the_server() -> void:
_server.respond_json("/music/list", [
{"SongNo": 0, "Soundtrack": "Chrono Trigger", "Song": "Corridors of Time"},
])
_server.respond_json("/music/info", {"SongNo": 1, "Soundtrack": "Final Fantasy VI", "Song": "Dancing Mad"})
_server.respond_json("/music/info",
{"SongNo": 1, "Soundtrack": "Final Fantasy VI", "Song": "Dancing Mad"})
var signals: GdUnitSignalAssert = assert_signal(monitor_signals(_loader))
_loader.load_song_list()
@@ -57,7 +60,8 @@ func test_fetch_song_downloads_queues_and_adds_the_song() -> void:
_loader.fetch_song()
await signals.is_emitted("song_added")
assert_array(_server.requested_paths).contains_exactly(["/music/rand", "/music/addQue", "/music/info"])
assert_array(_server.requested_paths) \
.contains_exactly(["/music/rand", "/music/addQue", "/music/info"])
var song: SongObject = Playlist.song_object_array[0]
assert_str(song.song_title).is_equal("Corridors of Time")
assert_object(song.song).is_not_null()
@@ -75,7 +79,8 @@ func test_fetch_song_keeps_going_until_the_cache_is_full() -> void:
func test_low_played_mode_uses_its_endpoint() -> void:
Settings.use_low_played_mode = true
_server.respond_bytes("/music/rand/low", FileAccess.get_file_as_bytes("res://tests/data/short.mp3"))
_server.respond_bytes("/music/rand/low",
FileAccess.get_file_as_bytes("res://tests/data/short.mp3"))
_loader.fetch_song()
+4 -3
View File
@@ -1,4 +1,5 @@
extends GdUnitTestSuite
## The sync popup on its own: starting a sync, its progress, and how the result is shown.
var _server: FakeHttpTransport
var _snapshot: AutoloadSnapshot
@@ -132,7 +133,7 @@ func test_errors_are_listed() -> void:
_finish_sync(result)
assert_bool(_node("CatchedErrorsVBox").visible).is_true()
assert_bool(_node("CaughtErrorsVBox").visible).is_true()
func test_reopening_after_sync_shows_result_without_syncing_again() -> void:
@@ -152,7 +153,7 @@ func test_errors_section_has_errors_header() -> void:
_finish_sync(result)
assert_str((_node("CatchedErrorsVBox").get_child(0) as Label).text).is_equal("Errors")
assert_str((_node("CaughtErrorsVBox").get_child(0) as Label).text).is_equal("Errors")
func test_long_names_wrap_instead_of_widening_the_window() -> void:
@@ -173,7 +174,7 @@ func test_long_names_wrap_instead_of_widening_the_window() -> void:
var panel_width: float = (_window.get_node("SyncPanel") as Control).size.x
assert_float(_node("SyncHBoxContainer").get_combined_minimum_size().x) \
.is_less(panel_width)
assert_float(_node("CatchedErrorsVBox").get_combined_minimum_size().x) \
assert_float(_node("CaughtErrorsVBox").get_combined_minimum_size().x) \
.is_less(panel_width)
+1
View File
@@ -1,4 +1,5 @@
extends GdUnitTestSuite
## ListRow: text, alternating backgrounds and wrapping.
func _background(list_row: ListRow) -> Color:
+1
View File
@@ -1,4 +1,5 @@
extends GdUnitTestSuite
## How the music player shows a song's time, including the hidden parts.
var _music_player: MusicPlayer
var _hide_beginning: bool
+3 -1
View File
@@ -1,4 +1,5 @@
extends GdUnitTestSuite
## MusicServerClient: which paths it asks for and how it handles the answers.
var _server: FakeHttpTransport
@@ -115,7 +116,8 @@ func test_mark_song_played_sends_put() -> void:
func test_character_name_is_url_encoded() -> void:
MusicServerClient.fetch_character_image("Paper Mario.png", func(_body: PackedByteArray) -> void: pass)
MusicServerClient.fetch_character_image("Paper Mario.png",
func(_body: PackedByteArray) -> void: pass)
assert_array(_server.requested_paths).contains_exactly(["/character?name=Paper%20Mario.png"])
+7 -6
View File
@@ -1,4 +1,5 @@
extends GdUnitTestSuite
## PlayerObject: points, and the first point, match point and winner signals.
var _winning_score: int
var _player: PlayerObject
@@ -20,7 +21,7 @@ func test_first_point_triggers_first_point_signal() -> void:
_player.add_point()
assert_int(_player.player_score).is_equal(1)
await assert_signal(_player).is_emitted("first_point_triggerd")
await assert_signal(_player).is_emitted("first_point_triggered")
await assert_signal(_player).is_emitted("player_point_given")
@@ -30,7 +31,7 @@ func test_one_point_from_winning_triggers_match_point() -> void:
_player.add_point()
await assert_signal(_player).is_emitted("match_point_triggerd")
await assert_signal(_player).is_emitted("match_point_triggered")
func test_reaching_winning_score_triggers_winner() -> void:
@@ -41,7 +42,7 @@ func test_reaching_winning_score_triggers_winner() -> void:
_player.add_point()
assert_int(_player.player_score).is_equal(3)
await assert_signal(_player).is_emitted("winner_triggerd")
await assert_signal(_player).is_emitted("winner_triggered")
func test_score_does_not_go_past_winning_score() -> void:
@@ -91,7 +92,7 @@ func test_dropping_back_to_zero_allows_first_point_again() -> void:
_player.add_point()
await assert_signal(_player).is_emitted("first_point_triggerd")
await assert_signal(_player).is_emitted("first_point_triggered")
func test_reset_points() -> void:
@@ -125,7 +126,7 @@ func test_winning_score_of_one_is_won_with_first_point() -> void:
_player.add_point()
await assert_signal(_player).is_emitted("winner_triggerd")
await assert_signal(_player).is_emitted("winner_triggered")
func test_first_point_is_match_point_when_winning_score_is_two() -> void:
@@ -134,5 +135,5 @@ func test_first_point_is_match_point_when_winning_score_is_two() -> void:
_player.add_point()
await assert_signal(_player).is_emitted("match_point_triggerd")
await assert_signal(_player).is_emitted("match_point_triggered")
assert_bool(_player.is_first_point).is_false()
+1
View File
@@ -1,4 +1,5 @@
extends GdUnitTestSuite
## Playlist: moving through the songs, answering them and keeping points.
var _server: FakeHttpTransport
var _add_to_stats: bool
+3 -1
View File
@@ -1,4 +1,5 @@
extends GdUnitTestSuite
## How the search popup finds and orders matching soundtracks.
const SearchWindowScript: GDScript = preload("res://search_popup.gd")
@@ -36,7 +37,8 @@ func test_ignores_spaces() -> void:
func test_and_matches_ampersand() -> void:
assert_array(_search.find_matches("mario and luigi")).contains(["Mario & Luigi: Superstar Saga"])
assert_array(_search.find_matches("mario and luigi")) \
.contains(["Mario & Luigi: Superstar Saga"])
func test_accented_letters_match_plain_letters() -> void:
+1
View File
@@ -1,4 +1,5 @@
extends GdUnitTestSuite
## Settings: the version number.
func test_version_comes_from_project_settings() -> void:
+1
View File
@@ -1,4 +1,5 @@
extends GdUnitTestSuite
## SongObject: what it shows before and after the answer, and its points.
var _is_debug: bool
var _song: SongObject
+12 -7
View File
@@ -1,19 +1,24 @@
class_name VolumeSlider
extends HSlider
## A slider that sets the volume of an audio bus.
@export
var audio_bus_name := "music"
## The audio bus whose volume the slider sets.
@onready
var _bus: int
@export var audio_bus_name: String = "music"
func _ready():
var _bus: int
func _ready() -> void:
_bus = AudioServer.get_bus_index(audio_bus_name)
value_changed.connect(_on_value_changed)
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:
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))
+5 -14
View File
@@ -1,17 +1,11 @@
class_name WinnerPopup
extends BasePopup
## Shows who won, with their character, and where the other players placed.
@onready
var winner_panel: Panel = %WinnerPanel
@onready
var winner_picture: TextureRect = %WinnerPicture
@onready
var winner_label: Label = %WinnerLabel
@onready
var players_list: VBoxContainer = %PlayersList
@onready var winner_panel: Panel = %WinnerPanel
@onready var winner_picture: TextureRect = %WinnerPicture
@onready var winner_label: Label = %WinnerLabel
@onready var players_list: VBoxContainer = %PlayersList
func _ready() -> void:
@@ -54,6 +48,3 @@ func show_winner(winner: PlayerObject) -> void:
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
players_list.add_child(label)
placing += 1