extends Spatial # MULTIPLAYER DATA var GC : GCClient # Game Coordinator connection/client, to be assigned by MainScene upon game creation/joining # END MULTIPLAYER DATA # 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": 3, "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 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 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 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 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)