class_name GCClient extends Node # CONNECTION DATA enum { NONE, SERVER_BROWSER, LOBBY_WAITING, LOBBY, VOICE } var connection_type : int = NONE var socket_client: WebSocketClient = null var socket: WebSocketPeer = null enum { CONNECTION_FAILED, CONNECTION_DISCONNECTED, CONNECTION_CONNECTING, CONNECTION_CONNECTED, CONNECTION_DISCONNECTING } var state: int = CONNECTION_DISCONNECTED var message_queue : Array = [] # messages to be sent upon connection # END CONNECTION DATA # GC DATA # connected from menus signal gc_connection_failed() signal lobby_list_acquired(lobbies) # connected before calling get_lobby_list to retrieve result signal join_request_success() signal join_failure(reason) signal join_success(args) signal host_failure(reason) signal host_success() # connected from in game signal disconnected_from_lobby(reason) signal join_request_received(player_id, args) signal connection_closed_dirty() # game should be quit for unexpected reasons var is_host : bool var host_id : String = "" var lobby_id : String = "" var player_id : String = "" var rejoin_key : String = "" var players : Array = [] var max_players : int = -1 var lobby_name : String = "" var is_private : bool = false var lobby_password : String = "" const GC_MSG_TYPES : Array = ["join_request_ok", "join_ok", "join_fail", "rejoin_ok", "rejoin_fail", "host_ok", "host_fail", "disconnected", "join_request", "lobby_closing"] # END GC DATA func _ready(): socket_client = WebSocketClient.new() socket_client.connect("connection_established", self, "on_connection_success") socket_client.connect("connection_closed", self, "on_connection_close") socket_client.connect("connection_error", self, "on_connection_error") #socket_client.connect("data_received", self, "receive") func gc_client_reset(): self.player_id = "" self.lobby_id = "" self.host_id = "" self.rejoin_key = "" self.players.clear() self.is_host = false self.connection_type = NONE self.max_players = -1 self.lobby_name = "" self.is_private = false func connect_to_gc(c_type: int): if !(c_type in [SERVER_BROWSER, LOBBY_WAITING, VOICE]): print("Invalid connection type!") return gc_client_reset() connection_type = c_type var url : String = Globals.GC_URL print("Connecting to game coordinator: %s..." % url) message_queue.clear() var error = socket_client.connect_to_url(url) if error != OK: return error state = CONNECTION_CONNECTING return OK func close_socket(code = 1000, reason = ""): if state == CONNECTION_DISCONNECTED or state == CONNECTION_FAILED: return print("Closing websocket...") socket_client.disconnect_from_host(code, reason) state = CONNECTION_DISCONNECTING # DISCONNECTING func on_connection_success(protocol): print("WebSocket connection success with protocol %s." % protocol) socket = socket_client.get_peer(1) socket.set_write_mode(WebSocketPeer.WRITE_MODE_TEXT) # defaults to text mode state = CONNECTION_CONNECTED # CONNECTED while len(message_queue) > 0: var msg = message_queue.pop_at(0) send(msg) func on_connection_close(clean): print("WebSocket closed successfully.") socket = null connection_type = NONE if clean: state = CONNECTION_DISCONNECTED # DISCONNECTED else: emit_signal("connection_closed_dirty") state = CONNECTION_FAILED # DISCONNECT DIRTY func on_connection_error(): # connection failed print("WebSocket connection failed!") socket = null state = CONNECTION_FAILED # DISCONNECT DIRTY connection_type = NONE emit_signal("gc_connection_failed") func send(message, as_bytes=false) -> int: if state != CONNECTION_CONNECTED: message_queue.push_back(message) return -1 return socket.put_packet(message) func send_json(message) -> int: if state != CONNECTION_CONNECTED: message_queue.push_back(JSON.print(message).to_utf8()) return -1 var message_json = JSON.print(message).to_utf8() return socket.put_packet(message_json) func receive(): if state != CONNECTION_CONNECTED: return null if socket.get_available_packet_count() < 1: return null var packet : PoolByteArray = socket.get_packet() if socket.was_string_packet(): var message = packet.get_string_from_utf8() var json = JSON.parse(message) if json.error: return null message = json.result process_gc_message(message) return message return null # bytes2var(packet) # process GC state specific message/content of messages # returns false in the case of game-breaking invalid message receipt func process_gc_message(message): # always update player information whenever an update is received from the GC if "current_players" in message: for player in players: if player["is_host"] and self.host_id != player["player_id"]: self.host_id = player["player_id"] self.is_host = self.host_id == self.player_id if "type" in message and (message["type"] in GC_MSG_TYPES): # validate GC message format if message["type"] == "join_request_ok": if !("player_id" in message and "rejoin_key" in message): close_socket() # GC did not provide necessary info self.player_id = message["player_id"] self.rejoin_key = message["rejoin_key"] self.lobby_name = message["lobby_name"] self.max_players = message["max_players"] self.is_private = message["private"] emit_signal("join_request_success") elif message["type"] == "join_ok": self.connection_type = LOBBY var join_args = {} if "args" in message: join_args = message["args"] emit_signal("join_success", join_args) elif message["type"] == "join_fail": var reason = "Unknown failure." if not ("message" in message) else message["message"] gc_client_reset() emit_signal( "join_failure", reason ) elif message["type"] == "disconnected": state = CONNECTION_DISCONNECTING # socket will be closed soon close_socket() gc_client_reset() emit_signal( "disconnected_from_lobby", message["reason"] ) elif message["type"] == "host_ok": if !(("player_id" in message) and ("rejoin_key" in message) and ("lobby_id" in message)): close_socket() # GC did not provide necessary info self.lobby_id = message["lobby_id"] self.player_id = message["player_id"] self.rejoin_key = message["rejoin_key"] self.is_host = true self.connection_type = LOBBY emit_signal("host_success") elif message["type"] == "host_fail": var reason = "Unknown failure." if not ("message" in message) else message["message"] close_socket() gc_client_reset() emit_signal("host_failure", reason) elif message["type"] == "join_request": if !("player_id" in message and "args" in message): print("Invalid join request received.") return emit_signal("join_request_received", message["player_id"], message["args"]) elif message["type"] == "lobby_list": emit_signal("lobby_list_acquired", message["lobby_list"]) gc_client_reset() close_socket() elif message["type"] == "lobby_closing": emit_signal("disconnected_from_lobby", "Lobby closing.") gc_client_reset() close_socket() return func get_lobby_list(): gc_client_reset() var message = {"type" : "list_open_lobbies"} if state != CONNECTION_CONNECTED: connect_to_gc(SERVER_BROWSER) send_json( message ) else: send_json( message ) func host_lobby(lobby_name : String, username : String, max_players : int, private : bool, password : String = ""): close_socket() gc_client_reset() var request = {"type" : "host_lobby", "lobby_name" : lobby_name, "game_type" : Globals.GAME_TYPE, "lobby_id" : lobby_id, "username" : username, "max_players" : max_players, "private" : private, "password" : password} connect_to_gc(LOBBY_WAITING) self.lobby_name = lobby_name self.max_players = max_players self.is_private = private self.lobby_password = password send_json(request) func join_lobby(lobby_id : String, password : String, username : String, args = null): close_socket() gc_client_reset() var request = {"type" : "join_lobby", "game_type" : Globals.GAME_TYPE, "lobby_id" : lobby_id, "password" : password, "username" : username} if args: request["args"] = args connect_to_gc(LOBBY_WAITING) self.lobby_id = lobby_id self.is_host = false self.lobby_password = password send_json(request) func _process(_delta): socket_client.poll()