Helper/RespawnHelper.cs
using Sandbox.Internal;
using System;
namespace Skateboard;
[ClassFileLocation( "RespawnHelper.cs" )]
public static class RespawnHelper
{
private const float PawnHeight = 50f;
private const float TraceHeight = 5000f;
private const float RespawnAreaSize = 5f;
private const int Steps = 5;
private const float StepDistance = 30f;
private const float MinimumUpwardsNormal = 0.7f;
private const float MaximumHeightDifference = 5f;
public static RespawnHelperResult FindRespawnPosition( Vector3 bailPosition )
{
var scene = Game.ActiveScene;
if ( scene is null )
return RespawnHelperResult.NotFound;
var origin = bailPosition + Vector3.Up * PawnHeight
- Vector3.Right * 75f
- Vector3.Forward * 75f;
var candidates = new List<Vector3>();
var traces = new SceneTraceResult[5];
for ( int y = 0; y < Steps; y++ )
{
for ( int x = 0; x < Steps; x++ )
{
var samplePos = origin + Vector3.Right * (x * StepDistance) + Vector3.Forward * (y * StepDistance);
DoSampleTraces( scene, samplePos, traces );
if ( AreAllTracesValid( traces ) && HasLineOfSightFromBail( scene, bailPosition, traces[0].EndPosition ) )
{
candidates.Add( traces[0].EndPosition );
}
}
}
if ( candidates.Count == 0 )
return RespawnHelperResult.NotFound;
var best = candidates[0];
var bestDist = Vector3.DistanceBetween( bailPosition, best );
for ( int i = 1; i < candidates.Count; i++ )
{
var d = Vector3.DistanceBetween( bailPosition, candidates[i] );
if ( d < bestDist )
{
bestDist = d;
best = candidates[i];
}
}
return new RespawnHelperResult( best );
}
private static void DoSampleTraces( Scene scene, Vector3 samplePos, SceneTraceResult[] results )
{
var offsets = new[]
{
Vector3.Zero,
Vector3.Right * RespawnAreaSize + Vector3.Forward * RespawnAreaSize,
Vector3.Right * RespawnAreaSize - Vector3.Forward * RespawnAreaSize,
-Vector3.Right * RespawnAreaSize - Vector3.Forward * RespawnAreaSize,
-Vector3.Right * RespawnAreaSize + Vector3.Forward * RespawnAreaSize
};
for ( int i = 0; i < results.Length; i++ )
{
var from = samplePos + offsets[i];
var to = from + Vector3.Down * TraceHeight;
results[i] = scene.Trace.Ray( from, to )
.WithAnyTags( "solid", "skateable" )
.WithoutTags( "unskateable" )
.Run();
}
}
private static bool HasLineOfSightFromBail( Scene scene, Vector3 bailPosition, Vector3 candidateGroundPos )
{
var from = bailPosition + Vector3.Up * PawnHeight;
var to = candidateGroundPos + Vector3.Up * PawnHeight;
return !scene.Trace.Ray( from, to )
.WithAnyTags( "solid", "playerclip", "passbullets", "unskateable" )
.Run()
.Hit;
}
private static bool AreAllTracesValid( SceneTraceResult[] traces )
{
var first = traces[0];
if ( !first.Hit ) return false;
if ( Vector3.Dot( first.Normal, Vector3.Up ) < MinimumUpwardsNormal )
return false;
var refNormal = first.Normal;
var refHeight = Vector3.Dot( first.EndPosition, refNormal );
for ( int i = 1; i < traces.Length; i++ )
{
var tr = traces[i];
if ( !tr.Hit )
return false;
if ( Vector3.Dot( tr.Normal, Vector3.Up ) < MinimumUpwardsNormal )
return false;
var h = Vector3.Dot( tr.EndPosition, refNormal );
if ( Math.Abs( refHeight - h ) > MaximumHeightDifference )
return false;
}
return true;
}
}
public struct RespawnHelperResult
{
public static readonly RespawnHelperResult NotFound = new RespawnHelperResult
{
Found = false
};
public bool Found;
public Vector3 Location;
public RespawnHelperResult( Vector3 location )
{
Found = false;
Location = Vector3.Zero;
Found = true;
Location = location;
}
}