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)