Files
VRyHungry1/Net/net_pickable.gd
T
algodoogle 4ca0a05d1b Fix world population race and add multiplayer diagnostics
The world was populated before NetworkManager.world_ready() actually
called host()/join(), so owns_world() read true for every peer
(including a joining client) and each one built its own local,
unreplicated copy instead of the client receiving the server's spawn
through the MultiplayerSpawner. Population now happens after the
session is actually established.

Also adds net-log coverage for spawn/despawn, station gating, item
authority handoff, station snapping, avatar spawn/despawn, and scene
transitions, so multiplayer behavior is visible in
%TEMP%\vryhungry_net_<pid>.log instead of failing silently.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-25 19:42:49 +01:00

79 lines
3.2 KiB
GDScript

extends MultiplayerSynchronizer
## Networked sync component for a pickable item. Added as a child literally
## named "NetPickable" (network_manager.gd's authority RPCs already expect
## this) of every net-synced pickable, replicating its transform and held
## state. Only the current multiplayer authority (the server while loose, or
## whichever peer is holding it) actually simulates physics for the item;
## every other peer freezes their local copy and just follows the synced
## transform.
## 0 = loose/server-simulated; otherwise the peer id currently holding it.
## Replicated at spawn and on change so a late joiner sees the current holder.
var net_held_by: int = 0: set = _set_net_held_by
var _pickable: XRToolsPickable
func _ready() -> void:
_pickable = get_parent() as XRToolsPickable
if not _pickable:
push_error("NetPickable must be a child of an XRToolsPickable")
return
_pickable.picked_up.connect(_on_picked_up)
_pickable.dropped.connect(_on_dropped)
# Deferred: the pickable root captures its own original_collision_mask/
# original_collision_layer via @onready, which runs AFTER this child's
# _ready() but BEFORE the root's _ready() body. Calling apply_held_state
# synchronously here would freeze/mask the item before that capture runs,
# permanently corrupting the "restore on drop" values.
apply_held_state.call_deferred()
func _set_net_held_by(value: int) -> void:
net_held_by = value
apply_held_state()
## Puts the item in the right physics state for whether this peer currently
## owns it. Called locally after net_held_by changes, and directly by
## NetworkManager._set_item_authority right after an authority handoff.
func apply_held_state() -> void:
if not _pickable:
return
if not NetworkManager.is_online() or is_multiplayer_authority():
# We own this item's simulation (offline, loose+server, or currently
# holding it): leave physics alone, XRToolsPickable manages the rest.
return
# Someone else owns it: stop simulating locally, just follow the sync.
if _pickable.is_picked_up():
_pickable.drop()
_pickable.freeze = true
_pickable.freeze_mode = RigidBody3D.FREEZE_MODE_KINEMATIC
_pickable.collision_mask = 0
_pickable.enabled = (net_held_by == 0)
## Local hand grab (not a station snap zone, which is server-only): request
## authority immediately so the throw/drop can be reconciled, but let the grab
## happen instantly here rather than waiting on the round trip.
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())
func _on_dropped(_p) -> void:
# Only forward if we're actually still the authority — a drop caused by
# 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
)