A unit status component that manages chained constraints for a Unit. It stores ChainedData entries, syncs them via RPCs on the Unit, updates lifetimes, enforces chain distance by applying a velocity impulse, and removes expired or invalid chains.
using System;
using Sandbox;
public class ChainedData
{
public int Id { get; set; }
public bool AnchoredToUnit { get; set; }
public Unit AnchorUnit { get; set; } // the unit that this unit is chained to, if any
public Vector2 ChainPos { get; set; }
public float ChainLength { get; set; }
public float Lifetime { get; set; }
}
public class UnitStatusChained : UnitStatus
{
private List<ChainedData> _chainDatas = new();
private int _chainIndex;
public UnitStatusChained()
{
}
public override void Init( Unit unit )
{
base.Init( unit );
Unit.SetStatusChained( true );
}
public void AddChain( Unit anchorUnit, Vector2 chainPos, float chainLength, float lifetime )
{
if ( anchorUnit.IsValid() )
{
ChainedData existingChain = null;
foreach ( var chain in _chainDatas )
{
if ( chain.AnchorUnit == anchorUnit )
{
existingChain = chain;
break;
}
}
if ( existingChain != null )
{
//existingChain.ChainPos = chainPos;
//existingChain.ChainLength = chainLength;
//existingChain.Lifetime = Lifetime;
return;
}
}
_chainDatas.Add( new ChainedData()
{
Id = _chainIndex,
AnchoredToUnit = anchorUnit.IsValid(),
AnchorUnit = anchorUnit,
ChainPos = chainPos,
ChainLength = chainLength,
Lifetime = lifetime
} );
Unit.AddChainRpc( _chainIndex, lifetime, chainLength );
Unit.SetChainAnchorPosRpc( _chainIndex, chainPos );
if( anchorUnit.IsValid() )
Unit.SetChainAnchorUnitRpc( _chainIndex, anchorUnit );
_chainIndex++;
}
public override void Update( float dt )
{
base.Update( dt );
if ( !Unit.IsValid() )
return;
//Gizmo.Draw.Color = Color.White;
//Gizmo.Draw.Text( $"{_chainDatas.Count}", new global::Transform( Unit.WorldPosition ) );
for ( int i = _chainDatas.Count - 1; i >= 0; i-- )
{
var chainData = _chainDatas[i];
chainData.Lifetime -= dt;
if ( chainData.Lifetime < 0f || ( chainData.AnchoredToUnit && !chainData.AnchorUnit.IsValid() ) )
{
_chainDatas.RemoveAt( i );
Unit.RemoveChainRpc( chainData.Id );
}
else
{
if ( chainData.AnchorUnit.IsValid() )
chainData.ChainPos = chainData.AnchorUnit.Position2D;
var lengthSqr = (Unit.Position2D - chainData.ChainPos).LengthSquared;
if ( lengthSqr > MathF.Pow( chainData.ChainLength, 2f ) )
{
Unit.Velocity += (chainData.ChainPos - Unit.Position2D).Normal * (lengthSqr - MathF.Pow( chainData.ChainLength, 2f )) * 0.1f * Time.Delta;
}
}
}
//Gizmo.Draw.Color = Color.White.WithAlpha( 0.2f );
//Gizmo.Draw.LineCircle(
// center: (Vector3)ChainPos + Vector3.Up * 1f,
// forward: Vector3.Up,
// radius: ChainLength,
// startAngle: Time.Now * -150f,
// totalDegrees: 360f,
// sections: 50
//);
//Gizmo.Draw.Color = Color.White;
//Gizmo.Draw.Line(
// (Vector3)ChainPos + Vector3.Up * 1f,
// (Vector3)Unit.Position2D + Vector3.Up * 1f
//);
}
public override void OnRemove( bool playEffects = true )
{
foreach ( var chainData in _chainDatas )
Unit.RemoveChainRpc( chainData.Id );
Unit.SetStatusChained( false );
}
public override void Refresh()
{
base.Refresh();
}
}