summaryrefslogtreecommitdiff
path: root/network/GameCoordinatorClient.gd
diff options
context:
space:
mode:
authorAnson Bridges <bridges.anson@gmail.com>2025-08-22 12:46:04 -0700
committerAnson Bridges <bridges.anson@gmail.com>2025-08-22 12:46:04 -0700
commit64f37f4209d80bfad976dd4a139c98002caef15f (patch)
tree94c91953986e960573ae0092f8ad7120c1c266f0 /network/GameCoordinatorClient.gd
parent255fbf19cc9499ef384d41f68515da5e49e8a3ce (diff)
the harmonious transition from menu to gameHEADmaster
Diffstat (limited to 'network/GameCoordinatorClient.gd')
-rw-r--r--network/GameCoordinatorClient.gd209
1 files changed, 181 insertions, 28 deletions
diff --git a/network/GameCoordinatorClient.gd b/network/GameCoordinatorClient.gd
index 3d87b7b..eac86e8 100644
--- a/network/GameCoordinatorClient.gd
+++ b/network/GameCoordinatorClient.gd
@@ -1,32 +1,80 @@
+class_name GCClient
extends Node
# CONNECTION DATA
-enum { NONE, SERVER_BROWSER, LOBBY, VOICE }
+
+enum { NONE, SERVER_BROWSER, LOBBY_WAITING, LOBBY, VOICE }
+var connection_type : int = NONE
+
var socket_client: WebSocketClient = null
var socket: WebSocketPeer = null
-var state: int = 0 # -1 = CONNECTION_FAILED, 0 = CONNECTION_DISCONNECTED, 1 = CONNECTION_CONNECTING, 2 = CONNECTION_CONNECTED, 3 = CONNECTION_DISCONNECTING
+
+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
-var connection_type : int = NONE
+
+# 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 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_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, VOICE]):
+ 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)
@@ -35,67 +83,172 @@ func connect_to_gc(c_type: int):
if error != OK:
return error
- state = 1 # CONNECTING
+ state = CONNECTION_CONNECTING
return OK
-func sock_close(code = 1000, reason = ""):
+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 = 3 # DISCONNECTING
+ 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 = 2 # CONNECTED
+ state = CONNECTION_CONNECTED # CONNECTED
while len(message_queue) > 0:
var msg = message_queue.pop_at(0)
send(msg)
-func on_connection_close_success(clean):
+func on_connection_close(clean):
print("WebSocket closed successfully.")
socket = null
connection_type = NONE
if clean:
- state = 0 # DISCONNECTED
+ state = CONNECTION_DISCONNECTED # DISCONNECTED
else:
- state = -1 # DISCONNECT DIRTY
+ emit_signal("connection_closed_dirty")
+ state = CONNECTION_FAILED # DISCONNECT DIRTY
func on_connection_error(): # connection failed
print("WebSocket connection failed!")
socket = null
- state = -1 # DISCONNECT DIRTY
+ state = CONNECTION_FAILED # DISCONNECT DIRTY
connection_type = NONE
+ emit_signal("gc_connection_failed")
func send(message, as_bytes=false) -> int:
- if state != 2:
+ if state != CONNECTION_CONNECTED:
message_queue.push_back(message)
return -1
return socket.put_packet(message)
-
func send_json(message) -> int:
- if state != 2:
+ 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(string_to_json=false):
- if state != 2: return null
+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()
- if string_to_json:
- var json = JSON.parse(message)
- if json.error:
- return null
- message = json.result
+ var json = JSON.parse(message)
+ if json.error:
+ return null
+ message = json.result
+ process_gc_message(message)
return message
- return bytes2var(packet)
+ 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()