diff --git a/main.gd b/main.gd index 621d8f4..3f1095d 100644 --- a/main.gd +++ b/main.gd @@ -1,6 +1,7 @@ extends Node const PORT = 25565 +const PORT_RANGE := 10 const MAX_CONNECTIONS = 20 var counter = 0 @@ -8,31 +9,129 @@ var counter = 0 @onready var before_connect_container = get_node("BeforeConnect") as Container @onready var host_text_edit = get_node("%HostTextEdit") as TextEdit @onready var connect_button = get_node("%ConnectButton") as Button +@onready var host_button = get_node("%HostButton") as Button +@onready var server_discovery = get_node("%ServerDiscovery") as ServerDiscovery @onready var after_connect_container = get_node("%AfterConnect") as Container @onready var counter_label = get_node("%CounterLabel") as Label @onready var decrement_button = get_node("%DecrementButton") as Button @onready var increment_button = get_node("%IncrementButton") as Button +@onready var disconnect_button = get_node("%DisconnectButton") as Button +@onready var start_game_button = get_node("%StartGameButton") as Button + +var active_port: int +var server_buttons: Dictionary = Dictionary() + +func _clean_room(): + # this is if someone left the room and we will just clean the game room again + # should only called on host + _set_counter(0) + pass + +func _on_start_game_clicked(): + if !multiplayer.multiplayer_peer: + return + if !multiplayer.get_unique_id() != 1: + return + if (multiplayer.get_peers().size() == 0): + return + # do start game here, should only run on host + pass func _ready() -> void: - if OS.has_feature("dedicated_server"): - print("Starting dedicated server") - var peer = ENetMultiplayerPeer.new() - var err = peer.create_server(PORT, MAX_CONNECTIONS) + host_button.button_up.connect(_on_host_pressed) + host_button.text = "Host (%s)" % server_discovery.id + + start_game_button.button_up.connect(_on_start_game_clicked) + + print("Attach client") + multiplayer.peer_connected.connect(_on_player_connected) + multiplayer.peer_disconnected.connect(_on_player_disconnected) + multiplayer.connected_to_server.connect(_on_connected_ok) + #multiplayer.connection_failed.connect(_on_connected_fail) + multiplayer.server_disconnected.connect(_on_server_disconnected) + + increment_button.button_up.connect(func(): _increment.rpc()) + decrement_button.button_up.connect(func(): _decrement.rpc()) + disconnect_button.button_up.connect(_on_disconnect_clicked) + connect_button.get_parent().remove_child(connect_button) + + server_discovery.server_added.connect(_on_server_added) + server_discovery.server_removed.connect(_on_server_removed) + +func _on_disconnect_clicked(): + if (multiplayer.is_server()): + server_discovery.disable_server() + for peer_id in multiplayer.get_peers(): + multiplayer.disconnect_peer(peer_id) + + multiplayer.multiplayer_peer = null + + $BeforeConnect.visible = true + $AfterConnect.visible = false + +func _on_server_added(id, ip, port): + print("%s | Server added %s %s" % [server_discovery.id, id, ip]) + + var button = connect_button.duplicate() + before_connect_container.add_child(button) + + var key = "%s|%s" % [id,ip] + server_buttons[key] = button + + button.text = "%s\nConnect\n%s:%d" % [id, ip, port] + for conn in button.button_down.get_connections(): + button.button_down.disconnect(conn) + + var _on_button_downed = func(): + _do_connect(ip, port) + button.button_down.connect(_on_button_downed) + +func _on_server_removed(id, ip): + print("%s | Server removed %s %s" % [server_discovery.id, id, ip]) + + var key = "%s|%s" % [id,ip] + var button = server_buttons[key] + button.queue_free() + server_buttons.erase(key) + +func _do_connect(ip: String, port: int): + var peer = ENetMultiplayerPeer.new() + print("%s Connecting to %s:%d" % [server_discovery.id, ip, port]) + peer.create_client(ip, port) + multiplayer.multiplayer_peer = peer + +func _on_host_pressed(): + print("%s | Starting dedicated server" % server_discovery.id) + var peer = ENetMultiplayerPeer.new() + for offset in range(PORT_RANGE): + if not is_port_available(PORT + offset): + continue + + active_port = PORT + offset + var err = peer.create_server(active_port, MAX_CONNECTIONS) if err: print(err) - multiplayer.multiplayer_peer = peer - multiplayer.peer_connected.connect(_on_player_connected) + break + + multiplayer.multiplayer_peer = peer + + _on_connected_ok() + + start_game_button.text = "Start Game (Not enough player)" + + server_discovery.enable_server(active_port) + +func is_port_available(port: int) -> bool: + var udp_peer = PacketPeerUDP.new() + + var error = udp_peer.bind(port) + + if error == OK: + udp_peer.close() + return true else: - print("Game starting") - #multiplayer.peer_disconnected.connect(_on_player_disconnected) - multiplayer.connected_to_server.connect(_on_connected_ok) - #multiplayer.connection_failed.connect(_on_connected_fail) - #multiplayer.server_disconnected.connect(_on_server_disconnected) - - increment_button.button_up.connect(func(): _increment.rpc()) - decrement_button.button_up.connect(func(): _decrement.rpc()) - connect_button.button_up.connect(_on_connect_pressed) + return false @rpc("any_peer", "call_local") func _increment(): @@ -49,28 +148,45 @@ func _set_counter(value: int): counter = value counter_label.text = str(counter) -func _on_connect_pressed(): - var peer = ENetMultiplayerPeer.new() - var parsed: PackedStringArray = host_text_edit.text.split(":") - print("Connecting to ", parsed[0]) - peer.create_client(parsed[0] if not parsed[0].is_empty() else "localhost", int(parsed[1]) if parsed.size() > 1 else PORT) - multiplayer.multiplayer_peer = peer - func _on_player_connected(id: int): if multiplayer.get_unique_id() == 1: _set_counter.rpc_id(id, counter) + + if (multiplayer.get_peers().size() > 0): + start_game_button.text = "Start Game" + server_discovery.disable_server() -func _on_player_disconnected(): - pass +func _on_player_disconnected(id): + print("%s | _on_player_disconnected %s" % [server_discovery.id, id]) + if multiplayer.get_unique_id() == id: + $BeforeConnect.visible = true + $AfterConnect.visible = false + + if (id != 1 and multiplayer.get_unique_id() == 1): + _clean_room() + start_game_button.text = "Start Game (Not enough player)" + server_discovery.enable_server(active_port) + func _on_connected_ok(): $AfterConnect.visible = true $BeforeConnect.visible = false counter_label.text = str(counter) + if (multiplayer.is_server()): + disconnect_button.text = "Disconnect (Host)" + if start_game_button.get_parent() != after_connect_container: + after_connect_container.add_child(start_game_button) + else: + disconnect_button.text = "Disconnect" + if start_game_button.get_parent() == after_connect_container: + after_connect_container.remove_child(start_game_button) + func _on_connected_fail(): pass func _on_server_disconnected(): - pass + print("%s | _on_server_disconnected" % [server_discovery.id]) + $AfterConnect.visible = false + $BeforeConnect.visible = true diff --git a/main.tscn b/main.tscn index d49cabf..d1043f9 100644 --- a/main.tscn +++ b/main.tscn @@ -1,6 +1,7 @@ -[gd_scene load_steps=2 format=3 uid="uid://c7gn46af6whf8"] +[gd_scene load_steps=3 format=3 uid="uid://c7gn46af6whf8"] [ext_resource type="Script" path="res://main.gd" id="1_e0ud3"] +[ext_resource type="Script" path="res://server_discovery.gd" id="2_hed18"] [node name="Main" type="Control"] layout_mode = 3 @@ -33,12 +34,31 @@ layout_mode = 2 size_flags_vertical = 3 placeholder_text = "192.168.*.*" +[node name="RefreshButton" type="Button" parent="BeforeConnect"] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 64) +layout_mode = 2 +text = "Refresh" + +[node name="HostButton" type="Button" parent="BeforeConnect"] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 64) +layout_mode = 2 +text = "Host" + [node name="ConnectButton" type="Button" parent="BeforeConnect"] unique_name_in_owner = true custom_minimum_size = Vector2(0, 64) layout_mode = 2 text = "Connect" +[node name="DiscoveryTimer" type="Timer" parent="BeforeConnect"] +unique_name_in_owner = true + +[node name="ServerDiscovery" type="Node" parent="BeforeConnect"] +unique_name_in_owner = true +script = ExtResource("2_hed18") + [node name="AfterConnect" type="VBoxContainer" parent="."] unique_name_in_owner = true visible = false @@ -64,6 +84,12 @@ text = "1" horizontal_alignment = 1 vertical_alignment = 1 +[node name="DisconnectButton" type="Button" parent="AfterConnect"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "Disconnect" + [node name="Actions" type="HBoxContainer" parent="AfterConnect"] layout_mode = 2 @@ -78,3 +104,9 @@ unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 text = "+" + +[node name="StartGameButton" type="Button" parent="AfterConnect"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "Start Game" diff --git a/server_discovery.gd b/server_discovery.gd new file mode 100644 index 0000000..e63154a --- /dev/null +++ b/server_discovery.gd @@ -0,0 +1,100 @@ +extends Node +class_name ServerDiscovery + +const PORT := 26566 +const PORT_RANGE := 10 # cannot listen to the same port on one device + +signal server_added(id: String, ip: String, port: int) +signal server_removed(id: String, ip: String) + +var server: UDPServer = null +var client: PacketPeerUDP = null +var port_offset: int +var id: String +var servers: Array[String] = [] +var enabled := false + +func enable_server(server_port: int): + enabled = true + var data = "%s|AVAILABLE|%d" %[id,server_port] + _broadcast_packet(data.to_utf8_buffer()) + +func disable_server(): + enabled = false + var data = "%s|NOT_AVAILABLE" %id + _broadcast_packet(data.to_utf8_buffer()) + +func _ready(): + id = _generate_id() + _init_server() + _init_client() + +func _process(_delta): + server.poll() + while(server.is_connection_available()): + var peer = server.take_connection() + while(peer.get_available_packet_count() > 0): + var packet = peer.get_packet() + var packet_str = packet.get_string_from_utf8() + if (packet_str.begins_with(id)): + continue + + var ip = peer.get_packet_ip() + var port = peer.get_packet_port() + print(id, " | Received data from ", ip, ":", port, " => ", packet_str) + + var data = packet_str.split("|") + var conn_id = data[0] + var conn_detail = data[1] + var key = "%s:%s" %[ip, port] + + if (conn_detail == "AVAILABLE" and key not in servers): + var conn_actual_server_port = int(data[2]) + servers.append(key) + server_added.emit(conn_id, ip, conn_actual_server_port) + elif (conn_detail == "NOT_AVAILABLE" and key in servers): + servers.erase(key) + server_removed.emit(conn_id, ip) + elif (conn_detail == "DISCOVERY" and enabled): + var available_data = "%s|AVAILABLE" %id + _broadcast_packet(available_data.to_utf8_buffer()) + +func _exit_tree(): + var data = "%s|NOT_AVAILABLE" %id + _broadcast_packet(data.to_utf8_buffer()) + +func _init_server(): + server = UDPServer.new() + + for offset in range(PORT_RANGE): + var err = server.listen(PORT + offset) + if !err: + port_offset = offset + break + + print("Server ", id, " initialized on port ", PORT + port_offset) + +func _init_client(): + client = PacketPeerUDP.new() + client.set_broadcast_enabled(true) + + var data = "%s|DISCOVERY" %id + _broadcast_packet(data.to_utf8_buffer()) + +func _broadcast_packet(buffer: PackedByteArray): + for offset in range(PORT_RANGE): + client.connect_to_host("255.255.255.255", PORT + offset) + client.put_packet(buffer) + +func _generate_id() -> String: + var id_len = 5 + var from = 'A'.unicode_at(0) + var to = 'Z'.unicode_at(0) + + var result:= "" + + for i in range(id_len): + var rand_char = char(randi_range(from, to)) + result += rand_char + + return result