SpawnProtectionEffect.cs
using Sandbox;
using System;
/// <summary>A local, pulsing glow driven by the player's replicated spawn protection timer.</summary>
public sealed class SpawnProtectionEffect : Component
{
[RequireComponent] private Player Player { get; set; }
[RequireComponent] private HealthComponent Health { get; set; }
private GameObject _effectObject;
private HighlightOutline _outline;
protected override void OnUpdate()
{
// The local player has a hidden body and sees protection on their health bar.
if ( !Health.IsInvulnerable || !Player.Avatar.IsValid() || (!IsProxy && !Player.IsBot) )
{
DestroyEffect();
return;
}
if ( !_effectObject.IsValid() )
{
_effectObject = new GameObject( GameObject, false, "Spawn Protection Glow" )
{
NetworkMode = NetworkMode.Never
};
_outline = _effectObject.AddComponent<HighlightOutline>();
_outline.OverrideTargets = true;
_outline.ObscuredColor = Color.Transparent;
_outline.InsideObscuredColor = Color.Transparent;
_effectObject.Enabled = true;
}
// Refresh while active so clothing that finishes loading after spawn also glows.
_outline.Targets = Player.Avatar.Components.GetAll<ModelRenderer>( FindMode.EverythingInSelfAndDescendants )
.Cast<Renderer>().ToList();
var pulse = 0.5f + 0.5f * MathF.Sin( Health.SpawnProtectionRemaining * MathF.PI * 4f );
var fade = Math.Clamp( Health.SpawnProtectionRemaining / 0.5f, 0f, 1f );
_outline.Color = new Color( 0.15f, 0.85f, 1f, (0.65f + pulse * 0.3f) * fade );
_outline.InsideColor = new Color( 0.15f, 0.85f, 1f, (0.08f + pulse * 0.08f) * fade );
_outline.Width = 0.25f + pulse * 0.15f;
}
protected override void OnDisabled() => DestroyEffect();
protected override void OnDestroy() => DestroyEffect();
private void DestroyEffect()
{
if ( _effectObject.IsValid() )
_effectObject.Destroy();
_effectObject = null;
_outline = null;
}
}