Loot/ContainerComponent.cs

A networked container component for loot. It stores a host-authoritative NetList of InventoryItem structs, exposes helpers to add, remove, query and clear items, and projects Items into gameplay ItemEntry tuples.

Networking
using System.Linq;
using System.Collections.Generic;
using Sandbox;

namespace BrickJam;

/// <summary>
/// A networkable inventory entry. Replaces the legacy non-networkable <c>ItemEntry</c> dictionary
/// key with a flat, host-synced struct (prefab resource name + rarity + count).
/// </summary>
public struct InventoryItem
{
	public string PrefabName { get; set; }
	public LootRarity Rarity { get; set; }
	public int Count { get; set; }
}

/// <summary>
/// Player inventory. Scene-System port of the legacy <c>ContainerComponent : EntityComponent</c>.
///
/// The legacy code kept split server/client dictionaries kept in sync via <c>[ClientRpc]</c>.
/// Here the state is a single host-authoritative <see cref="NetList{T}"/> (<see cref="SyncFlags.FromHost"/>),
/// which replicates to every client automatically.
///
/// Deferred: the <c>InventoryChanged</c> event + <c>Player.StoreSave</c> hooks belong to the
/// UI/persist systems and are wired when those land.
/// </summary>
[Title( "Container" )]
[Category( "Loot" )]
public sealed partial class ContainerComponent : Component
{
	[Property] public int Limit { get; set; } = 20;

	[Sync( SyncFlags.FromHost )] public NetList<InventoryItem> Items { get; set; } = new();

	/// <summary>All entries with their counts, as gameplay-facing <see cref="ItemEntry"/> values.</summary>
	public IEnumerable<(ItemEntry entry, int count)> Entries =>
		Items.Select( i => (new ItemEntry { Prefab = LootPrefab.Get( i.PrefabName ), Rarity = i.Rarity }, i.Count) );

	public int Count => Items.Count;

	private int IndexOf( ItemEntry entry )
	{
		var name = entry.Prefab?.ResourceName;
		for ( var i = 0; i < Items.Count; i++ )
			if ( Items[i].PrefabName == name && Items[i].Rarity == entry.Rarity )
				return i;

		return -1;
	}

	public bool Add( ItemEntry entry, int amount = 1 )
	{
		if ( !Networking.IsHost )
			return false;

		var index = IndexOf( entry );
		if ( index >= 0 )
		{
			var item = Items[index];
			item.Count += amount;
			Items[index] = item;
			return true;
		}

		if ( Items.Count >= Limit )
			return false;

		Items.Add( new InventoryItem
		{
			PrefabName = entry.Prefab?.ResourceName,
			Rarity = entry.Rarity,
			Count = amount
		} );
		return true;
	}

	public bool Remove( ItemEntry entry, int amount = 1 )
	{
		if ( !Networking.IsHost )
			return false;

		var index = IndexOf( entry );
		if ( index < 0 || Items[index].Count < amount )
			return false;

		var item = Items[index];
		item.Count -= amount;

		if ( item.Count <= 0 )
			Items.RemoveAt( index );
		else
			Items[index] = item;

		return true;
	}

	public int Has( ItemEntry entry )
	{
		var index = IndexOf( entry );
		return index < 0 ? 0 : Items[index].Count;
	}

	public void Clear()
	{
		if ( Networking.IsHost )
			Items.Clear();
	}
}