diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c1535c8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "godotTools.editorPath.godot4": "d:\\SteamLibrary\\steamapps\\common\\Godot Engine\\godot.windows.opt.tools.64.exe" +} \ No newline at end of file diff --git a/main/connection.gd b/main/connection.gd index c2307f8..787c79d 100644 --- a/main/connection.gd +++ b/main/connection.gd @@ -42,10 +42,14 @@ func start_server() -> void: multiplayer.peer_disconnected.connect(peer_disconnected) -func start_client() -> void: - var address = host - if OS.has_feature("editor") and use_localhost_in_editor: - address = "localhost" +func start_client(ip_address: String = "") -> void: + var address = ip_address + if address.is_empty(): + address = host + if OS.has_feature("editor") and use_localhost_in_editor: + address = "localhost" + + print("Connecting to server at: " + address) var peer = ENetMultiplayerPeer.new() var err = peer.create_client(address, port) diff --git a/ui/ui.gd b/ui/ui.gd index 93792a5..914b020 100644 --- a/ui/ui.gd +++ b/ui/ui.gd @@ -1,14 +1,24 @@ extends Control signal start_server -signal connect_client +signal connect_client(ip_address: String) @export var hide_ui_and_connect: bool +const SAVE_FILE_PATH = "user://ip_settings.dat" + func _ready(): if Connection.is_server(): return + # Connect to connection events for error handling + var connection_node = get_node("/root/Main/Connection") + if connection_node: + connection_node.disconnected.connect(_on_connection_failed) + + # Load saved IP address + _load_ip_address() + if hide_ui_and_connect: connect_client_emit() else: @@ -34,7 +44,19 @@ func start_server_emit() -> void: func connect_client_emit() -> void: - connect_client.emit() + var ip_address = $MainMenu/MenuContainer/IPSection/IPInput.text.strip_edges() + if ip_address.is_empty(): + ip_address = "localhost" + + # Basic IP validation + if not _is_valid_ip_or_hostname(ip_address): + _show_connection_error("Invalid IP address or hostname!") + return + + # Save IP address for next time + _save_ip_address(ip_address) + + connect_client.emit(ip_address) hide_ui() @@ -50,3 +72,67 @@ func hide_ui() -> void: func show_ui() -> void: $MainMenu.visible = true $InGameUI.visible = false + + +func _is_valid_ip_or_hostname(address: String) -> bool: + if address.is_empty(): + return false + + # Allow localhost + if address == "localhost": + return true + + # Basic hostname validation (alphanumeric, dots, hyphens) + var hostname_regex = RegEx.new() + hostname_regex.compile("^[a-zA-Z0-9.-]+$") + if hostname_regex.search(address): + return true + + # Basic IP address validation + var parts = address.split(".") + if parts.size() != 4: + return false + + for part in parts: + if not part.is_valid_int(): + return false + var num = part.to_int() + if num < 0 or num > 255: + return false + + return true + + +func _show_connection_error(message: String) -> void: + print("Connection error: " + message) + # Visual feedback by highlighting the IP input field + var ip_input = $MainMenu/MenuContainer/IPSection/IPInput + ip_input.modulate = Color.RED + + # Reset color after 2 seconds + await get_tree().create_timer(2.0).timeout + ip_input.modulate = Color.WHITE + + +func _on_connection_failed() -> void: + # Show UI again if connection fails + if not $MainMenu.visible: + show_ui() + _show_connection_error("Failed to connect to server!") + + +func _save_ip_address(ip_address: String) -> void: + var file = FileAccess.open(SAVE_FILE_PATH, FileAccess.WRITE) + if file: + file.store_string(ip_address) + file.close() + + +func _load_ip_address() -> void: + if FileAccess.file_exists(SAVE_FILE_PATH): + var file = FileAccess.open(SAVE_FILE_PATH, FileAccess.READ) + if file: + var saved_ip = file.get_as_text().strip_edges() + file.close() + if not saved_ip.is_empty(): + $MainMenu/MenuContainer/IPSection/IPInput.text = saved_ip diff --git a/ui/ui.tscn b/ui/ui.tscn index 2218393..24dd255 100644 --- a/ui/ui.tscn +++ b/ui/ui.tscn @@ -26,7 +26,7 @@ anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -[node name="Buttons" type="VBoxContainer" parent="MainMenu"] +[node name="MenuContainer" type="VBoxContainer" parent="MainMenu"] layout_mode = 1 anchors_preset = 8 anchor_left = 0.5 @@ -34,23 +34,47 @@ anchor_top = 0.5 anchor_right = 0.5 anchor_bottom = 0.5 offset_left = -150.0 -offset_top = -78.0 +offset_top = -130.0 offset_right = 150.0 -offset_bottom = 78.0 +offset_bottom = 130.0 grow_horizontal = 2 grow_vertical = 2 -[node name="Server" type="Button" parent="MainMenu/Buttons"] +[node name="IPSection" type="VBoxContainer" parent="MainMenu/MenuContainer"] +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="IPLabel" type="Label" parent="MainMenu/MenuContainer/IPSection"] +layout_mode = 2 +theme_override_font_sizes/font_size = 24 +text = "Server IP Address:" +horizontal_alignment = 1 + +[node name="IPInput" type="LineEdit" parent="MainMenu/MenuContainer/IPSection"] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +placeholder_text = "Enter IP address (e.g., 192.168.1.100)" +text = "localhost" +alignment = 1 + +[node name="Spacer" type="Control" parent="MainMenu/MenuContainer"] +custom_minimum_size = Vector2(0, 20) +layout_mode = 2 + +[node name="Buttons" type="VBoxContainer" parent="MainMenu/MenuContainer"] +layout_mode = 2 + +[node name="Server" type="Button" parent="MainMenu/MenuContainer/Buttons"] layout_mode = 2 theme_override_font_sizes/font_size = 30 text = "Server" -[node name="Client" type="Button" parent="MainMenu/Buttons"] +[node name="Client" type="Button" parent="MainMenu/MenuContainer/Buttons"] layout_mode = 2 theme_override_font_sizes/font_size = 30 text = "Client" -[node name="Exit" type="Button" parent="MainMenu/Buttons"] +[node name="Exit" type="Button" parent="MainMenu/MenuContainer/Buttons"] layout_mode = 2 theme_override_font_sizes/font_size = 30 text = "Exit" @@ -88,6 +112,6 @@ script = ExtResource("4_0te8w") user_data_events = ExtResource("5_tmjbr") player_panel = ExtResource("6_lta7j") -[connection signal="pressed" from="MainMenu/Buttons/Server" to="." method="start_server_emit"] -[connection signal="pressed" from="MainMenu/Buttons/Client" to="." method="connect_client_emit"] -[connection signal="pressed" from="MainMenu/Buttons/Exit" to="." method="exit_game_emit"] +[connection signal="pressed" from="MainMenu/MenuContainer/Buttons/Server" to="." method="start_server_emit"] +[connection signal="pressed" from="MainMenu/MenuContainer/Buttons/Client" to="." method="connect_client_emit"] +[connection signal="pressed" from="MainMenu/MenuContainer/Buttons/Exit" to="." method="exit_game_emit"]