Map/LockedComponent.cs

A scene component that adds an optional physical padlock to a GameObject and tracks a replicated Locked flag. Initialize spawns a lock model, model collider and renderer on the host and network-spawns it. Unlock clears the flag, detaches the lock, sets an 'unlocked' render parameter and enables rigidbody motion so it falls off.

NetworkingNative Interop
using Sandbox;

namespace BrickJam;

/// <summary>
/// Optional physical lock for a usable (spawns a lock model that falls off when unlocked).
/// Scene-System port of legacy <c>LockedComponent : EntityComponent</c>.
///
/// DEFERRED: the lockpicking UI (legacy <c>UI.Lockpicker</c>) and the request/grant round-trip.
/// The simple replicated <see cref="LegacyUsableComponent.Locked"/> flag is what the interaction
/// flow currently checks.
/// </summary>
[Title( "Locked" )]
[Category( "Map" )]
public sealed partial class LockedComponent : Component
{
	[Sync] public bool Locked { get; set; } = true;
	public GameObject Lock { get; set; }

	/// <summary>
	/// Spawn the physical padlock model (host-side). <paramref name="localTransform"/> positions it at
	/// the host usable's "lock" attachment; when omitted it sits at the GameObject origin.
	/// </summary>
	public LockedComponent Initialize( Transform? localTransform = null )
	{
		if ( !Networking.IsHost )
			return this;

		var go = new GameObject( true, "Lock" ) { Parent = GameObject };
		if ( localTransform.HasValue )
			go.LocalTransform = localTransform.Value;
		go.Tags.Add( "solid" );

		var model = Model.Load( "models/items/lock/lock.vmdl" );
		go.Components.Create<SkinnedModelRenderer>().Model = model;
		go.Components.Create<ModelCollider>().Model = model;

		go.NetworkSpawn();
		Lock = go;
		return this;
	}

	public void Unlock()
	{
		Locked = false;

		if ( !Lock.IsValid() )
			return;

		Lock.SetParent( null, true );

		if ( Lock.Components.TryGet<SkinnedModelRenderer>( out var renderer ) )
			renderer.Set( "unlocked", true );

		var body = Lock.Components.GetOrCreate<Rigidbody>();
		body.MotionEnabled = true;
	}
}