InventorySlot.cs
using Sandbox;
using System;

public class InventorySlot
{
    /// <summary>The item in this slot, or null if empty.</summary>
    public InventoryItem Item { get; private set; }

    /// <summary>How many of the item are in this slot.</summary>
    public int Quantity { get; private set; }

    /// <summary>Current durability. -1 means durability is not tracked for this slot.</summary>
    public float Durability { get; set; } = -1f;

    /// <summary>True if the slot has no item or zero quantity.</summary>
    public bool IsEmpty => Item == null || Quantity <= 0;

    /// <summary>
    /// Add items to this slot. Returns leftover amount that didn't fit.
    /// If the slot is empty it initialises with the given item.
    /// </summary>
    public int Add( InventoryItem item, int amount = 1 )
    {
        if ( IsEmpty )
        {
            Item = item;
            Quantity = 0;
            Durability = item.MaxDurability > 0 ? item.MaxDurability : -1f;
        }

        if ( Item != item ) return amount;

        int canFit = Item.MaxStack - Quantity;
        int toAdd = Math.Min( canFit, amount );
        Quantity += toAdd;
        return amount - toAdd;
    }

    /// <summary>Remove the given amount from this slot. Clears the slot if quantity reaches zero.</summary>
    public void Remove( int amount = 1 )
    {
        Quantity = Math.Max( 0, Quantity - amount );
        if ( Quantity == 0 )
        {
            Item = null;
            Durability = -1f;
        }
    }

    /// <summary>Clears the slot, removing the item and resetting durability.</summary>
    public void Clear()
    {
        Item = null;
        Quantity = 0;
        Durability = -1f;
    }

    /// <summary>
    /// Set the slot to a specific item and quantity.
    /// Durability defaults to the item's MaxDurability when not provided.
    /// </summary>
    public void Set( InventoryItem item, int quantity, float durability = -1f )
    {
        Item = item;
        Quantity = quantity;
        Durability = durability >= 0 ? durability
            : item?.MaxDurability > 0 ? item.MaxDurability
            : -1f;
    }

    /// <summary>
    /// Serialize to a string for network sync.
    /// Format: "resourcepath:quantity[:durability]" or "" if empty.
    /// </summary>
    public string Serialize()
    {
        if ( IsEmpty ) return "";
        if ( Durability >= 0 )
            return $"{Item.ResourcePath}:{Quantity}:{Durability:F0}";
        return $"{Item.ResourcePath}:{Quantity}";
    }

    /// <summary>Restore slot state from a serialized string.</summary>
    public void Deserialize( string data )
    {
        if ( string.IsNullOrEmpty( data ) )
        {
            Clear();
            return;
        }

        var parts = data.Split( ':' );
        if ( parts.Length < 2 ) { Clear(); return; }

        var item = ResourceLibrary.Get<InventoryItem>( parts[0] );
        if ( item == null ) { Clear(); return; }

        if ( !int.TryParse( parts[1], out int qty ) ) { Clear(); return; }
        float dur = -1f;
        if ( parts.Length >= 3 )
            float.TryParse( parts[2], out dur );

        Set( item, qty, dur );
    }
}