From 60a0a573f0d0329e8d5c73878165ba80fa2d2628 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 8 Feb 2024 16:29:48 -0500 Subject: Initial commit --- racing/multiplayer.gd | 293 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 racing/multiplayer.gd (limited to 'racing/multiplayer.gd') 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