Park/Buildings/Building.Slots.cs
using System;

namespace HC3;

public partial class Building : ISceneMetadata
{
	private SlotMarker[] _cachedSlots;

	IReadOnlyList<SlotMarker> AllSlots
	{
		get
		{
			_cachedSlots ??= GameObject.GetComponentsInChildren<SlotMarker>().ToArray();
			return _cachedSlots;
		}
	}

	/// <summary>
	/// Invalidate cached slot list (call when slots change)
	/// </summary>
	public void InvalidateSlotCache() => _cachedSlots = null;

	public virtual void OnLoadedIn( Guest guest ) { }
	public virtual void OnUnloaded( Guest guest ) { }

	/// <summary>
	/// Load in a Guest to the building
	/// </summary>
	public bool LoadIn( Guest guest )
	{
		SlotMarker slot = GetFreeSlot();
		if ( !slot.IsValid() ) return false;

		slot.LoadIn( guest );
		OnLoadedIn( guest );

		guest.Building = this;

		IBuildingEvents.PostToGameObject( GameObject, x => x.OnGuestEnter( guest ) );

		return true;
	}

	/// <summary>
	/// Unload a GameObject from this building
	/// </summary>
	public bool Unload( Guest guest )
	{
		SlotMarker found = null;
		foreach ( var s in AllSlots )
		{
			if ( s.Contents == guest )
			{
				found = s;
				break;
			}
		}
		if ( !found.IsValid() ) return false;

		found.LoadIn( null );
		OnUnloaded( guest );

		guest.Building = null;

		IBuildingEvents.PostToGameObject( GameObject, x => x.OnGuestLeave( guest ) );

		return true;
	}

	/// <summary>
	/// Unload everyone
	/// </summary>
	public void UnloadAll( bool useCompleted )
	{
		foreach ( var slot in AllSlots )
		{
			if ( !slot.Contents.IsValid() )
				continue;

			if ( useCompleted )
			{
				OnUse( slot.Contents );
			}

			Unload( slot.Contents );
		}
	}

	/// <summary>
	/// Do we have any slots for this building
	/// </summary>
	public bool HasSlots() => AllSlots.Count != 0;

	/// <summary>
	/// Do we have any free slots for this building
	/// </summary>
	public bool HasFreeSlot()
	{
		if ( AllSlots.Count == 0 ) return true;

		foreach ( var s in GetLoadableSlots() )
		{
			if ( s.IsFree() ) return true;
		}
		return false;
	}

	/// <summary>
	/// How many slots are occupied in this whole building
	/// </summary>
	public int GetTotalOccupiedSlots()
	{
		int count = 0;
		foreach ( var s in AllSlots )
		{
			if ( !s.IsFree() ) count++;
		}
		return count;
	}

	/// <summary>
	/// How many slots are occupied of the currently loadable
	/// </summary>
	public int GetOccupiedSlots()
	{
		int count = 0;
		foreach ( var s in GetLoadableSlots() )
		{
			if ( !s.IsFree() ) count++;
		}
		return count;
	}

	/// <summary>
	/// Get all guests in this building (NOT the queue)
	/// </summary>
	public IEnumerable<Guest> GetGuests()
	{
		foreach ( var s in GetLoadableSlots() )
		{
			if ( !s.IsFree() )
				yield return s.Contents;
		}
	}

	/// <summary>
	/// Fetches a free slot
	/// </summary>
	private SlotMarker GetFreeSlot()
	{
		// Pick a random free slot without LINQ allocation
		SlotMarker pick = null;
		int count = 0;
		foreach ( var s in GetLoadableSlots() )
		{
			if ( !s.IsFree() ) continue;
			count++;
			if ( Random.Shared.Next( count ) == 0 )
				pick = s;
		}
		return pick;
	}

	/// <summary>
	/// Current slots (incl occupied) for loading
	/// </summary>
	protected virtual IEnumerable<SlotMarker> GetLoadableSlots()
	{
		return AllSlots;
	}
}