Inventory/InventoryItem.cs
using Sandbox;
using Sandbox.Citizen;
using System.Linq;
public enum ItemType
{
Primary,
Secondary,
Utility
}
public sealed class InventoryItem : Component
{
/// <summary>Which inventory slot category this item can be placed in.</summary>
[Property] public ItemType Type { get; set; }
/// <summary>Icon material shown for this item in the inventory HUD.</summary>
[Property] public string ItemIconPath { get; set; } = "/materials/default.webp";
/// <summary>Citizen hold pose used while this item is in hand (desktop bodies and proxies).</summary>
[Property] public CitizenAnimationHelper.HoldTypes HoldType { get; private set; }
/// <summary>Which hand the citizen holds this item in.</summary>
[Property] public CitizenAnimationHelper.Hand Handedness { get; private set; }
[Property] public Vector3 RightHandPosition { get; private set; }
[Sync]
public GameObject SlotObject { get; private set; }
[Sync, Change( nameof( OnIsStowedChanged ) )]
public bool IsStowed { get; set; }
[Sync, Change( nameof( OnIsPhysicsEnabledChanged ) )]
public bool IsPhysicsEnabled { get; set; } = true;
public InventorySlot Slot => SlotObject?.Components.Get<InventorySlot>();
/// <summary>True while any grip on this item is still held by a hand.</summary>
public bool IsHeld => GameObject.Components.GetAll<GrabPoint>( FindMode.InDescendants ).Any( g => g.IsGrabbed );
protected override void OnStart()
{
OnIsPhysicsEnabledChanged( IsPhysicsEnabled, IsPhysicsEnabled );
// An item can spawn in already stowed (initial loadout, or a proxy of someone
// else's carried item), so apply that state once instead of waiting for a change.
if ( IsStowed )
OnIsStowedChanged( false, true );
}
public void AssignSlot( GameObject slotObject )
{
SlotObject = slotObject;
}
public void ClearSlot()
{
var slot = Slot;
if ( slot is not null && slot.StoredItemObject == GameObject )
slot.ClearStoredItem();
SlotObject = null;
IsStowed = false;
}
/// <summary>
/// Detaches this item from the player and restores its world model. Physics is only
/// re-enabled once nothing holds it; <see cref="GrabPoint.Drop"/> does that for the
/// last hand to let go, so a two-handed grip keeps driving the item.
/// </summary>
public void DropToWorld()
{
ClearSlot();
GameObject.SetParent( null, true );
SetWorldModelVisible( true );
if ( !IsHeld )
IsPhysicsEnabled = true;
}
/// <summary>
/// Shows or hides the item's world model for the local client only; rendering isn't
/// networked, so each machine decides what its own player gets to see.
/// </summary>
public void SetWorldModelVisible( bool visible )
{
var modelRenderer = GetComponent<ModelRenderer>();
if ( modelRenderer?.SceneObject is not null )
modelRenderer.SceneObject.RenderingEnabled = visible;
}
private void OnIsStowedChanged( bool wasStowed, bool isStowed )
{
if ( isStowed )
{
// A stowed item is only drawn for the VR player carrying it: desktop owners see a
// viewmodel instead, and nobody else should see items pinned to another body.
SetWorldModelVisible( !IsProxy && Game.IsRunningInVR );
return;
}
// Out of the slot: other players see it in the carrier's hands again, while a local
// desktop player stays on their viewmodel until the item is dropped into the world.
SetWorldModelVisible( IsProxy || Game.IsRunningInVR );
}
public void Stow()
{
IsStowed = true;
}
public void Unstow()
{
IsStowed = false;
}
private void OnIsPhysicsEnabledChanged( bool oldValue, bool newValue )
{
var modelCollider = GetComponent<ModelCollider>( includeDisabled: true );
modelCollider?.Enabled = newValue;
var rigidBody = GetComponent<Rigidbody>( includeDisabled: true );
rigidBody?.Enabled = newValue;
}
}