Player/Player.Lockpick.cs

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.

NetworkingFile Access
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 );
	}
}