Merge pull request 'Merge multiplayer_spawner and lobby-map into develop' (#3) from multiplayer_spawner into develop

Reviewed-on: #3
This commit was merged in pull request #3.
This commit is contained in:
2025-08-10 01:33:57 +00:00
32 changed files with 326 additions and 342 deletions

View File

@@ -0,0 +1,31 @@
extends Node
var players: Array[int] = []
var player_spawner: MultiplayerSpawner
@onready var debug_ui: PackedScene = preload("res://ui/multiplayer-debug-ui/multiplayer-debug-ui.tscn")
var debug_ui_instance
func _ready() -> void:
player_spawner = get_tree().root.get_node("Lobby/PlayerSpawner") as MultiplayerSpawner
debug_ui_instance = debug_ui.instantiate()
get_tree().root.add_child.call_deferred(debug_ui_instance)
debug_ui_instance.hide()
func _input(event: InputEvent) -> void:
if event.is_action_pressed("toggle_watch"):
debug_ui_instance.visible = !debug_ui_instance.visible
@rpc("any_peer", "call_local", "reliable")
func request_server_to_spawn_player(peer_id: int, player_name: String) -> void:
var data: Dictionary = {}
data["peer_id"] = peer_id
data["player_name"] = player_name
player_spawner.spawn(data)

View File

@@ -0,0 +1 @@
uid://dsl58fvqxa6ul

View File

@@ -4,12 +4,12 @@ importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://b5xb0fsfpn7r3" uid="uid://b5xb0fsfpn7r3"
path="res://.godot/imported/lobby-terrain.blend-1020d7f6f6a9a581e1b1682b5ac8d125.scn" path="res://.godot/imported/lobby-terrain.blend-a6c0be77548df4815861d8f088c594f5.scn"
[deps] [deps]
source_file="res://levels/lobby-scene/lobby-terrain.blend" source_file="res://levels/lobby-scene/lobby-terrain/lobby-terrain.blend"
dest_files=["res://.godot/imported/lobby-terrain.blend-1020d7f6f6a9a581e1b1682b5ac8d125.scn"] dest_files=["res://.godot/imported/lobby-terrain.blend-a6c0be77548df4815861d8f088c594f5.scn"]
[params] [params]

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

View File

@@ -2,18 +2,17 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://httteb3u35qr" uid="uid://dpkeaqjnr3bsb"
path.s3tc="res://.godot/imported/line.png-1b1fccbe8a0fdb9beb81829cbd429a35.s3tc.ctex" path.s3tc="res://.godot/imported/grass.png-72267d35d6d4105c20ad91641e676c75.s3tc.ctex"
metadata={ metadata={
"imported_formats": ["s3tc_bptc"], "imported_formats": ["s3tc_bptc"],
"vram_texture": true "vram_texture": true
} }
generator_parameters={}
[deps] [deps]
source_file="res://levels/lobby-scene/textures/line.png" source_file="res://levels/lobby-scene/lobby-terrain/textures/grass.png"
dest_files=["res://.godot/imported/line.png-1b1fccbe8a0fdb9beb81829cbd429a35.s3tc.ctex"] dest_files=["res://.godot/imported/grass.png-72267d35d6d4105c20ad91641e676c75.s3tc.ctex"]
[params] [params]

View File

@@ -1,8 +1,9 @@
[gd_scene load_steps=9 format=3 uid="uid://bj52j4ew2lfr6"] [gd_scene load_steps=10 format=3 uid="uid://bj52j4ew2lfr6"]
[ext_resource type="PackedScene" uid="uid://5vggmy1srgxb" path="res://tools/human-height-reference.tscn" id="1_yyu2g"] [ext_resource type="PackedScene" uid="uid://5vggmy1srgxb" path="res://tools/human-height-reference.tscn" id="1_yyu2g"]
[ext_resource type="PackedScene" uid="uid://b5xb0fsfpn7r3" path="res://levels/lobby-scene/lobby-terrain.blend" id="3_f73ky"] [ext_resource type="PackedScene" uid="uid://bhgahenvxqhec" path="res://levels/lobby-scene/tree/tree.blend" id="3_0u2wx"]
[ext_resource type="PackedScene" uid="uid://csmfxg011xisf" path="res://player/Player.tscn" id="4_0aw1h"] [ext_resource type="PackedScene" uid="uid://b5xb0fsfpn7r3" path="res://levels/lobby-scene/lobby-terrain/lobby-terrain.blend" id="3_f73ky"]
[ext_resource type="Script" uid="uid://bbhx0fwgcgjr4" path="res://networking/player_spawner.gd" id="3_qjimh"]
[ext_resource type="PackedScene" uid="uid://c4cew4af3h306" path="res://levels/lobby-scene/tent/tent.blend" id="4_qjimh"] [ext_resource type="PackedScene" uid="uid://c4cew4af3h306" path="res://levels/lobby-scene/tent/tent.blend" id="4_qjimh"]
[ext_resource type="PackedScene" uid="uid://wcsd1tb0quj3" path="res://levels/lobby-scene/campfire/campfire.tscn" id="5_qjimh"] [ext_resource type="PackedScene" uid="uid://wcsd1tb0quj3" path="res://levels/lobby-scene/campfire/campfire.tscn" id="5_qjimh"]
@@ -36,38 +37,47 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.33741, 0.953, -5.00694)
[node name="lobby-terrain" parent="." instance=ExtResource("3_f73ky")] [node name="lobby-terrain" parent="." instance=ExtResource("3_f73ky")]
[node name="MultiplayerSpawner" type="MultiplayerSpawner" parent="."] [node name="Node3D" type="Node3D" parent="lobby-terrain"]
[node name="tree" parent="lobby-terrain/Node3D" instance=ExtResource("3_0u2wx")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.47232, 0, -12.0981)
[node name="Node3D" type="Node3D" parent="lobby-terrain"]
[node name="tree" parent="lobby-terrain/Node3D" instance=ExtResource("3_0u2wx")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.47232, 0, -12.0981)
[node name="PlayerSpawner" type="MultiplayerSpawner" parent="."]
_spawnable_scenes = PackedStringArray("uid://csmfxg011xisf") _spawnable_scenes = PackedStringArray("uid://csmfxg011xisf")
spawn_path = NodePath("../Marker3D") spawn_path = NodePath("../Marker3D")
spawn_limit = 1 script = ExtResource("3_qjimh")
[node name="Marker3D" type="Marker3D" parent="."] [node name="Marker3D" type="Marker3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.269072, -8.35164) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.269072, -8.35164)
[node name="Player" parent="." instance=ExtResource("4_0aw1h")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.444747, 0.992996, -4.71496)
[node name="Campsite" type="Node3D" parent="."] [node name="Campsite" type="Node3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15.724, 0, -17.6721) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15.724, 0, -17.6721)
[node name="TentPlot1" type="Marker3D" parent="Campsite"] [node name="TentPlot1" type="Marker3D" parent="Campsite"]
transform = Transform3D(-0.998887, 0, -0.0471587, 0, 1, 0, 0.0471587, 0, -0.998887, -0.468871, 0, -6.31805) transform = Transform3D(-0.998887, 0, -0.0471587, 0, 1, 0, 0.0471587, 0, -0.998887, -0.468871, 0.334599, -6.31805)
[node name="tent" parent="Campsite/TentPlot1" instance=ExtResource("4_qjimh")] [node name="tent" parent="Campsite/TentPlot1" instance=ExtResource("4_qjimh")]
[node name="TentPlot2" type="Marker3D" parent="Campsite"] [node name="TentPlot2" type="Marker3D" parent="Campsite"]
transform = Transform3D(-0.240973, 0, -0.970532, 0, 1, 0, 0.970532, 0, -0.240973, -6.46887, 0, -2.31805) transform = Transform3D(-0.240973, 0, -0.970532, 0, 1, 0, 0.970532, 0, -0.240973, -6.46887, 0.334599, -2.31805)
[node name="tent" parent="Campsite/TentPlot2" instance=ExtResource("4_qjimh")] [node name="tent" parent="Campsite/TentPlot2" instance=ExtResource("4_qjimh")]
[node name="TentPlot3" type="Marker3D" parent="Campsite"] [node name="TentPlot3" type="Marker3D" parent="Campsite"]
transform = Transform3D(0.689594, 0, -0.724196, 0, 1, 0, 0.724196, 0, 0.689594, -4.46887, 0, 4.68195) transform = Transform3D(0.689594, 0, -0.724196, 0, 1, 0, 0.724196, 0, 0.689594, -4.46887, 0.334599, 4.68195)
[node name="tent" parent="Campsite/TentPlot3" instance=ExtResource("4_qjimh")] [node name="tent" parent="Campsite/TentPlot3" instance=ExtResource("4_qjimh")]
[node name="TentPlot4" type="Marker3D" parent="Campsite"] [node name="TentPlot4" type="Marker3D" parent="Campsite"]
transform = Transform3D(-0.0217975, 0, 0.999762, 0, 1, 0, -0.999762, 0, -0.0217975, 6.53113, 0, -2.31805) transform = Transform3D(-0.0217975, 0, 0.999762, 0, 1, 0, -0.999762, 0, -0.0217975, 6.53113, 0.334599, -2.31805)
[node name="tent" parent="Campsite/TentPlot4" instance=ExtResource("4_qjimh")] [node name="tent" parent="Campsite/TentPlot4" instance=ExtResource("4_qjimh")]
[node name="Campfire" parent="Campsite" instance=ExtResource("5_qjimh")] [node name="Campfire" parent="Campsite" instance=ExtResource("5_qjimh")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.392361, 0)

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 823 KiB

View File

@@ -1,35 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dxeibfk11lodj"
path.s3tc="res://.godot/imported/rocky_terrain_02_diff_1k.jpg-a0faf17242828e040608373f3cdaba10.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://levels/lobby-scene/textures/rocky_terrain_02_diff_1k.jpg"
dest_files=["res://.godot/imported/rocky_terrain_02_diff_1k.jpg-a0faf17242828e040608373f3cdaba10.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@@ -2,18 +2,17 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://dd8afqm2eo5tb" uid="uid://bd53ybrlho0d1"
path.s3tc="res://.godot/imported/pins.png-9e7d62b7100f3992201f360fd11b8753.s3tc.ctex" path.s3tc="res://.godot/imported/leaves.png-530373898a77b84d2e3909cbed29021f.s3tc.ctex"
metadata={ metadata={
"imported_formats": ["s3tc_bptc"], "imported_formats": ["s3tc_bptc"],
"vram_texture": true "vram_texture": true
} }
generator_parameters={}
[deps] [deps]
source_file="res://levels/lobby-scene/textures/pins.png" source_file="res://levels/lobby-scene/tree/textures/leaves.png"
dest_files=["res://.godot/imported/pins.png-9e7d62b7100f3992201f360fd11b8753.s3tc.ctex"] dest_files=["res://.godot/imported/leaves.png-530373898a77b84d2e3909cbed29021f.s3tc.ctex"]
[params] [params]

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

View File

@@ -2,18 +2,17 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://oemi5vymwrij" uid="uid://cxuxtdscvpcru"
path.s3tc="res://.godot/imported/tent-canvas.png-eb337bb8ec4691a22e92d3af49a9828d.s3tc.ctex" path.s3tc="res://.godot/imported/trunk.png-4d6cd7f252205478b8e4bf2b892c9b44.s3tc.ctex"
metadata={ metadata={
"imported_formats": ["s3tc_bptc"], "imported_formats": ["s3tc_bptc"],
"vram_texture": true "vram_texture": true
} }
generator_parameters={}
[deps] [deps]
source_file="res://levels/lobby-scene/textures/tent-canvas.png" source_file="res://levels/lobby-scene/tree/textures/trunk.png"
dest_files=["res://.godot/imported/tent-canvas.png-eb337bb8ec4691a22e92d3af49a9828d.s3tc.ctex"] dest_files=["res://.godot/imported/trunk.png-4d6cd7f252205478b8e4bf2b892c9b44.s3tc.ctex"]
[params] [params]

Binary file not shown.

View File

@@ -0,0 +1,53 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://bhgahenvxqhec"
path="res://.godot/imported/tree.blend-2e97b0c94b4b9f627dfe10b7f953c7fe.scn"
[deps]
source_file="res://levels/lobby-scene/tree/tree.blend"
dest_files=["res://.godot/imported/tree.blend-2e97b0c94b4b9f627dfe10b7f953c7fe.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
blender/nodes/visible=0
blender/nodes/active_collection_only=false
blender/nodes/punctual_lights=true
blender/nodes/cameras=true
blender/nodes/custom_properties=true
blender/nodes/modifiers=1
blender/meshes/colors=false
blender/meshes/uvs=true
blender/meshes/normals=true
blender/meshes/export_geometry_nodes_instances=false
blender/meshes/tangents=true
blender/meshes/skins=2
blender/meshes/export_bones_deforming_mesh_only=false
blender/materials/unpack_enabled=true
blender/materials/export_materials=1
blender/animation/limit_playback=true
blender/animation/always_sample=true
blender/animation/group_tracks=true

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

View File

@@ -2,16 +2,16 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://c31rlthxjvvbp" uid="uid://chrva3l6shfhg"
path="res://.godot/imported/rocky_terrain_02_spec_1k.png-79c9d42463680422048e46f05a653dfb.ctex" path="res://.godot/imported/trunk.png-b162fd3ffcc29a857809c16bdc1c00bb.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://levels/lobby-scene/textures/rocky_terrain_02_spec_1k.png" source_file="res://levels/lobby-scene/tree/trunk.png"
dest_files=["res://.godot/imported/rocky_terrain_02_spec_1k.png-79c9d42463680422048e46f05a653dfb.ctex"] dest_files=["res://.godot/imported/trunk.png-b162fd3ffcc29a857809c16bdc1c00bb.ctex"]
[params] [params]

View File

@@ -0,0 +1,43 @@
extends MultiplayerSpawner
@onready var player_scene: PackedScene = load("res://player/Player.tscn")
func _ready() -> void:
spawn_function = spawn_player
func spawn_player(data: Dictionary) -> Player:
var new_player = player_scene.instantiate() as Player
var peer_id = str(data["peer_id"])
var player_name = str(data["player_name"])
new_player.set_multiplayer_authority(int(peer_id), true)
new_player.set_player_name(peer_id, player_name)
new_player.position = Vector3.UP
GameManager.players.append(int(peer_id))
return new_player
#var steam_id_of_player = peer_to_steam_id_map.get(peer_id)
#if steam_id_of_player == null:
#print("!!! [%s] CRITICAL: Cannot spawn player for Peer %s, not in map." % [multiplayer.get_unique_id(), peer_id])
#return
#
#if players.has(peer_id): return
#
#var player_name = Steam.getFriendPersonaName(steam_id_of_player)
#print("-> [%s] Spawning character for Peer %s (Name: %s)." % [multiplayer.get_unique_id(), peer_id, player_name])
#
#var new_player = player_scene.instantiate() as Player
#new_player.name = str(peer_id)
#
#players[peer_id] = new_player
#add_child(new_player)
#new_player.position = Vector3.UP
#new_player.set_player_name(player_name)
#new_player.set_multiplayer_authority(peer_id, true)
#new_player.setup_player()

View File

@@ -0,0 +1 @@
uid://bbhx0fwgcgjr4

View File

@@ -1,15 +1,14 @@
extends Node extends Node
var is_owned: bool = false @onready var player_scene: PackedScene = preload("res://player/Player.tscn")
var steam_app_id: int = 480
var players: Array[int] = []
var steam_id: int = 0 var steam_id: int = 0
var steam_username: String = "" var steam_username: String = ""
var lobby_id: int = 0
var lobby_id = 0
var lobby_max_members = 4
var lobby_members: Array = []
var steam_initialized: bool = false var steam_initialized: bool = false
var steam_app_id: int = 480
var peer: SteamMultiplayerPeer var peer: SteamMultiplayerPeer
@@ -23,274 +22,148 @@ func _ready() -> void:
print("!!! Steam did not initialize. Multiplayer will be disabled.") print("!!! Steam did not initialize. Multiplayer will be disabled.")
return return
# Connect Steam lobby signals
Steam.lobby_created.connect(_on_lobby_created) Steam.lobby_created.connect(_on_lobby_created)
Steam.lobby_joined.connect(_on_lobby_joined) Steam.lobby_joined.connect(_on_lobby_joined)
#Steam.lobby_match_list.connect(_on_lobby_match_list)
Steam.lobby_chat_update.connect(_on_lobby_chat_update) Steam.lobby_chat_update.connect(_on_lobby_chat_update)
Steam.lobby_data_update.connect(_on_lobby_data_update) Steam.p2p_session_request.connect(_on_p2p_session_request)
#Steam.join_requested.connect(_on_lobby_join_requested)
Steam.persona_state_change.connect(_on_persona_change)
# Setup multiplayer signals
multiplayer.peer_connected.connect(_on_peer_connected) multiplayer.peer_connected.connect(_on_peer_connected)
multiplayer.peer_disconnected.connect(_on_peer_disconnected) multiplayer.peer_disconnected.connect(_on_peer_disconnected)
multiplayer.connected_to_server.connect(_on_connected_to_server) multiplayer.connected_to_server.connect(_on_connected_to_server)
multiplayer.connection_failed.connect(_on_connection_failed) multiplayer.connection_failed.connect(_on_connection_failed)
multiplayer.server_disconnected.connect(_on_server_disconnected)
Steam.p2p_session_request.connect(_on_p2p_session_request)
check_command_line() check_command_line()
multiplayer.server_relay = true
func _on_p2p_session_request(steam_id_remote: int) -> void:
## ADDED: More detailed logging to confirm P2P acceptance.
print("[P2P] ==> Session request from: %s. Accepting." % steam_id_remote)
var accepted = Steam.acceptP2PSessionWithUser(steam_id_remote)
if not accepted:
print("[P2P] !!! Failed to accept P2P session with %s." % steam_id_remote)
func _process(delta): func _process(delta):
## ADDED: Guard clause in case Steam fails to initialize.
if not steam_initialized: if not steam_initialized:
return return
Steam.run_callbacks() Steam.run_callbacks()
if Input.is_action_just_pressed("interact"):
## ADDED: Replaced simple print with a detailed diagnostic function. func create_lobby():
log_multiplayer_info() print("[HOST] 1. Attempting to create lobby...")
Steam.createLobby(Steam.LOBBY_TYPE_FRIENDS_ONLY, 4)
func check_command_line() -> void: func _on_lobby_created(connect: int, this_lobby_id: int):
var args: Array = OS.get_cmdline_args() if connect != 1:
if args.size() > 0: print("[HOST] !!! Lobby creation failed.")
if args[0] == "+connect_lobby": return
if args.size() > 1 and int(args[1]) > 0:
print("Command line lobby ID: %s" % args[1]) lobby_id = this_lobby_id
join_lobby(int(args[1])) print("[HOST] 2. Lobby created successfully (ID: %s)." % lobby_id)
Steam.setLobbyJoinable(lobby_id, true)
Steam.setLobbyData(lobby_id, "name", steam_username + "'s Lobby")
setup_multiplayer_peer(true)
print("[HOST] 3. Multiplayer host started (My Peer ID is always 1).")
#players.append(peer.get)
GameManager.request_server_to_spawn_player.rpc_id(1, 1, steam_username)
func join_lobby(this_lobby_id: int):
print("[CLIENT] 1. Attempting to join lobby (ID: %s)..." % this_lobby_id)
Steam.joinLobby(this_lobby_id)
func _on_lobby_joined(this_lobby_id: int, _p, _l, response: int):
if response != Steam.CHAT_ROOM_ENTER_RESPONSE_SUCCESS:
print("[CLIENT] !!! Failed to join lobby: %s" % get_join_fail_reason(response))
return
lobby_id = this_lobby_id
print("[CLIENT] 2. Successfully joined Steam lobby.")
if Steam.getLobbyOwner(lobby_id) != steam_id:
print("[CLIENT] 3. I am a client, creating multiplayer peer to connect to host.")
setup_multiplayer_peer(false)
func _on_connected_to_server():
print("[CLIENT] 4. Successfully connected to host's multiplayer peer.")
print("[CLIENT] 5. Sending my info to the server for registration...")
GameManager.request_server_to_spawn_player.rpc_id(1, multiplayer.get_unique_id(), steam_username)
func _on_p2p_session_request(steam_id_remote: int) -> void:
print("[P2P] ==> Session request from: %s. Accepting." % steam_id_remote)
Steam.acceptP2PSessionWithUser(steam_id_remote)
func init_steam() -> bool: func init_steam() -> bool:
var response: Dictionary = Steam.steamInitEx() var response: Dictionary = Steam.steamInitEx()
print("Steam init response: %s " % response)
if response['status'] > 0: if response['status'] > 0:
print("!!! Failed to init steam! Code: %s " % response) print("!!! Failed to init steam! Code: %s " % response)
return false return false
is_owned = Steam.isSubscribed()
steam_id = Steam.getSteamID() steam_id = Steam.getSteamID()
steam_username = Steam.getPersonaName() steam_username = Steam.getPersonaName()
print("Steam initialized successfully for %s (ID: %s)." % [steam_username, steam_id]) print("Steam initialized successfully for %s (ID: %s)." % [steam_username, steam_id])
if !is_owned:
print("!!! WARNING: Steam reports you do not own App ID %s." % steam_app_id)
# You might want to return false here in a real game
# return false
return true return true
func setup_multiplayer_peer(is_host: bool = false) -> void: func setup_multiplayer_peer(is_host: bool = false) -> void:
## ADDED: Check if a peer is already active before creating a new one.
if multiplayer.multiplayer_peer and multiplayer.multiplayer_peer.get_connection_status() != MultiplayerPeer.CONNECTION_DISCONNECTED: if multiplayer.multiplayer_peer and multiplayer.multiplayer_peer.get_connection_status() != MultiplayerPeer.CONNECTION_DISCONNECTED:
print("[Multiplayer] Peer already exists. Disconnecting old one.")
multiplayer.multiplayer_peer.close() multiplayer.multiplayer_peer.close()
peer = SteamMultiplayerPeer.new() peer = SteamMultiplayerPeer.new()
if is_host: if is_host:
print("[Multiplayer] Creating Host...") peer.create_host(0)
var err = peer.create_host(0) ## REMOVED: Channel is handled by the peer automatically now.
if err != OK:
print("[Multiplayer] !!! Failed to create host. Error: %s" % err)
return
multiplayer.multiplayer_peer = peer
print("[Multiplayer] Host created successfully. My Peer ID: %s" % multiplayer.get_unique_id())
else: else:
if lobby_id == 0:
print("[Multiplayer] !!! Cannot create client, not in a lobby.")
return
var host_id = Steam.getLobbyOwner(lobby_id) var host_id = Steam.getLobbyOwner(lobby_id)
print("[Multiplayer] Creating Client, attempting to connect to host: %s" % host_id) peer.create_client(host_id, 0)
var err = peer.create_client(host_id, 0) ## REMOVED: Channel is handled by the peer automatically.
if err != OK:
print("[Multiplayer] !!! Failed to create client. Error: %s" % err)
return
multiplayer.multiplayer_peer = peer multiplayer.multiplayer_peer = peer
print("[Multiplayer] Client peer created. Waiting for connection...")
func create_lobby(): func _on_peer_connected(id: int):
if lobby_id == 0: print("[INFO] Peer %s has established a multiplayer session." % id)
print("Creating lobby...") #players.append()
Steam.createLobby(Steam.LOBBY_TYPE_FRIENDS_ONLY, lobby_max_members)
func _on_peer_disconnected(id: int):
print("[INFO] Peer %s has disconnected." % id)
if players.has(id):
#players[id].queue_free()
players.erase(id)
func _on_lobby_created(connect: int, this_lobby_id: int): func _on_connection_failed() -> void:
if connect == 1: print("[CLIENT] !!! Connection to the host failed.")
lobby_id = this_lobby_id
print("Lobby created with id `%s`" % lobby_id)
Steam.setLobbyJoinable(lobby_id, true)
Steam.setLobbyData(lobby_id, "name", steam_username + "'s Lobby")
Steam.setLobbyData(lobby_id, "mode", "Splunk")
Steam.allowP2PPacketRelay(true)
setup_multiplayer_peer(true) # Setup as host
else :
print("!!! Failed to create lobby.")
func join_lobby(this_lobby_id: int): func _on_lobby_chat_update(_l_id, user_changed_id: int, _u_m_c_id, chat_state: int):
print("Attempting to join lobby: %s" % this_lobby_id)
lobby_members.clear()
Steam.joinLobby(this_lobby_id)
func _on_lobby_joined(this_lobby_id: int, permissions: int, locked: bool, response: int):
if response == Steam.CHAT_ROOM_ENTER_RESPONSE_SUCCESS:
lobby_id = this_lobby_id
print("Successfully joined lobby: %s" % lobby_id)
get_lobby_members()
# FIXED: Use Steam.getLobbyOwner() to determine if we should be host or client
var lobby_owner_id = Steam.getLobbyOwner(lobby_id)
var am_i_owner = (lobby_owner_id == steam_id)
print("[Multiplayer] Lobby Owner: %s, My Steam ID: %s, Am I Owner?: %s" % [lobby_owner_id, steam_id, am_i_owner])
if not am_i_owner:
print("[Multiplayer] I am not the lobby owner, setting up as client...")
setup_multiplayer_peer(false) # Setup as client
else:
print("[Multiplayer] I am the lobby owner, but multiplayer peer should already be set up as host.")
else:
## ADDED: Log the specific reason for the join failure.
print("!!! Failed to join lobby. Reason: %s" % get_join_fail_reason(response))
func get_lobby_members() -> void:
lobby_members.clear()
if lobby_id == 0: return
var num_members = Steam.getNumLobbyMembers(lobby_id)
print("--- Refreshing Lobby Members (%s) ---" % num_members)
for i in range(num_members):
var member_id = Steam.getLobbyMemberByIndex(lobby_id, i)
var member_name = Steam.getFriendPersonaName(member_id)
lobby_members.append({
"steam_id": member_id,
"steam_name": member_name
})
print(" - %s (%s)" % [member_name, member_id])
print("---------------------------------")
func _on_lobby_data_update(lobby: int, user: int, success: int) -> void:
if success:
if lobby == user:
print("Lobby data for lobby %s has been updated." % lobby)
var lobby_name = Steam.getLobbyData(lobby, "name")
print(" > New lobby name: %s" % lobby_name)
else:
print("Data for member %s in lobby %s has been updated." % [user, lobby])
func _on_persona_change(steam_id_changed: int, flag: int) -> void:
# This can be spammy, but useful for debugging name changes.
# print("Persona state changed for %s. Refreshing lobby members." % steam_id_changed)
get_lobby_members()
func leave_lobby() -> void:
pass
func _on_lobby_chat_update(lobby_id_update: int, user_changed_id: int, user_making_change_id: int, chat_state: int):
var state_string = "UNKNOWN" var state_string = "UNKNOWN"
match chat_state: match chat_state:
Steam.CHAT_MEMBER_STATE_CHANGE_ENTERED: state_string = "ENTERED" Steam.CHAT_MEMBER_STATE_CHANGE_ENTERED: state_string = "ENTERED"
Steam.CHAT_MEMBER_STATE_CHANGE_LEFT: state_string = "LEFT" Steam.CHAT_MEMBER_STATE_CHANGE_LEFT: state_string = "LEFT"
Steam.CHAT_MEMBER_STATE_CHANGE_DISCONNECTED: state_string = "DISCONNECTED" Steam.CHAT_MEMBER_STATE_CHANGE_DISCONNECTED: state_string = "DISCONNECTED"
Steam.CHAT_MEMBER_STATE_CHANGE_KICKED: state_string = "KICKED" print("[LOBBY INFO] User %s has %s the lobby." % [user_changed_id, state_string])
Steam.CHAT_MEMBER_STATE_CHANGE_BANNED: state_string = "BANNED"
print("[Lobby] Chat Update: User %s has %s." % [user_changed_id, state_string])
# Any change in lobby membership should trigger a refresh.
get_lobby_members()
func _on_peer_connected(id: int) -> void: func check_command_line() -> void:
print("[Multiplayer] ✅ Peer connected: %s" % id) var args: Array = OS.get_cmdline_args()
# It's good practice to re-check lobby members when a peer connects successfully. if args.size() > 1 and args[0] == "+connect_lobby":
get_lobby_members() if int(args[1]) > 0:
print("[CMD] Command line join request for lobby ID: %s" % args[1])
func _on_peer_disconnected(id: int) -> void: join_lobby(int(args[1]))
print("[Multiplayer] ❌ Peer disconnected: %s" % id)
func _on_connected_to_server() -> void:
print("[Multiplayer] ✅ Successfully connected to the host.")
print("[Multiplayer] - My Peer ID is now: %s" % multiplayer.get_unique_id())
func _on_connection_failed() -> void:
print("[Multiplayer] ❌ Connection to the host failed.")
func _on_server_disconnected() -> void:
print("[Multiplayer] ❌ Disconnected from the host.")
## ADDED: New function to log all relevant multiplayer and lobby information.
func log_multiplayer_info():
print("\n--- DIAGNOSTIC INFO ---")
print("## Multiplayer Status:")
if multiplayer.multiplayer_peer:
print(" - Peer State: Active")
print(" - Connection Status: %s" % get_connection_status_string(multiplayer.multiplayer_peer.get_connection_status()))
print(" - Is Server?: %s" % multiplayer.is_server())
print(" - My Peer ID: %s" % multiplayer.get_unique_id())
print(" - Connected Peer IDs: %s" % multiplayer.get_peers())
else:
print(" - Peer State: Inactive (null)")
print("\n## Steam Lobby Info:")
print(" - In Lobby?: %s" % (lobby_id != 0))
print(" - Lobby ID: %s" % lobby_id)
if lobby_id != 0:
print(" - Lobby Owner Steam ID: %s" % Steam.getLobbyOwner(lobby_id))
print(" - My Steam ID: %s" % steam_id)
print(" - Lobby Members Array (%s):" % lobby_members.size())
for member in lobby_members:
print(" - %s (%s)" % [member.steam_name, member.steam_id])
print("-------------------------\n")
## ADDED: Helper function to get a human-readable string for connection status.
func get_connection_status_string(status: int) -> String:
match status:
MultiplayerPeer.CONNECTION_DISCONNECTED: return "Disconnected"
MultiplayerPeer.CONNECTION_CONNECTING: return "Connecting"
MultiplayerPeer.CONNECTION_CONNECTED: return "Connected"
_: return "Unknown Status"
func get_join_fail_reason(response: int) -> String: func get_join_fail_reason(response: int) -> String:
match response: match response:
Steam.CHAT_ROOM_ENTER_RESPONSE_DOESNT_EXIST: return "Lobby no longer exists" 1: return "Doesnt Exist"
Steam.CHAT_ROOM_ENTER_RESPONSE_NOT_ALLOWED: return "Not allowed to join" 2: return "Not Allowed"
Steam.CHAT_ROOM_ENTER_RESPONSE_FULL: return "Lobby is full" 3: return "Full"
Steam.CHAT_ROOM_ENTER_RESPONSE_ERROR: return "Unknown error" 4: return "Error"
Steam.CHAT_ROOM_ENTER_RESPONSE_BANNED: return "You are banned" 5: return "Banned"
Steam.CHAT_ROOM_ENTER_RESPONSE_LIMITED: return "Limited account" 6: return "Limited"
Steam.CHAT_ROOM_ENTER_RESPONSE_CLAN_DISABLED: return "Lobby is locked" 7: return "Clan Disabled"
Steam.CHAT_ROOM_ENTER_RESPONSE_COMMUNITY_BAN: return "Community locked" 8: return "Community Ban"
Steam.CHAT_ROOM_ENTER_RESPONSE_MEMBER_BLOCKED_YOU: return "A member blocked you" 9: return "Member Blocked You"
Steam.CHAT_ROOM_ENTER_RESPONSE_YOU_BLOCKED_MEMBER: return "You blocked a member" 10: return "You Blocked Member"
_: return "Unknown reason" _: return "Unknown Reason"

View File

@@ -1,7 +1,6 @@
[gd_scene load_steps=8 format=3 uid="uid://csmfxg011xisf"] [gd_scene load_steps=7 format=3 uid="uid://csmfxg011xisf"]
[ext_resource type="Script" uid="uid://dopyfulbw2mx5" path="res://player/player.gd" id="1_ulp21"] [ext_resource type="Script" uid="uid://dopyfulbw2mx5" path="res://player/player.gd" id="1_ulp21"]
[ext_resource type="PackedScene" uid="uid://8phs2e161db1" path="res://ui/multiplayer-debug-ui/multiplayer-debug-ui.tscn" id="2_3c3w1"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_ehsmr"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_ehsmr"]
@@ -21,9 +20,6 @@ properties/0/replication_mode = 2
properties/1/path = NodePath(".:rotation") properties/1/path = NodePath(".:rotation")
properties/1/spawn = true properties/1/spawn = true
properties/1/replication_mode = 2 properties/1/replication_mode = 2
properties/2/path = NodePath("Label3D:text")
properties/2/spawn = true
properties/2/replication_mode = 1
[node name="Player" type="CharacterBody3D"] [node name="Player" type="CharacterBody3D"]
collision_layer = 2 collision_layer = 2
@@ -32,8 +28,6 @@ script = ExtResource("1_ulp21")
[node name="Camera3D" type="Camera3D" parent="."] [node name="Camera3D" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.399442, 0.0644827) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.399442, 0.0644827)
cull_mask = 1048573
current = true
[node name="CollisionShape3D" type="CollisionShape3D" parent="."] [node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("CapsuleShape3D_ehsmr") shape = SubResource("CapsuleShape3D_ehsmr")
@@ -65,14 +59,10 @@ layers = 2
mesh = SubResource("SphereMesh_wnvi2") mesh = SubResource("SphereMesh_wnvi2")
skeleton = NodePath("../..") skeleton = NodePath("../..")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_ulp21")
[node name="Label3D" type="Label3D" parent="."] [node name="Label3D" type="Label3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.887858, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.887858, 0)
billboard = 1 billboard = 1
text = "Username" text = "Username"
[node name="CanvasLayer" type="CanvasLayer" parent="."] [node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_ulp21")
[node name="Multiplayer-debug-ui" parent="CanvasLayer" instance=ExtResource("2_3c3w1")]

View File

@@ -1,4 +1,5 @@
extends CharacterBody3D extends CharacterBody3D
class_name Player
@export var speed = 5.0 @export var speed = 5.0
@export var jump_velocity = 4.5 @export var jump_velocity = 4.5
@@ -7,54 +8,72 @@ extends CharacterBody3D
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity") var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
var camera_node: Camera3D var camera_node: Camera3D
func _ready():
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
camera_node = $Camera3D # Assuming Camera3D is a direct child
$Label3D.text = SteamManager.steam_username
func _ready():
if is_multiplayer_authority():
print("-> [%s] Authority granted. Setting up camera and input." % name)
camera_node = $Camera3D
camera_node.make_current()
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
$Mesh.hide()
func set_player_name(peer_id: String, player_name: String):
name = peer_id
$Label3D.text = player_name
#@rpc("any_peer", "call_local", "unreliable")
#func update_remote_transform(new_transform: Transform3D):
#global_transform = new_transform
func _physics_process(delta): func _physics_process(delta):
# Apply gravity if is_multiplayer_authority():
if not is_on_floor(): # Apply gravity
velocity.y -= gravity * delta if not is_on_floor():
velocity.y -= gravity * delta
# Handle Jump # Handle Jump
if Input.is_action_just_pressed("jump") and is_on_floor(): if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = jump_velocity velocity.y = jump_velocity
# Get the input direction and apply movement # Get the input direction and apply movement
var input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_backward") var input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_backward")
var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized() var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
if is_on_floor(): if is_on_floor():
if direction: if direction:
velocity.x = direction.x * speed velocity.x = direction.x * speed
velocity.z = direction.z * speed velocity.z = direction.z * speed
else:
velocity.x = move_toward(velocity.x, 0, speed)
velocity.z = move_toward(velocity.z, 0, speed)
else: else:
velocity.x = move_toward(velocity.x, 0, speed) # Air control
velocity.z = move_toward(velocity.z, 0, speed) velocity.x = lerp(velocity.x, direction.x * speed, delta * 5.0)
else: velocity.z = lerp(velocity.z, direction.z * speed, delta * 5.0)
# Air control move_and_slide()
velocity.x = lerp(velocity.x, direction.x * speed, delta * 5.0)
velocity.z = lerp(velocity.z, direction.z * speed, delta * 5.0) #update_remote_transform.rpc(global_transform)
move_and_slide()
func _input(event): func _input(event):
if event is InputEventMouseMotion: if is_multiplayer_authority():
# Rotate the CharacterBody3D around the Y-axis for horizontal look if event is InputEventMouseMotion:
rotate_y(-event.relative.x * mouse_sensitivity) # Rotate the CharacterBody3D around the Y-axis for horizontal look
rotate_y(-event.relative.x * mouse_sensitivity)
# Rotate the Camera3D around its local X-axis for vertical look # Rotate the Camera3D around its local X-axis for vertical look
var change = -event.relative.y * mouse_sensitivity var change = -event.relative.y * mouse_sensitivity
var new_x_rotation = camera_node.rotation.x + change var new_x_rotation = camera_node.rotation.x + change
camera_node.rotation.x = clamp(new_x_rotation, deg_to_rad(-90), deg_to_rad(90)) camera_node.rotation.x = clamp(new_x_rotation, deg_to_rad(-90), deg_to_rad(90))
if event.is_action_pressed("ui_cancel"): # Typically Escape key if event.is_action_pressed("ui_cancel"): # Typically Escape key
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED: if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
else: else:
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
if event.is_action_pressed("toggle_watch"):
$"CanvasLayer/Multiplayer-debug-ui".visible = !$"CanvasLayer/Multiplayer-debug-ui".visible

View File

@@ -18,6 +18,7 @@ config/icon="res://icon.svg"
[autoload] [autoload]
SteamManager="*res://networking/steam-manager.gd" SteamManager="*res://networking/steam-manager.gd"
GameManager="*res://game-logic/game_manager.gd"
[editor_plugins] [editor_plugins]