diff --git a/addons/godotsteam/win64/~libgodotsteam.windows.template_debug.x86_64.dll b/addons/godotsteam/win64/~libgodotsteam.windows.template_debug.x86_64.dll new file mode 100644 index 0000000..c80cff6 Binary files /dev/null and b/addons/godotsteam/win64/~libgodotsteam.windows.template_debug.x86_64.dll differ diff --git a/addons/ingameconsole/GameConsole.tscn b/addons/ingameconsole/GameConsole.tscn index 283d204..bd92fa7 100644 --- a/addons/ingameconsole/GameConsole.tscn +++ b/addons/ingameconsole/GameConsole.tscn @@ -6,6 +6,7 @@ [node name="GameConsole" type="Control"] process_mode = 3 z_index = 4096 +z_as_relative = false layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 diff --git a/assets/core/enviroment/dev-level/dev-level.tscn b/assets/core/enviroment/dev-level/dev-level.tscn index 7d583f5..252a298 100644 --- a/assets/core/enviroment/dev-level/dev-level.tscn +++ b/assets/core/enviroment/dev-level/dev-level.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=10 format=3 uid="uid://ewovs6ns5y3k"] -[ext_resource type="Script" path="res://assets/core/networking/NetworkManager.gd" id="1_kki4t"] +[ext_resource type="Script" path="res://assets/core/networking/old/old_NetworkManager.gd" id="1_kki4t"] [ext_resource type="Texture2D" uid="uid://gymb0tju4y67" path="res://addons/kennysprototypetextures/Dark/texture_black (1).png" id="1_l0osb"] [ext_resource type="PackedScene" uid="uid://c6w0ivy4hetrl" path="res://assets/core/player-controller/scenes/player.tscn" id="2_q510b"] diff --git a/assets/core/networking/NetworkManager.gd b/assets/core/networking/NetworkManager.gd deleted file mode 100644 index 6a39f7c..0000000 --- a/assets/core/networking/NetworkManager.gd +++ /dev/null @@ -1,34 +0,0 @@ -class_name NetworkManager -extends Node - -var steamId: int -var username: String -var avatar_texture: ImageTexture - -func _ready() -> void: - var init_result = Steam.steamInit(true, 480) - if init_result["status"] <= 1: - _initialize() - else: - GameConsole.log_error("Steam failed to initialize: " + str(init_result)) - - -func _initialize() -> void: - steamId = Steam.getSteamID() - username = Steam.getFriendPersonaName(steamId) - GameConsole.print_line("Steam ID: " + username) - - Steam.getPlayerAvatar() - Steam.avatar_loaded.connect(_on_avatar_loaded) - - -func _on_avatar_loaded(_user_id: int, avatar_size: int, avatar_buffer: PackedByteArray) -> void: - var avatar_image: Image = Image.create_from_data(avatar_size, avatar_size, false, Image.FORMAT_RGBA8, avatar_buffer) - if avatar_size > 128: - avatar_image.resize(128, 128, Image.INTERPOLATE_LANCZOS) - - - if avatar_image != null: - avatar_texture = ImageTexture.create_from_image(avatar_image) - else: - GameConsole.log_error("Failed to create avatar image!") diff --git a/assets/core/networking/scenes/lobby.tscn b/assets/core/networking/scenes/lobby.tscn new file mode 100644 index 0000000..6f54eec --- /dev/null +++ b/assets/core/networking/scenes/lobby.tscn @@ -0,0 +1,67 @@ +[gd_scene load_steps=3 format=3 uid="uid://7t1x82gvrw8a"] + +[ext_resource type="Script" path="res://assets/core/networking/scripts/lobby.gd" id="1_o4fbq"] +[ext_resource type="PackedScene" uid="uid://biryul3n6thlw" path="res://assets/core/networking/scenes/user_box_prefab.tscn" id="2_dpthk"] + +[node name="Lobby" type="Control" node_paths=PackedStringArray("host_button", "join_button", "leave_button", "lobby_id_line_edit", "user_list_box")] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_o4fbq") +host_button = NodePath("Panel/HBoxContainer/Buttons/Host") +join_button = NodePath("Panel/HBoxContainer/Buttons/HBoxContainer/Join") +leave_button = NodePath("Panel/HBoxContainer/Buttons/Leave") +lobby_id_line_edit = NodePath("Panel/HBoxContainer/Buttons/HBoxContainer/LineEdit") +user_list_box = NodePath("Panel/HBoxContainer/PlayerList") +user_box_prefab = ExtResource("2_dpthk") + +[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/scenes/user_box_prefab.tscn b/assets/core/networking/scenes/user_box_prefab.tscn new file mode 100644 index 0000000..6297ee2 --- /dev/null +++ b/assets/core/networking/scenes/user_box_prefab.tscn @@ -0,0 +1,20 @@ +[gd_scene load_steps=2 format=3 uid="uid://biryul3n6thlw"] + +[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) +layout_mode = 2 +size_flags_horizontal = 4 +texture = ExtResource("1_gtutw") +expand_mode = 1 + +[node name="Username" type="Label" parent="."] +layout_mode = 2 +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 new file mode 100644 index 0000000..3ed5d8c --- /dev/null +++ b/assets/core/networking/scripts/NetworkManager.gd @@ -0,0 +1,322 @@ +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 = {} +var host_id: int = 0 + +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) +signal host_left_lobby() + + +func _ready() -> void: + _init_steam() + + +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: + user_left_lobby.emit(change_id) + 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)) + host_id = steam_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, _locked: bool, 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 and lobby_members: + for member in lobby_members: + if member["steam_id"] == this_steam_id: + var user: String = str(this_steam_id) + ":" + Steam.getFriendPersonaName(this_steam_id) + print("User (%s) had information change, updating lobby list." % user) + get_lobby_members() + break + + +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() + print("Left lobby.") + + +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: + 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 + ) + + + while not avatar_texture_cache.has(user_id): + await get_tree().process_frame + + +func get_player_avatar(user_id: int) -> ImageTexture: + if avatar_texture_cache.has(user_id): + return avatar_texture_cache[user_id] + else: + await request_player_avatar(user_id) + return avatar_texture_cache[user_id] + + + + + diff --git a/assets/core/networking/scripts/lobby.gd b/assets/core/networking/scripts/lobby.gd new file mode 100644 index 0000000..b843980 --- /dev/null +++ b/assets/core/networking/scripts/lobby.gd @@ -0,0 +1,80 @@ +extends Control + +@export var host_button: Button +@export var join_button: Button +@export var leave_button: Button +@export var lobby_id_line_edit: LineEdit +@export var user_list_box: VBoxContainer +@export var user_box_prefab: PackedScene + +var added_users = [] + + +func _ready() -> void: + 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) + + GameConsole.register_command(Command.new("update", update, [], "Updates the lobby UI")) + + +func _on_host(): + NetworkManager.host_lobby() + + +func _on_join(): + NetworkManager.join_lobby(int(lobby_id_line_edit.text)) + + +func _on_leave(): + NetworkManager.leave_lobby() + added_users.clear() + for child in user_list_box.get_children(): + child.queue_free() + + +func _on_lobby_created(lobby_id: int): + print("Lobby created") + lobby_id_line_edit.text = str(NetworkManager.lobby_id) + host_button.disabled = true + join_button.disabled = true + leave_button.disabled = false + lobby_id_line_edit.editable = false + + +func _on_lobby_joined(lobby_id: int): + host_button.disabled = true + join_button.disabled = true + leave_button.disabled = false + lobby_id_line_edit.editable = false + + +func _on_user_joined_lobby(user_id: int): + if user_id in added_users: + return # User already added, skip + + added_users.append(user_id) + update() + + +func _on_user_left_lobby(user_id: int): + added_users.erase(user_id) + update() + + +func update() -> void: + for child in user_list_box.get_children(): + child.queue_free() + + for user_id in added_users: + var user_box = user_box_prefab.instantiate() + user_box.get_node("Username").text = str(Steam.getFriendPersonaName(user_id)) + user_list_box.add_child(user_box) + + var avatar_texture = await NetworkManager.get_player_avatar(user_id) + user_box.get_node("PFP").texture = avatar_texture diff --git a/assets/core/player-controller/scripts/player.gd b/assets/core/player-controller/scripts/player.gd index b5368f9..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 = "" @@ -33,7 +33,7 @@ func _ready() -> void: func init_network_manager() -> void: network_manager = %NetworkManager if network_manager != null: - username = network_manager.username + username = network_manager.my_username player_tag.text = username else: player_tag.text = "Player" diff --git a/assets/scenes/main.tscn b/assets/scenes/main.tscn index a4fe0b1..aac1d47 100644 --- a/assets/scenes/main.tscn +++ b/assets/scenes/main.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://huq7dxk5yvjk"] -[ext_resource type="Script" path="res://assets/core/networking/NetworkManager.gd" id="1_dcack"] +[ext_resource type="Script" path="res://assets/core/networking/old/old_NetworkManager.gd" id="1_dcack"] [node name="Main" type="Node"] diff --git a/export_presets.cfg b/export_presets.cfg new file mode 100644 index 0000000..2d20e2d --- /dev/null +++ b/export_presets.cfg @@ -0,0 +1,39 @@ +[preset.0] + +name="Linux" +platform="Linux" +runnable=true +advanced_options=false +dedicated_server=false +custom_features="" +export_filter="all_resources" +include_filter="" +exclude_filter="" +export_path="../build/linux/debug/Steamforged Skies.x86_64" +encryption_include_filters="" +encryption_exclude_filters="" +encrypt_pck=false +encrypt_directory=false +script_export_mode=2 + +[preset.0.options] + +custom_template/debug="" +custom_template/release="" +debug/export_console_wrapper=1 +binary_format/embed_pck=false +texture_format/s3tc_bptc=true +texture_format/etc2_astc=false +binary_format/architecture="x86_64" +ssh_remote_deploy/enabled=false +ssh_remote_deploy/host="user@host_ip" +ssh_remote_deploy/port="22" +ssh_remote_deploy/extra_args_ssh="" +ssh_remote_deploy/extra_args_scp="" +ssh_remote_deploy/run_script="#!/usr/bin/env bash +export DISPLAY=:0 +unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\" +\"{temp_dir}/{exe_name}\" {cmd_args}" +ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash +kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\") +rm -rf \"{temp_dir}\"" 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]