From 3ff29730936b28527b6e36831d58a42779b74ece Mon Sep 17 00:00:00 2001 From: Nemrav <> Date: Sat, 11 Jan 2025 22:45:59 -0400 Subject: [PATCH] Add multiplayer menu --- .../localisation/locales/en_GB/menus.csv | 42 ++++ game/project.godot | 1 + game/src/Game/Autoload/Events.gd | 2 + game/src/Game/Autoload/Events/Multiplayer.gd | 64 ++++++ game/src/Game/GameMenu.gd | 11 + game/src/Game/GameMenu.tscn | 12 +- game/src/Game/Menu/LobbyMenu/LobbyMenu.gd | 2 +- game/src/Game/Menu/MainMenu/MainMenu.gd | 7 +- .../MultiplayerMenu/ConnectionFailDialog.gd | 21 ++ .../MultiplayerMenu/DirectConnectionEntry.gd | 23 ++ .../DirectConnectionEntry.tscn | 50 +++++ .../MultiplayerMenu/DirectConnectionTab.gd | 74 +++++++ .../MultiplayerMenu/DirectConnectionTab.tscn | 110 ++++++++++ game/src/Game/Menu/MultiplayerMenu/HostTab.gd | 55 +++++ .../Game/Menu/MultiplayerMenu/HostTab.tscn | 74 +++++++ .../Menu/MultiplayerMenu/MultiplayerMenu.gd | 206 ++++++++++++++++++ .../Menu/MultiplayerMenu/MultiplayerMenu.tscn | 90 ++++++++ .../Menu/MultiplayerMenu/PasswordDialog.gd | 13 ++ .../Game/Menu/MultiplayerMenu/SecretEdit.gd | 29 +++ .../Game/Menu/MultiplayerMenu/SecretEdit.tscn | 23 ++ .../Game/Menu/SaveLoadMenu/SaveLoadMenu.gd | 2 +- 21 files changed, 904 insertions(+), 7 deletions(-) create mode 100644 game/src/Game/Autoload/Events/Multiplayer.gd create mode 100644 game/src/Game/Menu/MultiplayerMenu/ConnectionFailDialog.gd create mode 100644 game/src/Game/Menu/MultiplayerMenu/DirectConnectionEntry.gd create mode 100644 game/src/Game/Menu/MultiplayerMenu/DirectConnectionEntry.tscn create mode 100644 game/src/Game/Menu/MultiplayerMenu/DirectConnectionTab.gd create mode 100644 game/src/Game/Menu/MultiplayerMenu/DirectConnectionTab.tscn create mode 100644 game/src/Game/Menu/MultiplayerMenu/HostTab.gd create mode 100644 game/src/Game/Menu/MultiplayerMenu/HostTab.tscn create mode 100644 game/src/Game/Menu/MultiplayerMenu/MultiplayerMenu.gd create mode 100644 game/src/Game/Menu/MultiplayerMenu/MultiplayerMenu.tscn create mode 100644 game/src/Game/Menu/MultiplayerMenu/PasswordDialog.gd create mode 100644 game/src/Game/Menu/MultiplayerMenu/SecretEdit.gd create mode 100644 game/src/Game/Menu/MultiplayerMenu/SecretEdit.tscn diff --git a/game/assets/localisation/locales/en_GB/menus.csv b/game/assets/localisation/locales/en_GB/menus.csv index d5db7306..12225bba 100644 --- a/game/assets/localisation/locales/en_GB/menus.csv +++ b/game/assets/localisation/locales/en_GB/menus.csv @@ -81,6 +81,48 @@ OPTIONS_SOUND_EXPLODE_EARS;Explode Eardrums on Startup? OPTIONS_CONTROLS;Controls OPTIONS_OTHER;Other +;; Multiplayer Menu +MP_BACK;X +MP_SERVERBROWSER;Online +MP_LANBROWSER;LAN +MP_DIRECT_CONNECTION;Direct Connection +MP_HOST;Host + +;; Server Browser Tab + +;; LAN Tab + +;; Direct Connection Tab +MP_USERNAME_ENTRY;Username + +MP_DIRECT_NAME_ENTRY;Connection Name +MP_DIRECT_IP_ENTRY;Connection IP +MP_DIRECT_ADD_IP;Add IP +MP_DIRECT_SAVE_IPS;Save IPs +MP_DIRECT_REVERT_IPS; Revert IPs + +MP_DIRECT_SAVED_IPS;Saved IPs +MP_DIRECT_CONNECT;Connect +MP_DIRECT_EDIT;Edit +MP_DIRECT_DELETE;X + +;; Connection +MP_CONNECT_PASSWORD_ENTRY;Password +MP_CONNECT_PASSWORD_CANCEL; Cancel +MP_CONNECT_PASSWORD_OK;Connect + +MP_CONNECT_FAIL;Connection Failed +MP_CONNECT_FAIL_OK; Ok +MP_CONNECT_FAIL_REASON;Reason: +MP_CONNECT_FAIL_PASSWORD;Incorrect Password +MP_CONNECT_FAIL_NO_SERVER;Could not Find the specified Server +MP_CONNECT_FAIL_BAD_IP; Invalid IP + +;; Host Tab +MP_HOST_GAME_NAME_ENTRY;Game Name +MP_HOST_HOST;Host +MP_HOST_IS_LAN_ONLY;LAN Connections only + ;; Credits Menu CREDITS_BACK;Back to Main Menu diff --git a/game/project.godot b/game/project.godot index cd3e6641..953b480c 100644 --- a/game/project.godot +++ b/game/project.godot @@ -163,6 +163,7 @@ limits/message_queue/max_size_kb=16384 settings/settings_file_path="user://settings.cfg" data/saves_directory="user://saves" project_file_req_comment.editor="FS-2, FS-20, FS-21, FS-22" +settings/ips_file_path="user://ips.cfg" [rendering] diff --git a/game/src/Game/Autoload/Events.gd b/game/src/Game/Autoload/Events.gd index 091a1226..10e34b61 100644 --- a/game/src/Game/Autoload/Events.gd +++ b/game/src/Game/Autoload/Events.gd @@ -4,8 +4,10 @@ extends Node var Options : OptionsEventsObject +var Multiplayer : MultiplayerEventsObject var NationManagementScreens : NationManagementScreensEventsObject func _init() -> void: Options = OptionsEventsObject.new() NationManagementScreens = NationManagementScreensEventsObject.new() + Multiplayer = MultiplayerEventsObject.new() diff --git a/game/src/Game/Autoload/Events/Multiplayer.gd b/game/src/Game/Autoload/Events/Multiplayer.gd new file mode 100644 index 00000000..cf17151b --- /dev/null +++ b/game/src/Game/Autoload/Events/Multiplayer.gd @@ -0,0 +1,64 @@ +class_name MultiplayerEventsObject +extends RefCounted + +signal save_ips(save_file: ConfigFile) +signal load_ips(load_file: ConfigFile) + +const ips_file_path_setting : String = "openvic/settings/ips_file_path" +const ips_file_path_default : String = "user://ips.cfg" + +var _saved_ips_file_path : String = ProjectSettings.get_setting(ips_file_path_setting, ips_file_path_default) +var _saved_ips_file := ConfigFile.new() + +var config_file_loaded : bool = false + +const USER : StringName = &"USER" +const SERVER_NAMES : StringName = &"SERVER_NAMES" +const SERVER_IPS : StringName = &"SERVER_IPS" +const PLAYER_NAME : StringName = &"player_name" +const HOST_GAME_NAME : StringName = &"host_game_name" +const HOST_GAME_PASSWORD : StringName = &"host_game_password" + +const DEFAULT_PLAYER_NAME : StringName = &"Player1" +const DEFAULT_GAME_NAME : StringName = &"Player1's Game" +const DEFAULT_GAME_PASSWORD : StringName = &"" + +func get_override_path() -> String: + var override_path : String = ProjectSettings.get_setting("application/config/project_settings_override", "") + if override_path.is_empty(): + override_path = _saved_ips_file_path + return override_path + +func get_ips_config_file() -> ConfigFile: + if config_file_loaded: + return _saved_ips_file + else: + return load_ips_config_file() + +func load_ips_config_file() -> ConfigFile: + var override_path := get_override_path() + if FileAccess.file_exists(override_path): + if _saved_ips_file.load(override_path) != OK: + push_error("Failed to load overrides from %s" % override_path) + else: + if !_saved_ips_file.has_section_key(USER,PLAYER_NAME): + _saved_ips_file.set_value(USER,PLAYER_NAME,DEFAULT_PLAYER_NAME) + if !_saved_ips_file.has_section_key(USER,HOST_GAME_NAME): + _saved_ips_file.set_value(USER,HOST_GAME_NAME,DEFAULT_GAME_NAME) + if !_saved_ips_file.has_section_key(USER,HOST_GAME_PASSWORD): + _saved_ips_file.set_value(USER,HOST_GAME_PASSWORD,DEFAULT_GAME_PASSWORD) + save_ips_config_file() + load_ips.emit(_saved_ips_file) + config_file_loaded = true + return _saved_ips_file + +func save_ips_config_file() -> void: + var override_path := get_override_path() + _saved_ips_file.save(_saved_ips_file_path) + if _saved_ips_file.save(override_path) != OK: + push_error("Failed to save ip overrides to %s" % override_path) + else: + save_ips.emit(_saved_ips_file) + +func _init() -> void: + load_ips_config_file() diff --git a/game/src/Game/GameMenu.gd b/game/src/Game/GameMenu.gd index 974c1d94..cbb60e53 100644 --- a/game/src/Game/GameMenu.gd +++ b/game/src/Game/GameMenu.gd @@ -2,6 +2,7 @@ extends Control @export var _main_menu : Control @export var _options_menu : Control +@export var _multiplayer_menu : Control @export var _lobby_menu : Control @export var _credits_menu : Control @@ -40,3 +41,13 @@ func _on_credits_back_button_pressed() -> void: func _on_main_menu_credits_button_pressed() -> void: _credits_menu.show() _main_menu.hide() + + +func _on_multiplayer_menu_back_button_pressed() -> void: + _multiplayer_menu.hide() + _main_menu.show() + + +func _on_main_menu_multiplayer_button_pressed() -> void: + _multiplayer_menu.show() + _main_menu.hide() diff --git a/game/src/Game/GameMenu.tscn b/game/src/Game/GameMenu.tscn index 73287a54..6e034bd8 100644 --- a/game/src/Game/GameMenu.tscn +++ b/game/src/Game/GameMenu.tscn @@ -1,13 +1,14 @@ -[gd_scene load_steps=7 format=3 uid="uid://o4u142w4qkln"] +[gd_scene load_steps=8 format=3 uid="uid://o4u142w4qkln"] [ext_resource type="Script" path="res://src/Game/GameMenu.gd" id="1_cafwe"] [ext_resource type="PackedScene" uid="uid://bp5n3mlu45ygw" path="res://src/Game/Menu/MainMenu/MainMenu.tscn" id="2_2jbkh"] [ext_resource type="PackedScene" uid="uid://cnbfxjy1m6wja" path="res://src/Game/Menu/OptionMenu/OptionsMenu.tscn" id="3_111lv"] [ext_resource type="PackedScene" uid="uid://c8knthxkwj1uj" path="res://src/Game/Menu/CreditsMenu/CreditsMenu.tscn" id="4_n0hoo"] [ext_resource type="PackedScene" uid="uid://do60kx0d3nrh4" path="res://src/Game/Menu/LobbyMenu/LobbyMenu.tscn" id="4_nofk1"] +[ext_resource type="PackedScene" uid="uid://btri1i0hkhdsh" path="res://src/Game/Menu/MultiplayerMenu/MultiplayerMenu.tscn" id="4_s7nkl"] [ext_resource type="PackedScene" uid="uid://cvl76duuym1wq" path="res://src/Game/MusicConductor/MusicPlayer.tscn" id="6_lts1m"] -[node name="GameMenu" type="Control" node_paths=PackedStringArray("_main_menu", "_options_menu", "_lobby_menu", "_credits_menu")] +[node name="GameMenu" type="Control" node_paths=PackedStringArray("_main_menu", "_options_menu", "_multiplayer_menu", "_lobby_menu", "_credits_menu")] layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 @@ -17,6 +18,7 @@ grow_vertical = 2 script = ExtResource("1_cafwe") _main_menu = NodePath("MainMenu") _options_menu = NodePath("OptionsMenu") +_multiplayer_menu = NodePath("MultiplayerMenu") _lobby_menu = NodePath("LobbyMenu") _credits_menu = NodePath("CreditsMenu") @@ -28,6 +30,10 @@ metadata/_edit_vertical_guides_ = [251.0, 269.0, 504.0, 523.0, 15.0, 759.0, 777. visible = false layout_mode = 1 +[node name="MultiplayerMenu" parent="." instance=ExtResource("4_s7nkl")] +visible = false +layout_mode = 1 + [node name="LobbyMenu" parent="." instance=ExtResource("4_nofk1")] visible = false layout_mode = 1 @@ -46,8 +52,10 @@ offset_right = -34.0 grow_horizontal = 0 [connection signal="credits_button_pressed" from="MainMenu" to="." method="_on_main_menu_credits_button_pressed"] +[connection signal="multiplayer_button_pressed" from="MainMenu" to="." method="_on_main_menu_multiplayer_button_pressed"] [connection signal="new_game_button_pressed" from="MainMenu" to="." method="_on_main_menu_new_game_button_pressed"] [connection signal="options_button_pressed" from="MainMenu" to="." method="_on_main_menu_options_button_pressed"] [connection signal="back_button_pressed" from="OptionsMenu" to="." method="_on_options_menu_back_button_pressed"] +[connection signal="back_button_pressed" from="MultiplayerMenu" to="." method="_on_multiplayer_menu_back_button_pressed"] [connection signal="back_button_pressed" from="LobbyMenu" to="." method="_on_lobby_menu_back_button_pressed"] [connection signal="back_button_pressed" from="CreditsMenu" to="." method="_on_credits_back_button_pressed"] diff --git a/game/src/Game/Menu/LobbyMenu/LobbyMenu.gd b/game/src/Game/Menu/LobbyMenu/LobbyMenu.gd index ca6b3fb9..f40f1e99 100644 --- a/game/src/Game/Menu/LobbyMenu/LobbyMenu.gd +++ b/game/src/Game/Menu/LobbyMenu/LobbyMenu.gd @@ -22,7 +22,7 @@ signal start_date_selected(index : int) func filter_for_tag(tag : StringName) -> void: for child : Control in game_select_save_list.get_children(): - if tag == &"": + if tag.is_empty(): child.show() else: if tag == child.resource.session_tag: diff --git a/game/src/Game/Menu/MainMenu/MainMenu.gd b/game/src/Game/Menu/MainMenu/MainMenu.gd index cc8841f9..b5983bc9 100644 --- a/game/src/Game/Menu/MainMenu/MainMenu.gd +++ b/game/src/Game/Menu/MainMenu/MainMenu.gd @@ -3,6 +3,8 @@ extends Control signal options_button_pressed signal new_game_button_pressed signal credits_button_pressed +signal multiplayer_button_pressed +signal continue_button_pressed @export var _new_game_button : BaseButton @@ -19,14 +21,13 @@ func _on_new_game_button_pressed() -> void: print("Start a new game!") new_game_button_pressed.emit() - func _on_continue_button_pressed() -> void: print("Continue last game!") - + continue_button_pressed.emit() func _on_multi_player_button_pressed() -> void: print("Have fun with friends!") - + multiplayer_button_pressed.emit() func _on_options_button_pressed() -> void: print("Check out some options!") diff --git a/game/src/Game/Menu/MultiplayerMenu/ConnectionFailDialog.gd b/game/src/Game/Menu/MultiplayerMenu/ConnectionFailDialog.gd new file mode 100644 index 00000000..a448ba96 --- /dev/null +++ b/game/src/Game/Menu/MultiplayerMenu/ConnectionFailDialog.gd @@ -0,0 +1,21 @@ +extends AcceptDialog +class_name ConnectionFailDialog + +enum FAIL_REASONS {NO_SERVER, PASSWORD, BAD_IP} + +@export var reason_text : String +@export var no_server_fail_text : String +@export var bad_password_fail_text : String +@export var bad_ip_text : String + +@onready var reason_text_map : Dictionary = { + FAIL_REASONS.NO_SERVER : no_server_fail_text, + FAIL_REASONS.PASSWORD : bad_password_fail_text, + FAIL_REASONS.BAD_IP : bad_ip_text +} + +@export var label : Label + +func display(reason : FAIL_REASONS) -> void: + label.text = "%s %s" % [tr(reason_text), tr(reason_text_map[reason])] + popup_centered() diff --git a/game/src/Game/Menu/MultiplayerMenu/DirectConnectionEntry.gd b/game/src/Game/Menu/MultiplayerMenu/DirectConnectionEntry.gd new file mode 100644 index 00000000..35da725f --- /dev/null +++ b/game/src/Game/Menu/MultiplayerMenu/DirectConnectionEntry.gd @@ -0,0 +1,23 @@ +extends HBoxContainer +class_name DirectConnectionEntry + +@export var ip_edit : SecretEdit +@export var name_edit : LineEdit +@export var connect_button : Button +@export var delete_button : Button + +# Collects connect and delete presses from its children, and emits +# the corresponding signals for the connection controller to handle + +signal connect_to_ip(ip : String) +signal delete() + +func setup(name_in : StringName, ip : StringName) -> void: + name_edit.text = name_in + ip_edit.set_text(ip) + +func _on_connect_pressed() -> void: + connect_to_ip.emit(ip_edit.get_text()) + +func _on_delete_pressed() -> void: + delete.emit() diff --git a/game/src/Game/Menu/MultiplayerMenu/DirectConnectionEntry.tscn b/game/src/Game/Menu/MultiplayerMenu/DirectConnectionEntry.tscn new file mode 100644 index 00000000..6862b58c --- /dev/null +++ b/game/src/Game/Menu/MultiplayerMenu/DirectConnectionEntry.tscn @@ -0,0 +1,50 @@ +[gd_scene load_steps=3 format=3 uid="uid://br26w6mwqilyr"] + +[ext_resource type="Script" path="res://src/Game/Menu/MultiplayerMenu/DirectConnectionEntry.gd" id="1_8v0qa"] +[ext_resource type="PackedScene" uid="uid://dcsy6w6ow2xxg" path="res://src/Game/Menu/MultiplayerMenu/SecretEdit.tscn" id="2_3yivm"] + +[node name="DirectConnectionEntry" type="HBoxContainer" node_paths=PackedStringArray("ip_edit", "name_edit", "connect_button", "delete_button")] +anchors_preset = 10 +anchor_right = 1.0 +offset_bottom = 31.0 +grow_horizontal = 2 +script = ExtResource("1_8v0qa") +ip_edit = NodePath("SecretEdit") +name_edit = NodePath("EditName") +connect_button = NodePath("Connect") +delete_button = NodePath("Delete") + +[node name="EditName" type="LineEdit" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Game1" +max_length = 32 +editable = false + +[node name="VSeparator" type="VSeparator" parent="."] +layout_mode = 2 + +[node name="SecretEdit" parent="." instance=ExtResource("2_3yivm")] +layout_mode = 2 +size_flags_horizontal = 3 +editable = false + +[node name="VSeparator2" type="VSeparator" parent="."] +layout_mode = 2 + +[node name="Connect" type="Button" parent="."] +layout_mode = 2 +text = "MP_DIRECT_CONNECT" + +[node name="Edit" type="CheckButton" parent="."] +layout_mode = 2 +text = "MP_DIRECT_EDIT" + +[node name="Delete" type="Button" parent="."] +layout_mode = 2 +text = "MP_DIRECT_DELETE" + +[connection signal="pressed" from="Connect" to="." method="_on_connect_pressed"] +[connection signal="toggled" from="Edit" to="EditName" method="set_editable"] +[connection signal="toggled" from="Edit" to="SecretEdit" method="set_editable"] +[connection signal="pressed" from="Delete" to="." method="_on_delete_pressed"] diff --git a/game/src/Game/Menu/MultiplayerMenu/DirectConnectionTab.gd b/game/src/Game/Menu/MultiplayerMenu/DirectConnectionTab.gd new file mode 100644 index 00000000..3ae15e5d --- /dev/null +++ b/game/src/Game/Menu/MultiplayerMenu/DirectConnectionTab.gd @@ -0,0 +1,74 @@ +extends HBoxContainer +class_name DirectConnectionTab + +signal connect(ip : String, player_name : String) +signal save_ips(names : PackedStringArray, ips : PackedStringArray) +signal revert_ips() +signal player_name_changed(player_name : String) + +@export var initial_focus: Control +@export var ip_edit : SecretEdit +@export var player_name_edit : LineEdit +@export var game_name_edit : LineEdit +@export var IP_grid : VBoxContainer + +var connection_entry : PackedScene = preload("res://src/Game/Menu/MultiplayerMenu/DirectConnectionEntry.tscn") + +func _notification(what : int) -> void: + match(what): + NOTIFICATION_VISIBILITY_CHANGED: + if visible and is_inside_tree(): + initial_focus.grab_focus() + +func _on_connect_pressed() -> void: + var player_name : StringName = player_name_edit.text + if player_name.is_empty(): + player_name = player_name_edit.placeholder_text + connect.emit(ip_edit.get_text(),player_name) + +func _on_connect_pressed_custom_ip(ip : String) -> void: + var player_name : StringName = player_name_edit.text + if player_name.is_empty(): + player_name = player_name_edit.placeholder_text + connect.emit(ip,player_name) + +func initial_setup(player_name : StringName) -> void: + player_name_edit.text = player_name + +func new_ip_entry() -> void: + var game_name : String = game_name_edit.text + if game_name.is_empty(): + game_name_edit.placeholder_text + add_ip_entry_to_list(game_name,ip_edit.get_text()) + +func add_ip_entry_to_list(connection_name : String, ip : String) -> void: + var entry : DirectConnectionEntry = connection_entry.instantiate() + IP_grid.add_child(entry) + + entry.name_edit.text = connection_name + entry.ip_edit.set_text(ip) + + entry.connect_to_ip.connect(_on_connect_pressed_custom_ip) + entry.delete.connect(delete_entry.bind(entry)) + +func delete_entry(entry : DirectConnectionEntry) -> void: + entry.queue_free() + +func set_player_name(player_name : String) -> void: + player_name_edit.text = player_name + +func _on_save_ips_pressed() -> void: + var names : PackedStringArray = [] + var ips : PackedStringArray = [] + for entry : DirectConnectionEntry in IP_grid.get_children(): + names.push_back(entry.name_edit.text) + ips.push_back(entry.ip_edit.get_text()) + save_ips.emit(names,ips) + +func _on_revert_ips_pressed() -> void: + for child : DirectConnectionEntry in IP_grid.get_children(): + child.queue_free() + revert_ips.emit() + +func _on_player_name_changed(new_text : String) -> void: + player_name_changed.emit(new_text) diff --git a/game/src/Game/Menu/MultiplayerMenu/DirectConnectionTab.tscn b/game/src/Game/Menu/MultiplayerMenu/DirectConnectionTab.tscn new file mode 100644 index 00000000..c882de5d --- /dev/null +++ b/game/src/Game/Menu/MultiplayerMenu/DirectConnectionTab.tscn @@ -0,0 +1,110 @@ +[gd_scene load_steps=3 format=3 uid="uid://28ummh3fgokc"] + +[ext_resource type="Script" path="res://src/Game/Menu/MultiplayerMenu/DirectConnectionTab.gd" id="1_154ba"] +[ext_resource type="PackedScene" uid="uid://dcsy6w6ow2xxg" path="res://src/Game/Menu/MultiplayerMenu/SecretEdit.tscn" id="2_xhui1"] + +[node name="DirectConnectionTab" type="HBoxContainer" node_paths=PackedStringArray("initial_focus", "ip_edit", "player_name_edit", "game_name_edit", "IP_grid")] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +alignment = 1 +script = ExtResource("1_154ba") +initial_focus = NodePath("VBoxContainer/UsernameEntryGrid/PlayerNameEntry") +ip_edit = NodePath("VBoxContainer/NewIPEntryGrid/IPEdit") +player_name_edit = NodePath("VBoxContainer/UsernameEntryGrid/PlayerNameEntry") +game_name_edit = NodePath("VBoxContainer/NewIPEntryGrid/GameNameEdit") +IP_grid = NodePath("VBoxContainer/ScrollContainer/SavedIPGrid") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="UsernameEntryGrid" type="GridContainer" parent="VBoxContainer"] +layout_mode = 2 +columns = 2 + +[node name="UserName" type="Label" parent="VBoxContainer/UsernameEntryGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "MP_USERNAME_ENTRY" + +[node name="PlayerNameEntry" type="LineEdit" parent="VBoxContainer/UsernameEntryGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Player1" +max_length = 32 + +[node name="HSeparator" type="HSeparator" parent="VBoxContainer"] +layout_mode = 2 + +[node name="NewIPEntryGrid" type="GridContainer" parent="VBoxContainer"] +layout_mode = 2 +columns = 2 + +[node name="UserName" type="Label" parent="VBoxContainer/NewIPEntryGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "MP_DIRECT_NAME_ENTRY" + +[node name="GameNameEdit" type="LineEdit" parent="VBoxContainer/NewIPEntryGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Game1" +max_length = 32 + +[node name="IP" type="Label" parent="VBoxContainer/NewIPEntryGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "MP_DIRECT_IP_ENTRY" + +[node name="IPEdit" parent="VBoxContainer/NewIPEntryGrid" instance=ExtResource("2_xhui1")] +layout_mode = 2 + +[node name="Add_ip" type="Button" parent="VBoxContainer/NewIPEntryGrid"] +layout_mode = 2 +text = "MP_DIRECT_ADD_IP" + +[node name="Connect" type="Button" parent="VBoxContainer/NewIPEntryGrid"] +layout_mode = 2 +text = "MP_DIRECT_CONNECT" + +[node name="HSeparator2" type="HSeparator" parent="VBoxContainer"] +custom_minimum_size = Vector2(0, 10) +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer"] +layout_mode = 2 +text = "MP_DIRECT_SAVED_IPS" + +[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +horizontal_scroll_mode = 0 + +[node name="SavedIPGrid" type="VBoxContainer" parent="VBoxContainer/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="bottomGrid" type="GridContainer" parent="VBoxContainer"] +layout_mode = 2 +columns = 2 + +[node name="revert_ips" type="Button" parent="VBoxContainer/bottomGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "MP_DIRECT_REVERT_IPS" + +[node name="save_ips" type="Button" parent="VBoxContainer/bottomGrid"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "MP_DIRECT_SAVE_IPS" + +[connection signal="text_changed" from="VBoxContainer/UsernameEntryGrid/PlayerNameEntry" to="." method="_on_player_name_changed"] +[connection signal="pressed" from="VBoxContainer/NewIPEntryGrid/Add_ip" to="." method="new_ip_entry"] +[connection signal="pressed" from="VBoxContainer/NewIPEntryGrid/Connect" to="." method="_on_connect_pressed"] +[connection signal="pressed" from="VBoxContainer/bottomGrid/revert_ips" to="." method="_on_revert_ips_pressed"] +[connection signal="pressed" from="VBoxContainer/bottomGrid/save_ips" to="." method="_on_save_ips_pressed"] diff --git a/game/src/Game/Menu/MultiplayerMenu/HostTab.gd b/game/src/Game/Menu/MultiplayerMenu/HostTab.gd new file mode 100644 index 00000000..dab20648 --- /dev/null +++ b/game/src/Game/Menu/MultiplayerMenu/HostTab.gd @@ -0,0 +1,55 @@ +extends HBoxContainer +class_name HostTab + +@export var initial_focus: Control + +@export var player_name_entry : LineEdit +@export var game_name_entry : LineEdit +@export var password_entry : SecretEdit +@export var is_lan : CheckButton + +signal host(player_name : String, game_name : String, password : String, lan_only : bool) +signal player_name_changed(player_name : String) + +func _notification(what : int) -> void: + match(what): + NOTIFICATION_VISIBILITY_CHANGED: + if visible and is_inside_tree(): + initial_focus.grab_focus() + +func set_user_values(player_name : StringName, game_name : StringName, game_password : StringName) -> void: + player_name_entry.text = player_name + game_name_entry.text = game_name + password_entry.set_text(game_password) + +func _on_host_pressed() -> void: + var player_name := get_player_name() + var game_name := game_name_entry.text + host.emit(player_name,game_name,password_entry.get_text(), is_lan.button_pressed) + +func get_player_name() -> StringName: + var player_name := player_name_entry.text + if player_name.is_empty(): + player_name = player_name_entry.placeholder_text + return player_name + +func set_player_name(player_name : String) -> void: + player_name_entry.text = player_name + +func set_game_name(game_name : String) -> void: + game_name_entry.text = game_name + +func set_game_password(game_password : String) -> void: + password_entry.set_text(game_password) + +func get_game_name() -> StringName: + var game_name := game_name_entry.text + if game_name.is_empty(): + game_name = game_name_entry.placeholder_text + return game_name + +func get_password() -> StringName: + return password_entry.get_text() + +func _on_player_name_entry_text_changed(new_text: String) -> void: + player_name_changed.emit(new_text) diff --git a/game/src/Game/Menu/MultiplayerMenu/HostTab.tscn b/game/src/Game/Menu/MultiplayerMenu/HostTab.tscn new file mode 100644 index 00000000..5b01302a --- /dev/null +++ b/game/src/Game/Menu/MultiplayerMenu/HostTab.tscn @@ -0,0 +1,74 @@ +[gd_scene load_steps=3 format=3 uid="uid://b4cu0p1wlux3s"] + +[ext_resource type="Script" path="res://src/Game/Menu/MultiplayerMenu/HostTab.gd" id="1_23sa3"] +[ext_resource type="PackedScene" uid="uid://dcsy6w6ow2xxg" path="res://src/Game/Menu/MultiplayerMenu/SecretEdit.tscn" id="2_j4o6c"] + +[node name="HostTab" type="HBoxContainer" node_paths=PackedStringArray("initial_focus", "player_name_entry", "game_name_entry", "password_entry", "is_lan")] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +alignment = 1 +script = ExtResource("1_23sa3") +initial_focus = NodePath("VBoxContainer/GridContainer/PlayerNameEntry") +player_name_entry = NodePath("VBoxContainer/GridContainer/PlayerNameEntry") +game_name_entry = NodePath("VBoxContainer/GridContainer/GameNameEntry") +password_entry = NodePath("VBoxContainer/GridContainer/SecretEdit") +is_lan = NodePath("VBoxContainer/GridContainer/IsLanOnlyCheck") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="GridContainer" type="GridContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +columns = 2 + +[node name="UsernameLabel" type="Label" parent="VBoxContainer/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "MP_USERNAME_ENTRY" + +[node name="PlayerNameEntry" type="LineEdit" parent="VBoxContainer/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Player1" + +[node name="GameNameLabel" type="Label" parent="VBoxContainer/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "MP_HOST_GAME_NAME_ENTRY" + +[node name="GameNameEntry" type="LineEdit" parent="VBoxContainer/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "My Game" + +[node name="GamePasswordLabel" type="Label" parent="VBoxContainer/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "MP_CONNECT_PASSWORD_ENTRY" + +[node name="SecretEdit" parent="VBoxContainer/GridContainer" instance=ExtResource("2_j4o6c")] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="IsLanOnly" type="Label" parent="VBoxContainer/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "MP_HOST_IS_LAN_ONLY" + +[node name="IsLanOnlyCheck" type="CheckButton" parent="VBoxContainer/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Host" type="Button" parent="VBoxContainer/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "MP_HOST_HOST" + +[connection signal="text_changed" from="VBoxContainer/GridContainer/PlayerNameEntry" to="." method="_on_player_name_entry_text_changed"] +[connection signal="pressed" from="VBoxContainer/GridContainer/Host" to="." method="_on_host_pressed"] diff --git a/game/src/Game/Menu/MultiplayerMenu/MultiplayerMenu.gd b/game/src/Game/Menu/MultiplayerMenu/MultiplayerMenu.gd new file mode 100644 index 00000000..dee1f5bc --- /dev/null +++ b/game/src/Game/Menu/MultiplayerMenu/MultiplayerMenu.gd @@ -0,0 +1,206 @@ +extends PanelContainer + +signal back_button_pressed + +@export var _tab_container : TabContainer +@export var _password_dialog : PasswordDialog +@export var _connection_fail_dialog : ConnectionFailDialog + +@export var _host_tab : HostTab +@export var _direct_connection_tab : DirectConnectionTab + +var last_ip : StringName = &"" +var last_player_name : StringName = &"" +var last_validated_ip : Array[int] = [] + +var last_game_name : StringName = &"" +var last_game_password : StringName = &"" + +@onready var MP : MultiplayerEventsObject = Events.Multiplayer + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + #Server browser not implemented yet + _tab_container.set_tab_title(0, "MP_DIRECT_CONNECTION") + _tab_container.set_tab_title(1, "MP_HOST") + + # Prepare options menu before loading user settings + var tab_bar : TabBar = _tab_container.get_child(0, true) + # This ends up easier to manage then trying to manually recreate the TabContainer's behavior + # These buttons can be accessed regardless of the tab + var button_list := HBoxContainer.new() + button_list.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + button_list.alignment = BoxContainer.ALIGNMENT_END + tab_bar.add_child(button_list) + + var back_button := Button.new() + back_button.text = "MP_BACK" + back_button.pressed.connect(_on_back_button_pressed) + button_list.add_child(back_button) + get_viewport().get_window().close_requested.connect(_on_window_close_requested) + load_saved_ips() + +func _input(event : InputEvent) -> void: + if is_visible_in_tree(): + if event.is_action_pressed("ui_cancel"): + _on_back_button_pressed() + +func _on_back_button_pressed() -> void: + last_game_name = _host_tab.get_game_name() + last_game_password = _host_tab.get_password() + _save_user(last_player_name,last_game_name,last_game_password) + _direct_connection_tab._on_save_ips_pressed() + back_button_pressed.emit() + +func _on_window_close_requested() -> void: + last_game_name = _host_tab.get_game_name() + last_game_password = _host_tab.get_password() + _save_user(last_player_name,last_game_name,last_game_password) + _direct_connection_tab._on_save_ips_pressed() + +func _notification(what : int) -> void: + match what: + NOTIFICATION_CRASH: + _on_window_close_requested() + +func join_game(ip : String, player_name : String) -> bool: + var success : bool = false + + last_ip = ip + last_player_name = player_name + + var ip_segments : Array[int] = validate_ipv4(ip) + print(ip_segments) + if(ip_segments.size() != 4 && ip_segments.size() != 5): + _connection_fail_dialog.display(ConnectionFailDialog.FAIL_REASONS.BAD_IP) + return false + last_validated_ip = ip_segments + + #TODO: Send a request to the host asking if a password is needed + var needs_password : bool = check_password_needed(ip) + #TODO: if the connection fails, show the appropriate fail dialog + + if needs_password: + #Let the password dialog call the connect to game function + _password_dialog.clear_and_display() + else: + connect_to_game("") + return success + +func check_password_needed(ip : String) -> bool: + #TODO: perform the check + return true + +#This function continues from join_game after the player +#has entered a password in the password dialog +func connect_to_game(password : String) -> bool: + #Use last_ip and last_player_name + var success : bool = false + print("Connecting to a game: IP: %s, Username: %s, Password: %s" % [last_ip,last_player_name,password]) + #TODO: Connect, use last_validated_ip as the ip + + #TODO: if successful, move to the game lobby. Otherwise, show the appropriate fail dialog. + if success: + return true + else: + #TODO: replace this with actual error handling + if password.is_empty(): + _connection_fail_dialog.display(ConnectionFailDialog.FAIL_REASONS.PASSWORD) + else: + _connection_fail_dialog.display(ConnectionFailDialog.FAIL_REASONS.NO_SERVER) + return false + +#cg0: 127.0.0.1:80, cg1 = 123.456.789/localhost, cg2=123... only, cg3=localhost only, cg4=:8052, cg5=8052 +static var IP_PATTERN : StringName = &"^((\\d{1,3}.\\d{1,3}.\\d{1,3}.\\d{1,3})|(localhost))(:(\\d+))?$" +static var IP_REGEX : RegEx = RegEx.new() + +func _init() -> void: + var regex_res := IP_REGEX.compile(IP_PATTERN) + if regex_res != OK: + push_error("Invalid Regex expression used in IP validation pattern IP_PATTERN") + +#returns the subroutes and port in an integer array +func validate_ipv4(ip : String) -> Array[int]: + var result : RegExMatch = IP_REGEX.search(ip) + if result == null: + return [] + var ret : Array[int] = [] + if !result: + return ret + var domain : StringName = result.get_string(1) + if domain == &"localhost": + ret = [127,0,0,1] + else: + var split : PackedStringArray = domain.split(".") + for val in split: + ret.push_back(int(val)) + + if ret.size() == 4: + var port : String = result.get_string(5) + if !port.is_empty(): + ret.push_back(int(port)) + + return ret + +func host_game(player_name : String, game_name : String, password : String = "", lan_only : bool = false) -> bool: + #TODO: Check if connections to us are possible? + var success : bool = true + print("Host game with Name: %s, password: %s, Player: %s, Is lan only %s" % [game_name,password,player_name, lan_only]) + #TODO: Go to the lobby + return success + +func load_saved_ips() -> void: + var file : ConfigFile = MP.get_ips_config_file() + + var player_name : StringName = file.get_value(MP.USER,MP.PLAYER_NAME) + var game_name : StringName = file.get_value(MP.USER,MP.HOST_GAME_NAME) + var game_password : StringName = file.get_value(MP.USER,MP.HOST_GAME_PASSWORD) + + last_player_name = player_name + last_game_name = game_name + last_game_password = game_password + + _host_tab.set_user_values(player_name,game_name,game_password) + _direct_connection_tab.initial_setup(player_name) + + var indices : PackedStringArray = file.get_section_keys(MP.SERVER_NAMES) + #if the file is malformed, we will just be missing some entries + for index : String in indices: + var entry_name : String = file.get_value(MP.SERVER_NAMES,index,&"") + var entry_ip : String = file.get_value(MP.SERVER_IPS,index,&"") + _direct_connection_tab.add_ip_entry_to_list(entry_name,entry_ip) + +func _save_ips(names : PackedStringArray, ips : PackedStringArray) -> void: + assert(names.size() == ips.size()) + var file : ConfigFile = MP.get_ips_config_file() + file.erase_section(MP.SERVER_NAMES) + file.erase_section(MP.SERVER_IPS) + for i : int in names.size(): + var index_str : String = String.num_int64(i) + file.set_value(MP.SERVER_NAMES, index_str, names[i]) + file.set_value(MP.SERVER_IPS, index_str, ips[i]) + MP.save_ips_config_file() + +func _save_user(player_name : String, game_name : String, game_password : String) -> void: + var file : ConfigFile = MP.get_ips_config_file() + file.set_value(MP.USER,MP.PLAYER_NAME,player_name) + file.set_value(MP.USER,MP.HOST_GAME_NAME,game_name) + file.set_value(MP.USER,MP.HOST_GAME_PASSWORD,game_password) + MP.save_ips_config_file() + +func _on_direct_connection_tab_revert_ips() -> void: + last_game_name = _host_tab.get_game_name() + last_game_password = _host_tab.get_password() + load_saved_ips() + _direct_connection_tab.set_player_name(last_player_name) + _host_tab.set_player_name(last_player_name) + _host_tab.set_game_name(last_game_name) + _host_tab.set_game_password(last_game_password) + +func update_player_name(player_name : String) -> void: + last_player_name = player_name + +#Make sure the player name entry is updated on the new tab +func _on_tab_changed(tab : int) -> void: + _direct_connection_tab.set_player_name(last_player_name) + _host_tab.set_player_name(last_player_name) diff --git a/game/src/Game/Menu/MultiplayerMenu/MultiplayerMenu.tscn b/game/src/Game/Menu/MultiplayerMenu/MultiplayerMenu.tscn new file mode 100644 index 00000000..22f68d2d --- /dev/null +++ b/game/src/Game/Menu/MultiplayerMenu/MultiplayerMenu.tscn @@ -0,0 +1,90 @@ +[gd_scene load_steps=8 format=3 uid="uid://btri1i0hkhdsh"] + +[ext_resource type="Theme" uid="uid://fbxssqcg1s0m" path="res://assets/graphics/theme/options_menu.tres" id="1_5k2lj"] +[ext_resource type="Script" path="res://src/Game/Menu/MultiplayerMenu/MultiplayerMenu.gd" id="2_2x2qh"] +[ext_resource type="PackedScene" uid="uid://28ummh3fgokc" path="res://src/Game/Menu/MultiplayerMenu/DirectConnectionTab.tscn" id="3_4iwik"] +[ext_resource type="PackedScene" uid="uid://b4cu0p1wlux3s" path="res://src/Game/Menu/MultiplayerMenu/HostTab.tscn" id="3_ha6kl"] +[ext_resource type="Script" path="res://src/Game/Menu/MultiplayerMenu/PasswordDialog.gd" id="5_t64eu"] +[ext_resource type="PackedScene" uid="uid://dcsy6w6ow2xxg" path="res://src/Game/Menu/MultiplayerMenu/SecretEdit.tscn" id="6_34d2n"] +[ext_resource type="Script" path="res://src/Game/Menu/MultiplayerMenu/ConnectionFailDialog.gd" id="7_cd4lp"] + +[node name="MultiplayerMenu" type="PanelContainer" node_paths=PackedStringArray("_tab_container", "_password_dialog", "_connection_fail_dialog", "_host_tab", "_direct_connection_tab")] +editor_description = "UI-25" +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = ExtResource("1_5k2lj") +theme_type_variation = &"BackgroundPanel" +script = ExtResource("2_2x2qh") +_tab_container = NodePath("Margin/Tab") +_password_dialog = NodePath("PasswordDialog") +_connection_fail_dialog = NodePath("ConnectionFailDialog") +_host_tab = NodePath("Margin/Tab/HostTab") +_direct_connection_tab = NodePath("Margin/Tab/DirectConnectionTab") + +[node name="Margin" type="MarginContainer" parent="."] +layout_mode = 2 +theme_type_variation = &"TabMargin" + +[node name="Tab" type="TabContainer" parent="Margin"] +editor_description = "UI-45" +layout_mode = 2 +tab_alignment = 1 +current_tab = 0 +use_hidden_tabs_for_min_size = true + +[node name="DirectConnectionTab" parent="Margin/Tab" instance=ExtResource("3_4iwik")] +layout_mode = 2 +metadata/_tab_index = 0 + +[node name="HostTab" parent="Margin/Tab" instance=ExtResource("3_ha6kl")] +visible = false +layout_mode = 2 +metadata/_tab_index = 1 + +[node name="PasswordDialog" type="ConfirmationDialog" parent="." node_paths=PackedStringArray("secret")] +auto_translate_mode = 1 +title = "MP_CONNECT_PASSWORD_ENTRY" +position = Vector2i(0, 36) +ok_button_text = "MP_CONNECT_PASSWORD_OK" +cancel_button_text = "MP_CONNECT_PASSWORD_CANCEL" +script = ExtResource("5_t64eu") +secret = NodePath("SecretEdit") + +[node name="SecretEdit" parent="PasswordDialog" instance=ExtResource("6_34d2n")] +offset_left = 8.0 +offset_top = 8.0 +offset_right = 192.0 +offset_bottom = 51.0 + +[node name="ConnectionFailDialog" type="AcceptDialog" parent="." node_paths=PackedStringArray("label")] +auto_translate_mode = 1 +title = "MP_CONNECT_FAIL" +position = Vector2i(0, 36) +size = Vector2i(352, 100) +ok_button_text = "MP_CONNECT_FAIL_OK" +script = ExtResource("7_cd4lp") +reason_text = "MP_CONNECT_FAIL_REASON" +no_server_fail_text = "MP_CONNECT_FAIL_NO_SERVER" +bad_password_fail_text = "MP_CONNECT_FAIL_PASSWORD" +bad_ip_text = "MP_CONNECT_FAIL_BAD_IP" +label = NodePath("Label") + +[node name="Label" type="Label" parent="ConnectionFailDialog"] +offset_left = 8.0 +offset_top = 8.0 +offset_right = 344.0 +offset_bottom = 51.0 +text = "MP_CONNECT_FAIL_REASON" + +[connection signal="tab_changed" from="Margin/Tab" to="." method="_on_tab_changed"] +[connection signal="connect" from="Margin/Tab/DirectConnectionTab" to="." method="join_game"] +[connection signal="player_name_changed" from="Margin/Tab/DirectConnectionTab" to="." method="update_player_name"] +[connection signal="revert_ips" from="Margin/Tab/DirectConnectionTab" to="." method="_on_direct_connection_tab_revert_ips"] +[connection signal="save_ips" from="Margin/Tab/DirectConnectionTab" to="." method="_save_ips"] +[connection signal="host" from="Margin/Tab/HostTab" to="." method="host_game"] +[connection signal="player_name_changed" from="Margin/Tab/HostTab" to="." method="update_player_name"] +[connection signal="confirmed" from="PasswordDialog" to="PasswordDialog" method="_on_confirmed"] +[connection signal="pword_confirmed" from="PasswordDialog" to="." method="connect_to_game"] diff --git a/game/src/Game/Menu/MultiplayerMenu/PasswordDialog.gd b/game/src/Game/Menu/MultiplayerMenu/PasswordDialog.gd new file mode 100644 index 00000000..c98cf303 --- /dev/null +++ b/game/src/Game/Menu/MultiplayerMenu/PasswordDialog.gd @@ -0,0 +1,13 @@ +extends ConfirmationDialog +class_name PasswordDialog + +@export var secret : SecretEdit + +signal pword_confirmed(password : String) + +func _on_confirmed() -> void: + pword_confirmed.emit(secret.get_text()) + +func clear_and_display() -> void: + secret.set_text("") + popup_centered() diff --git a/game/src/Game/Menu/MultiplayerMenu/SecretEdit.gd b/game/src/Game/Menu/MultiplayerMenu/SecretEdit.gd new file mode 100644 index 00000000..a86e0693 --- /dev/null +++ b/game/src/Game/Menu/MultiplayerMenu/SecretEdit.gd @@ -0,0 +1,29 @@ +extends HBoxContainer +class_name SecretEdit + +@export var editable : bool = true +@export var start_hidden : bool = true + +@export var line : LineEdit +@export var check : CheckButton + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + line.editable = editable + + check.button_pressed = start_hidden + line.secret = start_hidden + +func get_text() -> StringName: + return line.text + +func set_editable(value : bool) -> void: + editable = value + line.editable = editable + +func set_hidden(value : bool) -> void: + check.button_pressed = value + line.secret = value + +func set_text(value : StringName) -> void: + line.text = value diff --git a/game/src/Game/Menu/MultiplayerMenu/SecretEdit.tscn b/game/src/Game/Menu/MultiplayerMenu/SecretEdit.tscn new file mode 100644 index 00000000..3148eb24 --- /dev/null +++ b/game/src/Game/Menu/MultiplayerMenu/SecretEdit.tscn @@ -0,0 +1,23 @@ +[gd_scene load_steps=2 format=3 uid="uid://dcsy6w6ow2xxg"] + +[ext_resource type="Script" path="res://src/Game/Menu/MultiplayerMenu/SecretEdit.gd" id="1_yuqj6"] + +[node name="SecretEdit" type="HBoxContainer" node_paths=PackedStringArray("line", "check")] +script = ExtResource("1_yuqj6") +line = NodePath("LineEdit") +check = NodePath("CheckButton") + +[node name="LineEdit" type="LineEdit" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "..." +alignment = 1 +max_length = 32 +editable = false +secret = true + +[node name="CheckButton" type="CheckButton" parent="."] +layout_mode = 2 +button_pressed = true + +[connection signal="toggled" from="CheckButton" to="LineEdit" method="set_secret"] diff --git a/game/src/Game/Menu/SaveLoadMenu/SaveLoadMenu.gd b/game/src/Game/Menu/SaveLoadMenu/SaveLoadMenu.gd index 66706b99..a1351cb6 100644 --- a/game/src/Game/Menu/SaveLoadMenu/SaveLoadMenu.gd +++ b/game/src/Game/Menu/SaveLoadMenu/SaveLoadMenu.gd @@ -16,7 +16,7 @@ var _id_to_tag : Array[StringName] = [] func filter_for_tag(tag : StringName) -> void: for child : Control in _scroll_list.get_children(): - if tag == &"": + if tag.is_empty(): child.show() else: if tag == child.resource.session_tag: