Skip to content

PlayerPicker port

TL;DR: The original picks up another player by checking hand distance to their bones and teleporting that player’s origin each frame. CVR WASM never lets one client move another client’s player — only the local player can set its own position. The port inverts the mechanic: the picker broadcasts a “you’re being held” message, and the target’s own script moves them toward the picker on its own client. This respects the security boundary and still visually presents a pickup.

FeaturePorts?
Find nearest player within radiusYes — Player.GetRemotePlayers() + GetViewPoint().GetPointPosition()
Move the picked-up playerYes — via message, target self-moves via LocalPlayer.SetPosition()
WhitelistYes — comma-separated user-id list
Hand-to-bone distance checkNo — remote bones not exposed; approximates with view point
Legacy fake ground supportNo — not necessary in CVR
Logging / event spamNo — not ported

Two network messages:

TagPayloadDirectionEffect on receiver
MSG_HOLDtarget network idpicker → targettarget sets beingHeld = true, enters self-move
MSG_RELEASEtarget network idpicker → targettarget sets beingHeld = false, stops self-move

LateUpdate on the target moves LocalPlayer to the picker’s transform each frame. Only the local client does this, so there’s no cross-client authority.

UdonSharp’s VRCPlayerApi.SetPosition() works on any remote player because VRChat grants that cross-client capability. CVR does not — LocalPlayer.SetPosition() only moves the local client. Any pattern that relies on remote-move-another-player needs this inversion. The Networking page covers the message model in depth.

  1. Add PlayerPicker to a grab trigger volume or a handheld prop root.
  2. Hook a CCK button (or trigger volume) to OnGrabClicked() and OnReleaseClicked().
  3. Add the same PlayerPicker behaviour to any world / player context where you want pickup to be receivable. The HandleMessage path is what actually moves the target, so the target client needs an instance subscribed to the network channel.
  4. whitelist: leave empty to allow everyone, or fill with comma-separated user ids. The whitelist path calls GetUserId() which requires the AccessUserIdentity world permission — request it once at startup (see World Permissions) or leave the whitelist empty to skip the check.