RobberyZone.cs
using Sandbox;
using System.Collections.Generic;
using System.Linq;
// The area the crew has to hold while the robbery runs - the gas station
// interior. Put this on a GameObject with a trigger Collider covering it.
//
// MissionTimer only advances the robbery while somebody is inside, so
// wandering off stalls the job instead of letting you kite cops around the
// map for the whole timer. Assign it to MissionTimer.RobberyZone; leave
// that unset and the timer behaves as it did before, running regardless of
// where anyone is.
public sealed class RobberyZone : Component, Component.ITriggerListener
{
// Tracked as a set rather than a count because OnTriggerEnter can fire
// more than once for the same body (multiple colliders, re-entry), and
// a bare counter would drift out of step and never return to zero.
readonly HashSet<GameObject> _inside = new();
/// <summary>
/// Is anyone still in the fight standing in here?
/// </summary>
public bool AnyPlayerInside => LivePlayersInside().Any();
public int PlayerCountInside => LivePlayersInside().Count();
public void OnTriggerEnter( Collider other )
{
var player = other.GameObject.Root;
if ( player.Tags.Has( "player" ) )
_inside.Add( player );
}
public void OnTriggerExit( Collider other )
{
_inside.Remove( other.GameObject.Root );
}
// Filtered on read rather than on the way in, because a player's state
// changes while they're standing inside: someone who goes down in the
// store is physically still in the trigger but shouldn't be holding
// the robbery open for the rest of the crew.
IEnumerable<GameObject> LivePlayersInside()
{
return _inside.Where( go => go.IsValid()
&& !go.Tags.Has( "escaped" )
&& !go.Tags.Has( "eliminated" ) );
}
}