123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- extends Node
- const TIMEOUT = 1000 # Unresponsive clients times out after 1 sec
- const SEAL_TIME = 10000 # A sealed room will be closed after this time
- const ALFNUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
- var _alfnum = ALFNUM.to_ascii()
- var rand: RandomNumberGenerator = RandomNumberGenerator.new()
- var lobbies: Dictionary = {}
- var server: WebSocketServer = WebSocketServer.new()
- var peers: Dictionary = {}
- class Peer extends Reference:
- var id = -1
- var lobby = ""
- var time = OS.get_ticks_msec()
- func _init(peer_id):
- id = peer_id
- class Lobby extends Reference:
- var peers: Array = []
- var host: int = -1
- var sealed: bool = false
- var time = 0
- func _init(host_id: int):
- host = host_id
- func join(peer_id, server) -> bool:
- if sealed: return false
- if not server.has_peer(peer_id): return false
- var new_peer: WebSocketPeer = server.get_peer(peer_id)
- new_peer.put_packet(("I: %d\n" % (1 if peer_id == host else peer_id)).to_utf8())
- for p in peers:
- if not server.has_peer(p):
- continue
- server.get_peer(p).put_packet(("N: %d\n" % peer_id).to_utf8())
- new_peer.put_packet(("N: %d\n" % (1 if p == host else p)).to_utf8())
- peers.push_back(peer_id)
- return true
- func leave(peer_id, server) -> bool:
- if not peers.has(peer_id): return false
- peers.erase(peer_id)
- var close = false
- if peer_id == host:
- # The room host disconnected, will disconnect all peers.
- close = true
- if sealed: return close
- # Notify other peers.
- for p in peers:
- if not server.has_peer(p): return close
- if close:
- # Disconnect peers.
- server.disconnect_peer(p)
- else:
- # Notify disconnection.
- server.get_peer(p).put_packet(("D: %d\n" % peer_id).to_utf8())
- return close
- func seal(peer_id, server) -> bool:
- # Only host can seal the room.
- if host != peer_id: return false
- sealed = true
- for p in peers:
- server.get_peer(p).put_packet("S: \n".to_utf8())
- time = OS.get_ticks_msec()
- return true
- func _init():
- server.connect("data_received", self, "_on_data")
- server.connect("client_connected", self, "_peer_connected")
- server.connect("client_disconnected", self, "_peer_disconnected")
- func _process(delta):
- poll()
- func listen(port):
- stop()
- rand.seed = OS.get_unix_time()
- server.listen(port)
- func stop():
- server.stop()
- peers.clear()
- func poll():
- if not server.is_listening():
- return
- server.poll()
- # Peers timeout.
- for p in peers.values():
- if p.lobby == "" and OS.get_ticks_msec() - p.time > TIMEOUT:
- server.disconnect_peer(p.id)
- # Lobby seal.
- for k in lobbies:
- if not lobbies[k].sealed:
- continue
- if lobbies[k].time + SEAL_TIME < OS.get_ticks_msec():
- # Close lobby.
- for p in lobbies[k].peers:
- server.disconnect_peer(p)
- func _peer_connected(id, protocol = ""):
- peers[id] = Peer.new(id)
- func _peer_disconnected(id, was_clean = false):
- var lobby = peers[id].lobby
- print("Peer %d disconnected from lobby: '%s'" % [id, lobby])
- if lobby and lobbies.has(lobby):
- peers[id].lobby = ""
- if lobbies[lobby].leave(id, server):
- # If true, lobby host has disconnected, so delete it.
- print("Deleted lobby %s" % lobby)
- lobbies.erase(lobby)
- peers.erase(id)
- func _join_lobby(peer, lobby) -> bool:
- if lobby == "":
- for _i in range(0, 32):
- lobby += char(_alfnum[rand.randi_range(0, ALFNUM.length()-1)])
- lobbies[lobby] = Lobby.new(peer.id)
- elif not lobbies.has(lobby):
- return false
- lobbies[lobby].join(peer.id, server)
- peer.lobby = lobby
- # Notify peer of its lobby
- server.get_peer(peer.id).put_packet(("J: %s\n" % lobby).to_utf8())
- print("Peer %d joined lobby: '%s'" % [peer.id, lobby])
- return true
- func _on_data(id):
- if not _parse_msg(id):
- print("Parse message failed from peer %d" % id)
- server.disconnect_peer(id)
- func _parse_msg(id) -> bool:
- var pkt_str: String = server.get_peer(id).get_packet().get_string_from_utf8()
- var req = pkt_str.split("\n", true, 1)
- if req.size() != 2: # Invalid request size
- return false
- var type = req[0]
- if type.length() < 3: # Invalid type size
- return false
- if type.begins_with("J: "):
- if peers[id].lobby: # Peer must not have joined a lobby already!
- return false
- return _join_lobby(peers[id], type.substr(3, type.length() - 3))
- if not peers[id].lobby: # Messages across peers are only allowed in same lobby
- return false
- if not lobbies.has(peers[id].lobby): # Lobby not found?
- return false
- var lobby = lobbies[peers[id].lobby]
- if type.begins_with("S: "):
- # Client is sealing the room
- return lobby.seal(id, server)
- var dest_str: String = type.substr(3, type.length() - 3)
- if not dest_str.is_valid_integer(): # Destination id is not an integer
- return false
- var dest_id: int = int(dest_str)
- if dest_id == NetworkedMultiplayerPeer.TARGET_PEER_SERVER:
- dest_id = lobby.host
- if not peers.has(dest_id): # Destination ID not connected
- return false
- if peers[dest_id].lobby != peers[id].lobby: # Trying to contact someone not in same lobby
- return false
- if id == lobby.host:
- id = NetworkedMultiplayerPeer.TARGET_PEER_SERVER
- if type.begins_with("O: "):
- # Client is making an offer
- server.get_peer(dest_id).put_packet(("O: %d\n%s" % [id, req[1]]).to_utf8())
- elif type.begins_with("A: "):
- # Client is making an answer
- server.get_peer(dest_id).put_packet(("A: %d\n%s" % [id, req[1]]).to_utf8())
- elif type.begins_with("C: "):
- # Client is making an answer
- server.get_peer(dest_id).put_packet(("C: %d\n%s" % [id, req[1]]).to_utf8())
- return true
|