summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnson Bridges <bridges.anson@gmail.com>2025-08-19 12:38:02 -0700
committerAnson Bridges <bridges.anson@gmail.com>2025-08-19 12:38:02 -0700
commit255fbf19cc9499ef384d41f68515da5e49e8a3ce (patch)
tree13c838229198383b24644f613787e34842ea7ab2
parentf087c6a98b1da55525a6e3c1d7c82477f82eb5cd (diff)
added menus, reworking GC client architecture
-rw-r--r--network/GCClient.tscn6
-rw-r--r--network/GameCoordinatorClient.gd101
-rw-r--r--network/WSClient.tscn6
-rw-r--r--network/websocket_client_basic.gd (renamed from network/websocket_client.gd)1
-rw-r--r--objects/HexSpace.tscn1
-rw-r--r--pages/GameTable.tscn60
-rw-r--r--pages/MainMenu.tscn299
-rw-r--r--pages/MainScene.tscn15
-rw-r--r--pages/ServerBrowser.tscn8
-rw-r--r--project.godot2
-rw-r--r--resources/MenuOptions.themebin0 -> 337 bytes
-rw-r--r--resources/external/game_coordinator.py21
-rw-r--r--scripts/Board.gd213
-rw-r--r--scripts/GameTable.gd245
-rw-r--r--scripts/Globals.gd28
-rw-r--r--scripts/HexSpace.gd86
-rw-r--r--scripts/MainMenu.gd84
-rw-r--r--scripts/MainScene.gd13
-rw-r--r--scripts/Plane.gd15
-rw-r--r--scripts/ServerBrowser.gd5
20 files changed, 963 insertions, 246 deletions
diff --git a/network/GCClient.tscn b/network/GCClient.tscn
new file mode 100644
index 0000000..2b04171
--- /dev/null
+++ b/network/GCClient.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://network/GameCoordinatorClient.gd" type="Script" id=1]
+
+[node name="GCClient" type="Node"]
+script = ExtResource( 1 )
diff --git a/network/GameCoordinatorClient.gd b/network/GameCoordinatorClient.gd
new file mode 100644
index 0000000..3d87b7b
--- /dev/null
+++ b/network/GameCoordinatorClient.gd
@@ -0,0 +1,101 @@
+extends Node
+
+# CONNECTION DATA
+enum { NONE, SERVER_BROWSER, LOBBY, VOICE }
+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
+var message_queue : Array = [] # messages to be sent upon connection
+var connection_type : int = NONE
+
+# GC DATA
+var is_host : bool
+var host_id : String
+var lobby_id : String
+var player_id : String
+var rejoin_key : String
+var players : Array = []
+
+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_error", self, "on_connection_error")
+ #socket_client.connect("data_received", self, "receive")
+
+func connect_to_gc(c_type: int):
+ if !(c_type in [SERVER_BROWSER, LOBBY, VOICE]):
+ print("Invalid connection type!")
+ return
+ 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 = 1 # CONNECTING
+ return OK
+
+func sock_close(code = 1000, reason = ""):
+ print("Closing websocket...")
+ socket_client.disconnect_from_host(code, reason)
+ state = 3 # 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
+
+ while len(message_queue) > 0:
+ var msg = message_queue.pop_at(0)
+ send(msg)
+
+func on_connection_close_success(clean):
+ print("WebSocket closed successfully.")
+ socket = null
+ connection_type = NONE
+ if clean:
+ state = 0 # DISCONNECTED
+ else:
+ state = -1 # DISCONNECT DIRTY
+
+func on_connection_error(): # connection failed
+ print("WebSocket connection failed!")
+ socket = null
+ state = -1 # DISCONNECT DIRTY
+ connection_type = NONE
+
+func send(message, as_bytes=false) -> int:
+ if state != 2:
+ message_queue.push_back(message)
+ return -1
+ return socket.put_packet(message)
+
+
+func send_json(message) -> int:
+ if state != 2:
+ 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
+ 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
+ return message
+ return bytes2var(packet)
+
+
+func _process(_delta):
+ socket_client.poll()
diff --git a/network/WSClient.tscn b/network/WSClient.tscn
deleted file mode 100644
index 2224ed5..0000000
--- a/network/WSClient.tscn
+++ /dev/null
@@ -1,6 +0,0 @@
-[gd_scene load_steps=2 format=2]
-
-[ext_resource path="res://network/websocket_client.gd" type="Script" id=1]
-
-[node name="WSClient" type="Node"]
-script = ExtResource( 1 )
diff --git a/network/websocket_client.gd b/network/websocket_client_basic.gd
index d10f518..353dd81 100644
--- a/network/websocket_client.gd
+++ b/network/websocket_client_basic.gd
@@ -3,7 +3,6 @@ extends Node
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
-var id
var message_queue : Array = [] # messages to be sent upon connection
diff --git a/objects/HexSpace.tscn b/objects/HexSpace.tscn
index 121a5a9..2fa1d71 100644
--- a/objects/HexSpace.tscn
+++ b/objects/HexSpace.tscn
@@ -157,6 +157,7 @@ font = ExtResource( 2 )
[node name="AirportIcon" type="Sprite3D" parent="Airport"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0.366498, 0.366924, 0.20708 )
+visible = false
pixel_size = 0.0035
billboard = 1
transparent = false
diff --git a/pages/GameTable.tscn b/pages/GameTable.tscn
index 25a4c16..efd6540 100644
--- a/pages/GameTable.tscn
+++ b/pages/GameTable.tscn
@@ -1,8 +1,8 @@
-[gd_scene load_steps=7 format=2]
+[gd_scene load_steps=8 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://objects/Plane.tscn" type="PackedScene" id=3]
+[ext_resource path="res://scripts/Board.gd" type="Script" id=3]
[sub_resource type="Environment" id=1]
background_mode = 1
@@ -22,10 +22,52 @@ albedo_texture = ExtResource( 2 )
material = SubResource( 2 )
size = Vector2( 30, 30 )
+[sub_resource type="GDScript" id=4]
+resource_name = "testing_camera"
+script/source = "extends Camera
+
+const max_scroll_distance : float = 10.0
+const min_scroll_distance : float = 2.0
+onready var scroll_distance : float = transform.origin.z
+const scroll_step : float = 0.2
+
+var mouse_clicked : bool = false
+const max_pitch : float = -15.0 # deg
+const min_pitch : float = -90.0
+const pan_factor : float = 0.5
+
+onready var yaw = get_node(\"../..\")
+onready var pitch = get_node(\"..\")
+
+func _ready():
+ pass
+
+func _input(event):
+ if event is InputEventMouseButton:
+ if event.button_index == BUTTON_LEFT:
+ mouse_clicked = event.pressed
+ if event.button_index == BUTTON_WHEEL_UP and event.pressed:
+ if scroll_distance > min_scroll_distance:
+ scroll_distance -= scroll_step
+ if event.button_index == BUTTON_WHEEL_DOWN and event.pressed:
+ if scroll_distance < max_scroll_distance:
+ scroll_distance += scroll_step
+ transform.origin.z = scroll_distance
+ if event is InputEventMouseMotion and mouse_clicked:
+ var mouse_dir : Vector2 = event.get_relative()
+ yaw.rotation_degrees.y -= mouse_dir.x * pan_factor
+
+ var new_pitch = pitch.rotation_degrees.x - mouse_dir.y * pan_factor*2
+ if new_pitch < max_pitch and new_pitch > min_pitch:
+ transform.origin.y = -2 * (1 - (new_pitch - max_pitch) / (min_pitch - max_pitch))
+ pitch.rotation_degrees.x = new_pitch
+"
+
[node name="GameTable" type="Spatial"]
script = ExtResource( 1 )
[node name="Board" type="Spatial" parent="."]
+script = ExtResource( 3 )
[node name="ActivePieces" type="Spatial" parent="."]
@@ -40,15 +82,15 @@ spot_range = 45.4139
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource( 1 )
-[node name="Camera" type="Camera" parent="."]
-transform = Transform( 1, 0, 0, 0, 0.965939, 0.258768, 0, -0.258768, 0.965939, 0, 3.30213, 10.1497 )
-
[node name="Tabletop" type="MeshInstance" parent="."]
transform = Transform( 1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0, 0 )
mesh = SubResource( 3 )
-[node name="Plane" parent="." instance=ExtResource( 3 )]
-transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0.625405, 1.50965, -2.27158 )
+[node name="CameraHingeYaw" type="Spatial" parent="."]
+
+[node name="CameraHingePitch" type="Spatial" parent="CameraHingeYaw"]
+transform = Transform( 1, 0, 0, 0, 0.965926, 0.258819, 0, -0.258819, 0.965926, 0, 0, 0 )
-[node name="Plane2" parent="." instance=ExtResource( 3 )]
-transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -0.92594, 0.711815, -1.28117 )
+[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 )
diff --git a/pages/MainMenu.tscn b/pages/MainMenu.tscn
new file mode 100644
index 0000000..d216836
--- /dev/null
+++ b/pages/MainMenu.tscn
@@ -0,0 +1,299 @@
+[gd_scene load_steps=17 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]
+[ext_resource path="res://resources/fonts/Cochineal-Roman.otf" type="DynamicFontData" id=3]
+[ext_resource path="res://resources/MenuOptions.theme" type="Theme" id=4]
+
+[sub_resource type="DynamicFont" id=29]
+size = 72
+font_data = ExtResource( 2 )
+
+[sub_resource type="DynamicFont" id=1]
+size = 72
+font_data = ExtResource( 2 )
+
+[sub_resource type="Theme" id=2]
+default_font = SubResource( 1 )
+Button/fonts/font = SubResource( 29 )
+
+[sub_resource type="DynamicFont" id=30]
+size = 45
+font_data = ExtResource( 2 )
+
+[sub_resource type="Theme" id=31]
+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 )
+
+[sub_resource type="GradientTexture2D" id=21]
+gradient = SubResource( 23 )
+width = 32
+height = 32
+
+[sub_resource type="GDScript" id=24]
+resource_name = "set_colors_builtin"
+script/source = "extends OptionButton
+
+
+func _ready():
+ for i in range(get_item_count()):
+ set_item_icon(i, get_item_icon(i).duplicate())
+ var icon = get_item_icon(i)
+ icon.gradient = icon.gradient.duplicate()
+ icon.gradient.set_color(0, Globals.colors[i])
+ icon.gradient.colors.remove(0)
+
+
+"
+
+[sub_resource type="DynamicFont" id=27]
+size = 95
+font_data = ExtResource( 3 )
+
+[sub_resource type="Theme" id=28]
+default_font = SubResource( 27 )
+
+[node name="MainMenu" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+rect_pivot_offset = Vector2( 560, -158 )
+script = ExtResource( 1 )
+
+[node name="HostMenuButton" type="Button" parent="."]
+anchor_left = 0.1
+anchor_top = 0.25
+anchor_right = 0.25
+anchor_bottom = 0.35
+theme = SubResource( 2 )
+text = "HOST"
+
+[node name="JoinMenuButton" type="Button" parent="."]
+anchor_left = 0.1
+anchor_top = 0.4
+anchor_right = 0.25
+anchor_bottom = 0.5
+theme = SubResource( 2 )
+text = "JOIN"
+
+[node name="SettingsButton" type="Button" parent="."]
+anchor_left = 0.1
+anchor_top = 0.55
+anchor_right = 0.35
+anchor_bottom = 0.65
+theme = SubResource( 2 )
+text = "SETTINGS"
+
+[node name="BackButton" type="Button" parent="."]
+visible = false
+anchor_left = 0.1
+anchor_top = 0.88
+anchor_right = 0.212
+anchor_bottom = 0.963
+theme = SubResource( 31 )
+text = "BACK"
+
+[node name="PlayerInfo" type="GridContainer" parent="."]
+visible = false
+anchor_left = 0.1
+anchor_top = 0.257
+anchor_right = 0.358
+anchor_bottom = 0.9
+columns = 2
+
+[node name="UsernameLabel" type="Label" parent="PlayerInfo"]
+margin_top = 5.0
+margin_right = 138.0
+margin_bottom = 38.0
+theme = ExtResource( 4 )
+text = "Username:"
+align = 2
+
+[node name="Username" type="LineEdit" parent="PlayerInfo"]
+margin_left = 142.0
+margin_right = 342.0
+margin_bottom = 43.0
+rect_min_size = Vector2( 200, 0 )
+theme = ExtResource( 4 )
+text = "Player"
+max_length = 32
+
+[node name="ColorLabel" type="Label" parent="PlayerInfo"]
+margin_top = 50.0
+margin_right = 138.0
+margin_bottom = 83.0
+theme = ExtResource( 4 )
+text = "Color:"
+align = 2
+
+[node name="PlayerColor" type="OptionButton" parent="PlayerInfo"]
+margin_left = 142.0
+margin_top = 47.0
+margin_right = 202.0
+margin_bottom = 87.0
+rect_min_size = Vector2( 60, 40 )
+size_flags_horizontal = 0
+icon = SubResource( 22 )
+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
+script = SubResource( 24 )
+
+[node name="AltColorLabel" type="Label" parent="PlayerInfo"]
+margin_top = 94.0
+margin_right = 138.0
+margin_bottom = 127.0
+theme = ExtResource( 4 )
+text = "Alt color:"
+align = 2
+
+[node name="AltPlayerColor" type="OptionButton" parent="PlayerInfo"]
+margin_left = 142.0
+margin_top = 91.0
+margin_right = 202.0
+margin_bottom = 131.0
+rect_min_size = Vector2( 60, 40 )
+size_flags_horizontal = 0
+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 = 1
+script = SubResource( 24 )
+
+[node name="Title" type="Label" parent="."]
+anchor_left = 0.075
+anchor_top = 0.067
+anchor_right = 0.75
+anchor_bottom = 0.069
+margin_right = 40.0
+margin_bottom = 14.0
+theme = SubResource( 28 )
+text = "ATC: AIR TRAFFIC CHAOS"
+
+[node name="HostMenu" type="Control" parent="."]
+visible = false
+anchor_left = 0.1
+anchor_top = 0.45
+anchor_right = 1.0
+anchor_bottom = 0.8
+margin_right = 40.0
+margin_bottom = 40.0
+
+[node name="GameName" type="LineEdit" parent="HostMenu"]
+anchor_right = 0.229
+theme = ExtResource( 4 )
+text = "Player's Game"
+max_length = 39
+
+[node name="HostButton" type="Button" parent="HostMenu"]
+anchor_top = 0.597
+anchor_right = 0.229
+anchor_bottom = 0.698
+theme = SubResource( 2 )
+text = "Host"
+
+[node name="PlayerCountLabel" type="Label" parent="HostMenu"]
+anchor_top = 0.17
+anchor_bottom = 0.17
+theme = ExtResource( 4 )
+text = "Players:"
+
+[node name="PlayerCount" type="OptionButton" parent="HostMenu"]
+anchor_left = 0.083
+anchor_top = 0.165
+anchor_right = 0.229
+anchor_bottom = 0.196
+theme = ExtResource( 4 )
+text = "2"
+items = [ "2", null, false, 2, null, "3", null, false, 3, null, "4", null, false, 4, null, "5", null, false, 5, null, "6", null, false, 6, null ]
+selected = 0
+
+[node name="PrivateToggle" type="CheckButton" parent="HostMenu"]
+anchor_top = 0.3
+anchor_bottom = 0.3
+size_flags_horizontal = 0
+size_flags_vertical = 0
+theme = ExtResource( 4 )
+text = "Password Protected"
+
+[node name="Password" type="LineEdit" parent="HostMenu"]
+visible = false
+anchor_top = 0.428
+anchor_right = 0.23
+anchor_bottom = 0.465
+theme = ExtResource( 4 )
+max_length = 16
+placeholder_text = "Password"
+placeholder_alpha = 0.587
+
+[node name="JoinMenu" type="Control" parent="."]
+visible = false
+anchor_left = 0.1
+anchor_top = 0.45
+anchor_right = 1.0
+anchor_bottom = 0.8
+margin_right = 40.0
+margin_bottom = 40.0
+
+[node name="LobbyID" type="LineEdit" parent="JoinMenu"]
+anchor_right = 0.229
+theme = ExtResource( 4 )
+max_length = 39
+placeholder_text = "Lobby ID"
+placeholder_alpha = 0.389
+
+[node name="Password" type="LineEdit" parent="JoinMenu"]
+anchor_top = 0.165
+anchor_right = 0.229
+anchor_bottom = 0.165
+theme = ExtResource( 4 )
+max_length = 39
+placeholder_text = "Password (if private)"
+placeholder_alpha = 0.389
+
+[node name="JoinButton" type="Button" parent="JoinMenu"]
+anchor_top = 0.321
+anchor_right = 0.229
+anchor_bottom = 0.465
+theme = SubResource( 2 )
+text = "JOIN "
+
+[node name="SettingsMenu" type="GridContainer" parent="."]
+visible = false
+anchor_left = 0.1
+anchor_top = 0.267
+anchor_right = 1.0
+anchor_bottom = 0.8
+margin_right = 40.0
+margin_bottom = 40.0
+columns = 2
+
+[node name="GCURLLabel" type="Label" parent="SettingsMenu"]
+margin_top = 5.0
+margin_right = 322.0
+margin_bottom = 38.0
+theme = ExtResource( 4 )
+text = "Game Coordinator URL: "
+align = 2
+
+[node name="GameCoordinatorURL" type="LineEdit" parent="SettingsMenu"]
+margin_left = 326.0
+margin_right = 676.0
+margin_bottom = 43.0
+rect_min_size = Vector2( 350, 0 )
+theme = ExtResource( 4 )
+max_length = 39
+placeholder_text = "Game Coordinator URL"
+placeholder_alpha = 0.389
diff --git a/pages/MainScene.tscn b/pages/MainScene.tscn
new file mode 100644
index 0000000..da9bdcd
--- /dev/null
+++ b/pages/MainScene.tscn
@@ -0,0 +1,15 @@
+[gd_scene load_steps=4 format=2]
+
+[ext_resource path="res://network/websocket_client_basic.gd" type="Script" id=1]
+[ext_resource path="res://pages/MainMenu.tscn" type="PackedScene" id=2]
+[ext_resource path="res://scripts/MainScene.gd" type="Script" id=3]
+
+[node name="MainScene" type="Control"]
+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 )]
diff --git a/pages/ServerBrowser.tscn b/pages/ServerBrowser.tscn
index c4fcc61..c5d2ef4 100644
--- a/pages/ServerBrowser.tscn
+++ b/pages/ServerBrowser.tscn
@@ -1,14 +1,14 @@
[gd_scene load_steps=6 format=2]
-[ext_resource path="res://network/websocket_client.gd" type="Script" id=1]
+[ext_resource path="res://network/websocket_client_basic.gd" type="Script" id=1]
[ext_resource path="res://scripts/ServerBrowser.gd" type="Script" id=2]
-[sub_resource type="Gradient" id=15]
+[sub_resource type="Gradient" id=23]
offsets = PoolRealArray( 0 )
colors = PoolColorArray( 0, 0, 0, 1 )
[sub_resource type="GradientTexture2D" id=21]
-gradient = SubResource( 15 )
+gradient = SubResource( 23 )
width = 32
height = 32
@@ -23,7 +23,6 @@ func _ready():
var icon = get_item_icon(i)
icon.gradient = icon.gradient.duplicate()
icon.gradient.set_color(0, Globals.colors[i])
- print(icon.gradient.colors)
icon.gradient.colors.remove(0)
@@ -92,6 +91,7 @@ text = "Player"
max_length = 32
[node name="HostPopup" type="PopupPanel" parent="."]
+visible = true
margin_left = 383.0
margin_top = 129.0
margin_right = 633.0
diff --git a/project.godot b/project.godot
index 68af7ba..e90fb8a 100644
--- a/project.godot
+++ b/project.godot
@@ -11,7 +11,7 @@ config_version=4
[application]
config/name="ATC"
-run/main_scene="res://network/GameCoordinatorTester.tscn"
+run/main_scene="res://pages/MainScene.tscn"
boot_splash/show_image=false
config/icon="res://icon.png"
diff --git a/resources/MenuOptions.theme b/resources/MenuOptions.theme
new file mode 100644
index 0000000..899c3df
--- /dev/null
+++ b/resources/MenuOptions.theme
Binary files differ
diff --git a/resources/external/game_coordinator.py b/resources/external/game_coordinator.py
index bd2eb9c..0f04e18 100644
--- a/resources/external/game_coordinator.py
+++ b/resources/external/game_coordinator.py
@@ -15,13 +15,15 @@ import secrets
import sys
import random
import time
+from string import ascii_uppercase # optional, for 4-letter id format
import websockets
from websockets.asyncio.server import serve
DEFAULT_PORT = 8181
-DEFAULT_IP = "192.168.7.112"
+DEFAULT_IP = ""
+MAX_LOBBY_ID_GEN_ATTEMPTS = 20
LOBBY_CONTROL_COMMANDS = ["disconnect", "kick_player", "get_lobby_info", "deny_player", "accept_player", "end_lobby", "set_lobby_locked", "set_lobby_state", "set_paused"]
HOST_LOBBY_REQD_FIELDS = ["lobby_name", "game_type", "username", "private", "password", "max_players"]
HOST_LOBBY_REQD_FIELD_TYPES = [str, str, str, bool, str, int]
@@ -46,6 +48,19 @@ async def send_lobby_list(websocket):
""" LOBBY FUNCTIONS """
+# generate lobby id
+async def generate_lobby_id():
+ new_id = None
+ attempts = 0
+ while (attempts < MAX_LOBBY_ID_GEN_ATTEMPTS) and ( (not new_id) or (new_id in LOBBIES) ):
+ new_id = ''.join(random.choices(ascii_uppercase, k=4)) # DEFINE FID FORMAT HERE
+ attempts += 1
+
+ if attempts >= MAX_LOBBY_ID_GEN_ATTEMPTS:
+ return secrets.token_urlsafe(12) # fall back to non-repeating lobby ID
+ return new_id
+
+
# get information of all connected players to lobby for
async def get_player_info(lobby):
players = []
@@ -268,7 +283,7 @@ async def lobby_loop(websocket, lobby, player):
# message forwarding
elif event["type"] == "request":
- response = {"type" : "request", "destination" : player_id, "data" : event["request_fields"] }
+ 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"] }
@@ -397,7 +412,7 @@ async def host_lobby(websocket, event):
host_player = await create_player(websocket, event["username"], is_host=True)
- lobby_id = secrets.token_urlsafe(12)
+ lobby_id = await generate_lobby_id()
new_lobby = { "lobby_id" : lobby_id,
"lobby_name" : event["lobby_name"],
"game_type" : event["game_type"],
diff --git a/scripts/Board.gd b/scripts/Board.gd
new file mode 100644
index 0000000..f18ca74
--- /dev/null
+++ b/scripts/Board.gd
@@ -0,0 +1,213 @@
+extends Spatial
+
+enum { Y, X }
+
+# hex board represented in square-grid form like so (e.g., 3-length-side hex grid):
+# x x x
+# x x x x
+# x x x x x
+# x x x x
+# x x x
+# going up and to the right is done by decreasing the row by 1
+# going up and to the left is done by decreasing the row by 1 and the column by 1
+var board: Array = [] # 2D Array of JSON objects describing the board, which can be turned into objects
+var board_display: Array = []
+var available_board_coords: Array = [] # for population purposes
+var airports = {} # id : HexSpace of cell_type airport
+
+var side_len: int
+
+
+# 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) ]
+enum { NUMBER, COLOR }
+
+onready var hex_space = preload("res://objects/HexSpace.tscn")
+
+# cell types
+enum { PLAIN, HILLS, MOUNTAINS, AIRPORT }
+
+# directions: E, NE, NW, W, SW, SE
+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] ]
+
+
+func _ready():
+ pass
+
+func reset_board():
+ for node in get_children():
+ node.queue_free()
+ board.clear()
+ board_display.clear()
+ available_board_coords.clear()
+
+func create_board_base(hex_side_length : int):
+ side_len = hex_side_length
+ var number_of_cells = 3*( pow(hex_side_length, 2) - hex_side_length) + 1
+
+ reset_board()
+
+ var board_diameter = hex_side_length * 2 - 1
+ for r in range(board_diameter):
+ var row_length: int = board_diameter - abs(r-(hex_side_length-1))
+
+ var row = []
+ row.resize(board_diameter)
+ row.fill(null) # not in hex grid
+
+ var offset : int = 0
+ if r > (hex_side_length - 1): offset = r - (hex_side_length - 1)
+ for i in range(row_length):
+ row[offset+i] = { "cell_type" : PLAIN, "pos" : [r, offset+i] } # ground cell
+ available_board_coords.push_back( [r, offset+i] )
+
+ board.append(row)
+
+
+func display_board():
+ var cell_size_x = 1 # distance between center of two adjacent hex cells
+ var row_offset_y:float = cos(deg2rad(30)) * cell_size_x
+ var board_diam:int = len(board)
+ var side_len:int = ( board_diam + 1 ) / 2
+
+ for r in range(board_diam):
+ var row_display = []
+ row_display.resize(board_diam)
+ row_display.fill(null)
+ var row = board[r]
+ var z = row_offset_y * (r - board_diam/2)
+ var offset_x = abs(side_len - (r+1)) * (cell_size_x / 2.0) if (r+1) <= side_len else -1*abs(side_len - (r+1)) * (cell_size_x/2.0)
+ offset_x -= board_diam/2 * cell_size_x
+ for c in range(board_diam):
+ if row[c] == null: continue
+ var x = offset_x + c * cell_size_x
+
+ 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)
+ row_display[c] = new_cell
+ board_display.push_back(row_display)
+
+
+# populate board with airports, hills, and mountains
+# depending on game settings
+func populate_board(num_mountains : int, num_hills : int, num_airports : int, runway_count : int, use_names : bool = false) -> bool:
+ var board_diam:int = len(board)
+
+ for _m in range(num_mountains):
+ if len(available_board_coords) < 1: return false
+ var spot_i:int = randi() % len(available_board_coords)
+ var spot = available_board_coords[ spot_i ]
+ var args = {"cell_type" : MOUNTAINS, "orientation" : randi() % 6, "pos" : [spot[Y], spot[X]]}
+ board[spot[Y]][spot[X]] = args
+ available_board_coords.pop_at(spot_i)
+
+ for _h in range(num_hills):
+ if len(available_board_coords) < 1: return false
+ var spot_i:int = randi() % len(available_board_coords)
+ var spot = available_board_coords[ spot_i ]
+ var args = { "cell_type" : HILLS, "orientation" : randi() % 6, "pos" : [spot[Y], spot[X]] }
+ board[spot[Y]][spot[X]] = args
+ available_board_coords.pop_at(spot_i)
+
+ # airport identification
+ var used_airports : Array = []
+
+ var airport_id:int = 0
+ for a in range(num_airports):
+ var airport_display
+ if use_names:
+ airport_display = Globals.get_random_airport_name(used_airports)
+ else:
+ airport_display = [ randi() % 9 + 1, randi() % 4 ] # number, color
+ while airport_display in used_airports:
+ airport_display = [ randi() % 9 + 1, randi() % 4 ]
+ # find valid spot
+ var spot_okay:bool = false
+ var rot:int
+ var spot_r:int
+ var spot_c:int
+ var spot_i:int
+ var valid_approaches = []
+ var runways = (randi() % 3 + 1) if (runway_count == 0) else runway_count
+ while (not spot_okay) and (len(available_board_coords) > 0):
+ spot_i = randi() % len(available_board_coords)
+ var spot = available_board_coords[ spot_i ]
+ spot_r = spot[Y]
+ spot_c = spot[X]
+
+ var has_adjacent_airport = false
+ for offset in adjacent_offsets: # away from other airports
+ var new_r: int = spot_r + offset[Y]
+ var new_c: int = spot_c + offset[X]
+ if new_r < 0 or new_c < 0 or new_r >= board_diam or new_c >= board_diam: # offset out of square grid
+ continue
+ var adjacent_cell = board[new_r][new_c]
+ if adjacent_cell != null and adjacent_cell["cell_type"] == AIRPORT:
+ has_adjacent_airport = true
+ break
+ if has_adjacent_airport:
+ available_board_coords.pop_at(spot_i)
+ continue
+
+ spot_okay = true
+
+ # find rotation that leaves at least 1 runway open
+ rot = randi() % 3
+ var rot_okay = false
+ for _i in range(3):
+ var rot_approaches = adjacent_offsets.slice(rot, 5)
+ if rot != 0: rot_approaches += adjacent_offsets.slice(0, rot - 1)
+
+ var possible_approaches = []
+ for approach_index in approaches_i[runways]:
+ possible_approaches.push_back(rot_approaches[approach_index])
+
+ var has_runway = false
+ for approach in possible_approaches:
+ var app_r: int = spot_r + approach[0]
+ var app_c: int = spot_c + approach[1]
+ if app_r < 0 or app_r >= board_diam or app_c < 0 or app_c >= board_diam: continue # out of square map
+ if board[app_r][app_c] == null: continue # out of hex map
+ if board[app_r][app_c]["cell_type"] in [HILLS, MOUNTAINS]: continue # invalid approach square
+ has_runway = true
+ valid_approaches.push_back(approach)
+
+ if has_runway:
+ rot_okay = true
+ break
+ else:
+ rot += 1 # rotate 60 deg (effectively)
+ if not rot_okay:
+ available_board_coords.pop_at(spot_i)
+ continue
+
+ 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}
+ if use_names:
+ args["airport_name"] = airport_display
+ else:
+ args["airport_color"] = airport_colors[airport_display[COLOR]]
+ 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 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):
+ reset_board()
+ print("Invalid board creation parameters")
+ return false
+ display_board()
+ return true
diff --git a/scripts/GameTable.gd b/scripts/GameTable.gd
index cbee40f..b0bedf2 100644
--- a/scripts/GameTable.gd
+++ b/scripts/GameTable.gd
@@ -1,207 +1,68 @@
-tool
extends Spatial
-const num_mountains = {"easy" : 0, "medium" : 3, "hard" : 6}
-const num_hills = {"easy" : 4, "medium" : 6, "hard" : 8}
+# MULTIPLAYER DATA
-export var hex_side_length = 6 setget set_hex_side_len
-export var airports_per_color = 6 setget set_airports_per_color
-export var num_airport_colors = 3 setget set_num_airport_colors
-export var _generate_board_editor: bool = false setget generate_board_editor
+var gc_client # to be assigned by MainScene upon game creation/joining
-export (String, "easy", "medium", "hard") var game_difficulty = "easy"
-# hex board represented in square-grid form like so (e.g., 3-length-side hex grid):
-# x x x
-# x x x x
-# x x x x x
-# x x x x
-# x x x
-# going up and to the right is done by decreasing the row by 1
-# going up and to the left is done by decreasing the row by 1 and the column by 1
-var board = []
-var available_board_coords = []
-enum { GROUND_LAYER, WEATHER_LAYER, PLANES_LAYER }
-# 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) ]
-var airports = {} # id : HexSpace of cell_type airport
+# END MULTIPLAYER DATA
-onready var hex_space = preload("res://objects/HexSpace.tscn")
+# GAME DATA
+
+enum { PREGAME, PLACING, DETERMINE_ACTIONS, MOVEMENT }
+var game_state: int
+var BOARD_GEN_PARAMS = {
+ "airport_style" : 0, # 0 = colors + numbers, 1 = names
+ "num_airports" : 18,
+ "board_side_length" : 6,
+ "num_hills" : 8,
+ "num_mountains" : 4,
+ "runways_per_airport" : 0 # 0 random, 1-3
+}
+var RULES = {
+ "fly_over_airports" : 0, # 1 = only at altitude 2, 2 = any altitude
+ "must_land_on_final_action" : 0,
+ "takeoff_action" : 2, # 0 - 4
+ "move_forward_order" : 0, # 0 = either before or after, 1 = after, 2 = before
+ "gamemode" : 0, # 0 = cooperative, 1 = versus
+ "plane_assignment" : 0, # 0 = random, 1 = draft
+ "altitude_and_turn" : 0, # whether a plane can change altitude and turn in the same action
+ "event_frequency" : 1, # turns per event
+ "weather_enabled" : 1,
+ "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,
+ "round_timer" : 60, # seconds
+}
+
+onready var BOARD = $Board
+var PLANES = []
+
+var desired_player_count: int
+var is_board_generated: bool = false # determine whether host can begin the game
+
+# 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] ]
-# indices of the offsets that are valid cells to approach from
-const approaches_i = {"easy": [0, 1, 2, 3, 4, 5], "medium" : [0,1,3,4], "hard" : [0,3]}
-func _ready():
- if not Engine.editor_hint:
- generate_hex_board()
- generate_board_cells()
- populate_board()
-
-func set_hex_side_len(side_length):
- hex_side_length = side_length
-
-func set_airports_per_color(num_airports):
- airports_per_color = num_airports
-
-func set_num_airport_colors(num_colors):
- num_airport_colors = num_colors
-
-func generate_hex_board():
- var number_of_cells = 3*( pow(hex_side_length, 2) - hex_side_length) + 1
- var player_spaces = number_of_cells - 1 # center should always be a mountain
- for node in $Board.get_children():
- $Board.remove_child(node)
- board = [] # reset board + contents
- available_board_coords = []
- var board_diameter = hex_side_length * 2 - 1
- for r in range(board_diameter):
- var row_length: int = board_diameter - abs(r-(hex_side_length-1))
-
- var row = []
- row.resize(board_diameter)
- row.fill(null) # not in hex grid
-
- if r <= (hex_side_length - 1):
- for i in range(row_length):
- row[i] = [ 1, [], [] ] # ground cell, weather effects, planes
- else:
- for i in range(row_length):
- row[board_diameter-1-i] = [ 1, [], [] ] # ground cell, weather effects, planes
-
- board.append(row)
-
-
-func generate_board_cells():
- var cell_size_x = 1 # distance between center of two adjacent hex cells
- var row_offset_y:float = cos(deg2rad(30)) * cell_size_x
- var board_diam:int = len(board)
- var side_len:int = ( board_diam + 1 ) / 2
-
- for r in range(board_diam):
- var row = board[r]
- var z = row_offset_y * (r - board_diam/2)
- var offset_x = abs(side_len - (r+1)) * (cell_size_x / 2.0) if (r+1) <= side_len else -1*abs(side_len - (r+1)) * (cell_size_x/2.0)
- offset_x -= board_diam/2 * cell_size_x
- for c in range(board_diam):
- if row[c] == null: continue
- var x = offset_x + c * cell_size_x
-
- var new_cell = hex_space.instance()
- new_cell.call_deferred("set", "global_position", Vector3(x, randf()/15, z))
- $Board.add_child(new_cell)
-
- board[r][c][GROUND_LAYER] = new_cell
- if (r == c) and (r == (board_diam/2)): # central cell always a mountain
- var cell_type = "mountain"
- var args = {}
- args["rotation"] = randi() % 6
- new_cell.set_up(cell_type, args)
- else:
- available_board_coords.push_back( [r, c] )
-
-# populate board with airports, hills, and mountains
-# depending on game settings
-func populate_board():
- var board_diam:int = len(board)
-
- for _m in range(num_mountains[game_difficulty]):
- if len(available_board_coords) < 1: return null
- var spot_i:int = randi() % len(available_board_coords)
- var spot = available_board_coords[ spot_i ]
- var args = {"rotation" : randi() % 6}
- board[spot[0]][spot[1]][GROUND_LAYER].set_up("mountain", args)
- available_board_coords.pop_at(spot_i)
-
- for _h in range(num_hills[game_difficulty]):
- if len(available_board_coords) < 1: return null
- var spot_i:int = randi() % len(available_board_coords)
- var spot = available_board_coords[ spot_i ]
- var args = {"rotation" : randi() % 6}
- board[spot[0]][spot[1]][GROUND_LAYER].set_up("hills", args)
- available_board_coords.pop_at(spot_i)
-
- var airport_id:int = 0
- for c in range(num_airport_colors):
- for a in range(airports_per_color):
- # find valid spot
- var spot_okay:bool = false
- var rot:int
- var spot_r:int
- var spot_c:int
- var spot_i:int
- var valid_approaches = []
- while (not spot_okay) and (len(available_board_coords) > 0):
- spot_i = randi() % len(available_board_coords)
- var spot = available_board_coords[ spot_i ]
- spot_r = spot[0]
- spot_c = spot[1]
-
- # should no longer be necessary
- #if board[spot_r][spot_c] == null: continue
-
- var has_adjacent_airport = false
- for offset in adjacent_offsets: # away from other airports
- var new_r: int = spot_r + offset[0]
- var new_c: int = spot_c + offset[1]
- if new_r < 0 or new_c < 0 or new_r >= board_diam or new_c >= board_diam: # offset out of square grid
- continue
- var adjacent_cell = board[new_r][new_c]
- if adjacent_cell != null and adjacent_cell[GROUND_LAYER].cell_type == "airport":
- has_adjacent_airport = true
- break
- if has_adjacent_airport:
- available_board_coords.pop_at(spot_i)
- continue
-
- spot_okay = true
-
- # find rotation that leaves at least 1 runway open
- rot = randi() % 3
- var rot_okay = false
- for _i in range(3):
- var rot_approaches = adjacent_offsets.slice(rot, 5)
- if rot != 0: rot_approaches += adjacent_offsets.slice(0, rot - 1)
-
- var possible_approaches = []
- for approach_index in approaches_i[game_difficulty]:
- possible_approaches.push_back(rot_approaches[approach_index])
-
- var has_runway = false
- for approach in possible_approaches:
- var app_r: int = spot_r + approach[0]
- var app_c: int = spot_c + approach[1]
- if app_r < 0 or app_r >= board_diam or app_c < 0 or app_c >= board_diam: continue # out of square map
- if board[app_r][app_c] == null: continue # out of hex map
- if board[app_r][app_c][GROUND_LAYER].cell_type in ["hills", "mountain"]: continue # invalid approach square
- has_runway = true
- valid_approaches.push_back(approach)
-
- if has_runway:
- rot_okay = true
- break
- else:
- rot += 1 # rotate 60 deg (effectively)
- if not rot_okay:
- available_board_coords.pop_at(spot_i)
- continue
-
- if not spot_okay:
- print('couldnt find spot')
- return null # could not form valid map
- #print(c, " ", a, "(", spot_r, ", ", spot_c, ")")
- var args = {"rotation" : rot, "airport_color" : airport_colors[c], "airport_number" : a+1, "airport_id" : airport_id, "difficulty" : game_difficulty, 'valid_approaches' : valid_approaches}
- board[spot_r][spot_c][GROUND_LAYER].set_up("airport", args)
- available_board_coords.pop_at(spot_i)
- airport_id += 1
+func _ready():
+ 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
+
+# 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" }
-
-func generate_board_editor(_gbe):
- generate_hex_board()
- generate_board_cells()
- populate_board()
diff --git a/scripts/Globals.gd b/scripts/Globals.gd
index 18f3d19..3603dbd 100644
--- a/scripts/Globals.gd
+++ b/scripts/Globals.gd
@@ -1,7 +1,33 @@
extends Node
# Y R B G W Cy Pk O P dG
-const colors = [ Color(1, 1, 0), Color(1, 0, 0), Color(0.3, 0.3, 1), Color(0, 0.8, 0), Color(1, 1, 1), Color(0, 1, 1), Color(1, .35, 1), Color(1, 0.4, 0), Color(0.38, 0, 0.38), Color(0, 0.4, 0) ]
+const colors : Array = [ Color(1, 1, 0), Color(1, 0, 0), Color(0.3, 0.3, 1), Color(0, 0.8, 0), Color(1, 1, 1), Color(0, 1, 1), Color(1, .35, 1), Color(1, 0.4, 0), Color(0.38, 0, 0.38), Color(0, 0.4, 0) ]
+
+const airport_names_file : String = 'res://resources/airports.txt'
+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"
func _ready():
+ load_airport_names()
set_process(false)
+
+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)
+ var index = 1
+ while not f.eof_reached(): # iterate through all lines until the end of file is reached
+ airport_names.push_back(f.get_line())
+ f.close()
+
+func get_random_airport_name(exceptions=[]):
+ var name_index:int = randi() % len(airport_names)
+ while airport_names[name_index] in exceptions:
+ name_index = randi() % len(airport_names)
+ return airport_names[name_index]
diff --git a/scripts/HexSpace.gd b/scripts/HexSpace.gd
index c8269fd..bcf64f1 100644
--- a/scripts/HexSpace.gd
+++ b/scripts/HexSpace.gd
@@ -1,50 +1,92 @@
tool
extends StaticBody
-var cell_type : String = "normal"
+enum { PLAIN, HILLS, MOUNTAINS, AIRPORT }
+enum { Y, X }
+
+# general cell variables
+var x : int = -1
+var y : int = -1
+var cell_type : int = PLAIN
+var orientation : int = 0 # 0 - 5
# airport variables
var airport_number : int
var airport_color : Color
var airport_id : int
+var airport_name : String
+var runway_count : int
+var airport_closed : bool = false
+
# cell offsets that describe valid approaches based on runways and surroundings
# used to choose a takeoff position
+enum rotations { EAST, NORTHEAST, NORTHWEST, WEST, SOUTHWEST, SOUTHEAST }
const bearings = [ [0,1] , [-1, 0], [-1, -1], [0, -1], [1, 0], [1, 1] ]
-var valid_approaches = []
-var valid_bearings = []
+
+var valid_departure_bearings = []
+var valid_arrival_bearings = []
func _ready():
pass
-func set_up(tile_type, settings={}):
- valid_bearings = [] # reset
- cell_type = tile_type
+func reset():
+ $Hills.visible = false
+ $Mountain.visible = false
+ $Airport.visible = false
+ $Airport/EasyRunway.visible = true # reset runways
+ $Airport/MediumRunway.visible = true
+ $Airport/AirportName.visible = false
+ $Airport/AirportIcon.visible = false
+ orientation = 0
+ self.rotation.y = 0
+ cell_type = PLAIN
+ x = -1
+ y = -1
+ airport_closed = false
+
- if settings["rotation"]: # bearing according to E, NE, etc.
- self.global_rotation.y = settings["rotation"] * deg2rad(60)
+func set_up(settings):
+ x = settings["pos"][X] ; y = settings["pos"][Y]
+ cell_type = settings["cell_type"]
- if tile_type == "hills":
+ valid_departure_bearings.clear()
+ valid_arrival_bearings.clear()
+
+
+ if settings["orientation"]: # bearing according to E, NE, etc.
+ orientation = settings["orientation"]
+ self.global_rotation.y = orientation * deg2rad(60)
+
+ if cell_type == HILLS:
$Hills.visible = true
- elif tile_type == "mountain":
+ elif cell_type == MOUNTAINS:
$Mountain.visible = true
- elif tile_type == "airport":
+ elif cell_type == AIRPORT:
$Airport.visible = true
- airport_number = settings["airport_number"]
- airport_color = settings["airport_color"]
- valid_approaches = settings["valid_approaches"]
- for approach in valid_approaches:
- var bearing_i = bearings.find(approach)
- valid_bearings.push_back(bearings[bearing_i])
+ if settings["use_names"]:
+ airport_name = settings["airport_name"]
+ $Airport/AirportName.visible = true
+ else:
+ airport_number = settings["airport_number"]
+ airport_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
+
+ valid_departure_bearings = settings["valid_approach_offsets"]
airport_id = settings["airport_id"]
- $Airport/AirportIcon.texture = load("res://textures/airport_indicator_%d.png" % airport_number)
- $Airport/AirportIcon.modulate = airport_color
+ for departure_bearing in valid_departure_bearings:
+ var bearing_i = bearings.find(departure_bearing)
+ bearing_i = (bearing_i + 3) % 6 # opposite bearing
+ valid_arrival_bearings.push_back(bearings[bearing_i])
- if settings["difficulty"] == "easy": return
- $Airport/EasyRunway.visible = false
- if settings["difficulty"] == "hard":
+ runway_count = int(clamp(settings["runway_count"], 1, 3))
+ if runway_count < 3:
+ $Airport/EasyRunway.visible = false
+ if runway_count == 1:
$Airport/MediumRunway.visible = false
diff --git a/scripts/MainMenu.gd b/scripts/MainMenu.gd
new file mode 100644
index 0000000..fca6658
--- /dev/null
+++ b/scripts/MainMenu.gd
@@ -0,0 +1,84 @@
+extends Control
+
+signal game_host_request(args)
+signal game_join_request(args)
+
+var lobby_name_changed : bool = false # automatically update lobby name to be based on player's name
+
+func _ready():
+ $BackButton.connect("pressed", self, "back_button")
+ $HostMenuButton.connect("pressed", self, "host_menu_button_pressed")
+ $JoinMenuButton.connect("pressed", self, "join_menu_button_pressed")
+ $SettingsButton.connect("pressed", self, "settings_menu_button_pressed")
+ $HostMenu/PrivateToggle.connect("toggled", $HostMenu/Password, "set_visible")
+ $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")
+ #main_menu() # in case things are incorrectly visible from editing
+
+
+func set_lobby_name_changed(_disregard_new_text):
+ lobby_name_changed = true
+
+func automatically_update_lobby_name(username : String):
+ if lobby_name_changed: return
+ var suffix : String = "' Game" if username.ends_with("s") else "'s Game"
+ $HostMenu/GameName.set_text(username + suffix)
+
+func main_menu():
+ set_menu_buttons_visible(true)
+ set_player_info_visible(false)
+ set_join_menu_visible(false)
+ set_host_menu_visible(false)
+ set_settings_menu_visible(false)
+ set_back_button_visible(false)
+
+func set_menu_buttons_visible(visible : bool):
+ $HostMenuButton.visible = visible
+ $JoinMenuButton.visible = visible
+ $SettingsButton.visible = visible
+
+func set_player_info_visible(visible : bool):
+ $PlayerInfo.visible = visible
+
+func set_join_menu_visible(visible : bool):
+ $JoinMenu.visible = visible
+
+func set_host_menu_visible(visible : bool):
+ $HostMenu.visible = visible
+
+func set_settings_menu_visible(visible : bool):
+ $SettingsMenu.visible = visible
+
+func set_back_button_visible(visible : bool):
+ $BackButton.visible = visible
+ $BackButton.disabled = false # reset in case left disabled by other function
+
+# go to join game menu
+func join_menu_button_pressed(lobby_id_from_url : String = "", password_from_url : String = ""):
+ set_menu_buttons_visible(false)
+ set_player_info_visible(true)
+ set_join_menu_visible(true)
+ set_back_button_visible(true)
+ if lobby_id_from_url:
+ $JoinMenu/LobbyID.text = lobby_id_from_url
+ if password_from_url:
+ $JoinMenu/Password.text = password_from_url
+
+# go to host game menu
+func host_menu_button_pressed():
+ set_menu_buttons_visible(false)
+ set_player_info_visible(true)
+ set_host_menu_visible(true)
+ set_back_button_visible(true)
+
+# go to settings menu
+func settings_menu_button_pressed():
+ $SettingsMenu/GameCoordinatorURL.text = Globals.GC_URL
+ set_menu_buttons_visible(false)
+ set_settings_menu_visible(true)
+ set_back_button_visible(true)
+
+# return to main menu
+func back_button():
+ main_menu()
diff --git a/scripts/MainScene.gd b/scripts/MainScene.gd
new file mode 100644
index 0000000..fc15463
--- /dev/null
+++ b/scripts/MainScene.gd
@@ -0,0 +1,13 @@
+extends Control
+
+
+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)
diff --git a/scripts/Plane.gd b/scripts/Plane.gd
index 62ffc43..6889e31 100644
--- a/scripts/Plane.gd
+++ b/scripts/Plane.gd
@@ -1,22 +1,25 @@
extends Area
-var starting_altitude:int = 0 # initial altitude for the turn, determines number of actions
-var altitude:int = 0 # 0, 1, or 2
-var pos_x:int
-var pos_y:int
+var starting_altitude: int = 0 # initial altitude for the turn, determines number of actions
+var altitude: int = 0 # 0, 1, or 2
+var pos_x: int
+var pos_y: int
onready var meshes = [$Fuselage, $Cone, $Wings, $Tail]
# bearings: E, NE, NW, W, SW, SE
const bearings = [ [0,1] , [-1, 0], [-1, -1], [0, -1], [1, 0], [1, 1] ]
-var bearing:int = 0 # index of above list of potential bearings
+var bearing: int = 0 # index of above list of potential bearings
var destination_num: int # for display purposes only
var destination_col: Color # for display purposes only
+var destination_name: String # for display purposes
var destination_id: int # determines above ^
var rotation_tween: Tween = null
+var actions: Array = []
+
var plane_material
func _ready():
@@ -25,9 +28,7 @@ func _ready():
mesh.set_surface_material(0, plane_material)
var new_col = Color(randf(), randf(), randf() )
- print(new_col)
set_color(new_col)
-
func set_color(color: Color):
plane_material.set_albedo(color)
diff --git a/scripts/ServerBrowser.gd b/scripts/ServerBrowser.gd
index 3c59b9b..410eec2 100644
--- a/scripts/ServerBrowser.gd
+++ b/scripts/ServerBrowser.gd
@@ -16,6 +16,8 @@ var queued_messages = []
func _ready():
refresh_game_list()
$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")
func join_game():
$HostPopup.visible = false
@@ -50,6 +52,9 @@ func add_games_to_list(games):
game_list.add_item( game_str, null, true if game["state"] == "LOBBY" else false )
game_ids.append( game["id"] )
+func toggle_password_vis(pressed):
+ $HostPopup/Control/Password.visible = pressed
+
func _process(_delta):
$GameCoordinatorStatus.text = "Game Coordinator Connection: " + str(ws_client.state)