extends Node var player_steam_id var active_lobby_id = 0 var active_lobby_members = [] var next_message_id = 0 var messages_needing_ack = {} var is_vip = false var is_ready = false var everyone_ready = false var members_to_join = [] var is_starting = false var router const MAX_PLAYERS = 250 const PROTOCOL_VERSION = 2 const RECIPIENT_BROADCAST_ALL = -1 const LOBBY_MAP_NAME = "ll1_racing" const VERSION = "0.0.7" func _ready(): global._print("Starting Lobby") # P2P solve messages should still be received while paused. set_pause_mode(Node.PAUSE_MODE_PROCESS) # Undo the load screen removing our cursor get_tree().get_root().set_disable_input(false) Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) if global.get_node_or_null("RaceManager") == null: installScriptExtension(ResourceLoader.load("user://maps/racing/load.gd")) installScriptExtension(ResourceLoader.load("user://maps/racing/panelLevelSwitch.gd")) installScriptExtension(ResourceLoader.load("user://maps/racing/panelEnd.gd")) installScriptExtension(ResourceLoader.load("user://maps/racing/player.gd")) installScriptExtension(ResourceLoader.load("user://maps/racing/worldTransporter.gd")) var race_manager_script = load("user://maps/racing/manager.gd") var race_manager = race_manager_script.new() race_manager.name = "RaceManager" race_manager.SCRIPT_multiplayer = load("user://maps/racing/multiplayer.gd") global.add_child(race_manager) var race_manager = global.get_node("RaceManager") race_manager.held_messages.clear() var router_script = load("user://maps/racing/router.gd") router = router_script.new() var _ignore = get_node("Panel/main_button").connect("pressed", self, "_main_button_pressed") _ignore = get_node("Panel/return_button").connect("pressed", self, "_return_button_pressed") var dynamic_font = DynamicFont.new() dynamic_font.font_data = load("res://fonts/Lingo.ttf") dynamic_font.size = 36 dynamic_font.outline_color = Color(0, 0, 0, 1) dynamic_font.outline_size = 2 get_node("Panel/ItemList").add_font_override("font", dynamic_font) get_node("Panel/title").text = "LINGO RACING %s LOBBY (%s)" % [VERSION, global.save_file] player_steam_id = Steam.getSteamID() _ignore = Steam.connect("lobby_match_list", self, "_on_lobby_match_list") _ignore = Steam.connect("lobby_created", self, "_on_lobby_created") _ignore = Steam.connect("lobby_joined", self, "_on_lobby_joined") _ignore = Steam.connect("lobby_chat_update", self, "_on_lobby_chat_update") _ignore = Steam.connect("lobby_data_update", self, "_on_lobby_data_update") _ignore = Steam.connect("persona_state_change", self, "_on_persona_state_change") _ignore = Steam.connect("p2p_session_request", self, "_on_p2p_session_request") _setup_recurring_task(5.0, "_resend_messages_needing_ack") _setup_recurring_task(10.0, "_request_lobby_list") _request_lobby_list() func _process(_delta: float) -> void: _read_p2p_packet() func _exit_tree(): if active_lobby_id != 0 and !is_starting: global._print("Lobby Exit Tree") Steam.leaveLobby(active_lobby_id) active_lobby_id = 0 func _setup_recurring_task(wait_time, function): var timer = Timer.new() timer.set_wait_time(wait_time) timer.set_one_shot(false) timer.connect("timeout", self, function) add_child(timer) timer.start() func _resend_messages_needing_ack(): for message_id in messages_needing_ack: var message = messages_needing_ack[message_id] if message["recipient_id"] in active_lobby_members: global._print("Resending Packet") _send_p2p_packet(message["data"], message["recipient_id"], message["mode"], true) else: messages_needing_ack.erase(message_id) func _request_lobby_list(): Steam.addRequestLobbyListDistanceFilter(Steam.LOBBY_DISTANCE_FILTER_WORLDWIDE) Steam.addRequestLobbyListNumericalFilter( "protocol_version", PROTOCOL_VERSION, Steam.LOBBY_COMPARISON_EQUAL ) Steam.addRequestLobbyListStringFilter("map", LOBBY_MAP_NAME, Steam.LOBBY_COMPARISON_EQUAL) Steam.addRequestLobbyListStringFilter( "save_file", global.save_file.to_lower(), Steam.LOBBY_COMPARISON_EQUAL ) Steam.requestLobbyList() func _on_lobby_match_list(lobbies: Array) -> void: if active_lobby_id != 0 && not active_lobby_id in lobbies: # Not sure why this happens, but it seems to sometimes. lobbies.append(active_lobby_id) var best_lobby_id = 0 var best_lobby_size = -1 for lobby_id in lobbies: var lobby_size = Steam.getNumLobbyMembers(lobby_id) if ( lobby_size > best_lobby_size || (lobby_size == best_lobby_size && lobby_id < best_lobby_id) ): best_lobby_id = lobby_id best_lobby_size = lobby_size if best_lobby_id == 0: global._print("Creating Lobby") Steam.createLobby(Steam.LOBBY_TYPE_INVISIBLE, MAX_PLAYERS) elif best_lobby_id != active_lobby_id: global._print("Joining Lobby %d" % best_lobby_id) Steam.joinLobby(best_lobby_id) elif best_lobby_size <= 1: _request_lobby_list() func _on_lobby_created(result: int, lobby_id: int) -> void: if result != Steam.RESULT_OK: return var _ignore = Steam.setLobbyData(lobby_id, "map", LOBBY_MAP_NAME) _ignore = Steam.setLobbyData(lobby_id, "protocol_version", str(PROTOCOL_VERSION)) _ignore = Steam.setLobbyData(lobby_id, "save_file", global.save_file.to_lower()) _ignore = Steam.setLobbyData(lobby_id, "racing_vip", str(player_steam_id)) is_vip = true _request_lobby_list() func _on_lobby_joined(lobby_id: int, _permissions: int, _locked: bool, result: int) -> void: if result != Steam.RESULT_OK: return global._print("Joined Lobby %d" % lobby_id) if active_lobby_id != 0 && active_lobby_id != lobby_id: Steam.leaveLobby(active_lobby_id) active_lobby_id = lobby_id if Steam.getLobbyData(lobby_id, "racing_vip") == str(player_steam_id): is_vip = true _update_lobby_members() func _on_lobby_chat_update( _lobby_id: int, _member_id: int, _making_change_id: int, _chat_state: int ) -> void: _update_lobby_members() func _on_lobby_data_update(_lobby_id: int, _member_id: int, _key: int) -> void: _update_lobby_members() func _on_persona_state_change(_persona_id: int, _flag: int) -> void: _update_lobby_members() func _update_lobby_members(): if active_lobby_id == 0: return var lobby_size: int = Steam.getNumLobbyMembers(active_lobby_id) var itemlist = get_node("Panel/ItemList") itemlist.clear() active_lobby_members.clear() var temp_everyone_ready = true for i in range(0, lobby_size): var member_id: int = Steam.getLobbyMemberByIndex(active_lobby_id, i) var steam_name: String = Steam.getFriendPersonaName(member_id) itemlist.add_item(steam_name, null, false) active_lobby_members.append(member_id) var mem_is_ready = Steam.getLobbyMemberData(active_lobby_id, member_id, "ready") == "true" if mem_is_ready: itemlist.set_item_custom_fg_color(itemlist.get_item_count() - 1, Color.green) else: temp_everyone_ready = false if !everyone_ready and everyone_ready: global._print("Everyone Is Ready") everyone_ready = temp_everyone_ready var main_button = get_node("Panel/main_button") if everyone_ready and is_vip: main_button.text = "START GAME" main_button.disabled = false elif is_ready and is_vip: main_button.text = "START GAME" main_button.disabled = true elif is_ready: main_button.text = "READY" main_button.disabled = true else: main_button.text = "READY" main_button.disabled = false func _on_p2p_session_request(remote_id: int) -> void: if remote_id in active_lobby_members: var _ignore = Steam.acceptP2PSessionWithUser(remote_id) func _read_p2p_packet() -> void: var packet_size: int = Steam.getAvailableP2PPacketSize(0) if packet_size > 0: var packet: Dictionary = Steam.readP2PPacket(packet_size, 0) var remote_id = packet["steam_id_remote"] if remote_id in active_lobby_members: var race_manager = global.get_node("RaceManager") if is_starting: race_manager.held_messages.append(packet.duplicate(true)) var serialized: PoolByteArray = packet["data"] var data: Dictionary = bytes2var(serialized, false) global._print("RECEIVED Packet %s" % JSON.print(data)) if "message_id" in data: _send_p2p_packet( { "ack": data["message_id"], }, remote_id, Steam.P2P_SEND_RELIABLE_WITH_BUFFERING, false ) if "start_x" in data: race_manager.level = data["level"] race_manager.start_pos = [ data["start_name"], int(data["start_x"]), int(data["start_y"]), int(data["start_z"]) ] race_manager.end_pos = [ data["end_name"], int(data["end_x"]), int(data["end_y"]), int(data["end_z"]) ] is_starting = true _start_game() if "ack" in data: messages_needing_ack.erase(data["ack"]) if is_starting: global._print( "Members Remaining Before Starting: %s" % JSON.print(members_to_join) ) members_to_join.erase(remote_id) if members_to_join.empty(): _start_game() _read_p2p_packet() func _send_p2p_packet(data: Dictionary, recipient_id: int, mode: int, needs_ack: bool) -> void: global._print("SENDING Packet %s" % JSON.print(data)) if recipient_id == RECIPIENT_BROADCAST_ALL: for member_id in active_lobby_members: _send_p2p_packet(data.duplicate(), member_id, mode, needs_ack) return if needs_ack: var message_id if "message_id" in data: message_id = data["message_id"] else: message_id = next_message_id next_message_id += 1 data["message_id"] = message_id if not message_id in messages_needing_ack: messages_needing_ack[message_id] = { "data": data, "recipient_id": recipient_id, "mode": mode, } var serialized: PoolByteArray = [] serialized.append_array(var2bytes(data)) var _ignore = Steam.sendP2PPacket(recipient_id, serialized, mode) func _main_button_pressed(): if everyone_ready and is_vip: get_node("Panel/main_button").disabled = true var route = router.choose_route() var start_pos = route[1] var end_pos = route[2] members_to_join = active_lobby_members.duplicate() members_to_join.erase(player_steam_id) is_starting = true var race_manager = global.get_node("RaceManager") race_manager.level = route[0] race_manager.start_pos = start_pos race_manager.end_pos = end_pos if active_lobby_members.size() == 1: _start_game() else: _send_p2p_packet( { "level": route[0], "start_name": start_pos[0], "start_x": str(start_pos[1]), "start_y": str(start_pos[2]), "start_z": str(start_pos[3]), "end_name": end_pos[0], "end_x": str(end_pos[1]), "end_y": str(end_pos[2]), "end_z": str(end_pos[3]), }, RECIPIENT_BROADCAST_ALL, Steam.P2P_SEND_RELIABLE, true ) else: Steam.setLobbyMemberData(active_lobby_id, "ready", "true") is_ready = true _update_lobby_members() func _return_button_pressed(): _exit_tree() fader._fade_start("main_menu") func _start_game(): global._print("Starting Game") var race_manager = global.get_node("RaceManager") race_manager.lobby_id = active_lobby_id # Switch to LL1 Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) global.map = race_manager.level global.entry_point = Vector3( race_manager.start_pos[1], race_manager.start_pos[2] + 1, race_manager.start_pos[3] ) global.entry_rotate = Vector3(0, 0, 0) global.sets_entry_point = true var _discard = get_tree().change_scene("res://scenes/load_screen.tscn") # Adapted from https://gitlab.com/Delta-V-Modding/Mods/-/blob/main/game/ModLoader.gd func installScriptExtension(childScript: Resource): # Force Godot to compile the script now. # We need to do this here to ensure that the inheritance chain is # properly set up, and multiple mods can chain-extend the same # class multiple times. # This is also needed to make Godot instantiate the extended class # when creating singletons. # The actual instance is thrown away. childScript.new() var parentScript = childScript.get_base_script() var parentScriptPath = parentScript.resource_path global._print("ModLoader: Installing script extension over %s" % parentScriptPath) childScript.take_over_path(parentScriptPath)