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]