Player partial class methods for lockpicking. OpenLockpicker triggers a client-side UI via an owner RPC, FinishLockpick is a host RPC that re-traces what the player is looking at, unlocks a found usable component, plays a sound, tracks a stat, and optionally invokes the usable's Use method.
using Sandbox;
using BrickJam.UI;
namespace BrickJam;
public sealed partial class Player
{
/// <summary>Host → owner: open the lockpicking minigame on this player's client.</summary>
[Rpc.Owner]
public void OpenLockpicker()
{
LockpickerBus.Open();
}
/// <summary>
/// Owner → host: the minigame succeeded; unlock whatever the player is looking at. Re-traces on
/// the host (the player is frozen while picking) so no non-networked component ref crosses the wire.
/// </summary>
[Rpc.Host]
public void FinishLockpick()
{
var trace = Scene.Trace
.Ray( EyePosition, EyePosition + InputRotation.Forward * UseRange )
.WithTag( "usable" )
.IgnoreGameObjectHierarchy( GameObject )
.Radius( 2f )
.Run();
var usable = trace.GameObject?.Components.Get<LegacyUsableComponent>();
if ( !usable.IsValid() )
return;
usable.Locked = false;
if ( usable.Components.TryGet<LockedComponent>( out var locked ) )
locked.Unlock();
SoundExtensions.BroadcastPlay( "sounds/lockpicking/lockfall.sound", usable.WorldPosition );
TrackStat( GameStats.LocksPicked, 1 ); // [Rpc.Host] here -> routes to this player's own client
// Picking the lock completes the interaction you were doing - open the door (which for the shop
// door advances the level / teleports everyone) or chest, no second E press needed.
usable.User = this;
if ( usable.CanUse && usable.CheckUpgrades( this ) )
usable.Use( this );
}
}