Add working tests, fix bugs, rework popups and automate releases #36

Merged
Sansan merged 34 commits from feature/adding_tests into develop 2026-10-06 22:14:44 +02:00
24 changed files with 932 additions and 466 deletions
Showing only changes of commit 8c1987807c - Show all commits

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
+16
View File
@@ -0,0 +1,16 @@
[gd_scene format=3]
[ext_resource type="Script" uid="uid://elgn7gkxgagq" path="res://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
+23
View File
@@ -0,0 +1,23 @@
[gd_scene format=3]
[ext_resource type="Script" path="res://music_list_panel.gd" id="part_script"]
[ext_resource type="Script" uid="uid://gf1bk6xbe3j" path="res://MusicListScrollContainer.gd" id="7_dj026"]
[ext_resource type="Theme" uid="uid://rxexo3ur85as" path="res://LightGrayTheme.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]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/separation = 10
+89
View File
@@ -0,0 +1,89 @@
[gd_scene format=3]
[ext_resource type="Script" path="res://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://PlayerNameField.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]
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]
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]
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]
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]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 15
+6 -106
View File
@@ -1,16 +1,14 @@
[gd_scene format=3] [gd_scene format=3]
[ext_resource type="Script" path="res://quiz.gd" id="quiz_script"] [ext_resource type="Script" path="res://quiz.gd" id="quiz_script"]
[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://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="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://dldpeo5y3l5hq" path="res://SettingsWindow.tscn" id="11_k62u5"] [ext_resource type="PackedScene" uid="uid://dldpeo5y3l5hq" path="res://SettingsWindow.tscn" id="11_k62u5"]
[ext_resource type="PackedScene" uid="uid://bijh5h5yrivm3" path="res://Log.tscn" id="14_26rwn"] [ext_resource type="PackedScene" uid="uid://bijh5h5yrivm3" path="res://Log.tscn" id="14_26rwn"]
[ext_resource type="PackedScene" uid="uid://cxlsmxnrp0agh" path="res://WinnerWindow.tscn" id="17_winner_window"] [ext_resource type="PackedScene" uid="uid://cxlsmxnrp0agh" path="res://WinnerWindow.tscn" id="17_winner_window"]
[ext_resource type="PackedScene" path="res://MusicListPanel.tscn" id="scene_MusicListPanel"]
[ext_resource type="PackedScene" path="res://PlayersPanel.tscn" id="scene_PlayersPanel"]
[ext_resource type="PackedScene" path="res://InspirationList.tscn" id="scene_InspirationList"]
[sub_resource type="LabelSettings" id="LabelSettings_qspbu"] [sub_resource type="LabelSettings" id="LabelSettings_qspbu"]
font_size = 50 font_size = 50
@@ -18,21 +16,6 @@ font_size = 50
[sub_resource type="LabelSettings" id="LabelSettings_3m52w"] [sub_resource type="LabelSettings" id="LabelSettings_3m52w"]
font_size = 35 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"] [sub_resource type="InputEventKey" id="InputEventKey_06rl4"]
device = -1 device = -1
alt_pressed = true alt_pressed = true
@@ -79,39 +62,19 @@ file_mode = 2
access = 2 access = 2
show_hidden_files = true show_hidden_files = true
[node name="MusicListPanel" type="PanelContainer" parent="." unique_id=2011292375] [node name="MusicListPanel" parent="." unique_id=2011292375 instance=ExtResource("scene_MusicListPanel")]
layout_mode = 0 layout_mode = 0
offset_left = 64.0 offset_left = 64.0
offset_top = 384.0 offset_top = 384.0
offset_right = 1152.0 offset_right = 1152.0
offset_bottom = 896.0 offset_bottom = 896.0
theme = ExtResource("7_wxbv6")
[node name="ScrollContainer" type="ScrollContainer" parent="MusicListPanel" unique_id=707750459] [node name="InspirationList" parent="." unique_id=498283761 instance=ExtResource("scene_InspirationList")]
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 layout_mode = 0
offset_left = 1216.0 offset_left = 1216.0
offset_top = 128.0 offset_top = 128.0
offset_right = 1857.0 offset_right = 1857.0
offset_bottom = 512.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] [node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=42468498]
layout_mode = 0 layout_mode = 0
@@ -148,76 +111,13 @@ offset_bottom = 1024.0
grow_horizontal = 1 grow_horizontal = 1
grow_vertical = 1 grow_vertical = 1
[node name="Players" type="PanelContainer" parent="." unique_id=1708375945] [node name="PlayersPanel" parent="." unique_id=1708375945 instance=ExtResource("scene_PlayersPanel")]
layout_mode = 0 layout_mode = 0
offset_left = 1216.0 offset_left = 1216.0
offset_top = 576.0 offset_top = 576.0
offset_right = 1857.0 offset_right = 1857.0
offset_bottom = 977.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] [node name="ResetPlaylistButton" type="Button" parent="." unique_id=1754155620]
layout_mode = 0 layout_mode = 0
offset_left = 1360.0 offset_left = 1360.0
+67
View File
@@ -0,0 +1,67 @@
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
# 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
## Fetches every soundtrack from the server and shows them.
func load_from_server() -> void:
print("load_from_server")
var populate_list: Callable = func populate_list(array) -> void:
if typeof(array) == TYPE_ARRAY:
show_soundtracks(array)
else:
print("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://song_list_object.tscn")
@onready
var _rows: VBoxContainer = $ScrollContainer/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: SongListObject = _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
+90
View File
@@ -0,0 +1,90 @@
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: Player, player_name: String)
var _player_row: PackedScene = preload("res://Player.tscn")
@onready
var add_players_button: TextureButton = $VBoxContainer/HBoxContainer/AddPlayersButton
@onready
var add_player_container: HBoxContainer = $VBoxContainer/AddPlayerContainer
@onready
var add_player_button: Button = $VBoxContainer/AddPlayerContainer/AddPlayerButton
@onready
var new_player_name_field: TextEdit = $VBoxContainer/AddPlayerContainer/PlayerNameField
@onready
var players_list_container: VBoxContainer = $VBoxContainer/ScrollContainer/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)
## Shows or hides the row for adding players, and the buttons for removing them.
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()
## 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:
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)
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:
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_row.instantiate()
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))
players_list_container.add_child(new_player)
counter += 1
func _on_change_character_clicked(row: Player, player_name: String) -> void:
change_character_requested.emit(row, player_name)
func _on_player_removed(player_object: PlayerObject) -> void:
print("_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
+222 -301
View File
@@ -18,10 +18,7 @@ var open_button: Button = $Open
var fileDialog: FileDialog = $FileDialog var fileDialog: FileDialog = $FileDialog
@onready @onready
var inspiration_scroll: ScrollContainer = $ScrollContainer var inspiration_list: InspirationList = $InspirationList
@onready
var inspiration_list: VBoxContainer = $ScrollContainer/VBoxContainer
@onready @onready
var soundtrack_label: Label = $VBoxContainer/SoundtrackLabel var soundtrack_label: Label = $VBoxContainer/SoundtrackLabel
@@ -30,16 +27,7 @@ var soundtrack_label: Label = $VBoxContainer/SoundtrackLabel
var song_label: Label = $VBoxContainer/SongLabel var song_label: Label = $VBoxContainer/SongLabel
@onready @onready
var add_player_container: HBoxContainer = $Players/VBoxContainer/AddPlayerContainer var players_panel: PlayersPanel = $PlayersPanel
@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 @onready
var reset_playlist_button: Button = $ResetPlaylistButton var reset_playlist_button: Button = $ResetPlaylistButton
@@ -66,16 +54,7 @@ var show_answer_button: Button = $ShowAnswerButton
var next_button: Button = $NextButton var next_button: Button = $NextButton
@onready @onready
var music_list_scroll: ScrollContainer = $MusicListPanel/ScrollContainer var music_list_panel: MusicListPanel = $MusicListPanel
@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 @onready
var new_character_select: Control = %NewCharacterSelect var new_character_select: Control = %NewCharacterSelect
@@ -92,11 +71,7 @@ var log_window: Control = %Log
@onready @onready
var debug_label: Label = $DebugLabel 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 songs: Array= []
var soundtracks: Array = []
var current_player: Node var current_player: Node
## True while statistics are off because a player won, until points are reset. ## True while statistics are off because a player won, until points are reset.
@@ -110,354 +85,302 @@ var song_fetch_retry_seconds: float = 5.0
func _ready() -> void: func _ready() -> void:
popup_backdrop = PopupBackdrop.add_to(self, _popups()) popup_backdrop = PopupBackdrop.add_to(self, _popups())
if OS.has_feature("debug"): if OS.has_feature("debug"):
print("is_debug") print("is_debug")
debug_label.visible = true debug_label.visible = true
Settings.is_debug = true Settings.is_debug = true
settings_window.set_winning_score(5) settings_window.set_winning_score(5)
settings_window.set_selected_server(4) settings_window.set_selected_server(4)
next_button.pressed.connect(play_next_song) next_button.pressed.connect(play_next_song)
show_answer_button.pressed.connect(show_answer_pressed) show_answer_button.pressed.connect(show_answer_pressed)
sound_test_button.pressed.connect(music_player_container.get_sound_test_song) sound_test_button.pressed.connect(music_player_container.get_sound_test_song)
settings_button.pressed.connect(show_settings) settings_button.pressed.connect(show_settings)
reset_playlist_button.pressed.connect(reset_playlist) reset_playlist_button.pressed.connect(reset_playlist)
reset_points_button.pressed.connect(reset_points) reset_points_button.pressed.connect(reset_points)
new_character_select.connect("new_new_character_selected", _on_new_character_selected) new_character_select.connect("new_new_character_selected", _on_new_character_selected)
new_player_name_field.connect("enter_key_pressed", add_player) music_list_panel.song_clicked.connect(song_object_clicked)
music_player_container.connect("play_next_song", play_next_song) players_panel.player_added.connect(_on_player_added)
music_player_container.connect("update_song_list", update_song_list) players_panel.player_removed.connect(_on_player_removed)
settings_window.connect("server_changed", _on_server_changed) players_panel.change_character_requested.connect(_on_player_change_character_clicked)
settings_window.visibility_changed.connect(_on_settings_window_visibility_changed) music_player_container.connect("play_next_song", play_next_song)
music_player_container.connect("update_song_list", update_song_list)
settings_window.connect("server_changed", _on_server_changed)
settings_window.visibility_changed.connect(_on_settings_window_visibility_changed)
add_players_button.pressed.connect(add_players) open_button.pressed.connect(open)
add_player_button.pressed.connect(add_player) auto_repeat_song_button.pressed.connect(repeat_song)
open_button.pressed.connect(open)
auto_repeat_song_button.pressed.connect(repeat_song)
get_suggestion_list() inspiration_list.load_from_server()
fetch_song_list_at_start() fetch_song_list_at_start()
new_character_select.load_characters() new_character_select.load_characters()
func _input(event: InputEvent) -> void: 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_UP: if event.alt_pressed && event.keycode == KEY_UP:
print("Alt + UP pressed") print("Alt + UP pressed")
music_player_container.change_volume(0.05) music_player_container.change_volume(0.05)
if event.alt_pressed && event.keycode == KEY_DOWN: if event.alt_pressed && event.keycode == KEY_DOWN:
print("Alt + DOWN pressed") print("Alt + DOWN pressed")
music_player_container.change_volume(-0.05) music_player_container.change_volume(-0.05)
if event.alt_pressed && event.ctrl_pressed && event.keycode == KEY_LEFT: if event.alt_pressed && event.ctrl_pressed && event.keycode == KEY_LEFT:
print("Ctrl + Alt + LEFT pressed") print("Ctrl + Alt + LEFT pressed")
music_player_container.restart() music_player_container.restart()
elif event.alt_pressed && event.keycode == KEY_LEFT: elif event.alt_pressed && event.keycode == KEY_LEFT:
print("Alt + LEFT pressed") print("Alt + LEFT pressed")
music_player_container.seek(-5) music_player_container.seek(-5)
if event.alt_pressed && event.keycode == KEY_RIGHT: if event.alt_pressed && event.keycode == KEY_RIGHT:
print("Alt + RIGHT pressed") print("Alt + RIGHT pressed")
music_player_container.seek(5) music_player_container.seek(5)
if event.alt_pressed && event.keycode == KEY_L: if event.alt_pressed && event.keycode == KEY_L:
log_window.toggle() log_window.toggle()
## Disables the buttons that use the song list while the server syncs it. ## Disables the buttons that use the song list while the server syncs it.
func lock_for_sync() -> void: func lock_for_sync() -> void:
sound_test_button.disabled = true sound_test_button.disabled = true
reset_playlist_button.disabled = true reset_playlist_button.disabled = true
show_answer_button.disabled = true show_answer_button.disabled = true
next_button.disabled = true next_button.disabled = true
## Reloads from the server after a sync and starts a new game. ## Reloads from the server after a sync and starts a new game.
func on_sync_finished() -> void: func on_sync_finished() -> void:
refresh_from_server() refresh_from_server()
sound_test_button.disabled = false sound_test_button.disabled = false
reset_playlist_button.disabled = false reset_playlist_button.disabled = false
show_answer_button.disabled = false show_answer_button.disabled = false
reset_points() reset_points()
## Reloads the soundtracks, playlist and characters from the server. ## Reloads the soundtracks, playlist and characters from the server.
func refresh_from_server() -> void: func refresh_from_server() -> void:
print("refresh_from_server") print("refresh_from_server")
get_suggestion_list() inspiration_list.load_from_server()
reset_playlist() reset_playlist()
new_character_select.load_characters() new_character_select.load_characters()
func _on_server_changed() -> void: func _on_server_changed() -> void:
print("_on_server_changed") print("_on_server_changed")
refresh_from_server() refresh_from_server()
reset_points() reset_points()
server_changed.emit() server_changed.emit()
func repeat_song() -> void: func repeat_song() -> void:
Settings.auto_repeat_song = !Settings.auto_repeat_song Settings.auto_repeat_song = !Settings.auto_repeat_song
func show_answer_pressed() -> void: func show_answer_pressed() -> void:
print("show_answer_pressed") print("show_answer_pressed")
Playlist.song_is_answered() Playlist.song_is_answered()
update_song_list() update_song_list()
# Loads the songs already in the server playlist, then starts preloading new ones. # 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. # Preloading waits for the list, because loading the list clears the playlist.
func fetch_song_list_at_start() -> void: func fetch_song_list_at_start() -> void:
print("fetch_song_list_at_start") print("fetch_song_list_at_start")
var fetch_song_list_at_start_done: Callable = func fetch_songs_at_start_done(data) -> void: var fetch_song_list_at_start_done: Callable = func fetch_songs_at_start_done(data) -> void:
Playlist.clear_song_list() Playlist.clear_song_list()
if typeof(data) == TYPE_ARRAY: if typeof(data) == TYPE_ARRAY:
for d: Dictionary in data: for d: Dictionary in data:
var new_song_object: SongObject = SongObject.new() var new_song_object: SongObject = SongObject.new()
new_song_object.song_number = d.SongNo new_song_object.song_number = d.SongNo
new_song_object.soundtrack_title = d.Soundtrack new_song_object.soundtrack_title = d.Soundtrack
new_song_object.song_title = d.Song new_song_object.song_title = d.Song
new_song_object.is_answered = true new_song_object.is_answered = true
new_song_object.has_played = true new_song_object.has_played = true
Playlist.add_to_playlist(new_song_object) Playlist.add_to_playlist(new_song_object)
elif data != null: elif data != null:
print("Unexpected data") print("Unexpected data")
Playlist.update_last_played_song() Playlist.update_last_played_song()
update_song_list() update_song_list()
if !Playlist.is_song_downloading: if !Playlist.is_song_downloading:
fetch_song() fetch_song()
MusicServerClient.fetch_song_list(fetch_song_list_at_start_done, MusicServerClient.fetch_song_list(fetch_song_list_at_start_done,
_retry_later.bind(fetch_song_list_at_start)) _retry_later.bind(fetch_song_list_at_start))
func play_next_song() -> void: func play_next_song() -> void:
print("play_next_song ", Playlist.latest_played_song) print("play_next_song ", Playlist.latest_played_song)
if Playlist.first_song_played: if Playlist.first_song_played:
Playlist.set_latest_played_song() Playlist.set_latest_played_song()
Playlist.first_song_played = true Playlist.first_song_played = true
music_player_container.play_song_object(Playlist.latest_played_song) music_player_container.play_song_object(Playlist.latest_played_song)
if !(Playlist.get_preloaded_songs() > 0): if !(Playlist.get_preloaded_songs() > 0):
next_button.disabled = true next_button.disabled = true
if !Playlist.is_song_downloading: if !Playlist.is_song_downloading:
fetch_song() fetch_song()
func _popups() -> Array[PopupWindow]: func _popups() -> Array[PopupWindow]:
return [settings_window, new_character_select, winner_window, log_window] return [settings_window, new_character_select, winner_window, log_window]
# The cache size can be changed in the settings. Waiting until the settings are closed # 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. # avoids starting downloads while the user is still clicking the number up and down.
func _on_settings_window_visibility_changed() -> void: func _on_settings_window_visibility_changed() -> void:
if !settings_window.visible && !Playlist.is_song_downloading && Playlist.should_preload_more_songs(): if !settings_window.visible && !Playlist.is_song_downloading && Playlist.should_preload_more_songs():
fetch_song() fetch_song()
func fetch_song() -> void: func fetch_song() -> void:
print("fetch_song") print("fetch_song")
Playlist.is_song_downloading = true Playlist.is_song_downloading = true
_download_random_song(SongObject.new()) _download_random_song(SongObject.new())
# Fetching a song takes three requests. If one fails, only that step is retried, so # 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. # the server and the game keep the same songs in their playlists.
func _download_random_song(new_song_object: SongObject) -> void: func _download_random_song(new_song_object: SongObject) -> void:
var song_downloaded: Callable = func song_downloaded(body: PackedByteArray) -> void: var song_downloaded: Callable = func song_downloaded(body: PackedByteArray) -> void:
print("song_downloaded") print("song_downloaded")
var sound: AudioStream = AudioStreamMP3.new() var sound: AudioStream = AudioStreamMP3.new()
sound.data = body sound.data = body
new_song_object.song = sound new_song_object.song = sound
_add_song_to_queue(new_song_object) _add_song_to_queue(new_song_object)
MusicServerClient.fetch_random_song(Settings.use_low_played_mode, song_downloaded, MusicServerClient.fetch_random_song(Settings.use_low_played_mode, song_downloaded,
_retry_later.bind(_download_random_song.bind(new_song_object))) _retry_later.bind(_download_random_song.bind(new_song_object)))
func _add_song_to_queue(new_song_object: SongObject) -> void: func _add_song_to_queue(new_song_object: SongObject) -> void:
MusicServerClient.add_to_queue(_fetch_song_info.bind(new_song_object), MusicServerClient.add_to_queue(_fetch_song_info.bind(new_song_object),
_retry_later.bind(_add_song_to_queue.bind(new_song_object))) _retry_later.bind(_add_song_to_queue.bind(new_song_object)))
func _fetch_song_info(new_song_object: SongObject) -> void: func _fetch_song_info(new_song_object: SongObject) -> void:
var song_info_fetched: Callable = func song_info_fetched(data_received: Dictionary) -> void: var song_info_fetched: Callable = func song_info_fetched(data_received: Dictionary) -> void:
print("song_info_fetched") print("song_info_fetched")
new_song_object.song_number = data_received.SongNo new_song_object.song_number = data_received.SongNo
new_song_object.soundtrack_title = data_received.Soundtrack new_song_object.soundtrack_title = data_received.Soundtrack
new_song_object.song_title = data_received.Song new_song_object.song_title = data_received.Song
print("new_song_object: ", new_song_object.to_string()) print("new_song_object: ", new_song_object.to_string())
Playlist.add_to_playlist(new_song_object) Playlist.add_to_playlist(new_song_object)
next_button.disabled = false next_button.disabled = false
if Playlist.should_preload_more_songs(): if Playlist.should_preload_more_songs():
fetch_song() fetch_song()
else: else:
Playlist.is_song_downloading = false Playlist.is_song_downloading = false
if Settings.is_debug: if Settings.is_debug:
update_song_list() update_song_list()
MusicServerClient.fetch_song_info(song_info_fetched, MusicServerClient.fetch_song_info(song_info_fetched,
_retry_later.bind(_fetch_song_info.bind(new_song_object))) _retry_later.bind(_fetch_song_info.bind(new_song_object)))
func _retry_later(step: Callable) -> void: func _retry_later(step: Callable) -> void:
print("Song download failed, retrying in %s seconds" % song_fetch_retry_seconds) print("Song download failed, retrying in %s seconds" % song_fetch_retry_seconds)
get_tree().create_timer(song_fetch_retry_seconds).timeout.connect(step) get_tree().create_timer(song_fetch_retry_seconds).timeout.connect(step)
func update_song_list() -> void: func update_song_list() -> void:
print("update_song_list") print("update_song_list")
if Playlist.currently_playing_song >= 0: if Playlist.currently_playing_song >= 0:
soundtrack_label.text = Playlist.get_current_soundtrack_title() soundtrack_label.text = Playlist.get_current_soundtrack_title()
song_label.text = Playlist.get_current_song_title() song_label.text = Playlist.get_current_song_title()
Settings.delete_children(music_list) music_list_panel.refresh()
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: func song_object_clicked(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())
print("Clicked Song Object: ", Playlist.song_object_array[song_no].to_string()) if Settings.character_select_open == false:
if Settings.character_select_open == false: if Playlist.get_song(song_no) == null:
if Playlist.get_song(song_no) == null: print("has no song, need to download")
print("has no song, need to download") var song_downloaded: Callable = func song_downloaded(body: PackedByteArray) -> void:
var song_downloaded: Callable = func song_downloaded(body: PackedByteArray) -> void: var sound: AudioStream = AudioStreamMP3.new()
var sound: AudioStream = AudioStreamMP3.new() sound.data = body
sound.data = body Playlist.add_song_to_song_object(song_no, sound)
Playlist.add_song_to_song_object(song_no, sound) music_player_container.play_song_object(song_no)
music_player_container.play_song_object(song_no) MusicServerClient.fetch_song(song_no, song_downloaded)
MusicServerClient.fetch_song(song_no, song_downloaded) else:
else: print("song already downloaded")
print("song already downloaded") music_player_container.play_song_object(song_no)
music_player_container.play_song_object(song_no)
func reset_playlist() -> void: func reset_playlist() -> void:
print("reset_playlist") print("reset_playlist")
var playlist_reset_done: Callable = func playlist_reset_done() -> void: var playlist_reset_done: Callable = func playlist_reset_done() -> void:
Playlist.reset_playlist() Playlist.reset_playlist()
fetch_song_list_at_start() fetch_song_list_at_start()
MusicServerClient.reset_playlist(playlist_reset_done) MusicServerClient.reset_playlist(playlist_reset_done)
func reset_points() -> void: func reset_points() -> void:
print("reset_points") print("reset_points")
for player_objects: PlayerObject in Settings.player_array: for player_objects: PlayerObject in Settings.player_array:
player_objects.reset_points() player_objects.reset_points()
load_players() players_panel.reload()
# A new game starts, so turn statistics back on if winning turned them off # A new game starts, so turn statistics back on if winning turned them off
if stats_paused_by_win: if stats_paused_by_win:
Settings.add_to_stats = true Settings.add_to_stats = true
stats_paused_by_win = false stats_paused_by_win = false
func show_settings() -> void: func show_settings() -> void:
print("show_settings") print("show_settings")
settings_window.open() settings_window.open()
func get_suggestion_list() -> void: # Each new player's points reach the music player, the log and the winner window.
print("get_suggestion_list") func _on_player_added(new_player_object: PlayerObject) -> void:
var populate_list: Callable = func populate_list(array) -> void: new_player_object.connect("first_point_triggerd", music_player_container._on_point_triggered.bind("first"))
if typeof(array) == TYPE_ARRAY: new_player_object.connect("match_point_triggerd", music_player_container._on_point_triggered.bind("match"))
soundtracks = [] new_player_object.connect("winner_triggerd", _on_player_won.bind(new_player_object))
Settings.delete_children(inspiration_list) new_player_object.connect("point_given_sound", _on_make_point_given_sound)
soundtracks.append_array(array) new_player_object.connect("player_point_given", _on_point_given.bind(new_player_object))
for i: int in soundtracks.size(): new_player_object.connect("player_point_taken", _on_point_taken.bind(new_player_object))
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: func _on_point_given(player_object: PlayerObject) -> void:
print("_on_point_given") print("_on_point_given")
log_window.add_log_row(player_object.player_name + " got a point") log_window.add_log_row(player_object.player_name + " got a point")
if Playlist.currently_playing_song >= 0: if Playlist.currently_playing_song >= 0:
Playlist.add_point(player_object.id) Playlist.add_point(player_object.id)
update_song_list() update_song_list()
func _on_make_point_given_sound() -> void: func _on_make_point_given_sound() -> void:
music_player_container.play_sound_effect(preload("res://sounds/itemopen.wav")) music_player_container.play_sound_effect(preload("res://sounds/itemopen.wav"))
func _on_point_taken(player_object: PlayerObject) -> void: func _on_point_taken(player_object: PlayerObject) -> void:
print("_on_point_taken") print("_on_point_taken")
log_window.add_log_row(player_object.player_name + " lost a point") log_window.add_log_row(player_object.player_name + " lost a point")
music_player_container.play_sound_effect(preload("res://sounds/itemequip.wav")) music_player_container.play_sound_effect(preload("res://sounds/itemequip.wav"))
if Playlist.currently_playing_song >= 0: if Playlist.currently_playing_song >= 0:
Playlist.remove_point(player_object.id) Playlist.remove_point(player_object.id)
update_song_list() update_song_list()
func _on_player_won(player_object: PlayerObject) -> void: func _on_player_won(player_object: PlayerObject) -> void:
print("_on_player_won") print("_on_player_won")
winner_window.show_winner(player_object) winner_window.show_winner(player_object)
music_player_container.play_song(preload("res://sounds/winning.mp3")) music_player_container.play_song(preload("res://sounds/winning.mp3"))
# Songs played after the game is won don't count towards the statistics # Songs played after the game is won don't count towards the statistics
if Settings.add_to_stats: if Settings.add_to_stats:
Settings.add_to_stats = false Settings.add_to_stats = false
stats_paused_by_win = true stats_paused_by_win = true
func _on_player_removed(player_object: PlayerObject) -> void: func _on_player_removed(player_object: PlayerObject) -> void:
print("_on_player_removed ", player_object.player_name) Playlist.remove_player_from_list(player_object.id)
Settings.player_array.erase(player_object) update_song_list()
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: func _on_player_change_character_clicked(new_player: Player, player_name: String) -> void:
print("_on_player_change_character_clicked") print("_on_player_change_character_clicked")
current_player = new_player current_player = new_player
print("Change character for: " + player_name) print("Change character for: " + player_name)
new_character_select.show_grid(player_name) new_character_select.show_grid(player_name)
func _on_new_character_selected(character_texture: ImageTexture) -> void: func _on_new_character_selected(character_texture: ImageTexture) -> void:
print("_on_new_character_selected") print("_on_new_character_selected")
current_player.new_character_selected(character_texture) current_player.new_character_selected(character_texture)
###Local ###Local
@@ -465,32 +388,30 @@ 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' 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: func open() -> void:
fileDialog.popup() fileDialog.popup()
func _on_file_dialog_dir_selected(path: String) -> void: func _on_file_dialog_dir_selected(path: String) -> void:
print(path) print(path)
dir_contents(path) dir_contents(path)
func dir_contents(path: String) -> void: func dir_contents(path: String) -> void:
var dir: DirAccess = DirAccess.open(path) var dir: DirAccess = DirAccess.open(path)
if dir: if dir:
dir.list_dir_begin() dir.list_dir_begin()
var file_name: String = dir.get_next() var file_name: String = dir.get_next()
songs.clear() songs.clear()
soundtracks = [] var soundtracks: Array = []
while file_name != "": while file_name != "":
if dir.current_is_dir(): if dir.current_is_dir():
#print("Found directory: " + file_name) #print("Found directory: " + file_name)
soundtracks.append(file_name) soundtracks.append(file_name)
else: else:
#print("Found file: " + file_name) #print("Found file: " + file_name)
if file_name.ends_with(".mp3"): if file_name.ends_with(".mp3"):
songs.append(path + "/" + file_name) songs.append(path + "/" + file_name)
file_name = dir.get_next() file_name = dir.get_next()
else: inspiration_list.show_soundtracks(soundtracks)
print("An error occurred when trying to access the path.") 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))
+3 -3
View File
@@ -77,16 +77,16 @@ func _play_first_song() -> void:
## Adds a player through the name field, the same way the user does. ## Adds a player through the name field, the same way the user does.
func _add_player(player_name: String) -> PlayerObject: func _add_player(player_name: String) -> PlayerObject:
var name_field: TextEdit = _quiz.get_node( var name_field: TextEdit = _quiz.get_node(
"Players/VBoxContainer/AddPlayerContainer/PlayerNameField") "PlayersPanel/VBoxContainer/AddPlayerContainer/PlayerNameField")
name_field.text = player_name name_field.text = player_name
_quiz.add_player() _quiz.players_panel.add_player()
return Settings.player_array.back() if !Settings.player_array.is_empty() else null return Settings.player_array.back() if !Settings.player_array.is_empty() else null
## Returns the row in the player list that shows [param player_object]. ## Returns the row in the player list that shows [param player_object].
func _player_row(player_object: PlayerObject) -> Player: func _player_row(player_object: PlayerObject) -> Player:
var rows: Node = _quiz.get_node( var rows: Node = _quiz.get_node(
"Players/VBoxContainer/ScrollContainer/PlayersListVBoxContainer") "PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer")
for row: Player in rows.get_children(): for row: Player in rows.get_children():
if row.player_object == player_object: if row.player_object == player_object:
return row return row
+9 -6
View File
@@ -179,11 +179,14 @@ func _node(node_name: String) -> Node:
return null return null
## Calls [param method] on the main window, or on its Quiz scene if that has it. ## Calls [param method] on the players panel, the quiz or the main window, whichever
## has it first.
func _call(method: String, args: Array = []) -> Variant: func _call(method: String, args: Array = []) -> Variant:
var quiz: Node = _main.get_node_or_null("Quiz") for path: String in ["Quiz/PlayersPanel", "Quiz"]:
var target: Node = quiz if quiz != null && quiz.has_method(method) else _main var target: Node = _main.get_node_or_null(path)
return target.callv(method, args) if target != null && target.has_method(method):
return target.callv(method, args)
return _main.callv(method, args)
func _server() -> FakeHttpTransport: func _server() -> FakeHttpTransport:
@@ -196,13 +199,13 @@ func _settle() -> void:
func _add_player(player_name: String) -> void: func _add_player(player_name: String) -> void:
(_node("Players").get_node("VBoxContainer/AddPlayerContainer/PlayerNameField") \ (_node("PlayersPanel").get_node("VBoxContainer/AddPlayerContainer/PlayerNameField") \
as TextEdit).text = player_name as TextEdit).text = player_name
_call("add_player") _call("add_player")
func _player_row(index: int) -> Player: func _player_row(index: int) -> Player:
return _node("Players").get_node( return _node("PlayersPanel").get_node(
"VBoxContainer/ScrollContainer/PlayersListVBoxContainer").get_child(index) "VBoxContainer/ScrollContainer/PlayersListVBoxContainer").get_child(index)
@@ -0,0 +1,92 @@
extends GdUnitTestSuite
## The inspiration list on its own: it shows every soundtrack from the server and
## scrolls by itself.
const SOUNDTRACKS: Array[String] = ["Chrono Trigger", "Final Fantasy VI", "Secret of Mana"]
var _snapshot: AutoloadSnapshot
var _server: FakeHttpTransport
var _runner: GdUnitSceneRunner
var _list: InspirationList
func before_test() -> void:
_snapshot = AutoloadSnapshot.take([Settings])
Settings.inspiration_list_speed = 0
_server = FakeHttpTransport.new().install()
_server.respond_json("/music/all", SOUNDTRACKS)
_runner = scene_runner("res://InspirationList.tscn")
_list = _runner.scene()
func after_test() -> void:
await await_idle_frame()
_server.uninstall()
_snapshot.restore()
func _rows() -> Array[Node]:
return _list.get_node("VBoxContainer").get_children()
func _texts() -> Array:
return _rows().map(func(row: ListRow) -> String: return row.text)
func test_load_from_server_shows_every_soundtrack_in_order() -> void:
_list.load_from_server()
assert_array(_texts()).contains_exactly(SOUNDTRACKS)
assert_array(_list.soundtracks).contains_exactly(SOUNDTRACKS)
func test_rows_alternate_backgrounds() -> void:
_list.load_from_server()
var rows: Array[Node] = _rows()
assert_that((rows[0] as ListRow).get_theme_stylebox("panel")) \
.is_not_equal((rows[1] as ListRow).get_theme_stylebox("panel"))
assert_that((rows[0] as ListRow).get_theme_stylebox("panel")) \
.is_equal((rows[2] as ListRow).get_theme_stylebox("panel"))
func test_loading_again_replaces_the_rows() -> void:
_list.load_from_server()
_server.respond_json("/music/all", ["Terranigma"])
_list.load_from_server()
assert_array(_texts()).contains_exactly(["Terranigma"])
func test_failed_load_keeps_the_rows() -> void:
_list.load_from_server()
_server.respond_bytes("/music/all", PackedByteArray(), 500)
_list.load_from_server()
assert_array(_texts()).contains_exactly(SOUNDTRACKS)
func _long_list() -> Array[String]:
var names: Array[String] = []
for i: int in 40:
names.append("Soundtrack %d" % i)
return names
func test_scrolls_down_by_itself() -> void:
_list.show_soundtracks(_long_list())
Settings.inspiration_list_speed = 5
await _runner.simulate_frames(30, 16)
assert_int(_list.scroll_vertical).is_greater(0)
func test_does_not_scroll_at_speed_zero() -> void:
_list.show_soundtracks(_long_list())
await _runner.simulate_frames(30, 16)
assert_int(_list.scroll_vertical).is_equal(0)
@@ -0,0 +1 @@
uid://2hfs3x1k46wu
+10 -2
View File
@@ -12,12 +12,18 @@ const BASELINE_PATH: String = "res://tests/data/layout_baseline.json"
## Nodes that moved from the main window into the Quiz scene, by their old name. ## Nodes that moved from the main window into the Quiz scene, by their old name.
const QUIZ_NODES: Array[String] = [ const QUIZ_NODES: Array[String] = [
"Open", "FileDialog", "MusicListPanel", "ScrollContainer", "VBoxContainer", "Open", "FileDialog", "MusicListPanel", "VBoxContainer",
"MusicPlayer", "Players", "ResetPlaylistButton", "ResetPointsButton", "SettingsButton", "MusicPlayer", "ResetPlaylistButton", "ResetPointsButton", "SettingsButton",
"SoundTestButton", "ShowAnswerButton", "NextButton", "RepeatSongCheckButton", "SoundTestButton", "ShowAnswerButton", "NextButton", "RepeatSongCheckButton",
"DebugLabel", "SettingsWindow", "NewCharacterSelect", "WinnerWindow", "Log", "DebugLabel", "SettingsWindow", "NewCharacterSelect", "WinnerWindow", "Log",
] ]
## Nodes that moved into the Quiz scene and were renamed there: old name to new name.
const RENAMED_QUIZ_NODES: Dictionary = {
"Players": "PlayersPanel",
"ScrollContainer": "InspirationList",
}
## Nodes that moved from the main window into the TopBar scene, by their old name. ## Nodes that moved from the main window into the TopBar scene, by their old name.
const TOP_BAR_NODES: Array[String] = [ const TOP_BAR_NODES: Array[String] = [
"TopPanelContainer", "Title", "SearchButton", "SyncButton", "StatisticsButton", "TopPanelContainer", "Title", "SearchButton", "SyncButton", "StatisticsButton",
@@ -60,6 +66,8 @@ static func new_path(old_path: String) -> String:
var first: String = old_path.get_slice("/", 0) var first: String = old_path.get_slice("/", 0)
if first in QUIZ_NODES: if first in QUIZ_NODES:
return "Quiz/" + old_path return "Quiz/" + old_path
if first in RENAMED_QUIZ_NODES:
return "Quiz/" + RENAMED_QUIZ_NODES[first] + old_path.trim_prefix(first)
if first in TOP_BAR_NODES: if first in TOP_BAR_NODES:
return "TopBar/" + old_path return "TopBar/" + old_path
return old_path return old_path
-9
View File
@@ -240,12 +240,3 @@ func test_log_adds_one_row_per_point_with_padded_time() -> void:
var time_pattern: RegEx = RegEx.create_from_string("^\\d\\d:\\d\\d:\\d\\d Crono got a point$") var time_pattern: RegEx = RegEx.create_from_string("^\\d\\d:\\d\\d:\\d\\d Crono got a point$")
assert_object(time_pattern.search((log_box.get_child(0) as Label).text)).is_not_null() assert_object(time_pattern.search((log_box.get_child(0) as Label).text)).is_not_null()
assert_str((log_box.get_child(1) as Label).text).ends_with("Crono lost a point") assert_str((log_box.get_child(1) as Label).text).ends_with("Crono lost a point")
func test_inspiration_list_alternates_row_backgrounds() -> void:
var rows: Array[Node] = _quiz.get_node("ScrollContainer/VBoxContainer").get_children()
assert_int(rows.size()).is_equal(2)
assert_str((rows[0] as ListRow).text).is_equal("Chrono Trigger")
assert_that((rows[0] as ListRow).get_theme_stylebox("panel")) \
.is_not_equal((rows[1] as ListRow).get_theme_stylebox("panel"))
@@ -0,0 +1,79 @@
extends GdUnitTestSuite
## The music list on its own: one row per song in the playlist, and clicking a row
## tells which song was clicked.
var _snapshot: AutoloadSnapshot
var _runner: GdUnitSceneRunner
var _panel: MusicListPanel
func before_test() -> void:
_snapshot = AutoloadSnapshot.take([Settings, Playlist])
Playlist.reset_playlist()
for i: int in 3:
var song: SongObject = SongObject.new()
song.song_number = i
song.soundtrack_title = "Soundtrack %d" % i
song.song_title = "Song %d" % i
song.has_played = true
Playlist.add_to_playlist(song)
_runner = scene_runner("res://MusicListPanel.tscn")
_panel = _runner.scene()
func after_test() -> void:
await await_idle_frame()
_snapshot.restore()
func _rows() -> Array[Node]:
return _panel.get_node("ScrollContainer/MusicList").get_children()
func test_starts_empty() -> void:
assert_array(_rows()).is_empty()
func test_refresh_shows_one_row_per_song_in_order() -> void:
_panel.refresh()
assert_array(_rows().map(func(row: SongListObject) -> int: return row.song_object.song_number)) \
.contains_exactly([0, 1, 2])
func test_refresh_again_replaces_the_rows() -> void:
_panel.refresh()
Playlist.song_object_array.pop_back()
_panel.refresh()
assert_int(_rows().size()).is_equal(2)
func test_clicking_a_row_tells_which_song() -> void:
_panel.refresh()
await _runner.simulate_frames(2)
var signals: GdUnitSignalAssert = assert_signal(monitor_signals(_panel))
var row: Control = _rows()[1]
# The left end of the row is the song text, not one of the player icons
var target: Vector2 = row.get_global_rect().position + Vector2(20, row.size.y / 2)
_runner.set_mouse_position(_panel.get_viewport().get_final_transform() * target)
await _runner.simulate_mouse_button_pressed(MOUSE_BUTTON_LEFT)
await _runner.await_input_processed()
await signals.is_emitted("song_clicked", [1])
func test_right_click_does_nothing() -> void:
_panel.refresh()
await _runner.simulate_frames(2)
var signals: GdUnitSignalAssert = assert_signal(monitor_signals(_panel))
var row: Control = _rows()[0]
_runner.set_mouse_position(_panel.get_viewport().get_final_transform()
* (row.get_global_rect().position + Vector2(20, row.size.y / 2)))
await _runner.simulate_mouse_button_pressed(MOUSE_BUTTON_RIGHT)
await _runner.await_input_processed()
await signals.wait_until(200).is_not_emitted("song_clicked")
@@ -0,0 +1 @@
uid://c1dj75bnbdjle
+1 -1
View File
@@ -144,7 +144,7 @@ func test_player_list_shows_rows_in_player_order() -> void:
var robo: PlayerObject = _add_player("Robo") var robo: PlayerObject = _add_player("Robo")
var rows: Array[Node] = _quiz.get_node( var rows: Array[Node] = _quiz.get_node(
"Players/VBoxContainer/ScrollContainer/PlayersListVBoxContainer").get_children() "PlayersPanel/VBoxContainer/ScrollContainer/PlayersListVBoxContainer").get_children()
assert_array(rows.map(func(row: Player) -> PlayerObject: return row.player_object)) \ assert_array(rows.map(func(row: Player) -> PlayerObject: return row.player_object)) \
.contains_exactly([_marle, _lucca, robo]) .contains_exactly([_marle, _lucca, robo])
+146
View File
@@ -0,0 +1,146 @@
extends GdUnitTestSuite
## The player list on its own: adding players through the name field, removing them
## and asking for a new character.
var _snapshot: AutoloadSnapshot
var _runner: GdUnitSceneRunner
var _panel: PlayersPanel
func before_test() -> void:
_snapshot = AutoloadSnapshot.take([Settings])
Settings.player_array.clear()
Settings.edit_players = false
_runner = scene_runner("res://PlayersPanel.tscn")
_panel = _runner.scene()
func after_test() -> void:
await await_idle_frame()
_snapshot.restore()
func _rows() -> Array[Node]:
return _panel.players_list_container.get_children()
func _add(player_name: String) -> void:
_panel.new_player_name_field.text = player_name
_panel.add_player()
func _click(control: Control) -> void:
_runner.set_mouse_position(_panel.get_viewport().get_final_transform()
* control.get_global_rect().get_center())
await _runner.simulate_mouse_button_pressed(MOUSE_BUTTON_LEFT)
await _runner.await_input_processed()
func test_add_button_shows_and_hides_the_add_row() -> void:
assert_bool(_panel.add_player_container.visible).is_false()
await _click(_panel.add_players_button)
assert_bool(_panel.add_player_container.visible).is_true()
assert_bool(Settings.edit_players).is_true()
await _click(_panel.add_players_button)
assert_bool(_panel.add_player_container.visible).is_false()
assert_bool(Settings.edit_players).is_false()
func test_adding_a_name_adds_a_player_and_a_row() -> void:
var signals: GdUnitSignalAssert = assert_signal(monitor_signals(_panel))
_add("Crono")
assert_int(Settings.player_array.size()).is_equal(1)
var crono: PlayerObject = Settings.player_array[0]
assert_str(crono.player_name).is_equal("Crono")
await signals.is_emitted("player_added", [crono])
assert_str(_panel.new_player_name_field.text).is_empty()
assert_object((_rows()[0] as Player).player_object).is_same(crono)
func test_add_button_in_the_row_adds_the_player() -> void:
await _click(_panel.add_players_button)
_panel.new_player_name_field.text = "Marle"
await _click(_panel.add_player_button)
assert_int(Settings.player_array.size()).is_equal(1)
func test_enter_in_the_name_field_adds_the_player() -> void:
await _click(_panel.add_players_button)
_panel.new_player_name_field.text = "Lucca"
_panel.new_player_name_field.grab_focus()
_runner.simulate_key_pressed(KEY_ENTER)
await _runner.await_input_processed()
assert_array(Settings.player_array.map(
func(p: PlayerObject) -> String: return p.player_name)).contains_exactly(["Lucca"])
func test_empty_name_adds_nobody() -> void:
var signals: GdUnitSignalAssert = assert_signal(monitor_signals(_panel))
_add("")
assert_array(Settings.player_array).is_empty()
assert_array(_rows()).is_empty()
await signals.wait_until(200).is_not_emitted("player_added")
func test_cannot_add_more_than_max_players() -> void:
for i: int in Settings.MAX_NUMBER_OF_PLAYERS + 1:
_add("Player %d" % i)
assert_int(Settings.player_array.size()).is_equal(Settings.MAX_NUMBER_OF_PLAYERS)
assert_int(_rows().size()).is_equal(Settings.MAX_NUMBER_OF_PLAYERS)
func test_rows_follow_player_order_and_numbering() -> void:
_add("Crono")
_add("Marle")
_add("Lucca")
assert_array(_rows().map(func(row: Player) -> String: return row.player_object.player_name)) \
.contains_exactly(["Crono", "Marle", "Lucca"])
assert_array(_rows().map(func(row: Player) -> int: return row.list_index)) \
.contains_exactly([0, 1, 2])
func test_remove_button_removes_the_player_and_row() -> void:
_add("Crono")
_add("Marle")
var crono: PlayerObject = Settings.player_array[0]
var signals: GdUnitSignalAssert = assert_signal(monitor_signals(_panel))
(_rows()[0].get_node("HBoxContainer/RemovePlayer") as BaseButton).pressed.emit()
await signals.is_emitted("player_removed", [crono])
assert_array(Settings.player_array.map(
func(p: PlayerObject) -> String: return p.player_name)).contains_exactly(["Marle"])
assert_int(_rows().size()).is_equal(1)
assert_int((_rows()[0] as Player).list_index).is_equal(0)
func test_character_button_asks_for_a_new_character() -> void:
_add("Crono")
var row: Player = _rows()[0]
var signals: GdUnitSignalAssert = assert_signal(monitor_signals(_panel))
(row.get_node("HBoxContainer/Character") as BaseButton).pressed.emit()
await signals.is_emitted("change_character_requested", [row, "Crono"])
func test_reload_shows_new_scores() -> void:
_add("Crono")
Settings.player_array[0].player_score = 3
_panel.reload()
assert_str(((_rows()[0] as Player).get_node("HBoxContainer/Name") as Label).text) \
.ends_with("Crono: 3")
@@ -0,0 +1 @@
uid://chtn6hbf4vqb6
+48
View File
@@ -50,6 +50,12 @@ func test_top_bar_is_drawn_below_the_quiz() -> void:
.is_less(_window.get_node("Quiz").get_index()) .is_less(_window.get_node("Quiz").get_index())
func test_quiz_parts_are_their_own_scenes() -> void:
for node_name: String in ["MusicListPanel", "PlayersPanel", "InspirationList"]:
assert_str(_quiz.get_node(node_name).scene_file_path) \
.is_equal("res://%s.tscn" % node_name)
func test_top_bar_holds_only_title_and_buttons() -> void: func test_top_bar_holds_only_title_and_buttons() -> void:
assert_array(_children(_window.get_node("TopBar"))) \ assert_array(_children(_window.get_node("TopBar"))) \
.contains_exactly_in_any_order(TOP_BAR_CHILDREN) .contains_exactly_in_any_order(TOP_BAR_CHILDREN)
@@ -89,6 +95,48 @@ func test_clicking_quiz_buttons_plays_and_shows_answer() -> void:
.is_equal("Chrono Trigger") .is_equal("Chrono Trigger")
func test_clicking_a_song_in_the_music_list_plays_it() -> void:
_next_song_is(SONG_INFO[1])
await _play_first_song()
_press("NextButton")
# Changing song waits a moment for the old one to stop
await get_tree().create_timer(0.7).timeout
assert_int(Playlist.currently_playing_song).is_equal(1)
var rows: Array[Node] = _quiz.get_node("MusicListPanel/ScrollContainer/MusicList") \
.get_children()
var first_row: Control = rows.filter(
func(row: SongListObject) -> bool: return row.song_object.song_number == 0)[0]
await _click_at(first_row.get_global_rect().position + Vector2(20, first_row.size.y / 2))
await get_tree().create_timer(0.7).timeout
assert_int(Playlist.currently_playing_song).is_equal(0)
func test_points_from_the_player_list_reach_the_song_and_log() -> void:
await _play_first_song()
var crono: PlayerObject = _add_player("Crono")
(_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((_quiz.get_node("%Log").get_node("%LogVBoxContainer") as Node).get_child_count()) \
.is_equal(1)
func test_character_button_in_the_player_list_picks_a_new_character() -> void:
var crono: PlayerObject = _add_player("Crono")
var character_select: PopupWindow = _quiz.get_node("NewCharacterSelect")
(_player_row(crono).get_node("HBoxContainer/Character") as BaseButton).pressed.emit()
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)
assert_object(crono.character).is_same(texture)
func test_quiz_popup_backdrop_covers_the_top_bar() -> void: func test_quiz_popup_backdrop_covers_the_top_bar() -> void:
var settings: PopupWindow = _quiz.get_node("SettingsWindow") var settings: PopupWindow = _quiz.get_node("SettingsWindow")
settings.open() settings.open()