A StageEvent implementation that makes the ground turn into lava for a timed event. It tints the ground model, spawns circular safe spots, applies periodic fire damage to the local player if they are outside a safe spot, and restores the ground when finished.
using System;
using System.Reflection;
using Sandbox;
public class StageEventLava : StageEvent
{
GameObject _groundObj;
ModelRenderer _groundRenderer;
Color _originalGroundTint;
// todo: sometimes last for 2 minutes+ (but can't overlap with icy ground etc)
private const float LERP_TIME = 7f;
private Color _lavaColorA;
private Color _lavaColorB;
//private Color _groundColorLava;
private TimeSince _timeSinceDamage;
private float _damagePercent;
public float LavaDamage { get; set; }
public override void Start( int randSeed )
{
base.Start( randSeed );
// sfx
Manager.Instance.Chat.AddLocalChatMessage( "The ground is lava, find a safe spot!", from: "" );
Lifetime = 60f;
LavaDamage = 5f;
_groundObj = Manager.Instance.Scene.Directory.FindByName( "ground" ).FirstOrDefault();
_groundObj.Enabled = true;
_groundRenderer = _groundObj.GetComponent<ModelRenderer>();
_originalGroundTint = _groundRenderer.Tint;
_lavaColorA = new Color( 0.15f, 0f, 0f );
_lavaColorB = new Color( 0.17f, 0.03f, 0f );
//_groundColorLava = new Color( 0.15f, 0f, 0f, 1f );
_timeSinceDamage = 0f;
if ( !Networking.IsHost )
return;
int numSafeSpots = 20;
List<(Vector2, float)> safeSpotSpawns = new();
for ( int i = 0; i < numSafeSpots; i++ )
{
var radius = Game.Random.Float( 64f, 192f );
bool overlap = true;
Vector2 pos = Vector2.Zero;
while ( overlap )
{
pos = Manager.Instance.GetRandomSpawnPos( buffer: radius );
overlap = false;
foreach ( var pair in safeSpotSpawns )
{
if ( (pair.Item1 - pos).LengthSquared < Math.Pow( pair.Item2 + radius, 2f ) )
{
overlap = true;
break;
}
}
}
safeSpotSpawns.Add( (pos, radius) );
}
foreach ( var pair in safeSpotSpawns )
{
Manager.Instance.SpawnLavaSafeSpot( pos: pair.Item1, radius: pair.Item2, direction: Utils.GetRandomVector() );
}
}
public override void Update()
{
base.Update();
var lavaColor = Color.Lerp( _lavaColorA, _lavaColorB, 0.5f + Utils.FastSin(TimeSinceStart * 3f) * 0.5f );
if( TimeSinceStart < LERP_TIME )
{
_groundRenderer.Tint = Color.Lerp( _originalGroundTint, lavaColor, Utils.Map( TimeSinceStart, 0f, LERP_TIME, 0f, 1f, EasingType.SineOut ) );
_damagePercent = Utils.Map( TimeSinceStart, 0f, LERP_TIME, 0f, 1f, EasingType.ExpoIn );
}
else if(TimeSinceStart > Lifetime - LERP_TIME )
{
_groundRenderer.Tint = Color.Lerp( lavaColor, _originalGroundTint, Utils.Map( TimeSinceStart, Lifetime - LERP_TIME, Lifetime, 0f, 1f, EasingType.SineOut ) );
_damagePercent = Utils.Map( TimeSinceStart, Lifetime - LERP_TIME, Lifetime, 0f, 1f, EasingType.SineOut );
}
else
{
_groundRenderer.Tint = lavaColor;
}
if( TimeSinceStart > Lifetime )
{
Manager.Instance.RemoveEvent( EventType );
}
var localPlayer = Manager.Instance.LocalPlayer;
if ( localPlayer.IsValid() && !localPlayer.IsDead )
{
if ( _timeSinceDamage > 0.5f && TimeSinceStart > 5f && TimeSinceStart < Lifetime - 3f )
{
bool shouldDamage = true;
foreach ( var safeSpot in Manager.Instance.Scene.GetAll<LavaSafeSpot>() )
{
var distSqr = (localPlayer.Position2D - safeSpot.Position2D).LengthSquared;
if ( distSqr < MathF.Pow( safeSpot.Radius, 2f ) )
{
shouldDamage = false;
break;
}
}
if( shouldDamage )
{
float damage = LavaDamage * _damagePercent;
localPlayer.Damage( damage, DamageType.Fire, localPlayer.Position2D, Utils.GetRandomVector(), upwardAmount: 0f, force: 0f, ragdollForce: 0f, enemySource: null, enemyType: EnemyType.None );
}
_timeSinceDamage = 0f;
}
}
if ( !Networking.IsHost )
return;
}
public override void Remove()
{
base.Remove();
_groundRenderer.Tint = _originalGroundTint;
_groundObj.Enabled = false;
}
}