diff options
| author | Anson Bridges <bridges.anson@gmail.com> | 2025-08-22 12:46:04 -0700 |
|---|---|---|
| committer | Anson Bridges <bridges.anson@gmail.com> | 2025-08-22 12:46:04 -0700 |
| commit | 64f37f4209d80bfad976dd4a139c98002caef15f (patch) | |
| tree | 94c91953986e960573ae0092f8ad7120c1c266f0 | |
| parent | 255fbf19cc9499ef384d41f68515da5e49e8a3ce (diff) | |
| -rw-r--r-- | export_presets.cfg | 2 | ||||
| -rw-r--r-- | network/GameCoordinatorClient.gd | 209 | ||||
| -rw-r--r-- | network/GameCoordinatorTester.gd | 2 | ||||
| -rw-r--r-- | network/WSClient.tscn | 6 | ||||
| -rw-r--r-- | network/websocket_client_basic.gd | 5 | ||||
| -rw-r--r-- | objects/Airplane.tscn (renamed from objects/Plane.tscn) | 2 | ||||
| -rw-r--r-- | objects/HexSpace.tscn | 4 | ||||
| -rw-r--r-- | objects/PlaneControlBoard.tscn | 96 | ||||
| -rw-r--r-- | objects/PlayerListEntry.tscn | 42 | ||||
| -rw-r--r-- | pages/GameTable.tscn | 412 | ||||
| -rw-r--r-- | pages/MainMenu.tscn | 53 | ||||
| -rw-r--r-- | pages/MainScene.tscn | 7 | ||||
| -rw-r--r-- | pages/ServerBrowser.tscn | 1 | ||||
| -rw-r--r-- | project.godot | 20 | ||||
| -rw-r--r-- | resources/external/game_coordinator.py | 65 | ||||
| -rw-r--r-- | resources/unused_code.gd | 16 | ||||
| -rw-r--r-- | scripts/Airplane.gd (renamed from scripts/Plane.gd) | 3 | ||||
| -rw-r--r-- | scripts/Board.gd | 30 | ||||
| -rw-r--r-- | scripts/GameTable.gd | 161 | ||||
| -rw-r--r-- | scripts/Globals.gd | 3 | ||||
| -rw-r--r-- | scripts/HexSpace.gd | 5 | ||||
| -rw-r--r-- | scripts/MainMenu.gd | 27 | ||||
| -rw-r--r-- | scripts/MainScene.gd | 71 | ||||
| -rw-r--r-- | scripts/PlaneControlBoard.gd | 6 | ||||
| -rw-r--r-- | scripts/ServerBrowser.gd | 6 |
25 files changed, 1105 insertions, 149 deletions
diff --git a/export_presets.cfg b/export_presets.cfg index c34ad69..64cd65b 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -5,7 +5,7 @@ platform="HTML5" runnable=true custom_features="" export_filter="all_resources" -include_filter="" +include_filter="*.txt" exclude_filter="" export_path="" script_export_mode=1 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() diff --git a/network/GameCoordinatorTester.gd b/network/GameCoordinatorTester.gd index 12f92ab..bae0e82 100644 --- a/network/GameCoordinatorTester.gd +++ b/network/GameCoordinatorTester.gd @@ -3,7 +3,7 @@ extends Control onready var ws_client_template : PackedScene = preload("res://network/WSClient.tscn") -var game_coordinator_url : String = "ws://192.168.7.112:8181" +var game_coordinator_url : String = "ws://acetyl.net:8181" var gc_client_menus : Dictionary = {} var latest_gccid : int = 0 diff --git a/network/WSClient.tscn b/network/WSClient.tscn new file mode 100644 index 0000000..bfb9383 --- /dev/null +++ b/network/WSClient.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://network/websocket_client_basic.gd" type="Script" id=1] + +[node name="WSClient" type="Node"] +script = ExtResource( 1 ) diff --git a/network/websocket_client_basic.gd b/network/websocket_client_basic.gd index 353dd81..846e721 100644 --- a/network/websocket_client_basic.gd +++ b/network/websocket_client_basic.gd @@ -39,12 +39,13 @@ func on_connection_success(protocol): send(msg) func on_connection_close_success(clean): - print("WebSocket closed successfully.") socket = null if clean: state = 0 # DISCONNECTED + print("WebSocket closed successfully.") else: state = -1 # DISCONNECT DIRTY + print("WebSocket closed unsuccessfully!") func on_connection_error(): # connection failed print("WebSocket connection failed!") @@ -59,7 +60,9 @@ func send(message, as_bytes=false) -> int: func send_json(message) -> int: + print("sending") if state != 2: + print("adding to queue") message_queue.push_back(JSON.print(message).to_utf8()) return -1 var message_json = JSON.print(message).to_utf8() diff --git a/objects/Plane.tscn b/objects/Airplane.tscn index 21ce972..975fd6f 100644 --- a/objects/Plane.tscn +++ b/objects/Airplane.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=15 format=2] -[ext_resource path="res://scripts/Plane.gd" type="Script" id=1] +[ext_resource path="res://scripts/Airplane.gd" type="Script" id=1] [ext_resource path="res://resources/fonts/Cochineal-Bold.otf" type="DynamicFontData" id=2] [ext_resource path="res://textures/plane_action_indicator_board.png" type="Texture" id=3] [ext_resource path="res://textures/gradient_transparent.png" type="Texture" id=4] diff --git a/objects/HexSpace.tscn b/objects/HexSpace.tscn index 2fa1d71..f703422 100644 --- a/objects/HexSpace.tscn +++ b/objects/HexSpace.tscn @@ -147,9 +147,9 @@ transform = Transform( 0.866025, 0, -0.5, 0, 1, 0, 0.5, 0, 0.866025, 0.0160618, mesh = SubResource( 17 ) [node name="AirportName" type="Label3D" parent="Airport"] -transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.971102, 0 ) +transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -0.252818, 0.302783, -0.208905 ) visible = false -pixel_size = 0.001 +pixel_size = 0.0007 billboard = 1 fixed_size = true text = "AIRPORT NAME" diff --git a/objects/PlaneControlBoard.tscn b/objects/PlaneControlBoard.tscn index 4c58d5d..7f2364a 100644 --- a/objects/PlaneControlBoard.tscn +++ b/objects/PlaneControlBoard.tscn @@ -67,7 +67,7 @@ margin_right = -2.0 margin_bottom = -6.0 [node name="Label" type="Label" parent="DestinationHBOX"] -margin_top = 24.0 +margin_top = 23.0 margin_right = 111.0 margin_bottom = 40.0 theme = ExtResource( 1 ) @@ -75,11 +75,21 @@ custom_colors/font_color = Color( 0, 0, 0, 1 ) text = "DESTINATION: " [node name="DestinationIcon" type="TextureRect" parent="DestinationHBOX"] +visible = false margin_left = 115.0 margin_right = 179.0 margin_bottom = 64.0 texture = ExtResource( 5 ) +[node name="DestinationText" type="Label" parent="DestinationHBOX"] +visible = false +margin_left = 115.0 +margin_top = 23.0 +margin_right = 115.0 +margin_bottom = 40.0 +theme = ExtResource( 1 ) +custom_colors/font_color = Color( 0, 0, 0, 1 ) + [node name="AltitudeHBox" type="HBoxContainer" parent="."] anchor_left = 0.05 anchor_top = 0.3 @@ -87,30 +97,30 @@ anchor_right = 0.941 anchor_bottom = 0.3 [node name="Label" type="Label" parent="AltitudeHBox"] -margin_top = 24.0 -margin_right = 109.0 +margin_top = 23.0 +margin_right = 106.0 margin_bottom = 40.0 theme = ExtResource( 1 ) custom_colors/font_color = Color( 0, 0, 0, 1 ) text = "ALTITUDE: " [node name="Altitude1" type="TextureRect" parent="AltitudeHBox"] -margin_left = 113.0 -margin_right = 177.0 +margin_left = 110.0 +margin_right = 174.0 margin_bottom = 64.0 texture = ExtResource( 5 ) [node name="Altitude2" type="TextureRect" parent="AltitudeHBox"] modulate = Color( 0.3, 0.3, 0.3, 1 ) -margin_left = 181.0 -margin_right = 245.0 +margin_left = 178.0 +margin_right = 242.0 margin_bottom = 64.0 texture = ExtResource( 3 ) [node name="Altitude3" type="TextureRect" parent="AltitudeHBox"] modulate = Color( 0.3, 0.3, 0.3, 1 ) -margin_left = 249.0 -margin_right = 313.0 +margin_left = 246.0 +margin_right = 310.0 margin_bottom = 64.0 size_flags_horizontal = 0 size_flags_vertical = 0 @@ -124,160 +134,160 @@ anchor_bottom = 0.9 [node name="ActionSquare1" type="VBoxContainer" parent="ActionHBox"] margin_right = 50.0 -margin_bottom = 74.0 +margin_bottom = 77.0 script = SubResource( 3 ) [node name="MoveFirstButton" type="Button" parent="ActionHBox/ActionSquare1"] margin_right = 50.0 -margin_bottom = 22.0 +margin_bottom = 23.0 theme = ExtResource( 1 ) text = ">>" [node name="ActionOption" type="OptionButton" parent="ActionHBox/ActionSquare1"] -margin_top = 26.0 +margin_top = 27.0 margin_right = 50.0 -margin_bottom = 48.0 +margin_bottom = 50.0 rect_min_size = Vector2( 50, 0 ) theme = ExtResource( 1 ) items = [ "", null, false, 0, null, "←", null, false, 2, null, "→", null, false, 3, null, "↑", null, false, 4, null, "↓", null, false, 5, null ] selected = 0 [node name="MoveLastButton" type="Button" parent="ActionHBox/ActionSquare1"] -margin_top = 52.0 +margin_top = 54.0 margin_right = 50.0 -margin_bottom = 74.0 +margin_bottom = 77.0 theme = ExtResource( 1 ) [node name="Spacer1" type="Control" parent="ActionHBox"] margin_left = 54.0 margin_right = 73.0 -margin_bottom = 74.0 +margin_bottom = 77.0 size_flags_horizontal = 3 [node name="ActionSquare2" type="VBoxContainer" parent="ActionHBox"] margin_left = 77.0 margin_right = 127.0 -margin_bottom = 74.0 +margin_bottom = 77.0 script = SubResource( 3 ) [node name="MoveFirstButton" type="Button" parent="ActionHBox/ActionSquare2"] margin_right = 50.0 -margin_bottom = 22.0 +margin_bottom = 23.0 theme = ExtResource( 1 ) text = ">>" [node name="ActionOption" type="OptionButton" parent="ActionHBox/ActionSquare2"] -margin_top = 26.0 +margin_top = 27.0 margin_right = 50.0 -margin_bottom = 48.0 +margin_bottom = 50.0 rect_min_size = Vector2( 50, 0 ) theme = ExtResource( 1 ) items = [ "", null, false, 0, null, "←", null, false, 2, null, "→", null, false, 3, null, "↑", null, false, 4, null, "↓", null, false, 5, null ] selected = 0 [node name="MoveLastButton" type="Button" parent="ActionHBox/ActionSquare2"] -margin_top = 52.0 +margin_top = 54.0 margin_right = 50.0 -margin_bottom = 74.0 +margin_bottom = 77.0 theme = ExtResource( 1 ) [node name="Spacer2" type="Control" parent="ActionHBox"] margin_left = 131.0 margin_right = 151.0 -margin_bottom = 74.0 +margin_bottom = 77.0 size_flags_horizontal = 3 [node name="ActionSquare3" type="VBoxContainer" parent="ActionHBox"] margin_left = 155.0 margin_right = 205.0 -margin_bottom = 74.0 +margin_bottom = 77.0 script = SubResource( 3 ) [node name="MoveFirstButton" type="Button" parent="ActionHBox/ActionSquare3"] margin_right = 50.0 -margin_bottom = 22.0 +margin_bottom = 23.0 theme = ExtResource( 1 ) text = ">>" [node name="ActionOption" type="OptionButton" parent="ActionHBox/ActionSquare3"] -margin_top = 26.0 +margin_top = 27.0 margin_right = 50.0 -margin_bottom = 48.0 +margin_bottom = 50.0 rect_min_size = Vector2( 50, 0 ) theme = ExtResource( 1 ) items = [ "", null, false, 0, null, "←", null, false, 2, null, "→", null, false, 3, null, "↑", null, false, 4, null, "↓", null, false, 5, null ] selected = 0 [node name="MoveLastButton" type="Button" parent="ActionHBox/ActionSquare3"] -margin_top = 52.0 +margin_top = 54.0 margin_right = 50.0 -margin_bottom = 74.0 +margin_bottom = 77.0 theme = ExtResource( 1 ) [node name="Spacer3" type="Control" parent="ActionHBox"] margin_left = 209.0 margin_right = 228.0 -margin_bottom = 74.0 +margin_bottom = 77.0 size_flags_horizontal = 3 [node name="ActionSquare4" type="VBoxContainer" parent="ActionHBox"] margin_left = 232.0 margin_right = 282.0 -margin_bottom = 74.0 +margin_bottom = 77.0 script = SubResource( 3 ) [node name="MoveFirstButton" type="Button" parent="ActionHBox/ActionSquare4"] margin_right = 50.0 -margin_bottom = 22.0 +margin_bottom = 23.0 theme = ExtResource( 1 ) text = ">>" [node name="ActionOption" type="OptionButton" parent="ActionHBox/ActionSquare4"] -margin_top = 26.0 +margin_top = 27.0 margin_right = 50.0 -margin_bottom = 48.0 +margin_bottom = 50.0 rect_min_size = Vector2( 50, 0 ) theme = ExtResource( 1 ) items = [ "", null, false, 0, null, "←", null, false, 2, null, "→", null, false, 3, null, "↑", null, false, 4, null, "↓", null, false, 5, null ] selected = 0 [node name="MoveLastButton" type="Button" parent="ActionHBox/ActionSquare4"] -margin_top = 52.0 +margin_top = 54.0 margin_right = 50.0 -margin_bottom = 74.0 +margin_bottom = 77.0 theme = ExtResource( 1 ) [node name="Spacer4" type="Control" parent="ActionHBox"] margin_left = 286.0 margin_right = 306.0 -margin_bottom = 74.0 +margin_bottom = 77.0 size_flags_horizontal = 3 [node name="ActionSquare5" type="VBoxContainer" parent="ActionHBox"] margin_left = 310.0 margin_right = 360.0 -margin_bottom = 74.0 +margin_bottom = 77.0 script = SubResource( 3 ) [node name="MoveFirstButton" type="Button" parent="ActionHBox/ActionSquare5"] margin_right = 50.0 -margin_bottom = 22.0 +margin_bottom = 23.0 theme = ExtResource( 1 ) text = ">>" [node name="ActionOption" type="OptionButton" parent="ActionHBox/ActionSquare5"] -margin_top = 26.0 +margin_top = 27.0 margin_right = 50.0 -margin_bottom = 48.0 +margin_bottom = 50.0 rect_min_size = Vector2( 50, 0 ) theme = ExtResource( 1 ) items = [ "", null, false, 0, null, "←", null, false, 2, null, "→", null, false, 3, null, "↑", null, false, 4, null, "↓", null, false, 5, null ] selected = 0 [node name="MoveLastButton" type="Button" parent="ActionHBox/ActionSquare5"] -margin_top = 52.0 +margin_top = 54.0 margin_right = 50.0 -margin_bottom = 74.0 +margin_bottom = 77.0 theme = ExtResource( 1 ) [connection signal="pressed" from="ActionHBox/ActionSquare1/MoveFirstButton" to="ActionHBox/ActionSquare1" method="set_move_first" binds= [ true ]] diff --git a/objects/PlayerListEntry.tscn b/objects/PlayerListEntry.tscn new file mode 100644 index 0000000..2a280b1 --- /dev/null +++ b/objects/PlayerListEntry.tscn @@ -0,0 +1,42 @@ +[gd_scene load_steps=6 format=2] + +[ext_resource path="res://resources/fonts/Cochineal-Roman.otf" type="DynamicFontData" id=1] +[ext_resource path="res://resources/fonts/Cochineal-Bold.otf" type="DynamicFontData" id=2] + +[sub_resource type="DynamicFont" id=1] +size = 26 +font_data = ExtResource( 2 ) + +[sub_resource type="DynamicFont" id=2] +size = 26 +font_data = ExtResource( 1 ) + +[sub_resource type="DynamicFont" id=3] +size = 22 +font_data = ExtResource( 1 ) + +[node name="PlayerListEntry" type="HBoxContainer"] + +[node name="ColorRect" type="ColorRect" parent="."] +margin_right = 32.0 +margin_bottom = 32.0 +rect_min_size = Vector2( 32, 32 ) + +[node name="RichTextLabel" type="RichTextLabel" parent="."] +margin_left = 36.0 +margin_right = 236.0 +margin_bottom = 32.0 +rect_min_size = Vector2( 200, 30 ) +custom_fonts/bold_font = SubResource( 1 ) +custom_fonts/normal_font = SubResource( 2 ) +bbcode_enabled = true +bbcode_text = "Waiting..." +text = "Waiting..." + +[node name="KickButton" type="Button" parent="."] +margin_left = 240.0 +margin_right = 292.0 +margin_bottom = 32.0 +custom_fonts/font = SubResource( 3 ) +disabled = true +text = "Kick" diff --git a/pages/GameTable.tscn b/pages/GameTable.tscn index efd6540..dec2312 100644 --- a/pages/GameTable.tscn +++ b/pages/GameTable.tscn @@ -1,8 +1,9 @@ -[gd_scene load_steps=8 format=2] +[gd_scene load_steps=9 format=2] [ext_resource path="res://scripts/GameTable.gd" type="Script" id=1] [ext_resource path="res://textures/wood_board_knotty.png" type="Texture" id=2] [ext_resource path="res://scripts/Board.gd" type="Script" id=3] +[ext_resource path="res://resources/MenuOptions.theme" type="Theme" id=4] [sub_resource type="Environment" id=1] background_mode = 1 @@ -76,7 +77,6 @@ transform = Transform( 1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 15.3 light_color = Color( 1, 0.941176, 0.733333, 1 ) light_energy = 1.512 light_specular = 0.0 -shadow_enabled = true spot_range = 45.4139 [node name="WorldEnvironment" type="WorldEnvironment" parent="."] @@ -94,3 +94,411 @@ transform = Transform( 1, 0, 0, 0, 0.965926, 0.258819, 0, -0.258819, 0.965926, 0 [node name="Camera" type="Camera" parent="CameraHingeYaw/CameraHingePitch"] transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 7 ) script = SubResource( 4 ) + +[node name="PregameUI" type="VBoxContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +mouse_filter = 2 + +[node name="Header" type="HBoxContainer" parent="PregameUI"] +margin_right = 1600.0 +margin_bottom = 39.0 +mouse_filter = 2 +size_flags_horizontal = 3 + +[node name="GameMenuButton" type="Button" parent="PregameUI/Header"] +margin_right = 30.0 +margin_bottom = 39.0 +theme = ExtResource( 4 ) +text = "=" + +[node name="TitleText" type="Label" parent="PregameUI/Header"] +margin_left = 34.0 +margin_top = 3.0 +margin_right = 387.0 +margin_bottom = 36.0 +mouse_filter = 0 +mouse_default_cursor_shape = 2 +theme = ExtResource( 4 ) +text = "$LOBBY_NAME ($JCODE)" + +[node name="PregameControls" type="HBoxContainer" parent="PregameUI"] +margin_top = 43.0 +margin_right = 1600.0 +margin_bottom = 443.0 +mouse_filter = 2 + +[node name="PlayerTab" type="VBoxContainer" parent="PregameUI/PregameControls"] +margin_right = 186.0 +margin_bottom = 400.0 + +[node name="PlayersLabel" type="Label" parent="PregameUI/PregameControls/PlayerTab"] +margin_right = 186.0 +margin_bottom = 14.0 +text = "Players ($CONNECTED/$MAX)" + +[node name="HSeparator" type="HSeparator" parent="PregameUI/PregameControls/PlayerTab"] +margin_top = 18.0 +margin_right = 186.0 +margin_bottom = 22.0 + +[node name="PlayerList" type="VBoxContainer" parent="PregameUI/PregameControls/PlayerTab"] +margin_top = 26.0 +margin_right = 186.0 +margin_bottom = 26.0 + +[node name="StartButton" type="Button" parent="PregameUI/PregameControls/PlayerTab"] +margin_top = 30.0 +margin_right = 186.0 +margin_bottom = 69.0 +theme = ExtResource( 4 ) +text = "Start " + +[node name="ControlsTabContainer" type="TabContainer" parent="PregameUI/PregameControls"] +margin_left = 190.0 +margin_right = 460.0 +margin_bottom = 400.0 +rect_min_size = Vector2( 250, 400 ) + +[node name="Board Params" type="GridContainer" parent="PregameUI/PregameControls/ControlsTabContainer"] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 4.0 +margin_top = 32.0 +margin_right = -4.0 +margin_bottom = -4.0 +columns = 2 + +[node name="BoardSideLenLabel" type="Label" parent="PregameUI/PregameControls/ControlsTabContainer/Board Params"] +margin_top = 5.0 +margin_right = 112.0 +margin_bottom = 19.0 +text = "Board Side Lenth:" +align = 2 + +[node name="board_side_length" type="SpinBox" parent="PregameUI/PregameControls/ControlsTabContainer/Board Params"] +margin_left = 116.0 +margin_right = 262.0 +margin_bottom = 24.0 +min_value = 4.0 +max_value = 10.0 +value = 6.0 + +[node name="AirportStyleLabel" type="Label" parent="PregameUI/PregameControls/ControlsTabContainer/Board Params"] +margin_top = 31.0 +margin_right = 112.0 +margin_bottom = 45.0 +text = "Airport Style:" +align = 2 + +[node name="airport_style" type="OptionButton" parent="PregameUI/PregameControls/ControlsTabContainer/Board Params"] +margin_left = 116.0 +margin_top = 28.0 +margin_right = 262.0 +margin_bottom = 48.0 +text = "Colors & Numbers" +items = [ "Colors & Numbers", null, false, 0, null, "Names", null, false, 1, null ] +selected = 0 + +[node name="NumAirportsLabel" type="Label" parent="PregameUI/PregameControls/ControlsTabContainer/Board Params"] +margin_top = 57.0 +margin_right = 112.0 +margin_bottom = 71.0 +text = "# Airports:" +align = 2 + +[node name="num_airports" type="SpinBox" parent="PregameUI/PregameControls/ControlsTabContainer/Board Params"] +margin_left = 116.0 +margin_top = 52.0 +margin_right = 262.0 +margin_bottom = 76.0 +min_value = 4.0 +max_value = 32.0 +value = 18.0 + +[node name="NumHillsLabel" type="Label" parent="PregameUI/PregameControls/ControlsTabContainer/Board Params"] +margin_top = 85.0 +margin_right = 112.0 +margin_bottom = 99.0 +text = "# Hills:" +align = 2 + +[node name="num_hills" type="SpinBox" parent="PregameUI/PregameControls/ControlsTabContainer/Board Params"] +margin_left = 116.0 +margin_top = 80.0 +margin_right = 262.0 +margin_bottom = 104.0 +max_value = 15.0 +value = 8.0 + +[node name="NumMtnsLabel" type="Label" parent="PregameUI/PregameControls/ControlsTabContainer/Board Params"] +margin_top = 113.0 +margin_right = 112.0 +margin_bottom = 127.0 +text = "# Mountains:" +align = 2 + +[node name="num_mountains" type="SpinBox" parent="PregameUI/PregameControls/ControlsTabContainer/Board Params"] +margin_left = 116.0 +margin_top = 108.0 +margin_right = 262.0 +margin_bottom = 132.0 +max_value = 15.0 +value = 4.0 + +[node name="RunwayCountLabel" type="Label" parent="PregameUI/PregameControls/ControlsTabContainer/Board Params"] +margin_top = 139.0 +margin_right = 112.0 +margin_bottom = 153.0 +text = "Runway Count:" +align = 2 + +[node name="runways_per_airport" type="OptionButton" parent="PregameUI/PregameControls/ControlsTabContainer/Board Params"] +margin_left = 116.0 +margin_top = 136.0 +margin_right = 262.0 +margin_bottom = 156.0 +text = "Random" +items = [ "Random", null, false, 0, null, "1", null, false, 1, null, "2", null, false, 2, null, "3", null, false, 3, null ] +selected = 0 + +[node name="GenerateBoardButton" type="Button" parent="PregameUI/PregameControls/ControlsTabContainer/Board Params"] +margin_top = 160.0 +margin_right = 112.0 +margin_bottom = 180.0 +text = "Generate Board" + +[node name="Game Rules" type="GridContainer" parent="PregameUI/PregameControls/ControlsTabContainer"] +visible = false +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 4.0 +margin_top = 32.0 +margin_right = -4.0 +margin_bottom = -4.0 +columns = 2 + +[node name="FlyOverLabel" type="Label" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_top = 3.0 +margin_right = 166.0 +margin_bottom = 17.0 +text = "Fly Over Airports:" +align = 2 + +[node name="fly_over_airports" type="OptionButton" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_left = 170.0 +margin_right = 355.0 +margin_bottom = 20.0 +text = "No" +items = [ "No", null, false, 0, null, "Altitude 2 Only", null, false, 1, null, "All Altitudes", null, false, 2, null ] +selected = 0 + +[node name="MustLandLabel" type="Label" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_top = 27.0 +margin_right = 166.0 +margin_bottom = 41.0 +text = "Must Land Final Action:" +align = 2 + +[node name="must_land_on_final_action" type="OptionButton" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_left = 170.0 +margin_top = 24.0 +margin_right = 355.0 +margin_bottom = 44.0 +text = "No" +items = [ "No", null, false, 0, null, "Yes", null, false, 1, null ] +selected = 0 + +[node name="TakeoffActionLabel" type="Label" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_top = 51.0 +margin_right = 166.0 +margin_bottom = 65.0 +text = "Takeoff Action:" +align = 2 + +[node name="takeoff_action" type="OptionButton" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_left = 170.0 +margin_top = 48.0 +margin_right = 355.0 +margin_bottom = 68.0 +text = "3" +items = [ "1", null, false, 0, null, "2", null, false, 1, null, "3", null, false, 2, null, "4", null, false, 3, null, "5", null, false, 4, null ] +selected = 2 + +[node name="MoveForwardLabel" type="Label" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_top = 75.0 +margin_right = 166.0 +margin_bottom = 89.0 +text = "Move Forward:" +align = 2 + +[node name="move_forward_order" type="OptionButton" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_left = 170.0 +margin_top = 72.0 +margin_right = 355.0 +margin_bottom = 92.0 +text = "Either" +items = [ "Either", null, false, 0, null, "After Action", null, false, 1, null, "Before Action", null, false, 2, null ] +selected = 0 + +[node name="GamemodeLabel" type="Label" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_top = 99.0 +margin_right = 166.0 +margin_bottom = 113.0 +text = "Gamemode:" +align = 2 + +[node name="gamemode" type="OptionButton" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_left = 170.0 +margin_top = 96.0 +margin_right = 355.0 +margin_bottom = 116.0 +text = "Cooperative" +items = [ "Cooperative", null, false, 0, null, "Versus", null, false, 1, null ] +selected = 0 + +[node name="PlaneAssignmentLabel" type="Label" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_top = 123.0 +margin_right = 166.0 +margin_bottom = 137.0 +text = "Plane Assignment:" +align = 2 + +[node name="plane_assignment" type="OptionButton" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_left = 170.0 +margin_top = 120.0 +margin_right = 355.0 +margin_bottom = 140.0 +text = "Random" +items = [ "Random", null, false, 0, null, "Draft", null, false, 1, null ] +selected = 0 + +[node name="AltitudeChangeLabel" type="Label" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_top = 147.0 +margin_right = 166.0 +margin_bottom = 161.0 +text = "Altitude Change:" +align = 2 + +[node name="altitude_and_turn" type="OptionButton" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_left = 170.0 +margin_top = 144.0 +margin_right = 355.0 +margin_bottom = 164.0 +text = "Turn OR Altitude Change" +items = [ "Turn OR Altitude Change", null, false, 0, null, "Turn AND Altitude Change", null, false, 1, null ] +selected = 0 + +[node name="TurnsPerEventLabel" type="Label" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_top = 173.0 +margin_right = 166.0 +margin_bottom = 187.0 +text = "Turns Per Event:" +align = 2 + +[node name="event_frequency" type="SpinBox" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_left = 170.0 +margin_top = 168.0 +margin_right = 355.0 +margin_bottom = 192.0 +min_value = 1.0 +max_value = 4.0 +value = 1.0 + +[node name="WeatherEnabledLabel" type="Label" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_top = 199.0 +margin_right = 166.0 +margin_bottom = 213.0 +text = "Weather:" +align = 2 + +[node name="weather_enabled" type="OptionButton" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_left = 170.0 +margin_top = 196.0 +margin_right = 355.0 +margin_bottom = 216.0 +text = "Enabled" +items = [ "Disabled", null, false, 0, null, "Enabled", null, false, 1, null ] +selected = 1 + +[node name="OtherEventsLabel" type="Label" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_top = 223.0 +margin_right = 166.0 +margin_bottom = 237.0 +text = "Other Events:" +align = 2 + +[node name="misc_enabled" type="OptionButton" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_left = 170.0 +margin_top = 220.0 +margin_right = 355.0 +margin_bottom = 240.0 +text = "Enabled" +items = [ "Disabled", null, false, 0, null, "Enabled", null, false, 1, null ] +selected = 1 + +[node name="NewPlanesLabel" type="Label" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_top = 249.0 +margin_right = 166.0 +margin_bottom = 263.0 +text = "New Planes Per Turn:" +align = 2 + +[node name="new_planes_per_turn" type="SpinBox" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_left = 170.0 +margin_top = 244.0 +margin_right = 355.0 +margin_bottom = 268.0 +min_value = 1.0 +max_value = 7.0 +value = 4.0 + +[node name="NewPlaneRampupLabel" type="Label" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_top = 275.0 +margin_right = 166.0 +margin_bottom = 289.0 +text = "New Plane Ramp Up:" +align = 2 + +[node name="ramp_up_enabled" type="OptionButton" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_left = 170.0 +margin_top = 272.0 +margin_right = 355.0 +margin_bottom = 292.0 +text = "Disabled" +items = [ "Disabled", null, false, 0, null, "Enabled", null, false, 1, null ] +selected = 0 + +[node name="StartingPlanesLabel" type="Label" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_top = 301.0 +margin_right = 166.0 +margin_bottom = 315.0 +text = "Starting Planes Per Player:" +align = 2 + +[node name="starting_planes_per_player" type="SpinBox" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_left = 170.0 +margin_top = 296.0 +margin_right = 355.0 +margin_bottom = 320.0 +min_value = 1.0 +max_value = 10.0 +value = 3.0 + +[node name="RoundTimerLabel" type="Label" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_top = 329.0 +margin_right = 166.0 +margin_bottom = 343.0 +text = "Round Timer:" +align = 2 + +[node name="round_timer" type="SpinBox" parent="PregameUI/PregameControls/ControlsTabContainer/Game Rules"] +margin_left = 170.0 +margin_top = 324.0 +margin_right = 355.0 +margin_bottom = 348.0 +min_value = 15.0 +max_value = 300.0 +value = 15.0 + +[connection signal="gui_input" from="PregameUI/Header/TitleText" to="." method="_on_TitleText_gui_input"] diff --git a/pages/MainMenu.tscn b/pages/MainMenu.tscn index d216836..5d1f21c 100644 --- a/pages/MainMenu.tscn +++ b/pages/MainMenu.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=17 format=2] +[gd_scene load_steps=15 format=2] [ext_resource path="res://scripts/MainMenu.gd" type="Script" id=1] [ext_resource path="res://resources/fonts/Cochineal-Bold.otf" type="DynamicFontData" id=2] @@ -25,15 +25,6 @@ font_data = ExtResource( 2 ) default_font = SubResource( 1 ) Button/fonts/font = SubResource( 30 ) -[sub_resource type="Gradient" id=15] -offsets = PoolRealArray( 0 ) -colors = PoolColorArray( 0, 0, 0, 1 ) - -[sub_resource type="GradientTexture2D" id=22] -gradient = SubResource( 15 ) -width = 32 -height = 32 - [sub_resource type="Gradient" id=23] offsets = PoolRealArray( 0 ) colors = PoolColorArray( 0, 0, 0, 1 ) @@ -145,7 +136,7 @@ margin_right = 202.0 margin_bottom = 87.0 rect_min_size = Vector2( 60, 40 ) size_flags_horizontal = 0 -icon = SubResource( 22 ) +icon = SubResource( 21 ) expand_icon = true items = [ "", SubResource( 21 ), false, 0, null, "", SubResource( 21 ), false, 1, null, "", SubResource( 21 ), false, 2, null, "", SubResource( 21 ), false, 3, null, "", SubResource( 21 ), false, 4, null, "", SubResource( 21 ), false, 5, null, "", SubResource( 21 ), false, 6, null, "", SubResource( 21 ), false, 7, null, "", SubResource( 21 ), false, 8, null, "", SubResource( 21 ), false, 9, null ] selected = 0 @@ -297,3 +288,43 @@ theme = ExtResource( 4 ) max_length = 39 placeholder_text = "Game Coordinator URL" placeholder_alpha = 0.389 + +[node name="NotifPopup" type="PopupPanel" parent="."] +anchor_left = 0.4 +anchor_top = 0.35 +anchor_right = 0.6 +anchor_bottom = 0.65 +margin_right = 8.0 +margin_bottom = 8.0 +popup_exclusive = true + +[node name="PopupControl" type="Control" parent="NotifPopup"] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 4.0 +margin_top = 4.0 +margin_right = -4.0 +margin_bottom = -4.0 + +[node name="TitleLabel" type="Label" parent="NotifPopup/PopupControl"] +anchor_left = 0.1 +anchor_right = 0.9 +anchor_bottom = 0.15 +theme = ExtResource( 4 ) + +[node name="MessageLabel" type="Label" parent="NotifPopup/PopupControl"] +anchor_left = 0.05 +anchor_top = 0.25 +anchor_right = 0.95 +anchor_bottom = 0.75 + +[node name="ClosePopupButton" type="Button" parent="NotifPopup/PopupControl"] +anchor_left = 0.3 +anchor_top = 0.8 +anchor_right = 0.3 +anchor_bottom = 0.8 +rect_min_size = Vector2( 120, 50 ) +theme = ExtResource( 4 ) +text = "Close" + +[connection signal="pressed" from="NotifPopup/PopupControl/ClosePopupButton" to="NotifPopup" method="set_visible" binds= [ false ]] diff --git a/pages/MainScene.tscn b/pages/MainScene.tscn index da9bdcd..ce04e54 100644 --- a/pages/MainScene.tscn +++ b/pages/MainScene.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=4 format=2] -[ext_resource path="res://network/websocket_client_basic.gd" type="Script" id=1] +[ext_resource path="res://network/GCClient.tscn" type="PackedScene" id=1] [ext_resource path="res://pages/MainMenu.tscn" type="PackedScene" id=2] [ext_resource path="res://scripts/MainScene.gd" type="Script" id=3] @@ -9,7 +9,6 @@ anchor_right = 1.0 anchor_bottom = 1.0 script = ExtResource( 3 ) -[node name="GameCoordinator" type="Node" parent="."] -script = ExtResource( 1 ) - [node name="MainMenu" parent="." instance=ExtResource( 2 )] + +[node name="GCClient" parent="." instance=ExtResource( 1 )] diff --git a/pages/ServerBrowser.tscn b/pages/ServerBrowser.tscn index c5d2ef4..5c1c4bf 100644 --- a/pages/ServerBrowser.tscn +++ b/pages/ServerBrowser.tscn @@ -96,6 +96,7 @@ margin_left = 383.0 margin_top = 129.0 margin_right = 633.0 margin_bottom = 399.0 +popup_exclusive = true [node name="Control" type="Control" parent="HostPopup"] margin_left = 4.0 diff --git a/project.godot b/project.godot index e90fb8a..d5bbade 100644 --- a/project.godot +++ b/project.godot @@ -8,6 +8,22 @@ config_version=4 +_global_script_classes=[ { +"base": "Area", +"class": "Airplane", +"language": "GDScript", +"path": "res://scripts/Airplane.gd" +}, { +"base": "Node", +"class": "GCClient", +"language": "GDScript", +"path": "res://network/GameCoordinatorClient.gd" +} ] +_global_script_class_icons={ +"Airplane": "", +"GCClient": "" +} + [application] config/name="ATC" @@ -15,6 +31,10 @@ run/main_scene="res://pages/MainScene.tscn" boot_splash/show_image=false config/icon="res://icon.png" +[audio] + +driver="Dummy" + [autoload] Globals="*res://scripts/Globals.gd" diff --git a/resources/external/game_coordinator.py b/resources/external/game_coordinator.py index 0f04e18..8daae7f 100644 --- a/resources/external/game_coordinator.py +++ b/resources/external/game_coordinator.py @@ -104,6 +104,7 @@ async def remove_player(lobby, player_id): del LOBBIES[lobby["lobby_id"]] elif was_host: # host must be transferred + print("changing host") new_host_id = None for player_id in lobby["players"]: @@ -128,7 +129,7 @@ async def disconnect_player(lobby, player_id, reason='disconnect'): dc_player = lobby["players"][player_id] dc_player["connection_status"] = 0 if "dead" not in dc_player: - response = {"type" : "disconnected", "message" : f"Disconnected. Reason: {reason}"} + response = {"type" : "disconnected", "message" : f"Disconnected. Reason: {reason}", "reason" : reason} try: await dc_player["socket"].send(json.dumps(response)) except: @@ -147,6 +148,9 @@ async def disconnect_player(lobby, player_id, reason='disconnect'): async def connection_lost(lobby, player_id): if player_id in lobby["players"]: lobby["players"][player_id]["connection_status"] = 0 + current_players = await get_player_info(lobby) + announcement = {"type" : "announcement_player_connerr", "player_id" : player_id, "current_players" : current_players } + await broadcast_active(lobby, announcement) await asyncio.sleep( DISCONNECT_GRACE_PERIOD if not lobby["paused"] else PAUSED_GRACE_PERIOD ) if player_id in lobby["players"] and lobby["players"][player_id]["connection_status"] == 0: lobby["players"][player_id]["dead"] = True @@ -188,6 +192,8 @@ async def lobby_loop(websocket, lobby, player): lobby_id = lobby["lobby_id"] async for message in websocket: event = json.loads(message) + lobby = LOBBIES[lobby_id] + player = lobby["players"][player_id] # handle messages sent from connected players yet unconfirmed by host if "waiting" in player: @@ -197,7 +203,10 @@ async def lobby_loop(websocket, lobby, player): if player["waiting_retries"] > MAX_JOIN_REQUESTS: await end_lobby(lobby_id) # does this need to be awaited? return True - await send_join_request_to_host(lobby, player_id) + try: + await send_join_request_to_host(lobby, player_id) + except Exception as e: + print(e) continue # deny any other requests @@ -218,33 +227,38 @@ async def lobby_loop(websocket, lobby, player): continue if not player["is_host"]: - response = {"type" : "gc_error", "message" : f"GC ERROR: Only host has access to command {event['command']}."} + response = {"type" : "gc_error", "message" : f"Only host has access to command {event['command']}."} await websocket.send(json.dumps(response)) continue # following commands only available to game host if event["command"] == "accept_player": if ("player_id" not in event) or (event["player_id"] not in lobby["players"]): - response = {"type" : "gc_error", "message" : "GC ERROR: No player ID given, or given player ID is not in the lobby."} + response = {"type" : "gc_error", "message" : "No player ID given, or given player ID is not in the lobby."} await websocket.send(json.dumps(response)) continue - + accepted_id = event["player_id"] if "waiting" not in lobby["players"][accepted_id]: - response = {"type" : "gc_error", "message" : "GC ERROR: Player already accepted."} + + response = {"type" : "gc_error", "message" : "Player already accepted."} await websocket.send(json.dumps(response)) continue + accepted_player = lobby["players"][accepted_id] del accepted_player["waiting"] del accepted_player["waiting_args"] del accepted_player["waiting_since"] del accepted_player["waiting_retries"] - notification = { "type" : "join_ok" } - + + current_players = await get_player_info(lobby) + notification = { "type" : "join_ok", "current_players" : current_players} + if "args" in event: notification["args"] = event["args"] + await accepted_player["socket"].send(json.dumps(notification)) # notify waiting player of acceptance elif event["command"] == "deny_player": if ("player_id" not in event) or (event["player_id"] not in lobby["players"]): - response = {"type" : "gc_error", "message" : "GC ERROR: No player ID given, or given player ID is not in the lobby."} + response = {"type" : "gc_error", "message" : "No player ID given, or given player ID is not in the lobby."} await websocket.send(json.dumps(response)) continue denied_id = event["player_id"] @@ -253,7 +267,7 @@ async def lobby_loop(websocket, lobby, player): await disconnect_player(lobby, denied_id, "denied"+reason_text) elif event["command"] == "kick_player": if ("player_id" not in event) or (event["player_id"] not in lobby["players"]): - response = {"type" : "gc_error", "message" : "GC ERROR: No player ID given, or given player ID is not in the lobby."} + response = {"type" : "gc_error", "message" : "No player ID given, or given player ID is not in the lobby."} await websocket.send(json.dumps(response)) continue kicked_id = event["player_id"] @@ -286,12 +300,13 @@ async def lobby_loop(websocket, lobby, player): response = {"type" : "request", "destination" : player_id, "request_fields" : event["request_fields"] } await game["players"][ event["source"] ]["socket"].send( json.dumps(response) ) elif event["type"] == "request_response": - response = {"type" : "request_response", "data" : event["requested_data"] } + response = {"type" : "request_response", "source" : player_id ,"requested_data" : event["requested_data"] } await game["players"][ event["destination"] ]["socket"].send( json.dumps(response) ) elif event["type"] == "broadcast": - message = {"type" : "broadcast", "payload" : event["payload"]} - for player in game["players"]: - if player["connection_status"] == 1 and ( (player != player_id) or ("to_self" in event) ): await player["socket"].send(json.dumps(message)) + message = {"type" : "broadcast", "command" : event["command"], "payload" : event["payload"]} + for _player_id in lobby["players"]: + player = lobby["players"][_player_id] + if player["connection_status"] == 1 and ( (_player_id != player_id) or ("to_self" in event) ): await player["socket"].send(json.dumps(message)) if lobby_id not in LOBBIES: return True # lobby closed if player_id not in LOBBIES[lobby_id]["players"] or ("dead" in LOBBIES[lobby_id]["players"][player_id]): return True # player removed @@ -301,39 +316,39 @@ async def lobby_loop(websocket, lobby, player): async def join_lobby(websocket, event): for required_field in ["lobby_id", "game_type", "username"]: if required_field not in event: - response = {"type" : "gc_error", "message" : f"GC ERROR: Missing required field: '{required_field}'."} + response = {"type" : "join_fail", "message" : f"Missing required field: '{required_field}'."} await websocket.send(json.dumps(response)) await asyncio.sleep(SOCKET_GRACE_PERIOD) return # close connection lobby_id = event["lobby_id"] if lobby_id not in LOBBIES: - response = {"type" : "gc_error", "message" : f"GC ERROR: Lobby '{lobby_id}' does not exist."} + response = {"type" : "join_fail", "message" : f"Lobby '{lobby_id}' does not exist."} await websocket.send(json.dumps(response)) await asyncio.sleep(SOCKET_GRACE_PERIOD) return # close connection lobby = LOBBIES[lobby_id] if str(event["game_type"]) != lobby["game_type"]: - response = {"type" : "gc_error", "message" : f"GC ERROR: Mismatched game types. Expected {LOBBIES[lobby_id]['game_type']}, received {event['game_type']}."} + response = {"type" : "join_fail", "message" : f"Mismatched game types. Expected {LOBBIES[lobby_id]['game_type']}, received {event['game_type']}."} await websocket.send(json.dumps(response)) await asyncio.sleep(SOCKET_GRACE_PERIOD) return # close connection if len(lobby["players"]) >= lobby["max_players"]: - response = {"type" : "gc_error", "message" : "GC ERROR: Lobby full."} + response = {"type" : "join_fail", "message" : "Lobby full."} await websocket.send(json.dumps(response)) await asyncio.sleep(SOCKET_GRACE_PERIOD) return # close connection if lobby["locked"] and ("rejoin_key" not in event): - response = {"type" : "gc_error", "message" : "GC ERROR: Lobby is locked, no new connections are being accepted."} + response = {"type" : "join_fail", "message" : "Lobby is locked, no new connections are being accepted."} await websocket.send(json.dumps(response)) await asyncio.sleep(SOCKET_GRACE_PERIOD) return # close connection if lobby["private"] and (event["password"] != lobby["password"]) and ("rejoin_key" not in event): - response = {"type" : "gc_error", "message" : "GC ERROR: Incorrect password for private lobby."} + response = {"type" : "join_fail", "message" : "Incorrect password for private lobby."} await websocket.send(json.dumps(response)) await asyncio.sleep(SOCKET_GRACE_PERIOD) return # close connection @@ -365,7 +380,7 @@ async def join_lobby(websocket, event): return # end of socket # Could not rejoin - response = {"type" : "gc_error", "message" : "GC ERROR: Could not rejoin with given key."} + response = {"type" : "rejoin_fail", "message" : "Could not rejoin with given key."} await websocket.send(json.dumps(response)) await asyncio.sleep(SOCKET_GRACE_PERIOD) return # end of socket @@ -379,7 +394,7 @@ async def join_lobby(websocket, event): player_info = await get_player_info(lobby) notification = {"type" : "announcement_player_joining", "current_players" : player_info} await broadcast_active(lobby, notification) - response = { "type" : "join_request_ok", "player_id" : new_player["player_id"], "rejoin_key" : new_player["rejoin_key"] } + response = { "type" : "join_request_ok", "player_id" : new_player["player_id"], "rejoin_key" : new_player["rejoin_key"], "current_players" : player_info, "max_players" : lobby["max_players"], "lobby_name" : lobby["lobby_name"], "private" : lobby["private"]} await websocket.send(json.dumps(response)) @@ -398,14 +413,14 @@ async def join_lobby(websocket, event): async def host_lobby(websocket, event): for reqd_i, required_field in enumerate(HOST_LOBBY_REQD_FIELDS): if required_field not in event: - response = {"type" : "gc_error", "message" : f"GC ERROR: Missing required field: '{required_field}'."} + response = {"type" : "host_fail", "message" : f"Missing required field: '{required_field}'."} await websocket.send(json.dumps(response)) await asyncio.sleep(SOCKET_GRACE_PERIOD) return # close connection try: - test_argument_type = HOST_LOBBY_REQD_FIELD_TYPES[reqd_i](event[required_field]) + HOST_LOBBY_REQD_FIELD_TYPES[reqd_i](event[required_field]) except: - response = { "type" : "gc_error", "message" : f"GC_ERROR: Invalid type for field '{required_field}'." } + response = { "type" : "host_fail", "message" : f"Invalid type for field '{required_field}'." } await websocket.send(json.dumps(response)) await asyncio.sleep(SOCKET_GRACE_PERIOD) return # close connection diff --git a/resources/unused_code.gd b/resources/unused_code.gd index 43570b5..9f8a9e8 100644 --- a/resources/unused_code.gd +++ b/resources/unused_code.gd @@ -15,3 +15,19 @@ func get_random_airport_name(exceptions=[]): while airport_names[name_index] in exceptions: name_index = randi() % len(airport_names) return airport_names[name_index] + +func on_player_join_request(): + var taken_colors : Array = [] + for player_id in PLAYER_INFO.keys(): + taken_colors.push_back( PLAYER_INFO[player_id]["color_id"] ) + var available_colors : Array = [] + for color in range(len(Globals.colors)): + if !(color in taken_colors): available_colors.push_back(color) + var response_args = { "color_id" : available_colors.pick_random() } # pick random color by default + + if ("color_pref_1" in args) and (args["color_pref_1"] in available_colors): + response_args["color_id"] = args["color_pref_1"] + elif ("color_pref_2" in args) and (args["color_pref_2"] in available_colors): + response_args["color_id"] = args["color_pref_2"] + + response["args"] = response_args diff --git a/scripts/Plane.gd b/scripts/Airplane.gd index 6889e31..b13ed33 100644 --- a/scripts/Plane.gd +++ b/scripts/Airplane.gd @@ -1,3 +1,4 @@ +class_name Airplane extends Area var starting_altitude: int = 0 # initial altitude for the turn, determines number of actions @@ -16,6 +17,8 @@ var destination_col: Color # for display purposes only var destination_name: String # for display purposes var destination_id: int # determines above ^ +var owner_id : String + var rotation_tween: Tween = null var actions: Array = [] diff --git a/scripts/Board.gd b/scripts/Board.gd index f18ca74..04f2d90 100644 --- a/scripts/Board.gd +++ b/scripts/Board.gd @@ -17,6 +17,8 @@ var airports = {} # id : HexSpace of cell_type airport var side_len: int +const BOARD_GEN_ATTEMPTS : int = 5 + # Y R B G var airport_colors = [ Color(1, 1, 0), Color(1, 0, 0), Color(0.3, 0.3, 1), Color(0, 0.8, 0) ] @@ -31,7 +33,7 @@ enum { PLAIN, HILLS, MOUNTAINS, AIRPORT } const adjacent_offsets = [ [0,1] , [-1, 0], [-1, -1], [0, -1], [1, 0], [1, 1] ] # indices of the offsets that are valid cells to approach from -const approaches_i: Array= [ [0, 1, 2, 3, 4, 5], [0,1,3,4], [0,3] ] +const approaches_i: Array= [ [], [0,3], [0,1,3,4], [0, 1, 2, 3, 4, 5] ] func _ready(): @@ -87,8 +89,8 @@ func display_board(): var new_cell = hex_space.instance() new_cell.call_deferred("set", "global_position", Vector3(x, randf()/15, z)) - new_cell.set_up(row[c]) add_child(new_cell) + new_cell.set_up(row[c]) row_display[c] = new_cell board_display.push_back(row_display) @@ -126,6 +128,7 @@ func populate_board(num_mountains : int, num_hills : int, num_airports : int, ru airport_display = [ randi() % 9 + 1, randi() % 4 ] # number, color while airport_display in used_airports: airport_display = [ randi() % 9 + 1, randi() % 4 ] + used_airports.push_back(airport_display) # find valid spot var spot_okay:bool = false var rot:int @@ -189,25 +192,36 @@ func populate_board(num_mountains : int, num_hills : int, num_airports : int, ru if not spot_okay: return false # could not form valid map - var args = {"cell_type" : AIRPORT , "pos" : [spot_r, spot_c], "orientation" : rot, "airport_id" : airport_id, "runways" : runways, 'valid_approach_offsets' : valid_approaches, "use_names" : use_names} + var args = {"cell_type" : AIRPORT , "pos" : [spot_r, spot_c], "orientation" : rot, "airport_id" : airport_id, "runway_count" : runways, 'valid_approach_offsets' : valid_approaches, "use_names" : use_names} if use_names: args["airport_name"] = airport_display else: - args["airport_color"] = airport_colors[airport_display[COLOR]] + args["airport_color"] = airport_colors[airport_display[COLOR]].to_html() args["airport_number"] = airport_display[NUMBER] board[spot_r][spot_c] = args available_board_coords.pop_at(spot_i) airport_id += 1 return true -func get_json(): - return board +func get_board(): + return self.board + +func set_board(board_array): + reset_board() + self.board = board_array + display_board() + func generate_board(hex_side_len: int, num_mountains : int, num_hills : int, num_airports : int, runway_count : int, use_names : bool = false) -> bool: create_board_base(hex_side_len) - if not populate_board(num_mountains, num_hills, num_airports, runway_count, use_names): + var attempts : int = 1 + while attempts < BOARD_GEN_ATTEMPTS and (not populate_board(num_mountains, num_hills, num_airports, runway_count, use_names)): + attempts += 1 + reset_board() + + if attempts >= BOARD_GEN_ATTEMPTS: reset_board() - print("Invalid board creation parameters") + display_board() return false display_board() return true diff --git a/scripts/GameTable.gd b/scripts/GameTable.gd index b0bedf2..f21d027 100644 --- a/scripts/GameTable.gd +++ b/scripts/GameTable.gd @@ -2,7 +2,7 @@ extends Spatial # MULTIPLAYER DATA -var gc_client # to be assigned by MainScene upon game creation/joining +var GC : GCClient # Game Coordinator connection/client, to be assigned by MainScene upon game creation/joining @@ -33,7 +33,7 @@ var RULES = { "misc_enabled" : 1, "new_planes_per_turn" : 4, # 1 - 8 "ramp_up_enabled" : 0, # whether to ramp up to max planes per turn - "starting_planes_per_player": 4, + "starting_planes_per_player": 3, "round_timer" : 60, # seconds } @@ -43,26 +43,163 @@ var PLANES = [] var desired_player_count: int var is_board_generated: bool = false # determine whether host can begin the game +var PLAYER_INFO = {} # + # END GAME DATA # directions: E, NE, NW, W, SW, SE const adjacent_offsets = [ [0,1] , [-1, 0], [-1, -1], [0, -1], [1, 0], [1, 1] ] +func _ready(): + pass +func _process(_delta): + var msg = GC.receive() + if msg: handle_gc_message(msg) -func _ready(): +func set_up(gc_client): + GC = gc_client + GC.connect("join_request_received", self, "on_join_request_received") + GC.connect("disconnected_from_lobby", self, "on_disconnect") + GC.connect("connection_closed_dirty", self, "on_connection_closed_dirty") + + var titletext = $PregameUI/Header/TitleText.text.replace("$JCODE", GC.lobby_id) + titletext = titletext.replace("$LOBBY_NAME", GC.lobby_name) + if OS.get_name() == "HTML5": # running on web + titletext += " (click to copy)" + $PregameUI/Header/TitleText.text = titletext + + # set up pregame controls + # TODO : function where editability/enabled is changed on game host change + var i = 0 + for control_group in ["Board Params", "Game Rules"]: + var path_root : String = "PregameUI/PregameControls/ControlsTabContainer/"+control_group+"/" + var dict = BOARD_GEN_PARAMS if i == 0 else RULES + for key in dict.keys(): + var setting_node = get_node(path_root + key) + if setting_node is OptionButton: + setting_node.connect("item_selected", self, "on_setting_update", [control_group, key]) + if not GC.is_host: setting_node.disabled = true + elif setting_node is SpinBox: + setting_node.connect("value_changed", self, "on_setting_update", [control_group, key]) + if not GC.is_host: setting_node.editable = false + i += 1 + + $PregameUI/PregameControls/PlayerTab/StartButton.disabled = not GC.is_host + var gen_btn : Button = get_node("PregameUI/PregameControls/ControlsTabContainer/Board Params/GenerateBoardButton") + gen_btn.disabled = not GC.is_host + gen_btn.connect("pressed", self, "generate_board_ui") + +func set_up_args(args): + if !(GC.player_id in PLAYER_INFO): PLAYER_INFO[GC.player_id] = {} + PLAYER_INFO[GC.player_id]["color_id"] = args["color_id"] + +func on_join_request_received(player_id, args): + if !GC.is_host: return # just in case + var response = { "type" : "lobby_control", "command": "accept_player", "player_id" : player_id } + + GC.send_json(response) + +# new_value filled by UI element signal, control_group and setting_name from custom binds +func on_setting_update(new_value, control_group, setting_name): + if !GC.is_host: return # prevent non-hosts from broadcasting updates when setting the value because of https://github.com/godotengine/godot/issues/70821 + var dict = BOARD_GEN_PARAMS if control_group == "Board Params" else RULES + dict[setting_name] = new_value + + var command = "set_board_gen_params" if control_group == "Board Params" else "set_game_rules" + var message = {"type" : "broadcast", "command" : command, "payload" : { setting_name : new_value } } + GC.send_json(message) + +# fields being dictionary of form { field : value } +func set_setting_remote(control_group, fields): + var dict = BOARD_GEN_PARAMS if control_group == "Board Params" else RULES + var path_root : String = "PregameUI/PregameControls/ControlsTabContainer/"+control_group+"/" + for field in fields: + var new_value = fields[field] + dict[field] = new_value + var setting_node = get_node(path_root + field) + if setting_node is SpinBox: + setting_node.set_value(new_value) + elif setting_node is OptionButton: + setting_node.select(new_value) # does not emit signal + +func generate_board_ui(): + is_board_generated = BOARD.generate_board(BOARD_GEN_PARAMS["board_side_length"], + BOARD_GEN_PARAMS["num_mountains"], + BOARD_GEN_PARAMS["num_hills"], + BOARD_GEN_PARAMS["num_airports"], + BOARD_GEN_PARAMS["runways_per_airport"], + BOARD_GEN_PARAMS["airport_style"] + ) + var message = {"type" : "broadcast", "command" : "set_board", "payload" : BOARD.board } + GC.send_json(message) + +func on_disconnect(reason): pass -func set_up(ws_client, is_host: bool, lobby_id: String, player_id: String, rejoin_key: String): - self.ws_client = ws_client - self.is_host = is_host - self.lobby_id = lobby_id - self.player_id = player_id - self.rejoin_key = rejoin_key +func on_connection_closed_dirty(): + get_parent() + +# messages have been pre-processed by game coordinator client to ensure +# valid JSON with "type" field +func handle_gc_message(message): + if "current_players" in message: update_player_info() # add new players to PLAYER_INFO, remove disconnected players + if message["type"] == "request": + #var data = get_data() + #var response = { "type" : "request_response", "requested_data" : data, "destination" : event["source"] } + #GC.send_json + pass + elif message["type"] == "request_response": + #set_data() + pass + elif message["type"] == "broadcast" and "command" in message: + var cmd : String = message["command"] + if cmd == "set_game_rules": + set_setting_remote("Game Rules", message["payload"]) + elif cmd == "set_board_gen_params": + set_setting_remote("Board Params", message["payload"]) + elif cmd == "set_board": + BOARD.set_board(message["payload"]) + +# info being a dictionary of format { player_id : {field : value} } +func update_player_info(info = null): + # ADD NEW PLAYERS + var connected_player_ids : Array = [] + for player in GC.players: + connected_player_ids.push_back(player["player_id"]) + if !(player["player_id"] in PLAYER_INFO): + PLAYER_INFO[player["player_id"]] = {"username" : player["username"]} + + # EDIT PLAYER ATTRIBUTES + if info is Dictionary: + for player_id in info.keys(): + if !(info[player_id] is Dictionary) or !(player_id in PLAYER_INFO): return + for field in info[player_id].keys(): + PLAYER_INFO[player_id][field] = info[player_id][field] + + # DELETE DISCONNECTED PLAYERS + var player_ids_info = PLAYER_INFO.keys() + for player_id in player_ids_info: + if !(player_id in connected_player_ids) and (player_id in PLAYER_INFO): + PLAYER_INFO.erase(player_id) + continue # ask host for complete game state. returned fields will depend on game state func request_complete_game_state(): - if is_host: return - var request = { "type" : "request", "source" : host_id, "requested_data" : "ALL" } - + if GC.is_host: return + var request = { "type" : "request", "source" : GC.host_id, "requested_data" : "ALL" } + GC.send_json(request) + + +func _on_TitleText_gui_input(event): + if (event is InputEventMouseButton && event.pressed && event.button_index == 1): + if OS.get_name() == "HTML5": # running on web + var direct_join_URL : String = JavaScript.eval("window.location.href") + direct_join_URL += "?lobby_id="+GC.lobby_id + if Globals.GC_URL != Globals.DEFAULT_GC_URL: + direct_join_URL += "&gc_url="+Globals.GC_URL + if GC.is_private: + direct_join_URL += "&pw="+GC.lobby_password + OS.set_clipboard(direct_join_URL) + diff --git a/scripts/Globals.gd b/scripts/Globals.gd index 3603dbd..286b579 100644 --- a/scripts/Globals.gd +++ b/scripts/Globals.gd @@ -9,6 +9,8 @@ var airport_names : Array = [] const DEFAULT_GC_URL : String = "ws://192.168.7.112:8181" var GC_URL : String = "ws://192.168.7.112:8181" +const GAME_TYPE : String = "ATC" + func _ready(): load_airport_names() set_process(false) @@ -17,7 +19,6 @@ func update_gc_url(new_url : String): GC_URL = new_url - func load_airport_names(): var f = File.new() f.open(airport_names_file, File.READ) diff --git a/scripts/HexSpace.gd b/scripts/HexSpace.gd index bcf64f1..614039a 100644 --- a/scripts/HexSpace.gd +++ b/scripts/HexSpace.gd @@ -55,7 +55,7 @@ func set_up(settings): valid_arrival_bearings.clear() - if settings["orientation"]: # bearing according to E, NE, etc. + if "orientation" in settings: # bearing according to E, NE, etc. orientation = settings["orientation"] self.global_rotation.y = orientation * deg2rad(60) @@ -68,10 +68,11 @@ func set_up(settings): if settings["use_names"]: airport_name = settings["airport_name"] + $Airport/AirportName.text = airport_name $Airport/AirportName.visible = true else: airport_number = settings["airport_number"] - airport_color = settings["airport_color"] + airport_color = Color(settings["airport_color"]) $Airport/AirportIcon.visible = true $Airport/AirportIcon.texture = load("res://textures/airport_indicator_%d.png" % airport_number) $Airport/AirportIcon.modulate = airport_color diff --git a/scripts/MainMenu.gd b/scripts/MainMenu.gd index fca6658..3411054 100644 --- a/scripts/MainMenu.gd +++ b/scripts/MainMenu.gd @@ -1,7 +1,7 @@ extends Control -signal game_host_request(args) -signal game_join_request(args) +signal game_host_request(game_name, username, max_players, private, password) +signal game_join_request(lobby_id, password, username, args) var lobby_name_changed : bool = false # automatically update lobby name to be based on player's name @@ -14,8 +14,19 @@ func _ready(): $SettingsMenu/GameCoordinatorURL.connect("text_changed", Globals, "update_gc_url") $PlayerInfo/Username.connect("text_changed", self, "automatically_update_lobby_name") $HostMenu/GameName.connect("text_changed", self, "set_lobby_name_changed") + + $HostMenu/HostButton.connect("pressed", self, "on_host_button_pressed") + $JoinMenu/JoinButton.connect("pressed", self, "on_join_button_pressed") #main_menu() # in case things are incorrectly visible from editing +func open_popup(title, message, close_disabled = false): + $NotifPopup.popup_centered() + $NotifPopup/PopupControl/TitleLabel.text = title + $NotifPopup/PopupControl/MessageLabel.text = message + $NotifPopup/PopupControl/ClosePopupButton.disabled = close_disabled + +func close_popup(): + $NotifPopup.visible = false func set_lobby_name_changed(_disregard_new_text): lobby_name_changed = true @@ -54,6 +65,8 @@ func set_back_button_visible(visible : bool): $BackButton.visible = visible $BackButton.disabled = false # reset in case left disabled by other function +## MENU CALLBACKS + # go to join game menu func join_menu_button_pressed(lobby_id_from_url : String = "", password_from_url : String = ""): set_menu_buttons_visible(false) @@ -82,3 +95,13 @@ func settings_menu_button_pressed(): # return to main menu func back_button(): main_menu() + +## GAME CALLBACKS +func on_join_button_pressed(): + open_popup("Connecting...", "Connecting to game coordinator...", true) + var args = { "color_pref_1" : $PlayerInfo/PlayerColor.selected, "color_pref_2" : $PlayerInfo/AltPlayerColor.selected } + emit_signal("game_join_request", $JoinMenu/LobbyID.text, $JoinMenu/Password.text, $PlayerInfo/Username.text, args) + +func on_host_button_pressed(): + open_popup("Connecting...", "Connecting to game coordinator...", true) + emit_signal("game_host_request", $HostMenu/GameName.text, $PlayerInfo/Username.text, $HostMenu/PlayerCount.get_selected_id(), $HostMenu/PrivateToggle.pressed, $HostMenu/Password.text) diff --git a/scripts/MainScene.gd b/scripts/MainScene.gd index fc15463..c15ed1c 100644 --- a/scripts/MainScene.gd +++ b/scripts/MainScene.gd @@ -1,13 +1,78 @@ extends Control +onready var GC = $GCClient +onready var MM = $MainMenu + +enum { MENU, INGAME } +var game_state : int = MENU + +onready var game_scene_base : PackedScene = preload("res://pages/GameTable.tscn") +var game_scene = null func _ready(): if OS.get_name() == "HTML5": # running on web var lobby_id = JavaScript.eval("new URLSearchParams(document.location.search).get('lobby_id')") var gc_url = JavaScript.eval("new URLSearchParams(document.location.search).get('gc_url')") var password = JavaScript.eval("new URLSearchParams(document.location.search).get('pw')") - if lobby_id: - var pw : String = password if password else "" - $MainMenu.join_menu_button_pressed(lobby_id, pw) if gc_url: Globals.update_gc_url(gc_url) + if lobby_id: + var pw : String = password if password else "" + MM.join_menu_button_pressed(lobby_id, pw) + + GC.connect("join_request_success", self, "on_join_request_success") + GC.connect("host_success", self, "on_host_success") + GC.connect("host_failure", self, "on_host_failure") + GC.connect("join_success", self, "on_join_success") + GC.connect("join_failure", self, "on_join_failure") + GC.connect("gc_connection_failed", self, "on_gc_connection_failure") + + MM.connect("game_host_request", GC, "host_lobby") + MM.connect("game_join_request", GC, "join_lobby") + +func _process(_delta): + if GC.state > 1 and (game_state == MENU): + GC.receive() + +func to_main_menu(): + if game_state == MENU: return + if game_scene: + remove_child(game_scene) + game_scene.queue_free() + GC.reset_gc_client() + GC.close_socket() + MM.visible = true + game_state = MENU + +func to_game(args = null): + if game_state == INGAME: return + MM.close_popup() + MM.visible = false + game_scene = game_scene_base.instance() + game_scene.set_up(GC) + if args: + game_scene.set_up_args(args) + add_child(game_scene) + game_state = INGAME + +# GC FUNCTIONS + +func on_gc_connection_failure(): + MM.open_popup("Error", "Could not connect to game coordinator.") + +func on_join_request_success(): + MM.close_popup() + MM.open_popup("Connected", "Waiting for acknowledgment from host...") + +func on_host_success(): + to_game() + +func on_host_failure(reason): + MM.open_popup("Error", "Could not host game: %s" % reason) + +func on_join_success(join_args): + to_game(join_args) + +func on_join_failure(reason): + MM.open_popup("Error", "Could not join game: %s" % reason) + diff --git a/scripts/PlaneControlBoard.gd b/scripts/PlaneControlBoard.gd index cfe94b5..8c9c87e 100644 --- a/scripts/PlaneControlBoard.gd +++ b/scripts/PlaneControlBoard.gd @@ -28,5 +28,7 @@ func set_destination(number, color): $DestinationHBOX/DestinationIcon.texture = airport_indicators[number - 1] $DestinationHBOX/DestinationIcon.modulate = color -func update_display_from_plane(plane): - pass +func on_plane_selected(plane : Airplane, destination): + + + self.show() diff --git a/scripts/ServerBrowser.gd b/scripts/ServerBrowser.gd index 410eec2..41f478a 100644 --- a/scripts/ServerBrowser.gd +++ b/scripts/ServerBrowser.gd @@ -18,6 +18,11 @@ func _ready(): $RefreshButton.connect("pressed", self, "refresh_game_list") $HostPopup/Control/PrivateToggle.connect("toggled", self, "toggle_password_vis") $Username.connect("text_changed", $HostPopup/Control/GameName, "set_text") + $HostPopup/Control/PlayerCount.connect("item_selected", self, "test_select_signal") + + +func test_select_signal(index): + print("selected %d" % index) func join_game(): $HostPopup.visible = false @@ -53,6 +58,7 @@ func add_games_to_list(games): game_ids.append( game["id"] ) func toggle_password_vis(pressed): + $HostPopup/Control/PlayerCount.select(3) $HostPopup/Control/Password.visible = pressed |
