stageEvents/StageEventLava.cs

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.

Networking
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;
	}
}