Partial class for BaseSandboxWeapon handling the weapon world model and dropped state. It creates/destroys a non-networked world model attached to a renderer bone, toggles physics/collider/standalone dropped GameObject when IsDropped changes, and posts create/destroy events via IEvent.
using Sandbox.Citizen;
public partial class BaseSandboxWeapon
{
public interface IEvent : ISceneEvent<IEvent>
{
public void OnCreateWorldModel() { }
public void OnDestroyWorldModel() { }
}
// WorldModelPrefab, HoldBone and HoldType are inherited from the engine BaseCombatWeapon.
[Property, Feature( "WorldModel" )] public GameObject DroppedGameObject { get; set; }
protected override void CreateWorldModel()
{
var player = GetComponentInParent<PlayerController>();
if ( player?.Renderer is null ) return;
CreateWorldModel( player.Renderer );
}
/// <summary>
/// True while this weapon is loose in the world as a pickup rather than held. Host authoritative -
/// the change callback toggles the pickup components (physics, the standalone model) on every peer.
/// </summary>
[Sync( SyncFlags.FromHost ), Change( nameof( OnIsDroppedChanged ) )]
public bool IsDropped { get; set; } = true;
private void OnIsDroppedChanged( bool oldValue, bool newValue ) => ApplyDroppedState( newValue );
private void ApplyDroppedState( bool dropped )
{
// Carried weapons follow their parent exactly; dropped ones are physics objects that want
// interpolated network transforms.
GameObject.Network.Interpolation = dropped;
var rb = GetComponent<Rigidbody>( true );
if ( rb.IsValid() ) rb.Enabled = dropped;
var col = GetComponent<ModelCollider>( true );
if ( col.IsValid() ) col.Enabled = dropped;
if ( DroppedGameObject.IsValid() ) DroppedGameObject.Enabled = dropped;
}
/// <summary>
/// Creates and attaches the world model to the given renderer's bone.
/// Use this overload when the weapon is held by something other than a player (e.g. an NPC).
/// </summary>
public void CreateWorldModel( SkinnedModelRenderer renderer )
{
if ( renderer is null ) return;
if ( Networking.IsHost )
IsDropped = false;
var worldModel = WorldModelPrefab?.Clone( new CloneConfig
{
Parent = renderer.GetBoneObject( HoldBone ) ?? GameObject,
StartEnabled = true,
Transform = global::Transform.Zero
} );
if ( worldModel.IsValid() )
{
worldModel.Flags |= GameObjectFlags.NotSaved | GameObjectFlags.NotNetworked;
WorldModel = worldModel;
IEvent.PostToGameObject( WorldModel, x => x.OnCreateWorldModel() );
}
}
protected override void DestroyWorldModel()
{
if ( WorldModel.IsValid() )
IEvent.PostToGameObject( WorldModel, x => x.OnDestroyWorldModel() );
WorldModel?.Destroy();
WorldModel = default;
if ( Networking.IsHost )
IsDropped = true;
}
}