diff --git a/Source/Server/HighLevelServer.gd b/Source/Server/HighLevelServer.gd new file mode 100644 index 0000000..acfe623 --- /dev/null +++ b/Source/Server/HighLevelServer.gd @@ -0,0 +1,65 @@ +extends Node +class_name HighLevelServer + +var server = NetworkedMultiplayerENet.new() +var request_handler = null + +signal user_connected(user_id) +signal user_disconnected(user_id) + +func set_request_handler(new_handler): + request_handler = new_handler + +func start_server(port = 1909, max_players=2000): + if server.create_server(port, max_players) == OK: + connect_server_signals() + print("The server has started.") + get_tree().network_peer = server + return true + else: + print("Unable to start the server.") + return false + +func connect_server_signals(): + if get_tree().is_connected("network_peer_connected", self, "client_connected"): + return + get_tree().connect("network_peer_connected", self, "client_connected") + get_tree().connect("network_peer_disconnected", self, "client_disconnected") + +func client_connected(id): + print("Client %d has just connected." % [id]) + emit_signal("user_connected", id) + +func client_disconnected(id): + print("Client %d has just disconnected." % [id]) + emit_signal("user_disconnected", id) + +func send_data_to_client(id, method, data=null): + var method_info = {"purpose": "response", "method": method, "data": data} + rpc_id(id, "received_data_from_server", method_info) + +remote func received_data_from_client(method_info): + var id = get_tree().get_rpc_sender_id() + print("Just received data from client %d: %s." % [id, method_info]) + process_method_info(id, method_info) + +func process_method_info(id, method_info): + if !request_handler: return + # method_info = {"purpose": "request", "method": "method", "data": data} + if method_info is Dictionary and method_info.has("purpose") and method_info["purpose"] == "request": + if request_handler.has_method(method_info["method"]): + if method_info["data"]: + request_handler.call_deferred(method_info["method"], id, method_info["data"]) + else: + request_handler.call_deferred(method_info["method"], id) + +func stop_server(): + disconnect_server_signals() + server.close_connection() + get_tree().network_peer = null + +func disconnect_server_signals(): + if !get_tree().is_connected("network_peer_connected", self, "client_connected"): + return + get_tree().disconnect("network_peer_connected", self, "client_connected") + get_tree().disconnect("network_peer_disconnected", self, "client_disconnected") diff --git a/Source/Server/HighLevelServer.tscn b/Source/Server/HighLevelServer.tscn new file mode 100644 index 0000000..c1409aa --- /dev/null +++ b/Source/Server/HighLevelServer.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://Source/Server/HighLevelServer.gd" type="Script" id=1] + +[node name="HighLevelServer" type="Node"] +script = ExtResource( 1 ) diff --git a/Source/Server/Server.gd b/Source/Server/Server.gd index cc6d985..b9d891f 100644 --- a/Source/Server/Server.gd +++ b/Source/Server/Server.gd @@ -1,5 +1,11 @@ extends Node +export var websockets_server = true + +var server = null +var hl_server = preload("res://Source/Server/HighLevelServer.tscn") +var ws_server = preload("res://Source/Server/WebSocketsServer.tscn") + const SERVER_PORT = 1909 const MAX_PLAYERS = 2000 @@ -24,136 +30,167 @@ var colors = { } func _ready(): - connect_signals() start_server() -func connect_signals(): - get_tree().connect("network_peer_connected", self, "_player_connected") - get_tree().connect("network_peer_disconnected", self, "_player_disconnected") - func start_server(): - var peer = NetworkedMultiplayerENet.new() - var error = peer.create_server(SERVER_PORT, MAX_PLAYERS) - if error == OK: - get_tree().network_peer = peer + if websockets_server: + server = ws_server.instance() else: - print("Error creating the server: ", str(error)) - -func _player_connected(player_id): + server = hl_server.instance() + connect_connection_signals() + server.set_request_handler(self) + add_child(server) + server.start_server() + +func connect_connection_signals(): + if server.is_connected("user_connected", self, "player_connected"): + return + server.connect("user_connected", self, "player_connected") + server.connect("user_disconnected", self, "player_disconnected") + +func player_connected(player_id): players_online.append(player_id) - rpc_id(player_id, "send_player_name") - print("Plyer: " + str(player_id) + " connected.") + server.send_data_to_client(player_id, "set_player_id", player_id) + server.send_data_to_client(player_id, "send_player_name") -func _player_disconnected(player_id): +func player_disconnected(player_id): if players_in_lobbies.has(player_id): var reason = players_names[player_id] + " disconnected." - remove_player_from_lobby(players_in_lobbies[player_id], player_id, reason) + var data = {"lobby_code": players_in_lobbies[player_id], "lobby": lobbies[players_in_lobbies[player_id]], "reason": reason} + remove_player_from_game_lobby(player_id, data) players_online.erase(player_id) players_names.erase(player_id) - print("Plyer: " + str(player_id) + " disconnected.") -remote func get_player_name(player_name): - var player_id = get_tree().get_rpc_sender_id() - players_names[player_id] = player_name - print("Plyer " + str(player_id) + " has name " + player_name + ".") +func set_player_name(player_id, data): + players_names[player_id] = data["player_name"] + print("%s is set as the name for player with id: %d." % [data["player_name"], player_id]) -remote func create_lobby(lobby_data: Dictionary): - # Lobby Code [ID] +func create_game_lobby(player_id, lobby_data: Dictionary): lobby_data["code"] = lobbies.size() - - # Setting players data for lobby - var player_id = get_tree().get_rpc_sender_id() - var player_number = 0 #lobby_data["players"].size() + var player_number = player_id lobby_data["players"][player_number]["id"] = player_id lobby_data["players"][player_number]["name"] = players_names[player_id] - lobby_data["players"][player_number]["color"] = colors[player_number] + lobby_data["players"][player_number]["color"] = get_random_color(lobby_data["players"]) + lobby_data["players"][player_number]["host"] = true lobby_data["current_players"] += 1 - # Append lobbies list lobbies[lobbies.size()] = lobby_data players_in_lobbies[player_id] = lobby_data["code"] - # Notify player - rpc_id(player_id, "lobby_created", lobby_data) + var data = {"lobby_data": lobby_data, "player_number": player_number} + server.send_data_to_client(player_id, "game_lobby_created", data) -remote func join_lobby(lobby_code, lobby_pass): - var player_id = get_tree().get_rpc_sender_id() +func join_game_lobby(player_id, lobby_auth_info): var reason = "Lobby does not exist!" + var lobby_code = lobby_auth_info["lobby_code"] + var lobby_pass = lobby_auth_info["lobby_pass"] if lobbies.has(lobby_code): - if lobbies[lobby_code]["current_players"] < lobbies[lobby_code]["max_players"]: - if lobbies[lobby_code]["pass"] == lobby_pass: - var player_number = lobbies[lobby_code]["current_players"] - lobbies[lobby_code]["players"][player_number]["id"] = player_id - lobbies[lobby_code]["players"][player_number]["name"] = players_names[player_id] - lobbies[lobby_code]["players"][player_number]["color"] = colors[player_number] + var lobby = lobbies[lobby_code] + if lobby["current_players"] < lobby["max_players"]: + if lobby["pass"] == lobby_pass: + var player_number = player_id + lobby["players"][player_number] = { + "id": player_id, "name": players_names[player_id], + "color": get_random_color(lobby["players"]), "host": false + } players_in_lobbies[player_id] = lobby_code reason = players_names[player_id] + " has joined." - lobbies[lobby_code]["current_players"] += 1 - update_lobby_to_players(lobby_code, reason) + lobby["current_players"] += 1 + var data = {"lobby": lobby, "reason": reason, "player_number": player_number} + update_game_lobby_to_players(data) + return else: reason = "Invalid Password!" - rpc_id(player_id, "failed_to_join_lobby", reason) else: reason = "Lobby is full!" - rpc_id(player_id, "failed_to_join_lobby", reason) + server.send_data_to_client(player_id, "failed_to_join_game_lobby", reason) + +func update_game_lobby_to_players(data): + var lobby = data["lobby"] + for p_id in lobby["players"]: + var player_id = lobby["players"][p_id]["id"] + if data.has("player_number"): + data["player_number"] = p_id + if player_id: + server.send_data_to_client(player_id, "update_game_lobby", data) + +func leave_game_lobby(player_id, data): + remove_player_from_game_lobby( + player_id, {"lobby_code": data["lobby_code"], "lobby": lobbies[data["lobby_code"]], "reason": ""} + ) + +func kick_player_from_game_lobby(player_id, data): + var reason = "kicked" + server.send_data_to_client(data["kicked_player_id"], "kicked_from_lobby", reason) + remove_player_from_game_lobby( + data["kicked_player_id"], {"lobby_code": data["lobby_code"], "lobby": lobbies[data["lobby_code"]], "reason": reason} + ) + +func remove_player_from_game_lobby(player_id, data): + if not data["reason"]: + data["reason"] = players_names[player_id] + " left." + var lobby = data["lobby"] + var player_number = null + var is_host = false + for p_id in lobby["players"].keys(): + if p_id != player_id: + continue + player_number = player_id + is_host = lobby["players"][player_number]["host"] + players_in_lobbies.erase(player_number) + if lobby["current_players"] == 1: + lobbies.erase(data["lobby_code"]) else: - rpc_id(player_id, "failed_to_join_lobby", reason) - -func update_lobby_to_players(lobby_code, reason = ""): - for p_id in range(lobbies[lobby_code]["players"].size()): - if lobbies[lobby_code]["players"][p_id]["id"]: - rpc_id(lobbies[lobby_code]["players"][p_id]["id"], "update_lobby", lobbies[lobby_code], reason) - -remote func leave_lobby(lobby_code): - var player_id = get_tree().get_rpc_sender_id() - remove_player_from_lobby(lobby_code, player_id) - -remote func kick_player_from_lobby(lobby_code, player_id): - var kicker_id = get_tree().get_rpc_sender_id() - var reason = players_names[player_id] + " kicked by " + players_names[kicker_id] + "." - rpc_id(player_id, "kicked_from_lobby", reason) - remove_player_from_lobby(lobby_code, player_id, reason) - -func remove_player_from_lobby(lobby_code, player_id, reason = ""): - if not reason: - reason = players_names[player_id] + " left." - if lobbies.has(lobby_code): - for p_id in range(lobbies[lobby_code]["players"].size()): - if lobbies[lobby_code]["players"][p_id]["id"] == player_id: - players_in_lobbies.erase(player_id) - for other_p_id in range(p_id, lobbies[lobby_code]["players"].size() - 1): - var color = lobbies[lobby_code]["players"][other_p_id].color - lobbies[lobby_code]["players"][other_p_id] = lobbies[lobby_code]["players"][other_p_id+1] - lobbies[lobby_code]["players"][other_p_id].color = color - lobbies[lobby_code]["players"][lobbies[lobby_code]["players"].size() - 1] = player_dictionary_template - if lobbies[lobby_code]["current_players"] == 1: - lobbies.erase(lobby_code) - else: - lobbies[lobby_code]["current_players"] -= 1 - update_lobby_to_players(lobby_code, reason) - break - -remote func send_active_lobbies(): - var player_id = get_tree().get_rpc_sender_id() - rpc_id(player_id, "get_active_lobbies", lobbies) - -func update_colors(lobby_code): - pass - -remote func send_message(lobby_code, message, sender): - for i in range(lobbies[lobby_code].current_players): - var player_id = lobbies[lobby_code].players[i].id - rpc_id(player_id, "get_message", message, sender) - -remote func start_game(lobby_code): - for i in range(lobbies[lobby_code].current_players): - var player_id = lobbies[lobby_code].players[i].id - rpc_id(player_id, "game_started") - -remote func send_node_func_call(lobby_code, node_path, function, parameter=null): - var sender_id = get_tree().get_rpc_sender_id() - for i in range(lobbies[lobby_code].current_players): - var player_id = lobbies[lobby_code].players[i].id - if player_id != sender_id: - rpc_id(player_id, "get_node_func_call", node_path, function, parameter) + lobby["current_players"] -= 1 + lobby["players"].erase(player_number) + if is_host: + lobby["players"][lobby["players"].keys()[0]]["host"] = true + update_game_lobby_to_players(data) + +func send_active_game_lobbies(player_id, data=null): + server.send_data_to_client(player_id, "get_active_lobbies", lobbies) + +func send_message_in_game_lobby(sender_id, data): + var lobby = lobbies[data["lobby_code"]] + for i in lobby.players: + var player_id = lobby.players[i].id + var message_info = {"sender": data["sender"], "message": data["message"]} + server.send_data_to_client(player_id, "get_message", message_info) + +func start_the_game(sender_id, data): + var lobby = lobbies[data["lobby_code"]] + for i in lobby.players: + var player_id = lobby.players[i].id + server.send_data_to_client(player_id, "game_started") + +func send_node_method_call(sender_id, data): + var lobby = lobbies[data["lobby_code"]] + for i in lobby.players: + var player_id = lobby.players[i].id + if player_id == sender_id: continue + server.send_data_to_client( + player_id, + "get_node_method_call", data + ) + +func stop_server(): + server.stop_server() + disconnect_connection_signals() + server.queue_free() + +func disconnect_connection_signals(): + if !server.is_connected("user_connected", self, "player_connected"): + return + server.disconnect("user_connected", self, "player_connected") + server.disconnect("user_disconnected", self, "player_disconnected") + +func get_random_color(players_dict): + randomize() + var color = colors[randi() % colors.size()] + var players_colors = [] + for player in players_dict: + players_colors.append(players_dict[player].color) + while color in colors: + color = colors[randi() % colors.size()] + return color diff --git a/Source/Server/WebSocketsServer.gd b/Source/Server/WebSocketsServer.gd new file mode 100644 index 0000000..3b3e735 --- /dev/null +++ b/Source/Server/WebSocketsServer.gd @@ -0,0 +1,72 @@ +extends Node +class_name WebSocketsServer + +var server = WebSocketServer.new() +var request_handler = null + +signal user_connected(user_id) +signal user_disconnected(user_id) + +func _ready(): + set_process(false) + +func set_request_handler(new_handler): + request_handler = new_handler + +func start_server(port = 9080): + if server.listen(port) == OK: + connect_server_signals() + set_process(true) + print("The server has started.") + return true + else: + print("Unable to start the server.") + set_process(false) + return false + +func connect_server_signals(): + if server.is_connected("client_connected", self, "client_connected"): + return + server.connect("client_connected", self, "client_connected") + server.connect("client_disconnected", self, "client_disconnected") + server.connect("data_received", self, "received_data_from_client") + +func client_connected(id, _protocol): + print("Client %d has just connected." % [id]) + emit_signal("user_connected", id) + +func client_disconnected(id, was_clean=false): + print("Client %d has just disconnected." % [id]) + emit_signal("user_disconnected", id) + +func received_data_from_client(id): + var method_info = server.get_peer(id).get_var(true) + print("Just received data from client %d: %s." % [id, method_info]) + process_method_info(id, method_info) + +func process_method_info(id, method_info): + if !request_handler: return + if method_info is Dictionary and method_info.has("purpose") and method_info["purpose"] == "request": + if request_handler.has_method(method_info["method"]): + if method_info["data"]: + request_handler.call_deferred(method_info["method"], id, method_info["data"]) + else: + request_handler.call_deferred(method_info["method"], id) + +func _process(delta): + server.poll() + +func send_data_to_client(id, method, data=null): + var method_info = {"purpose": "response", "method": method, "data": data} + server.get_peer(id).put_var(method_info, true) + +func stop_server(): + disconnect_server_signals() + server.stop() + +func disconnect_server_signals(): + if !server.is_connected("client_connected", self, "client_connected"): + return + server.disconnect("client_connected", self, "client_connected") + server.disconnect("client_disconnected", self, "client_disconnected") + server.disconnect("data_received", self, "received_data_from_client") diff --git a/Source/Server/WebSocketsServer.tscn b/Source/Server/WebSocketsServer.tscn new file mode 100644 index 0000000..ce3467c --- /dev/null +++ b/Source/Server/WebSocketsServer.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://Source/Server/WebSocketsServer.gd" type="Script" id=1] + +[node name="WebSocketsServer" type="Node"] +script = ExtResource( 1 ) diff --git a/project.godot b/project.godot index ac5a0c1..cb599d0 100644 --- a/project.godot +++ b/project.godot @@ -8,6 +8,22 @@ config_version=4 +_global_script_classes=[ { +"base": "Node", +"class": "HighLevelServer", +"language": "GDScript", +"path": "res://Source/Server/HighLevelServer.gd" +}, { +"base": "Node", +"class": "WebSocketsServer", +"language": "GDScript", +"path": "res://Source/Server/WebSocketsServer.gd" +} ] +_global_script_class_icons={ +"HighLevelServer": "", +"WebSocketsServer": "" +} + [application] config/name="Conquest Server"