summary refs log blame commit diff stats
path: root/racing/lobby.gd
blob: 3a04460be6fcf2fe9b8080af39f8aea3a447c3ee (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13











                             
          


                                   
                       
 
              
                                       







                                                                   
                                                                                         
                                                                                                     
                                                                                           
                                                                                                     
 






                                                                                           

                                                         

                                                                








                                                                                                    
                                                                                                 




                                                                                 
                                                                                   














                                                                                         
                                                















                                                                   
                                                         












                                                                                                         
                                                                                              
















                                                                                      
                                               
                                                                          
                                                                 










                                                                                         
                                                                 
                     




                                                                                            
                                                   


                                                                              
                             

                               




                                                                                 

























                                                                                                          
                                                  

























                                                                            

                                                                                         
                                                                           
                                                                              







                                                                               
                                                
                                                                  
                                                                                                           





                                                                       

                                                                                                                     



                                                                        
                                  
                                                                                               
                                                             






















                                                                                      




                                                    


                                                             
                                                                                   


                                                                                 
                                        




                                                                  
                                             






                                                    
                                                          
                                                                         
                                                                     















                                                                          
                                      



                                                         
                                       
                                     

                                                     

                                              
 
















                                                                                          
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

var router

const MAX_PLAYERS = 250
const PROTOCOL_VERSION = 2
const RECIPIENT_BROADCAST_ALL = -1
const LOBBY_MAP_NAME = "ll1_racing"
const VERSION = "0.2.0"


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/panelLevelSwitch.gd"))
		installScriptExtension(ResourceLoader.load("user://maps/racing/panelEnd.gd"))
		installScriptExtension(ResourceLoader.load("user://maps/racing/player.gd"))
		installScriptExtension(ResourceLoader.load("user://maps/racing/worldTransporter.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 router_script = load("user://maps/racing/router.gd")
	router = router_script.new()

	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.addRequestLobbyListStringFilter("closed", "false", 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))
	_ignore = Steam.setLobbyData(lobby_id, "closed", "false")
	_become_vip()
	_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):
		_become_vip()
	_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_name" in data:
				race_manager.level = data["level"]
				race_manager.start_pos = router.get_area(data["level"], data["start_name"])
				race_manager.end_pos = router.get_area(data["level"], data["end_name"])

				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 _become_vip():
	is_vip = true
	get_node("Panel/ItemList").margin_left = 226
	get_node("Panel/Settings").visible = true


func _main_button_pressed():
	if everyone_ready and is_vip:
		get_node("Panel/main_button").disabled = true

		var _ignore = Steam.setLobbyData(active_lobby_id, "closed", "true")

		var route = router.choose_route(
			get_node("Panel/Settings/LevelOption").get_selected_id(),
			get_node("Panel/Settings/LengthOption").get_selected_id()
		)
		var start_pos = route[1]
		var end_pos = route[2]

		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.level = route[0]
		race_manager.start_pos = start_pos
		race_manager.end_pos = end_pos

		if active_lobby_members.size() == 1:
			_start_game()
		else:
			_send_p2p_packet(
				{
					"level": route[0],
					"start_name": start_pos["title"],
					"end_name": end_pos["title"],
				},
				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 = race_manager.level
	global.entry_point = Vector3(
		race_manager.start_pos["pos"][0],
		race_manager.start_pos["pos"][1] + 1,
		race_manager.start_pos["pos"][2]
	)
	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)