Park/Paths/Queue.cs
using HC3.Terrain;
using System;
namespace HC3;
/// <summary>
/// An inherited version of <see cref="Path"/>, which behaves differently. Queues can snake around, next to eachother, and connect to ride entrances/exits.
/// </summary>
public class Queue : Path
{
// This queue piece can connect to 2 others at max
// (feels weird to just rawdog Component like this but there's no need for anything else?)
public Component[] Connections = new Component[2];
[Property, ReadOnly]
public QueueGuestSystem QueueSystem
{
get => _queueSystem;
set
{
_queueSystem = value;
UpdateTile();
}
}
private QueueGuestSystem _queueSystem;
public BasicRide Ride => QueueSystem?.Ride;
public override int Hash => HashCode.Combine( base.Hash, Ride );
protected IEnumerable<Component> GetDesirableConnections( PathMask mask = PathMask.All )
{
foreach ( TileEdge edge in mask.GetEdges() )
{
var pos = TilePosition + ((Vector3Int)edge.GetDirection()).WithZ( GetHeightOffset( edge ) );
var cell = GridManager.Instance?.GetCell( new Vector2Int( pos.x, pos.y ) );
if ( cell is null )
continue;
var entrance = cell.GetComponents<RideEntranceExit>( pos.z ).FirstOrDefault( x => !x.IsExit && x.Ride == Ride );
if ( entrance is not null && entrance.CanConnectTile( TilePosition, edge.GetOpposite() ) )
{
yield return entrance;
}
var neighbour = cell.GetComponents<Path>( pos.z ).FirstOrDefault( x => !x.GhostPreview );
if ( neighbour is null )
continue;
yield return neighbour;
}
}
public void OnPostLoad( SaveData data )
{
for ( int i = 0; i < Connections.Length; i++ )
{
Connections[i] = null;
}
foreach ( var connection in GetDesirableConnections( data.Mask ) )
{
Connect( connection );
}
}
/// <summary>
/// Called when enabled or neighbours changed, before the tile is updated
/// </summary>
protected override void UpdateConnections()
{
if ( GhostPreview )
return;
if ( QueueSystem.IsValid() )
{
QueueSystem.BuildQueuePath();
}
for ( int i = 0; i < Connections.Length; i++ )
{
var connection = Connections[i];
if ( !connection.IsValid() )
{
Connections[i] = null;
continue;
}
if ( connection is Queue queue && !queue.IsConnectedTo( this ) )
{
Connections[i] = null;
}
}
bool dirty = false;
// Find the most desirable edges and try to connect them
foreach ( var connection in GetDesirableConnections() )
{
if ( Connect( connection ) )
dirty = true;
}
if ( dirty )
{
GetComponent<GridObject>().ForceUpdateNeighbors();
}
}
public bool CanConnectTo( Component target )
{
if ( !target.IsValid() )
return false;
if ( IsConnectedTo( target ) )
return false;
// must be part of this queueline (or none)
if ( target is Queue queue )
{
if ( QueueSystem.IsValid() && queue.QueueSystem.IsValid() && queue.QueueSystem != QueueSystem )
{
return false;
}
}
int priority = GetPriority( target );
if ( priority <= 0 )
return false;
return Connections.Any( c => c == null || GetPriority( c ) < priority );
}
public bool Connect( Component target )
{
if ( !CanConnectTo( target ) )
return false;
int priority = GetPriority( target );
for ( int i = 0; i < Connections.Length; i++ )
{
var existing = Connections[i];
if ( existing == null || GetPriority( existing ) < priority )
{
Connections[i] = target;
if ( target is Queue queue )
{
if ( QueueSystem.IsValid() )
{
// connected - make sure it's considered on the system even if it's not really yet
// (otherwise others might try and steal it for THEIRS and cause hell)
queue.QueueSystem = QueueSystem;
}
queue.ConnectBack( this );
}
return true;
}
}
return false;
}
public bool ConnectBack( Path path )
{
if ( !path.IsValid() || Connections.Contains( path ) )
return false;
int priority = GetPriority( path );
for ( int i = 0; i < Connections.Length; i++ )
{
var existing = Connections[i];
if ( existing == null || GetPriority( existing ) < priority )
{
Connections[i] = path;
return true;
}
}
return false;
}
/// <summary>
/// For queues we already know our connections, so just check against those
/// </summary>
public override bool CanConnectTile( Vector3Int gridPos, TileEdge edge )
{
if ( GhostPreview )
return false;
// Only connects to connections
var pos = gridPos + edge.GetDirection();
var cell = GridManager.Instance?.GetCell( new Vector2Int( pos.x, pos.y ) );
if ( cell is null )
return false;
int height = GetHeightOffset( edge );
int heightSpan = height > 0 ? 0 : 1; // may be a connection from below
return cell.GetComponents<IPathConnector>( gridPos.z, heightSpan ).Any( x => IsConnectedTo( x as Component ) );
}
public bool IsConnectedTo( Component c ) => Connections.Contains( c );
int GetPriority( Component connection )
{
if ( !connection.IsValid() ) return -1;
if ( connection is RideEntranceExit ) return 99;
if ( connection is not Path ) return -1;
int priority = 1;
// Prioritize other queues over normal paths
if ( connection is Queue queue )
{
if ( QueueSystem.IsValid() && queue.QueueSystem.IsValid() && queue.QueueSystem != QueueSystem )
return -1;
int connectedCount = 0;
if ( queue.Connections[0] is { } c1 && c1 != this ) connectedCount++;
if ( queue.Connections[1] is { } c2 && c2 != this ) connectedCount++;
if ( connectedCount >= 2 )
return -1; // fully connected queues aren't valid
priority += 10;
// Must be an end point
if ( queue.QueueSystem != QueueSystem )
priority -= 5;
if ( connectedCount <= 1 )
priority += 3;
}
return priority;
}
protected override void OnTileUpdated()
{
if ( Ride.IsValid() )
{
foreach ( var entry in Ride.QueueColors )
{
UpdateTint( entry.Key, entry.Value );
}
}
}
public void UpdateTint( TintChannel channel, Color color )
{
var tintComp = GameObject.GetComponentsInChildren<TintMaskComponent>().FirstOrDefault();
if ( tintComp is not null )
{
if ( channel == TintChannel.Primary )
tintComp.PrimaryColor = color;
else if ( channel == TintChannel.Secondary )
tintComp.SecondaryColor = color;
else if ( channel == TintChannel.Accent )
tintComp.AccentColor = color;
else if ( channel == TintChannel.Detail )
tintComp.DetailColor = color;
}
}
}