hotel-madness/scripts/utils/networking.gd
2024-12-12 15:23:16 +01:00

295 lines
11 KiB
GDScript

## SPDX-License-Identifier: GPL-3.0-or-later
## Copyright (c) 2024 interstellardevelopment.org
extends Node
signal playerlist_changed()
## Connection Code
# Connect the signals.
func _ready() -> void:
if multiplayer.connected_to_server.connect(_on_connected_ok):
pass
if multiplayer.connection_failed.connect(close_network):
pass
if multiplayer.server_disconnected.connect(close_network):
pass
if multiplayer.peer_disconnected.connect(_on_peer_disconnected):
pass
if playerlist_changed.connect(_on_playerlist_changed):
pass
func _on_playerlist_changed() -> void:
pass
# Start the network listener.
func start_server() -> Error:
var peer: ENetMultiplayerPeer = ENetMultiplayerPeer.new()
if peer.create_server(Game.port, Game.max_clients) != OK:
Log.warning("Couldn't create the server at port %d!" % Game.port)
return FAILED
multiplayer.multiplayer_peer = peer
Log.info("Created the server at port %d." % Game.port)
return OK
# Connect to a server.
func join_server() -> Error:
var peer: ENetMultiplayerPeer = ENetMultiplayerPeer.new()
if peer.create_client(Game.ip, Game.port) != OK:
Log.warning("Couldn't connect to the server at %s:%d!" % [Game.ip, Game.port])
return FAILED
multiplayer.multiplayer_peer = peer
Log.info("Connected to the server at %s:%d." % [Game.ip, Game.port])
return OK
# Close all network connections.
func close_network() -> void:
multiplayer.multiplayer_peer = null
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
Log.info("Closed all network connections.")
## Matchmaking Code
var mutex: Mutex = Mutex.new()
var rooms: Dictionary = {}
var server_peers: Dictionary = {}
var peers: Dictionary = {}
var isManager: bool = false
var managerID: int
func join_room() -> void:
if join_server() != OK:
Log.warning("Failed to connect to sever.")
func _on_connected_ok() -> void:
if rpc_id(1, "send_roomname", Game.roomname) != OK:
Log.warning("Failed to send roomname!")
func _on_peer_disconnected(id: int) -> void:
mutex.lock()
if multiplayer.get_unique_id() == 1:
if rooms.has(id):
var roomname: String = rooms[id]
if !rooms.erase(roomname):
Log.warning("The room %s associated with %d doesn't exist in rooms, even though it should!" % [roomname, id])
mutex.unlock()
return
if !rooms.erase(id):
Log.warning("The peer %d doesn't exist in rooms, even though it should!" % id)
mutex.unlock()
return
for peer: int in server_peers[roomname]:
if peer == id:
continue
if rpc_id(peer, "host_disconnected_message"):
Log.warning("Peer %d couldn't be disconnected!" % peer)
mutex.unlock()
return
if !server_peers.erase(roomname):
Log.warning("Room %s couldn't be found, even though it should exist!" % roomname)
mutex.unlock()
return
Log.info("Room %s terminated." % roomname)
else:
for room: String in server_peers:
for peer: int in server_peers[room]:
if peer == id:
var peer_array: Array = server_peers[room]
peer_array.erase(id)
server_peers[room] = peer_array
for found_peer: int in server_peers[room]:
if rpc_id(found_peer, "peer_disconnected_message", id) != OK:
Log.warning("Failed to send disconnect message to %d!" % found_peer)
mutex.unlock()
return
mutex.unlock()
# The following function sends the roomname selected by the client.
@rpc("any_peer", "call_remote", "reliable")
func send_roomname(roomname: String) -> void:
mutex.lock()
if multiplayer.get_unique_id() == 1:
if !rooms.has(roomname) and !rooms.has(multiplayer.get_remote_sender_id()):
rooms[roomname] = multiplayer.get_remote_sender_id()
rooms[multiplayer.get_remote_sender_id()] = roomname
server_peers[roomname] = [multiplayer.get_remote_sender_id()]
Log.info("Room %s registered." % roomname)
if rpc_id(multiplayer.get_remote_sender_id(), "server_response", 0, rooms[roomname]) != OK:
Log.warning("Failed to send elevation reply to %d!")
elif server_peers[roomname] is Array:
# For some odd reason, Godot doesn't allow casting server_peers[roomname] to an Array, but this works?
var peer_array: Array = server_peers[roomname]
peer_array.append(multiplayer.get_remote_sender_id())
server_peers[roomname] = peer_array
if rpc_id(multiplayer.get_remote_sender_id(), "server_response", 1, rooms[roomname]) != OK:
Log.warning("Failed to send join reply to %d!")
mutex.unlock()
return
Log.debug("Room %s joined by peer %d." % [roomname, multiplayer.get_remote_sender_id()])
else:
Log.warning("Peer %d tried to send this client a request meant for the authority!" % multiplayer.get_remote_sender_id())
if rpc_id(multiplayer.get_remote_sender_id(), "server_response", 2, 0) != OK:
Log.warning("Failed to send fail reply to %d!")
mutex.unlock()
# This is the message sent out if the host of the room has left.
@rpc("authority", "call_remote", "reliable")
func host_disconnected_message() -> void:
Log.info("Host disconnected from the session.")
managerID = 1
close_network()
Game._ready()
# This is the message sent out if a peer in the room has left.
@rpc("authority", "call_remote", "reliable")
func peer_disconnected_message(id: int) -> void:
for peer: String in peers:
if peers[peer] == id:
if !peers.erase(peer):
Log.warning("Peer %d never existed, but was tasked to remove it!" % id)
return
if emit_signal("playerlist_changed") != OK:
Log.warning("Couldn't emit playerlist_changed signal.")
return
Log.info("Peer %d successfully removed." % id)
# This is the initial server response, which tells the client what its role is.
@rpc("authority", "call_remote", "reliable")
func server_response(status: int, manager: int) -> void:
match status:
0:
# create new room host
isManager = true
if get_tree().change_scene_to_file("res://scenes/ui/lobby.tscn") != OK:
Log.error("Couldn't change to the lobby scene! Closing application.", "Couldn't change to the lobby scene!")
peers[Game.username] = manager
managerID = manager
1:
# create new room member
managerID = manager
if rpc_id(managerID, "request_playerlist", Game.username) != OK:
Log.warning("Failed to send username to room host %d." % multiplayer.get_remote_sender_id())
2:
Log.warning("Sent a join request to a regular peer, not the server!")
# This informs the server of the recently joined player.
@rpc("any_peer", "call_remote", "reliable")
func inform_server(roomname: String) -> void:
mutex.lock()
if multiplayer.get_unique_id() == 1:
if multiplayer.get_remote_sender_id() == rooms[roomname]:
var peer_array: Array = server_peers[roomname]
peer_array.append(multiplayer.get_remote_sender_id())
server_peers[roomname] = peer_array
else:
Log.warning("Peer %d attempted to trick the server into adding it to the room!" % multiplayer.get_remote_sender_id())
else:
Log.warning("Peer %d tried to send this client a request meant for the authority!" % multiplayer.get_remote_sender_id())
mutex.unlock()
# Get the playerlist from the room host.
@rpc("any_peer", "call_remote", "reliable")
func request_playerlist(peername: String) -> void:
if isManager:
if !peers.has(peername) and !peers.has(multiplayer.get_remote_sender_id()):
peers[peername] = multiplayer.get_remote_sender_id()
if rpc_id(multiplayer.get_remote_sender_id(), "send_playerlist", 0, peers) != OK:
Log.warning("Failed to send join status to %d." % multiplayer.get_remote_sender_id())
if !peers.erase(peername):
Log.warning("Couldn't reverse adding %d to the playerlist." % peername)
return
if rpc_id(1, "inform_server", Game.roomname) != OK:
Log.warning("Failed to send playerinfo to server.")
return
for peer: String in peers:
var remote_id: int = peers[peer]
if remote_id != multiplayer.get_unique_id():
if rpc_id(remote_id, "send_playerlist", 1, peers) != OK:
Log.warning("Failed to send playerlist to %d." % multiplayer.get_remote_sender_id())
if emit_signal("playerlist_changed") != OK:
Log.warning("Couldn't emit playerlist_changed signal.")
else:
if rpc_id(multiplayer.get_remote_sender_id(), "send_playerlist", 2, {}) != OK:
Log.warning("Couldn't send failure response to peer %d!" % multiplayer.get_remote_sender_id())
Log.warning("Peer %d tried to register with name %s, which already exists!" % [multiplayer.get_remote_sender_id(), peername])
else:
if rpc_id(multiplayer.get_remote_sender_id(), "send_playerlist", 3, {}) != OK:
Log.warning("Couldn't send failure response to peer %d!" % multiplayer.get_remote_sender_id())
Log.warning("Peer %d tried to send this client a request meant for the room host!" % multiplayer.get_remote_sender_id())
# Receive the playerlist on the client.
@rpc("any_peer", "call_remote", "reliable")
func send_playerlist(status: int, newPeers: Dictionary) -> void:
if multiplayer.get_remote_sender_id() == managerID:
match status:
0:
peers = newPeers
Log.info("Room %s successfully joined." % Game.roomname)
if get_tree().change_scene_to_file("res://scenes/ui/lobby.tscn") != OK:
Log.error("Couldn't change to the lobby scene! Closing application.", "Couldn't change to the lobby scene!")
1:
peers = newPeers
Log.debug("Playerlist received.")
if emit_signal("playerlist_changed") != OK:
Log.warning("Couldn't emit playerlist_changed signal.")
2:
Log.warning("Name %s is already taken in the room!" % Game.username)
3:
Log.warning("Sent a playerlist request to a regular peer, not the host!")
## General Game RPCs
# Does RPCs to all clients.
func get_ids() -> Array:
var ret: Array = []
for peer: String in peers:
ret.append(peers[peer])
return ret
func verify_id(id: int) -> bool:
if id in get_ids():
return true
return false
func start_game_call() -> void:
for peer: int in Networking.get_ids():
if rpc_id(peer, "start_game") != OK:
Log.warning("Couldn't send RPC to %d!" % peer)
# Starts the game.
@rpc("any_peer", "call_local", "reliable")
func start_game() -> void:
if multiplayer.get_remote_sender_id() == managerID:
if get_tree().change_scene_to_file("res://scenes/maps/testmap.tscn") != OK:
Log.warning("Couldn't load the map!")
func player_sync_call(position: Vector3, rotation: Vector3) -> void:
for peer: int in Networking.get_ids():
if peer != multiplayer.get_unique_id():
if rpc_id(peer, "player_sync", position, rotation) != OK:
Log.warning("Couldn't send RPC to %d!" % peer)
# Synchronizes the player state.
@rpc("any_peer", "call_local", "reliable")
func player_sync(position: Vector3, rotation: Vector3) -> void:
if verify_id(multiplayer.get_remote_sender_id()):
for peer: String in peers:
if peers[peer] == multiplayer.get_remote_sender_id() and has_node("/root/"+Game.mapname+"/"+peer):
var player: CharacterBody3D = get_node("/root/"+Game.mapname+"/"+peer)
player.position = position
player.rotation = rotation
func npc_sync_call(position: Vector3, rotation: Vector3, node_name: String) -> void:
for peer: int in Networking.get_ids():
if peer != multiplayer.get_unique_id():
if rpc_id(peer, "npc_sync", position, rotation, node_name) != OK:
Log.warning("Couldn't send RPC to %d!" % peer)
# Synchronizes the npc state.
@rpc("any_peer", "call_local", "reliable")
func npc_sync(position: Vector3, rotation: Vector3, node_name: String) -> void:
if managerID == multiplayer.get_remote_sender_id():
var npc: CharacterBody3D = get_node("/root/"+Game.mapname+"/"+node_name)
npc.position = position
npc.rotation = rotation