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 const MAX_PLAYERS = 250 const PROTOCOL_VERSION = 2 const RECIPIENT_BROADCAST_ALL = -1 const LOBBY_MAP_NAME = "ll1_racing" const VERSION = "0.0.7" const LL1_AREAS = [ ["Starting Room", 0, 0, 0], ["Second Room", 0, 0, -15], ["The Traveled", 34, 0, -18], ["The Agreeable", 30, 0, -45], ["The Colorful", 10, 0, -83], ["Suits Area", 0, 0, -78, true], ["Arrow Garden", -93, 1, -93], ["The Wondrous (Table)", -108, 1, -78], ["Courtyard", -64, 0, -71], ["Yellow Backside Nine", -38, 0, -58], ["Hot Crusts Area", -20, 0, -81], ["Crossroads Corner", -28, 0, -54], ["The Discerning", -54, 0, -34, true], ["Green Backside", 22, 0, -94], ["Observant Upstairs", 40, 9, -92, true], ["Eight Room", 95, 15, -28], ["The Perceptive", 60, 9, -57], ["The Tenacious", 0, 0, -43], ["Rainbow", -96, 0, -41], ["The Undeterred", -87, 0, 25, true], ["Directional Gallery", -57, 0, 0], ["The Eyes They See", -54, 0, -23], ["Tower First Floor", -27, 0, -23], ["The Optimistic", 76, 0, -17], ["The Initiated", 63, 0, -0, true], ["Art Gallery", 92, 0, 15], ["Art Gallery Top", 80, 30, 15], ["Lookout", 75, 18, 51], ["Knight Night Room", 37, 0, 7], ["The Seeker", 9, 0, 16, true], ["Hidden Room", 13, 0, 4], ["Owl Hallway", 44, 0, -26], ["Challenge Room", -9, 6, 13], ["Pilgrim Room", -22, 0, 24, true], ["Cellar Replica", -44, 0, 30], ["Elements Area", -61, 0, 40], ["The Artistic", -25, 0, 54, true], ["Outside The Wise", -44, 0, 71], ["The Wise", -72, 0, 72, true], ["The Scientific", -18, 0, 89], ["The Wanderer", 0, 0, 80], ["The Fearless", 18, 10, 90], ["Champion's Rest", 23, 0, 62, true], ["The Steady", 31, 0, 77, true], ["The Bold", 67, 0, 77, true], ["Color Hunt", 45, 0, 69], ["Room Room", 95, 6, 84], ["The Bearer", 61, 0, 51], ["Tower Third Floor", 18, 0, 33], ["Rhyme Room (Cross)", 0, 9, 42], ["Tower Seventh Floor", 0, 37, 64], ] 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/player.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 _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.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 rng = RandomNumberGenerator.new() rng.randomize() var start_pos var end_pos var found = false while !found: var areas_dupe = LL1_AREAS.duplicate() var i = rng.randi_range(0, areas_dupe.size() - 1) start_pos = areas_dupe[i] areas_dupe.remove(i) i = rng.randi_range(0, areas_dupe.size() - 1) end_pos = areas_dupe[i] var start_vec = Vector3(start_pos[1], start_pos[2], start_pos[3]) var end_vec = Vector3(end_pos[1], end_pos[2], end_pos[3]) if start_vec.distance_to(end_vec) > 50 and not (start_pos.size() >= 5 and start_pos[4]): found = true 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.start_pos = start_pos race_manager.end_pos = end_pos if active_lobby_members.size() == 1: _start_game() else: _send_p2p_packet( { "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 = "level1" 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)