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() var race_manager = global.get_node("RaceManager") for packet in race_manager.held_messages: global._print("(MP) Handling held packet") _handle_packet(packet) race_manager.held_messages.clear() 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.addRequestLobbyListStringFilter("closed", "true", 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: if chat_state == Steam.CHAT_MEMBER_STATE_CHANGE_LEFT: messages.showMessage("%s has left the room" % Steam.getFriendPersonaName(member_id)) _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 global._print("%d is not ready" % member_id) if Steam.getLobbyMemberData(active_lobby_id, member_id, "timeout") != "true": everyone_activated = false global._print("%d is not activated" % member_id) 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: _handle_packet(packet) _read_p2p_packet() func _handle_packet(packet): var remote_id = packet["steam_id_remote"] var serialized: PoolByteArray = packet["data"] var data: Dictionary = bytes2var(serialized, false) global._print("(MP) RECEIVED Packet %s" % JSON.print(data)) 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), data["victory"]) messages.showMessage("%s reached the goal" % Steam.getFriendPersonaName(remote_id)) if "ack" in data: messages_needing_ack.erase(data["ack"]) _update_lobby_members() 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: global._print("(MP) SENDING packet %s" % JSON.print(data)) 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(): global._print("(MP) Player has 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(): global._print("(MP) Player is activating") 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(): global._print("(MP) Time To Start") var player = get_tree().get_root().get_node("Spatial/player") player.playable = true get_node("/root/Spatial/label").text = ( "Start: %s\nDestination: %s" % [ global.get_node("RaceManager").start_pos["title"], global.get_node("RaceManager").end_pos["title"] ] ) global.get_node("RaceManager").start_timer() get_node("/root/Spatial").ingame_achieve("Go!") func player_victory(): if !is_victory: global._print("(MP) Victory!") is_victory = true _send_p2p_packet( {"victory": get_node("/root/Spatial/timer_label").text}, RECIPIENT_BROADCAST_ALL, Steam.P2P_SEND_RELIABLE, true ) _someone_victory( Steam.getFriendPersonaName(player_steam_id), get_node("/root/Spatial/timer_label").text ) func _someone_victory(name, time): if !victorious_players.has(name): global._print("(MP) Someone's victory") victorious_players.append(name) var label = get_node("/root/Spatial/label") label.text = "%s\n#%d: %s (%s)" % [label.text, victorious_players.size(), name, time]