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 /scripts | |
| parent | 255fbf19cc9499ef384d41f68515da5e49e8a3ce (diff) | |
Diffstat (limited to 'scripts')
| -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 |
9 files changed, 282 insertions, 30 deletions
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 |
