From 60a0a573f0d0329e8d5c73878165ba80fa2d2628 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 8 Feb 2024 16:29:48 -0500 Subject: Initial commit --- level_race.tscn | 58 +++++++ racing/load.gd | 70 +++++++++ racing/lobby.gd | 407 ++++++++++++++++++++++++++++++++++++++++++++++++++ racing/manager.gd | 28 ++++ racing/multiplayer.gd | 293 ++++++++++++++++++++++++++++++++++++ 5 files changed, 856 insertions(+) create mode 100644 level_race.tscn create mode 100644 racing/load.gd create mode 100644 racing/lobby.gd create mode 100644 racing/manager.gd create mode 100644 racing/multiplayer.gd diff --git a/level_race.tscn b/level_race.tscn new file mode 100644 index 0000000..365f57e --- /dev/null +++ b/level_race.tscn @@ -0,0 +1,58 @@ +[gd_scene load_steps=6 format=2] + +[ext_resource path="res://nodes/label.tscn" type="PackedScene" id=1] +[ext_resource path="res://fonts/Lingo.tres" type="DynamicFont" id=2] +[ext_resource path="res://nodes/button.tscn" type="PackedScene" id=3] +[ext_resource path="user://maps/racing/lobby.gd" type="Script" id=4] +[ext_resource path="res://lingo_3.png" type="Texture" id=5] + +[node name="racing" type="Spatial"] +script = ExtResource( 4 ) + +[node name="lingo_3" type="Sprite" parent="."] +position = Vector2( 960, 540 ) +texture = ExtResource( 5 ) + +[node name="Panel" type="Panel" parent="."] +margin_right = 1920.0 +margin_bottom = 1080.0 + +[node name="ItemList" type="ItemList" parent="Panel"] +margin_left = 642.0 +margin_top = 275.0 +margin_right = 1298.0 +margin_bottom = 804.0 +auto_height = true + +[node name="ScrollContainer" type="ScrollContainer" parent="Panel"] +margin_left = -70.0 +margin_top = 251.0 +margin_right = 572.0 +margin_bottom = 805.0 + +[node name="title" parent="Panel" instance=ExtResource( 1 )] +margin_left = 0.0 +margin_top = 75.0 +margin_right = 1920.0 +margin_bottom = 225.0 +custom_fonts/font = ExtResource( 2 ) +text = "LINGO RACING LOBBY" +valign = 1 + +[node name="return_button" parent="Panel" instance=ExtResource( 3 )] +margin_left = 1002.0 +margin_top = 879.0 +margin_right = 1640.0 +margin_bottom = 1029.0 +custom_colors/font_color_hover = Color( 1, 0, 0, 1 ) +custom_fonts/font = ExtResource( 2 ) +text = "RETURN" + +[node name="main_button" parent="Panel" instance=ExtResource( 3 )] +margin_left = 254.0 +margin_top = 879.0 +margin_right = 892.0 +margin_bottom = 1029.0 +custom_colors/font_color_hover = Color( 1, 0, 0, 1 ) +custom_fonts/font = ExtResource( 2 ) +text = "READY" diff --git a/racing/load.gd b/racing/load.gd new file mode 100644 index 0000000..cd40e42 --- /dev/null +++ b/racing/load.gd @@ -0,0 +1,70 @@ +extends "res://scripts/load.gd" + + +func _load(): + disable_saving = true + + var player = get_node("player") + player.playable = false + + var blindfold = ColorRect.new() + blindfold.color = Color.black + blindfold.name = "blindfold" + add_child(blindfold) + + var race_manager = global.get_node("RaceManager") + var multiplayer = race_manager.SCRIPT_multiplayer.new() + multiplayer.name = "Multiplayer" + multiplayer.active_lobby_id = race_manager.lobby_id + add_child(multiplayer) + + var label = Label.new() + label.set_name("label") + label.margin_right = 1920.0 - 20.0 + label.margin_top = 20.0 + label.align = Label.ALIGN_RIGHT + label.valign = Label.VALIGN_TOP + + 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 + label.add_font_override("font", dynamic_font) + + add_child(label) + + var panel_script = load("res://nodes/panel_1.tscn") + var victory_panel = panel_script.instance() + victory_panel.name = "Victory" + victory_panel.text = "i win" + victory_panel.answer = "i win" + victory_panel.translation.x = race_manager.end_pos[1] + 0.5 + victory_panel.translation.y = race_manager.end_pos[2] + 1.5 + victory_panel.translation.z = race_manager.end_pos[3] + 0.01 + add_child(victory_panel) + victory_panel.get_node("Viewport/GUI/Panel/TextEdit").connect("answer_correct", self, "victory") + + set_gridmap_tile( + race_manager.end_pos[1], + race_manager.end_pos[2] + 1.5, + race_manager.end_pos[3] - 0.5, + "MeshInstance5" + ) + + ._load() + + multiplayer.player_loaded() + + +func victory(): + get_node("Multiplayer").player_victory() + ingame_achieve("Victory!") + + +func set_gridmap_tile(x, y, z, tile): + var gridmap = self.get_node("GridMap") + var mesh_library = gridmap.mesh_library + var mapvec = gridmap.world_to_map(gridmap.to_local(Vector3(x, y, z))) + + gridmap.set_cell_item(mapvec.x, mapvec.y, mapvec.z, mesh_library.find_item_by_name(tile)) diff --git a/racing/lobby.gd b/racing/lobby.gd new file mode 100644 index 0000000..87647bb --- /dev/null +++ b/racing/lobby.gd @@ -0,0 +1,407 @@ +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 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(): + installScriptExtension(ResourceLoader.load("user://maps/racing/load.gd")) + + # 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: + 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 _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 LOBBY (%s)" % 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_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: + 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: + _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: + Steam.createLobby(Steam.LOBBY_TYPE_INVISIBLE, MAX_PLAYERS) + elif best_lobby_id != active_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 + 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_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 + 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 serialized: PoolByteArray = packet["data"] + var data: Dictionary = bytes2var(serialized, false) + 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: + var race_manager = global.get_node("RaceManager") + 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: + members_to_join.erase(remote_id) + + if members_to_join.empty(): + _start_game() + + +func _send_p2p_packet(data: Dictionary, recipient_id: int, mode: int, needs_ack: bool) -> void: + 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(): + 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] + 0.25, 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) diff --git a/racing/manager.gd b/racing/manager.gd new file mode 100644 index 0000000..e2cd728 --- /dev/null +++ b/racing/manager.gd @@ -0,0 +1,28 @@ +extends Node + +var SCRIPT_multiplayer + +var start_pos +var end_pos +var lobby_id + + +func everyone_ready(): + var player = get_tree().get_root().get_node("Spatial/player") + get_node("/root/Spatial").remove_child(get_node("/root/Spatial/blindfold")) + var indicator = player.get_node("pivot/camera/achievement_label") + indicator.visible = true + indicator.text = "Destination:\n%s" % end_pos[0] + + yield(get_tree().create_timer(10), "timeout") + + indicator.text = "3..." + yield(get_tree().create_timer(1), "timeout") + + indicator.text = "2..." + yield(get_tree().create_timer(1), "timeout") + + indicator.text = "1..." + yield(get_tree().create_timer(0.5), "timeout") + + get_node("/root/Spatial/Multiplayer").send_timeout() diff --git a/racing/multiplayer.gd b/racing/multiplayer.gd new file mode 100644 index 0000000..1cd3b26 --- /dev/null +++ b/racing/multiplayer.gd @@ -0,0 +1,293 @@ +extends Node + +var player_steam_id +var player_node +var active_lobby_id = 0 +var active_lobby_members = {} +var is_remote_signal = false +var next_message_id = 0 +var messages_needing_ack = {} +var panel_nodes +var is_ready = false +var is_loaded = false +var is_activating = false +var letsgo = false +var is_victory = false +var victorious_players = [] + +const MAX_PLAYERS = 250 +const PROTOCOL_VERSION = 2 +const RECIPIENT_BROADCAST_ALL = -1 + + +func _ready(): + # P2P solve messages should still be received while paused. + set_pause_mode(Node.PAUSE_MODE_PROCESS) + + player_steam_id = Steam.getSteamID() + player_node = get_node("/root/Spatial/player") + + var _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(0.1, "_broadcast_player_location") + _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: + Steam.leaveLobby(active_lobby_id) + + +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 _broadcast_player_location(): + if active_lobby_id == 0: + return + _send_p2p_packet( + { + "global_transform": player_node.global_transform, + }, + RECIPIENT_BROADCAST_ALL, + Steam.P2P_SEND_UNRELIABLE_NO_DELAY, + false + ) + + +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: + _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", global.map, 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: + Steam.createLobby(Steam.LOBBY_TYPE_INVISIBLE, MAX_PLAYERS) + elif best_lobby_id != active_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", global.map) + _ignore = Steam.setLobbyData(lobby_id, "protocol_version", str(PROTOCOL_VERSION)) + _ignore = Steam.setLobbyData(lobby_id, "save_file", global.save_file.to_lower()) + _request_lobby_list() + + +func _on_lobby_joined(lobby_id: int, _permissions: int, _locked: bool, result: int) -> void: + if result != Steam.RESULT_OK: + return + if active_lobby_id != 0 && active_lobby_id != lobby_id: + Steam.leaveLobby(active_lobby_id) + active_lobby_id = lobby_id + if is_loaded: + Steam.setLobbyMemberData(active_lobby_id, "loaded", "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 prior_lobby_members = active_lobby_members + active_lobby_members = {} + var everyone_ready = is_loaded + var everyone_activated = is_activating + for i in range(0, lobby_size): + var member_id: int = Steam.getLobbyMemberByIndex(active_lobby_id, i) + if member_id != player_steam_id: + var steam_name: String = Steam.getFriendPersonaName(member_id) + if member_id in prior_lobby_members: + active_lobby_members[member_id] = prior_lobby_members[member_id] + else: + active_lobby_members[member_id] = ( + load("res://nodes/multiplayer_avatar.tscn").instance() + ) + add_child(active_lobby_members[member_id]) + active_lobby_members[member_id].steam_id = member_id + active_lobby_members[member_id].steam_name = steam_name + if Steam.getLobbyMemberData(active_lobby_id, member_id, "loaded") != "true": + everyone_ready = false + if Steam.getLobbyMemberData(active_lobby_id, member_id, "timeout") != "true": + everyone_activated = false + for member in prior_lobby_members.values(): + if not member.steam_id in active_lobby_members: + member.queue_free() + if !is_ready and everyone_ready: + is_ready = everyone_ready + global.get_node("RaceManager").everyone_ready() + if !letsgo and everyone_activated: + letsgo = true + time_to_start() + + +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 serialized: PoolByteArray = packet["data"] + var data: Dictionary = bytes2var(serialized, false) + if "global_transform" in data: + _receive_member_location(remote_id, data["global_transform"]) + if "message_id" in data: + _send_p2p_packet( + { + "ack": data["message_id"], + }, + remote_id, + Steam.P2P_SEND_RELIABLE_WITH_BUFFERING, + false + ) + if "loaded" in data: + _update_lobby_members() + if "timeout" in data: + _update_lobby_members() + if "victory" in data: + _someone_victory(Steam.getFriendPersonaName(remote_id)) + if "ack" in data: + messages_needing_ack.erase(data["ack"]) + + +func _receive_member_location(member_id: int, global_transform) -> void: + var member = active_lobby_members[member_id] + member.global_move_to(global_transform) + if !member.visible: + member.show() + + +func _send_p2p_packet(data: Dictionary, recipient_id: int, mode: int, needs_ack: bool) -> void: + if recipient_id == RECIPIENT_BROADCAST_ALL: + for member_id in active_lobby_members.keys(): + _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 player_loaded(): + is_loaded = true + if active_lobby_id != 0: + Steam.setLobbyMemberData(active_lobby_id, "loaded", "true") + _send_p2p_packet({"loaded": "true"}, RECIPIENT_BROADCAST_ALL, Steam.P2P_SEND_RELIABLE, true) + _update_lobby_members() + + +func send_timeout(): + is_activating = true + Steam.setLobbyMemberData(active_lobby_id, "timeout", "true") + _send_p2p_packet({"timeout": "true"}, RECIPIENT_BROADCAST_ALL, Steam.P2P_SEND_RELIABLE, true) + _update_lobby_members() + + +func time_to_start(): + var player = get_tree().get_root().get_node("Spatial/player") + player.playable = true + get_node("/root/Spatial/label").text = ( + "Destination: %s" % global.get_node("RaceManager").end_pos[0] + ) + get_node("/root/Spatial").ingame_achieve("Go!") + + +func player_victory(): + if !is_victory: + is_victory = true + _send_p2p_packet( + {"victory": "true"}, RECIPIENT_BROADCAST_ALL, Steam.P2P_SEND_RELIABLE, true + ) + _someone_victory(Steam.getFriendPersonaName(player_steam_id)) + + +func _someone_victory(name): + if !victorious_players.has(name): + victorious_players.append(name) + + var label = get_node("/root/Spatial/label") + label.text = "%s\n%s is #%d" % [label.text, name, victorious_players.size()] -- cgit 1.4.1