Rides/TrackRides/TrackElement.cs
using System.Text.Json.Serialization;
using System;
namespace HC3.Rides;
#nullable enable
/// <summary>
/// Size category to show in the track builder UI when selecting elements.
/// </summary>
public enum TrackElementSize : byte
{
None = 0,
Tiny = 1,
Small = 2,
Medium = 3,
Large = 4,
Giant = 5
}
/// <summary>
/// How a particular <see cref="TrackElement"/> is transformed compared to its original <see cref="TrackElementDefinition"/>.
/// </summary>
[Flags]
public enum TrackElementFlags : byte
{
FlipX = 1,
FlipY = 2,
FlipZ = 4
}
/// <summary>
/// Describes a special function for a <see cref="TrackElement"/>.
/// </summary>
public enum TrackFeature
{
/// <summary>
/// Don't show this element in the track builder.
/// </summary>
Disabled = -1,
/// <summary>
/// Normal piece of track.
/// </summary>
None = 0,
/// <summary>
/// Part of a station for trains to load / unload guests.
/// </summary>
Station,
/// <summary>
/// Pulls trains up inclines.
/// </summary>
ChainLift,
/// <summary>
/// TODO
/// </summary>
Break,
/// <summary>
/// TODO
/// </summary>
BlockBreak,
/// <summary>
/// TODO
/// </summary>
Booster,
/// <summary>
/// For elements like inversions and s-bends.
/// </summary>
Special
}
/// <summary>
/// Used for filtering the direction of a track element.
/// </summary>
public enum TurnDirection
{
/// <summary>
/// Track performs a left U-turn.
/// </summary>
[Icon( "u_turn_left" )]
UTurnLeft = -2,
/// <summary>
/// Track performs a left turn.
/// </summary>
[Icon( "turn_left" )]
Left = -1,
/// <summary>
/// Track doesn't turn left or right.
/// </summary>
[Icon( "straight" )]
Straight = 0,
/// <summary>
/// Track performs a right turn.
/// </summary>
[Icon( "turn_right" )]
Right = 1,
/// <summary>
/// Track performs a right U-turn.
/// </summary>
[Icon( "u_turn_right" )]
UTurnRight = 2,
}
/// <summary>
/// The smallest piece of track that you can build with.
/// A <see cref="ITrackSection"/> is a list of these connected end to end.
/// </summary>
/// <param name="Definition">Describes the general shape of this element.</param>
/// <param name="Flags">Describes how this element is modified compared to <see cref="Definition"/>.</param>
/// <param name="StartBanking">Banking at the very start of this element.</param>
/// <param name="EndBanking">Banking at the very end of this element.</param>
public readonly record struct TrackElement(
TrackElementDefinition Definition,
TrackElementFlags Flags,
TrackBanking StartBanking,
TrackBanking EndBanking ) : IComparable<TrackElement>
{
public readonly record struct RpcSafe(
TrackElementDefinition Definition,
TrackElementFlags Flags,
TrackBanking StartBanking,
TrackBanking EndBanking )
{
public static implicit operator TrackElement( RpcSafe value )
{
return new TrackElement( value.Definition, value.Flags, value.StartBanking, value.EndBanking );
}
public static implicit operator RpcSafe( TrackElement value )
{
return new RpcSafe( value.Definition, value.Flags, value.StartBanking, value.EndBanking );
}
}
/// <summary>
/// Was this element flipped on the X axis relative to its <see cref="Definition"/>?
/// </summary>
[JsonIgnore]
public bool FlipX => (Flags & TrackElementFlags.FlipX) != 0;
/// <summary>
/// Was this element flipped on the Y axis relative to its <see cref="Definition"/>?
/// </summary>
[JsonIgnore]
public bool FlipY => (Flags & TrackElementFlags.FlipY) != 0;
/// <summary>
/// Was this element flipped on the Z axis relative to its <see cref="Definition"/>?
/// </summary>
[JsonIgnore]
public bool FlipZ => (Flags & TrackElementFlags.FlipZ) != 0;
/// <summary>
/// Gets the starting incline of this element. Elements can only connect if the
/// previous element's <see cref="EndIncline"/> matches the next element's <see cref="StartIncline"/>.
/// </summary>
[JsonIgnore]
public TrackIncline StartIncline
{
get
{
var incline = FlipX ? Definition.EndIncline.Inverse() : Definition.StartIncline;
return FlipZ ? incline.Inverse() : incline;
}
}
/// <summary>
/// Gets the final incline of this element. Elements can only connect if the
/// previous element's <see cref="EndIncline"/> matches the next element's <see cref="StartIncline"/>.
/// </summary>
[JsonIgnore]
public TrackIncline EndIncline
{
get
{
var incline = FlipX ? Definition.StartIncline.Inverse() : Definition.EndIncline;
return FlipZ ? incline.Inverse() : incline;
}
}
[JsonIgnore]
public bool StartInverted => FlipX && Definition.EndInverted;
[JsonIgnore]
public bool EndInverted => !FlipX && Definition.EndInverted;
/// <summary>
/// For filtering track elements by incline in the track builder.
/// </summary>
public TrackIncline GetInclineCategory( TrackBuildKind buildKind ) =>
buildKind == TrackBuildKind.Append
? Definition.CustomSpline ? FlipX != FlipZ ? Definition.CustomIncline.Inverse() : Definition.CustomIncline : EndIncline
: Definition.CustomSpline ? FlipX != FlipZ ? Definition.CustomIncline : Definition.CustomIncline.Inverse() : StartIncline.Inverse();
/// <summary>
/// Gets the final heading of this element, relative to <see cref="TrackHeading.Forward"/>.
/// </summary>
[JsonIgnore]
public TrackHeading EndHeading => FlipY
? Definition.EndHeading.Mirror()
: Definition.EndHeading;
/// <summary>
/// Gets how much this element moves the track spline, in track units.
/// Track grid units are at twice the resolution of terrain grid units.
/// </summary>
[JsonIgnore]
public Vector3Int EndOffset
{
get
{
var offset = Definition.EndOffset;
if ( FlipX )
{
offset.z = -offset.z;
}
if ( FlipY )
{
offset.y = -offset.y;
}
if ( FlipZ )
{
offset.z = -offset.z;
}
return offset;
}
}
/// <summary>
/// Descriptive size to help categorize track elements in the <see cref="TrackBuilder"/>.
/// </summary>
[JsonIgnore]
public TrackElementSize Size => Definition?.Size ?? TrackElementSize.None;
/// <summary>
/// Descriptive turn direction to help filter track elements in the <see cref="TrackBuilder"/>.
/// </summary>
[JsonIgnore]
public TurnDirection Direction => GetTurnDirection( EndHeading, EndOffset, StartInverted, EndInverted, FlipY );
/// <summary>
/// Does this element have a special function, like a lift hill or station platform?
/// </summary>
[JsonIgnore]
public TrackFeature Feature => Definition?.Feature ?? TrackFeature.Disabled;
[JsonIgnore]
public float StartTangentScale => Definition?.CustomSpline is true
? FlipX ? Definition.EndTangentScale : Definition.StartTangentScale
: 1f;
[JsonIgnore]
public float EndTangentScale => Definition?.CustomSpline is true
? FlipX ? Definition.StartTangentScale : Definition.EndTangentScale
: 1f;
/// <summary>
/// Player-facing name of this element.
/// </summary>
[JsonIgnore]
public string Title =>
$"{(Definition.Size != TrackElementSize.None ? $"{Definition.Size} " : "")}" +
$"{(Direction != TurnDirection.Straight ? $"{Direction} " : "")}" +
$"{Definition.Title}";
[JsonIgnore]
public float Length => Definition.Length;
[JsonIgnore]
public float Length2D => Definition.Length2D;
[JsonIgnore]
public IEnumerable<float> TileBoundaries
{
get
{
var length = Length;
return FlipX ? Definition.TileBoundaries.Reverse().Select( x => length - x ) : Definition.TileBoundaries;
}
}
public override string ToString()
{
return Flags == 0 ? "Default" : $"Flip{(FlipX ? "X" : "")}{(FlipY ? "Y" : "")}{(FlipZ ? "Z" : "")}";
}
public int CompareTo( TrackElement other )
{
return Definition.Size.CompareTo( other.Definition.Size );
}
internal static TurnDirection GetTurnDirection( TrackHeading endHeading, Vector3Int endOffset, bool startInverted, bool endInverted, bool flipY ) =>
(endHeading, endOffset.y) switch
{
(TrackHeading.ForwardLeft or TrackHeading.Left or TrackHeading.BackwardLeft, _ ) => TurnDirection.Left,
(TrackHeading.ForwardRight or TrackHeading.Right or TrackHeading.BackwardRight, _ ) => TurnDirection.Right,
(TrackHeading.Backward, > 0 ) => TurnDirection.UTurnLeft,
(TrackHeading.Backward, < 0 ) => TurnDirection.UTurnRight,
(_, > 0 ) => TurnDirection.Left,
(_, < 0 ) => TurnDirection.Right,
_ => startInverted && !flipY || endInverted && flipY ? TurnDirection.Left
: endInverted && !flipY || startInverted && flipY ? TurnDirection.Right
: TurnDirection.Straight
};
}