364 lines
14 KiB
GDScript
364 lines
14 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!")
|
|
else:
|
|
Log.warning("Non-manager peer tried to send a manager only request: send_playerlist")
|
|
|
|
## 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!")
|
|
else:
|
|
Log.warning("Non-manager peer tried to send a manager only request: start_game")
|
|
|
|
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_remote", "unreliable")
|
|
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: Player = get_node("/root/"+Game.mapname+"/"+peer)
|
|
player.position = position
|
|
player.rotation = rotation
|
|
else:
|
|
Log.warning("A non affiliated peer tried to send: player_sync")
|
|
|
|
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_remote", "unreliable")
|
|
func npc_sync(position: Vector3, rotation: Vector3, node_name: String) -> void:
|
|
if managerID == multiplayer.get_remote_sender_id():
|
|
var npc: NPC = get_node("/root/"+Game.mapname+"/"+node_name)
|
|
npc.position = position
|
|
npc.rotation = rotation
|
|
else:
|
|
Log.warning("Non-manager peer tried to send a manager only request: npc_sync")
|
|
|
|
func object_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, "object_sync", position, rotation, node_name) != OK:
|
|
Log.warning("Couldn't send RPC to %d!" % peer)
|
|
|
|
# Synchronizes the npc state.
|
|
@rpc("any_peer", "call_remote", "unreliable")
|
|
func object_sync(position: Vector3, rotation: Vector3, node_name: String) -> void:
|
|
if managerID == multiplayer.get_remote_sender_id():
|
|
var carryable: Carryable = get_node("/root/"+Game.mapname+"/NPCCarryables/"+node_name)
|
|
carryable.position = position
|
|
carryable.rotation = rotation
|
|
else:
|
|
Log.warning("Non-manager peer tried to send a manager only request: npc_sync")
|
|
|
|
func pick_up_sync_call(npc: NPC, carryable: Carryable) -> void:
|
|
var npc_path: NodePath = npc.get_path()
|
|
var carryable_path: NodePath = carryable.get_path()
|
|
for peer: int in Networking.get_ids():
|
|
if rpc_id(peer, "pick_up_sync", npc_path, carryable_path) != OK:
|
|
Log.warning("Couldn't send RPC to %d!" % peer)
|
|
|
|
# Synchronizes the text above the npc.
|
|
@rpc("any_peer", "call_local", "reliable")
|
|
func npc_text_sync(text: String, npc: NodePath) -> void:
|
|
if managerID == multiplayer.get_remote_sender_id():
|
|
(get_node(npc) as NPC).set_text(text)
|
|
else:
|
|
Log.warning("Non-manager peer tried to send a manager only request: npc_text_sync")
|
|
|
|
func npc_text_sync_call(text: String, npc: NPC) -> void:
|
|
var npc_path: NodePath = npc.get_path()
|
|
for peer: int in Networking.get_ids():
|
|
if rpc_id(peer, "npc_text_sync", text, npc_path) != OK:
|
|
Log.warning("Couldn't send RPC to %d!" % peer)
|
|
|
|
# Synchronizes the object that is picked up by the npc.
|
|
@rpc("any_peer", "call_local", "reliable")
|
|
func pick_up_sync(npc: NodePath, carryable: NodePath) -> void:
|
|
if managerID == multiplayer.get_remote_sender_id():
|
|
(get_node(npc) as NPC).pick_up(get_node(carryable) as Carryable)
|
|
else:
|
|
Log.warning("Non-manager peer tried to send a manager only request: pick_up_sync")
|
|
|
|
func set_down_sync_call(npc: NPC, carryable: Carryable) -> void:
|
|
var npc_path: NodePath = npc.get_path()
|
|
var carryable_path: NodePath = carryable.get_path()
|
|
for peer: int in Networking.get_ids():
|
|
if rpc_id(peer, "set_down_sync", npc_path, carryable_path) != OK:
|
|
Log.warning("Couldn't send RPC to %d!" % peer)
|
|
|
|
# Synchronizes the object that is picked up by the npc.
|
|
@rpc("any_peer", "call_local", "reliable")
|
|
func set_down_sync(npc: NodePath, carryable: NodePath) -> void:
|
|
if managerID == multiplayer.get_remote_sender_id():
|
|
(get_node(npc) as NPC).set_down(get_node(carryable) as Carryable)
|
|
else:
|
|
Log.warning("Non-manager peer tried to send a manager only request: set_down_sync")
|