diff --git a/assets/core/networking/old/old_network_test_ui.gd b/assets/core/networking/old/old_network_test_ui.gd index 34ec5c4..6659f3f 100644 --- a/assets/core/networking/old/old_network_test_ui.gd +++ b/assets/core/networking/old/old_network_test_ui.gd @@ -1,6 +1,6 @@ extends Control -@export var network_manager: NetworkManager +@export var network_manager: OldnetworkManager @export var host_button: Button @export var join_button: Button diff --git a/assets/core/networking/old/old_test-lobby.tscn b/assets/core/networking/old/old_test-lobby.tscn index 9bb17bc..62f39fc 100644 --- a/assets/core/networking/old/old_test-lobby.tscn +++ b/assets/core/networking/old/old_test-lobby.tscn @@ -1,28 +1,18 @@ -[gd_scene load_steps=4 format=3 uid="uid://dfa8n5rw2qpfd"] +[gd_scene load_steps=2 format=3 uid="uid://dfa8n5rw2qpfd"] [ext_resource type="PackedScene" uid="uid://bpmn7t87tfk7f" path="res://assets/core/networking/old/old_network_manager.tscn" id="1_i1w5w"] -[ext_resource type="Script" path="res://assets/core/networking/old/old_network_test_ui.gd" id="2_rbs4r"] -[ext_resource type="PackedScene" uid="uid://biryul3n6thlw" path="res://assets/core/networking/old/old_user_box_prefab.tscn" id="3_if51w"] [node name="Test-lobby" type="Node"] [node name="NetworkManager" parent="." instance=ExtResource("1_i1w5w")] -[node name="UiRoot" type="Control" parent="." node_paths=PackedStringArray("network_manager", "host_button", "join_button", "lobby_id", "hosted_lobby_id", "users_list")] +[node name="UiRoot" type="Control" parent="."] layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -script = ExtResource("2_rbs4r") -network_manager = NodePath("../NetworkManager") -host_button = NodePath("Panel/HBoxContainer/VBoxContainer/Host") -join_button = NodePath("Panel/HBoxContainer/VBoxContainer/Join") -lobby_id = NodePath("Panel/HBoxContainer/VBoxContainer/LobbyID") -hosted_lobby_id = NodePath("Panel/HBoxContainer/VBoxContainer/HostedLobbyID") -users_list = NodePath("Panel/HBoxContainer/UsersList") -user_box_prefab = ExtResource("3_if51w") [node name="Panel" type="Panel" parent="UiRoot"] layout_mode = 1 diff --git a/assets/core/networking/old/old_NetworkManager.gd b/assets/core/networking/old/oldnetwork_manager.gd similarity index 99% rename from assets/core/networking/old/old_NetworkManager.gd rename to assets/core/networking/old/oldnetwork_manager.gd index 8b94e8c..bee4d8e 100644 --- a/assets/core/networking/old/old_NetworkManager.gd +++ b/assets/core/networking/old/oldnetwork_manager.gd @@ -1,4 +1,4 @@ -class_name NetworkManager +class_name OldnetworkManager extends Node const PACKET_READ_LIMIT: int = 32 diff --git a/assets/core/networking/scenes/lobby.tscn b/assets/core/networking/scenes/lobby.tscn index 85c5f58..5740019 100644 --- a/assets/core/networking/scenes/lobby.tscn +++ b/assets/core/networking/scenes/lobby.tscn @@ -1,4 +1,6 @@ -[gd_scene format=3 uid="uid://7t1x82gvrw8a"] +[gd_scene load_steps=2 format=3 uid="uid://7t1x82gvrw8a"] + +[ext_resource type="Script" path="res://assets/core/networking/scripts/lobby.gd" id="1_o4fbq"] [node name="Lobby" type="Control"] layout_mode = 3 @@ -7,3 +9,52 @@ anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 +script = ExtResource("1_o4fbq") + +[node name="Panel" type="Panel" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="Panel"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Buttons" type="VBoxContainer" parent="Panel/HBoxContainer"] +custom_minimum_size = Vector2(500, 0) +layout_mode = 2 +size_flags_horizontal = 6 +size_flags_vertical = 4 + +[node name="Host" type="Button" parent="Panel/HBoxContainer/Buttons"] +layout_mode = 2 +size_flags_vertical = 4 +text = "Host Lobby" + +[node name="HBoxContainer" type="HBoxContainer" parent="Panel/HBoxContainer/Buttons"] +layout_mode = 2 + +[node name="LineEdit" type="LineEdit" parent="Panel/HBoxContainer/Buttons/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Join" type="Button" parent="Panel/HBoxContainer/Buttons/HBoxContainer"] +custom_minimum_size = Vector2(90, 0) +layout_mode = 2 +text = "Join" + +[node name="Leave" type="Button" parent="Panel/HBoxContainer/Buttons"] +layout_mode = 2 +disabled = true +text = "Leave Lobby" + +[node name="PlayerList" type="VBoxContainer" parent="Panel/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 diff --git a/assets/core/networking/old/old_user_box_prefab.tscn b/assets/core/networking/scenes/user_box_prefab.tscn similarity index 83% rename from assets/core/networking/old/old_user_box_prefab.tscn rename to assets/core/networking/scenes/user_box_prefab.tscn index 34a8fd3..6297ee2 100644 --- a/assets/core/networking/old/old_user_box_prefab.tscn +++ b/assets/core/networking/scenes/user_box_prefab.tscn @@ -3,6 +3,8 @@ [ext_resource type="Texture2D" uid="uid://fwub8fvl2u4i" path="res://addons/ingameconsole/ps1hagrid.png" id="1_gtutw"] [node name="UserBox" type="HBoxContainer"] +offset_right = 610.0 +offset_bottom = 128.0 [node name="PFP" type="TextureRect" parent="."] custom_minimum_size = Vector2(128, 128) @@ -13,5 +15,6 @@ expand_mode = 1 [node name="Username" type="Label" parent="."] layout_mode = 2 -size_flags_horizontal = 8 +size_flags_horizontal = 3 text = "Hagrid 1" +horizontal_alignment = 1 diff --git a/assets/core/networking/scripts/NetworkManager.gd b/assets/core/networking/scripts/NetworkManager.gd index fae2c76..27c95e9 100644 --- a/assets/core/networking/scripts/NetworkManager.gd +++ b/assets/core/networking/scripts/NetworkManager.gd @@ -1,11 +1,312 @@ extends Node +const STEAM_APP_ID: int = 480 + +const PACKET_READ_LIMIT: int = 32 + +var lobby_data +var lobby_id: int = 0 +var lobby_members: Array = [] +var lobby_members_max: int = 10 +var lobby_vote_kick: bool = false +var lobby_type: int = Steam.LOBBY_TYPE_PUBLIC +var steam_id: int = 0 +var steam_username: String = "" +var avatar_texture_cache: Dictionary = {} + +signal lobby_created(lobby_id: int) +signal lobby_joined(lobby_id: int) +signal user_joined_lobby(user_id: int) +signal user_left_lobby(user_id: int) + -# Called when the node enters the scene tree for the first time. func _ready() -> void: - pass # Replace with function body. + _init_steam() -# Called every frame. 'delta' is the elapsed time since the previous frame. -func _process(delta: float) -> void: +func _process(_delta: float) -> void: + Steam.run_callbacks() + + if lobby_id > 0: + read_all_p2p_packets() + + +func _init_steam() -> void: + var init_result = Steam.steamInitEx(true, STEAM_APP_ID) + print("Steam Init Result: " + str(init_result)) + + if init_result['status'] > 0: + printerr("Steam failed to initialize, shutting down: " + str(init_result)) + #get_tree().quit() + + steam_id = Steam.getSteamID() + steam_username = Steam.getFriendPersonaName(steam_id) + + Steam.p2p_session_request.connect(_on_p2p_session_request) + Steam.p2p_session_connect_fail.connect(_on_p2p_session_connect_fail) + + Steam.join_requested.connect(_on_lobby_join_requested) + Steam.lobby_chat_update.connect(_on_lobby_chat_update) + Steam.lobby_created.connect(_on_lobby_created) + Steam.lobby_data_update.connect(_on_lobby_data_update) + Steam.lobby_invite.connect(_on_lobby_invite) + Steam.lobby_joined.connect(_on_lobby_joined) + Steam.lobby_match_list.connect(_on_lobby_match_list) + Steam.lobby_message.connect(_on_lobby_message) + Steam.persona_state_change.connect(_on_persona_change) + + +func _on_lobby_join_requested(this_lobby_id: int, friend_id: int): + var owner_name: String = Steam.getFriendPersonaName(friend_id) + print("Trying to join " + owner_name + "'s lobby...") + join_lobby(this_lobby_id) + + +func _on_lobby_chat_update(this_lobby_id: int, change_id: int, making_change_id: int, chat_state: int) -> void: + var changer_name: String = Steam.getFriendPersonaName(change_id) + + # If a player has joined the lobby + if chat_state == Steam.CHAT_MEMBER_STATE_CHANGE_ENTERED: + print("%s has joined the lobby." % changer_name) + + elif chat_state == Steam.CHAT_MEMBER_STATE_CHANGE_LEFT: + print("%s has left the lobby." % changer_name) + + elif chat_state == Steam.CHAT_MEMBER_STATE_CHANGE_KICKED: + print("%s has been kicked from the lobby." % changer_name) + + elif chat_state == Steam.CHAT_MEMBER_STATE_CHANGE_BANNED: + print("%s has been banned from the lobby." % changer_name) + + else: + print("%s did... something." % changer_name) + + get_lobby_members() + + +func _on_lobby_created(connect: int, this_lobby_id: int) -> void: + if connect == 1: + lobby_id = this_lobby_id + lobby_created.emit(lobby_id) + print("Lobby created with ID: " + str(lobby_id)) + + Steam.setLobbyJoinable(lobby_id, true) + Steam.setLobbyData(lobby_id, "name", str(steam_username) + "'s Lobby") + + var set_relay: bool = Steam.allowP2PPacketRelay(true) + + +func _on_lobby_data_update(): pass + + +func _on_lobby_invite(): + pass + + +func _on_lobby_joined(this_lobby_id: int, _permissions: int, response: int) -> void: + if response == Steam.CHAT_ROOM_ENTER_RESPONSE_SUCCESS: + lobby_id = this_lobby_id + get_lobby_members() + make_p2p_handshake() + lobby_joined.emit(lobby_id) + else: + var fail_reason: String + + match response: + Steam.CHAT_ROOM_ENTER_RESPONSE_DOESNT_EXIST: fail_reason = "This lobby no longer exists." + Steam.CHAT_ROOM_ENTER_RESPONSE_NOT_ALLOWED: fail_reason = "You don't have permission to join this lobby." + Steam.CHAT_ROOM_ENTER_RESPONSE_FULL: fail_reason = "The lobby is now full." + Steam.CHAT_ROOM_ENTER_RESPONSE_ERROR: fail_reason = "Uh... something unexpected happened!" + Steam.CHAT_ROOM_ENTER_RESPONSE_BANNED: fail_reason = "You are banned from this lobby." + Steam.CHAT_ROOM_ENTER_RESPONSE_LIMITED: fail_reason = "You cannot join due to having a limited account." + Steam.CHAT_ROOM_ENTER_RESPONSE_CLAN_DISABLED: fail_reason = "This lobby is locked or disabled." + Steam.CHAT_ROOM_ENTER_RESPONSE_COMMUNITY_BAN: fail_reason = "This lobby is community locked." + Steam.CHAT_ROOM_ENTER_RESPONSE_MEMBER_BLOCKED_YOU: fail_reason = "A user in the lobby has blocked you from joining." + Steam.CHAT_ROOM_ENTER_RESPONSE_YOU_BLOCKED_MEMBER: fail_reason = "A user you have blocked is in the lobby." + + print("Failed to join this chat room: %s" % fail_reason) + + +func _on_lobby_match_list(): + pass + + +func _on_lobby_message(): + pass + + +func _on_persona_change(this_steam_id: int, _flags: int) -> void: + if lobby_id > 0: + print("User (%s) had information change, updating lobby list." % str(this_steam_id)) + get_lobby_members() + + +func _on_p2p_session_request(remote_id: int): + var this_requester: String = Steam.getFriendPersonaName(remote_id) + print("P2P session request from: " + this_requester) + Steam.acceptP2PSessionWithUser(remote_id) + make_p2p_handshake() + + +func _on_p2p_session_connect_fail(steam_id: int, session_error: int) -> void: + # If no error was given + if session_error == 0: + print("WARNING: Session failure with %s: no error given" % steam_id) + + # Else if target user was not running the same game + elif session_error == 1: + print("WARNING: Session failure with %s: target user not running the same game" % steam_id) + + # Else if local user doesn't own app / game + elif session_error == 2: + print("WARNING: Session failure with %s: local user doesn't own app / game" % steam_id) + + # Else if target user isn't connected to Steam + elif session_error == 3: + print("WARNING: Session failure with %s: target user isn't connected to Steam" % steam_id) + + # Else if connection timed out + elif session_error == 4: + print("WARNING: Session failure with %s: connection timed out" % steam_id) + + # Else if unused + elif session_error == 5: + print("WARNING: Session failure with %s: unused" % steam_id) + + # Else no known error + else: + print("WARNING: Session failure with %s: unknown error %s" % [steam_id, session_error]) + + +func host_lobby(): + if lobby_id == 0: + Steam.createLobby(lobby_type, lobby_members_max) + else: + printerr("Cannot host lobby, already in a lobby") + + +func join_lobby(this_lobby_id: int): + print("Attempting to join lobby: " + str(this_lobby_id)) + lobby_members.clear() + Steam.joinLobby(this_lobby_id) + + +func leave_lobby(): + if lobby_id != 0: + Steam.leaveLobby(lobby_id) + lobby_id = 0 + + for this_member in lobby_members: + if this_member["steam_id"] != steam_id: + Steam.closeP2PSessionWithUser(this_member["steam_id"]) + + lobby_members.clear() + + +func get_lobby_members(): + lobby_members.clear() + var member_count: int = Steam.getNumLobbyMembers(lobby_id) + + for this_member in range(0, member_count): + var member_steam_id: int = Steam.getLobbyMemberByIndex(lobby_id, this_member) + var member_steam_name: String = Steam.getFriendPersonaName(member_steam_id) + lobby_members.append({"steam_id":member_steam_id, "steam_name":member_steam_name}) + user_joined_lobby.emit(member_steam_id) + await request_player_avatar(member_steam_id) + + +func make_p2p_handshake(): + print("Sending p2p handshake to the lobby...") + send_p2p_packet(0, {"message":"handshake", "from":steam_id}) + + +func send_p2p_packet(target: int, packet_data: Dictionary) -> void: + # Set the send_type and channel + var send_type: int = Steam.P2P_SEND_RELIABLE + var channel: int = 0 + + # Create a data array to send the data through + var this_data: PackedByteArray + + # Compress the PackedByteArray we create from our dictionary using the GZIP compression method + var compressed_data: PackedByteArray = var_to_bytes(packet_data).compress(FileAccess.COMPRESSION_GZIP) + this_data.append_array(compressed_data) + + # If sending a packet to everyone + if target == 0: + # If there is more than one user, send packets + if lobby_members.size() > 1: + # Loop through all members that aren't you + for this_member in lobby_members: + if this_member['steam_id'] != steam_id: + Steam.sendP2PPacket(this_member['steam_id'], this_data, send_type, channel) + + # Else send it to someone specific + else: + Steam.sendP2PPacket(target, this_data, send_type, channel) + + +func read_all_p2p_packets(read_count: int = 0): + if read_count >= PACKET_READ_LIMIT: + return + + if Steam.getAvailableP2PPacketSize(0) > 0: + read_p2p_packet() + read_all_p2p_packets(read_count + 1) + + +func read_p2p_packet() -> void: + var packet_size: int = Steam.getAvailableP2PPacketSize(0) + + # There is a packet + if packet_size > 0: + var this_packet: Dictionary = Steam.readP2PPacket(packet_size, 0) + + if this_packet.is_empty() or this_packet == null: + print("WARNING: read an empty packet with non-zero size!") + + # Get the remote user's ID + var packet_sender: int = this_packet['remote_steam_id'] + + # Make the packet data readable + var packet_code: PackedByteArray = this_packet['data'] + + # Decompress the array before turning it into a useable dictionary + var readable_data: Dictionary = bytes_to_var(packet_code.decompress_dynamic(-1, FileAccess.COMPRESSION_GZIP)) + + # Print the packet to output + print("Packet: %s" % readable_data) + + +func request_player_avatar(user_id: int, size: int = 128) -> void: + if avatar_texture_cache.has(user_id): + return + + var avatar_texture: ImageTexture = null + Steam.getPlayerAvatar(Steam.AVATAR_LARGE, user_id) + print("Attempting to get avatar for " + Steam.getFriendPersonaName(user_id) + ".....") + + Steam.avatar_loaded.connect(func(loaded_user_id: int, avatar_size: int, avatar_buffer: PackedByteArray): + if loaded_user_id == user_id: + var avatar_image: Image = Image.create_from_data(avatar_size, avatar_size, false, Image.FORMAT_RGBA8, avatar_buffer) + + if avatar_size != size: + avatar_image.resize(size, size, Image.INTERPOLATE_LANCZOS) + + avatar_texture = ImageTexture.create_from_image(avatar_image) + avatar_texture_cache[user_id] = avatar_texture + ) + + # Wait for the avatar to load asynchronously + await get_tree().process_frames(2) + + if avatar_texture == null: + printerr("Could not load image for user: " + Steam.getFriendPersonaName(user_id)) + + +func get_player_avatar(user_id: int) -> ImageTexture: + if avatar_texture_cache.has(user_id): + return avatar_texture_cache[user_id] + else: + return await request_player_avatar(user_id) \ No newline at end of file diff --git a/assets/core/networking/scripts/lobby.gd b/assets/core/networking/scripts/lobby.gd index a1d4176..7d0a85c 100644 --- a/assets/core/networking/scripts/lobby.gd +++ b/assets/core/networking/scripts/lobby.gd @@ -1,11 +1,62 @@ extends Control +@export var host_button: Button +@export var join_button: Button +@export var leave_button: Button +@export var lobby_id: LineEdit +@export var user_list_box: VBoxContainer +@export var user_box_prefab: PackedScene + -# Called when the node enters the scene tree for the first time. func _ready() -> void: - pass # Replace with function body. + host_button.pressed.connect(_on_host) + join_button.pressed.connect(_on_join) + leave_button.pressed.connect(_on_leave) + + NetworkManager.lobby_created.connect(_on_lobby_created) + NetworkManager.lobby_joined.connect(_on_lobby_joined) + NetworkManager.user_joined_lobby.connect(_on_user_joined_lobby) + NetworkManager.user_left_lobby.connect(_on_user_left_lobby) + + +func _on_host(): + NetworkManager.host_lobby() + host_button.disabled = true + join_button.disabled = true + leave_button.disabled = false + lobby_id.disabled = true + lobby_id.text = str(NetworkManager.lobby_id) -# Called every frame. 'delta' is the elapsed time since the previous frame. -func _process(delta: float) -> void: - pass +func _on_join(): + NetworkManager.join_lobby(int(lobby_id.text)) + host_button.disabled = true + join_button.disabled = true + leave_button.disabled = false + lobby_id.disabled = true + + +func _on_leave(): + NetworkManager.leave_lobby() + + +func _on_lobby_created(): + print("Lobby created") + + +func _on_lobby_joined(): + print("Lobby joined") + + +func _on_user_joined_lobby(user_id:int): + var user_box = user_box_prefab.instance() + user_box.get_node("Username").text = str(user_id) + user_list_box.add_child(user_box) + user_box.get_node("PFP").texture = await NetworkManager.get_player_avatar(user_id) + + +func _on_user_left_lobby(user_id:int): + for child in user_list_box.get_children(): + if child.get_node("Username").text == str(user_id): + user_list_box.remove_child(child) + break \ No newline at end of file diff --git a/assets/core/player-controller/scripts/player.gd b/assets/core/player-controller/scripts/player.gd index 1675df8..2c48536 100644 --- a/assets/core/player-controller/scripts/player.gd +++ b/assets/core/player-controller/scripts/player.gd @@ -1,6 +1,6 @@ class_name Player extends CharacterBody3D -var network_manager: NetworkManager +var network_manager: OldnetworkManager var player_tag: Label3D var username: String = "" diff --git a/project.godot b/project.godot index 851af80..07c347d 100644 --- a/project.godot +++ b/project.godot @@ -18,6 +18,7 @@ config/icon="res://assets/icon.png" [autoload] GameConsole="*res://addons/ingameconsole/GameConsole.tscn" +NetworkManager="*res://assets/core/networking/scripts/NetworkManager.gd" [debug]