summary refs log blame commit diff stats
path: root/racing/multiplayer.gd
blob: 2aafbb9ec89ea5e9013d7a835b2954342dda66ed (plain) (tree)





































                                                                                         
                                                            
 
                               
 
                                                         
                                                          

                                          
















































                                                                                                         
                                                                                             












































                                                                                            
                                                                               
          
                                                                                                    
































                                                                                                    
                                                                            
                                                                                                     
                                                                                




















                                                                            
                                              
                                  



                                                           
                                                                   














                                                                             
                                                                                                   
                                                       
                                       









                                                                                               
                                                                          






















                                                                                      
                                               






                                                                                                            
                                                  





                                                                                                     
                                           

                                                                     
                                            


                                                                          
         
                                                    



                                                       
                                              
                                 





                                                                                                               
                 
 
                                  
                                         
                                                       

                                                           
                                                                                                     
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]