diff --git a/Net/net_pickable.gd b/Net/net_pickable.gd index c0e5ba1..f4089f5 100644 --- a/Net/net_pickable.gd +++ b/Net/net_pickable.gd @@ -61,6 +61,8 @@ func _on_picked_up(_p) -> void: var by := _pickable.get_picked_up_by() if not (by is XRToolsFunctionPickup): return + if NetworkManager.is_online(): + NetworkManager.log_line("Grabbed %s by hand, requesting authority" % _pickable.name) NetworkManager.request_item_authority.rpc_id(1, _pickable.get_path()) @@ -69,6 +71,8 @@ func _on_dropped(_p) -> void: # apply_held_state() losing authority (see above) must not re-report. if not is_multiplayer_authority(): return + if NetworkManager.is_online(): + NetworkManager.log_line("Dropped %s, reporting release to server" % _pickable.name) NetworkManager.release_item_authority.rpc_id( 1, _pickable.get_path(), _pickable.linear_velocity, _pickable.angular_velocity ) diff --git a/Net/network_manager.gd b/Net/network_manager.gd index 1270a19..fb2a526 100644 --- a/Net/network_manager.gd +++ b/Net/network_manager.gd @@ -101,6 +101,8 @@ func spawn_item(scene_path: String, xform: Transform3D, node_name: String = "", if is_online() and not is_server(): return null var data := {"scene": scene_path, "xform": xform, "name": node_name, "props": props} + var via := "spawner" if (is_online() and _items_spawner) else "offline" + log_line("spawn_item: %s (name=%s, via=%s)" % [scene_path.get_file(), node_name, via]) if is_online() and _items_spawner: return _items_spawner.spawn(data) # Offline: instantiate directly under the registered content root, or (for @@ -120,6 +122,7 @@ func spawn_item(scene_path: String, xform: Transform3D, node_name: String = "", ## the single seam for destroying spawned items (works offline too). func despawn_item(node: Node) -> void: if owns_world() and is_instance_valid(node): + log_line("despawn_item: %s" % node.name) node.queue_free() @@ -157,6 +160,7 @@ func _gate_station(node: Node) -> void: child.enabled = false child.set_process(false) node.set_process(false) + log_line("gated station (non-owner peer): %s" % node.name) # --- Item grab-authority transfer ----------------------------------------- @@ -175,8 +179,10 @@ func request_item_authority(item_path: NodePath) -> void: if np and np.net_held_by != 0 and np.net_held_by != sender: # Already legitimately held by a different live peer: reject the # requester's optimistic client-side grab instead of stealing it. + log_line("request_item_authority: DENIED %s to peer %d (already held by %d)" % [item.name, sender, np.net_held_by]) force_release_item.rpc_id(sender, item_path) return + log_line("request_item_authority: granting %s to peer %d" % [str(item.name) if item else str(item_path), sender]) # Assign authority + held state first (disables the item on the server so its # snap zone won't re-grab it), then release it from any station. _set_item_authority.rpc(item_path, sender) @@ -191,6 +197,7 @@ func request_item_authority(item_path: NodePath) -> void: func release_item_authority(item_path: NodePath, lin: Vector3, ang: Vector3) -> void: if not is_server(): return + log_line("release_item_authority: %s released by peer %d" % [str(item_path), multiplayer.get_remote_sender_id()]) _set_item_authority.rpc(item_path, 1) var item := get_node_or_null(item_path) if item is RigidBody3D: @@ -226,6 +233,7 @@ func _try_snap_into_station(item: Node) -> void: if is_instance_valid(zone.picked_up_object): continue if zone.global_position.distance_to(item.global_position) <= zone.grab_distance: + log_line("snapped %s into %s" % [item.name, zone.get_parent().name]) zone.pick_up_object(item) return @@ -237,6 +245,7 @@ func _set_item_authority(item_path: NodePath, peer: int) -> void: var item := get_node_or_null(item_path) if not item: return + log_line("_set_item_authority: %s -> peer %d" % [item.name, peer]) item.set_multiplayer_authority(peer) # recursive: item + synchronizer + NetPickable var np := item.get_node_or_null("NetPickable") if np: @@ -248,6 +257,7 @@ func _set_item_authority(item_path: NodePath, peer: int) -> void: ## item was already legitimately held by someone else). The client drops it. @rpc("authority", "reliable") func force_release_item(item_path: NodePath) -> void: + log_line("force_release_item: dropping %s (server rejected our grab)" % str(item_path)) var item := get_node_or_null(item_path) if item and item.has_method("drop"): item.drop() @@ -283,6 +293,7 @@ func submit_work(station_path: NodePath, amount: float) -> void: return var station := get_node_or_null(station_path) if station and station.has_method("add_work"): + log_line("submit_work: peer %d contributed %.2f to %s" % [multiplayer.get_remote_sender_id(), amount, station.name]) station.add_work(multiplayer.get_remote_sender_id(), amount) diff --git a/Scenes/multiPlayer.tscn b/Scenes/multiPlayer.tscn index be95e81..5e4e715 100644 --- a/Scenes/multiPlayer.tscn +++ b/Scenes/multiPlayer.tscn @@ -43,13 +43,13 @@ mesh = SubResource("BoxMesh_24d3s") [node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1177077250] environment = SubResource("Environment_bvwq1") -[node name="WorldContent" type="Node3D" parent="."] +[node name="WorldContent" type="Node3D" parent="." unique_id=291550153] -[node name="Players" type="Node3D" parent="."] +[node name="Players" type="Node3D" parent="." unique_id=1595463693] -[node name="ItemsSpawner" type="MultiplayerSpawner" parent="."] +[node name="ItemsSpawner" type="MultiplayerSpawner" parent="." unique_id=627119248] spawn_path = NodePath("../WorldContent") -[node name="PlayersSpawner" type="MultiplayerSpawner" parent="."] +[node name="PlayersSpawner" type="MultiplayerSpawner" parent="." unique_id=106645565] _spawnable_scenes = PackedStringArray("res://Player/net_player.tscn") spawn_path = NodePath("../Players") diff --git a/Scenes/multiplayer_world.gd b/Scenes/multiplayer_world.gd index 2917a22..dec672b 100644 --- a/Scenes/multiplayer_world.gd +++ b/Scenes/multiplayer_world.gd @@ -31,11 +31,27 @@ func _ready() -> void: NetworkManager.session_ended.connect(_on_session_ended) NetworkManager.connection_failed.connect(_on_connection_failed) - # Offline (single player / dev run): owns_world() is already true, so - # populate immediately. Online host case is handled by session_started. + # world_ready() is what actually calls host()/join() (or the cmdline + # equivalent). Populating before this point is wrong for EVERY case, not + # just offline: is_online() is still false until host()/join() runs, so + # owns_world() would read true for a joining client too, and it would + # build its own local copy instead of receiving the server's via the + # spawner. host() emits session_started synchronously, which populates + # via _on_session_started below; the explicit call after world_ready() + # only matters for the case where neither host() nor join() ran (no + # pending session, no cmdline args) — running this scene directly offline. + NetworkManager.world_ready() _populate_world_if_owner() - NetworkManager.world_ready() + get_tree().create_timer(3.0).timeout.connect(_log_world_state) + + +# Temporary-ish sanity check: confirms WorldContent actually ended up +# populated on this peer (whether by spawning it or by receiving it via +# replication), so a silent replication failure shows up in the net log +# instead of just an empty-looking world. +func _log_world_state() -> void: + NetworkManager.log_line("World state: WorldContent=%d children, Players=%d children" % [$WorldContent.get_child_count(), $Players.get_child_count()]) func _exit_tree() -> void: @@ -53,10 +69,14 @@ func _populate_world_if_owner() -> void: return _populated = true GameManager.meals_in_play = ["hamburger"] - for d in WorldLayout.get_stations(): + var stations := WorldLayout.get_stations() + var items := WorldLayout.get_items() + NetworkManager.log_line("Populating world: %d stations, %d items" % [stations.size(), items.size()]) + for d in stations: NetworkManager.spawn_item(d["scene"], d["xform"], d["name"], d["props"]) - for d in WorldLayout.get_items(): + for d in items: NetworkManager.spawn_item(d["scene"], d["xform"], d["name"], d["props"]) + NetworkManager.log_line("World populated") ## Only the server (or the single offline machine) materialises player @@ -68,6 +88,7 @@ func _on_player_joined(peer_id: int) -> void: var p := PLAYER_SCENE.instantiate() p.name = str(peer_id) $Players.add_child(p, true) + NetworkManager.log_line("Spawned avatar for peer %d" % peer_id) func _on_player_left(peer_id: int) -> void: @@ -76,11 +97,14 @@ func _on_player_left(peer_id: int) -> void: var p := $Players.get_node_or_null(str(peer_id)) if p: p.queue_free() + NetworkManager.log_line("Despawned avatar for peer %d" % peer_id) func _on_session_ended() -> void: + NetworkManager.log_line("Session ended, returning to main menu") get_tree().change_scene_to_file("res://Scenes/mainMenu.tscn") func _on_connection_failed() -> void: + NetworkManager.log_line("Connection failed, returning to main menu") get_tree().change_scene_to_file("res://Scenes/mainMenu.tscn")