Game/ControlSystem/ControlSystem.cs
/// <summary>
/// Lets players drive contraptions from a seat. Every tick, this finds who's sitting in each
/// chair and calls <see cref="IPlayerControllable.OnControl"/> on the attached contraption.
/// </summary>
public sealed class ControlSystem : GameObjectSystem<ControlSystem>
{
private readonly Dictionary<BaseChair, RealTimeSince> _occupiedSince = new();
public ControlSystem( Scene scene ) : base( scene )
{
Listen( Stage.StartFixedUpdate, 10, OnTick, "ControlSystem" );
}
private void OnTick()
{
if ( Scene != Game.ActiveScene ) return;
if ( !Networking.IsHost ) return;
// Whoever sat down first claims the contraption — a second seat on the same vehicle does nothing.
var driven = new HashSet<GameObject>();
foreach ( var chair in GetSortedSeats() )
{
if ( driven.Contains( chair.GameObject.Root ) ) continue;
var linked = new LinkedGameObjectBuilder();
linked.AddConnected( chair.GameObject );
if ( linked.Objects.Any( driven.Contains ) ) continue;
driven.UnionWith( linked.Objects );
RunControl( chair, linked );
}
}
private IEnumerable<BaseChair> GetSortedSeats()
{
var chairs = Scene.GetAll<BaseChair>();
foreach ( var chair in chairs )
{
if ( !chair.IsValid() || !chair.IsOccupied )
_occupiedSince.Remove( chair );
else
_occupiedSince.TryAdd( chair, 0 );
}
return chairs
.Where( chair => chair.IsValid() && chair.IsOccupied )
.OrderBy( chair => (float)_occupiedSince.GetValueOrDefault( chair, default ) );
}
private static void RunControl( BaseChair chair, LinkedGameObjectBuilder linked )
{
var player = chair.GetOccupant()?.GetComponent<Player>();
if ( !player.IsValid() ) return;
var connection = player.Network?.Owner;
if ( connection is null ) return;
using var scope = ControlContext.Push( player, connection );
foreach ( var gameObject in linked.Objects )
{
foreach ( var component in gameObject.GetComponentsInChildren<Component>() )
{
if ( !component.IsValid() ) continue;
if ( component is not IPlayerControllable controllable ) continue;
if ( !controllable.CanControl( player ) ) continue;
controllable.OnControl();
}
}
}
}